DashboardPage.tsx 6.17 KB
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import {
  catalog,
  finance,
  inventory,
  partners,
  production,
  purchaseOrders,
  salesOrders,
} from '@/api/client'
import { PageHeader } from '@/components/PageHeader'
import { Loading } from '@/components/Loading'
import { ErrorBox } from '@/components/ErrorBox'
import { useAuth } from '@/auth/AuthContext'

interface DashboardCounts {
  items: number
  partners: number
  locations: number
  salesOrders: number
  purchaseOrders: number
  workOrders: number
  journalEntries: number
  inProgressWorkOrders: number
}

export function DashboardPage() {
  const { username } = useAuth()
  const [counts, setCounts] = useState<DashboardCounts | null>(null)
  const [error, setError] = useState<Error | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    let active = true
    setLoading(true)
    Promise.all([
      catalog.listItems(),
      partners.list(),
      inventory.listLocations(),
      salesOrders.list(),
      purchaseOrders.list(),
      production.listWorkOrders(),
      finance.listJournalEntries(),
      production.shopFloor(),
    ])
      .then(([items, parts, locs, sos, pos, wos, jes, sf]) => {
        if (!active) return
        setCounts({
          items: items.length,
          partners: parts.length,
          locations: locs.length,
          salesOrders: sos.length,
          purchaseOrders: pos.length,
          workOrders: wos.length,
          journalEntries: jes.length,
          inProgressWorkOrders: sf.length,
        })
      })
      .catch((e: unknown) => {
        if (active) setError(e instanceof Error ? e : new Error(String(e)))
      })
      .finally(() => active && setLoading(false))
    return () => {
      active = false
    }
  }, [])

  return (
    <div>
      <PageHeader
        title={`Welcome${username ? ', ' + username : ''}`}
        subtitle="The framework's buy-make-sell loop, end to end through the same Postgres."
      />
      {loading && <Loading />}
      {error && <ErrorBox error={error} />}
      {counts && (
        <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
          <DashboardCard label="Items" value={counts.items} to="/items" />
          <DashboardCard label="Partners" value={counts.partners} to="/partners" />
          <DashboardCard label="Locations" value={counts.locations} to="/locations" />
          <DashboardCard
            label="Work orders in progress"
            value={counts.inProgressWorkOrders}
            to="/shop-floor"
            highlight
          />
          <DashboardCard label="Sales orders" value={counts.salesOrders} to="/sales-orders" />
          <DashboardCard
            label="Purchase orders"
            value={counts.purchaseOrders}
            to="/purchase-orders"
          />
          <DashboardCard label="Work orders" value={counts.workOrders} to="/work-orders" />
          <DashboardCard
            label="Journal entries"
            value={counts.journalEntries}
            to="/journal-entries"
          />
        </div>
      )}
      <div className="mt-8 card p-5 text-sm text-slate-600">
        <h2 className="mb-2 text-base font-semibold text-slate-800">
          Demo: EBC-PP-001 Work Order Management Flow
        </h2>
        <p className="mb-3 text-slate-500">
          Walk the printing company's production flow end-to-end — sales order to finished goods.
        </p>
        <ol className="list-decimal space-y-2 pl-5">
          <li>
            <strong>Create or open a sales order</strong> —{' '}
            <Link to="/sales-orders/new" className="text-brand-600 hover:underline">
              create a new order
            </Link>{' '}
            or open an existing one from the{' '}
            <Link to="/sales-orders" className="text-brand-600 hover:underline">list</Link>.
          </li>
          <li>
            <strong>Confirm the order</strong> — click{' '}
            <span className="font-mono bg-slate-100 px-1 rounded">Confirm</span>. The system
            auto-generates one production work order per line (EBC-PP-001 step B-010). Check the{' '}
            <Link to="/work-orders" className="text-brand-600 hover:underline">Work Orders</Link>{' '}
            page to see them appear. Finance posts an AR entry automatically.
          </li>
          <li>
            <strong>Walk the pre-seeded work order</strong> — open{' '}
            <Link to="/work-orders" className="text-brand-600 hover:underline">WO-PRINT-0001</Link>.
            It has a 3-step routing (CTP plate-making → offset printing → post-press finishing) and
            a BOM with paper, ink, and CTP plates. Click <span className="font-mono bg-slate-100 px-1 rounded">Start</span>{' '}
            to put it in progress, then watch it on the{' '}
            <Link to="/shop-floor" className="text-brand-600 hover:underline">Shop Floor</Link> dashboard.
          </li>
          <li>
            <strong>Complete the work order</strong> — materials are consumed from the warehouse,
            finished goods are credited. Check{' '}
            <Link to="/balances" className="text-brand-600 hover:underline">Stock Balances</Link>{' '}
            and{' '}
            <Link to="/movements" className="text-brand-600 hover:underline">Movements</Link>{' '}
            to see the ledger entries.
          </li>
          <li>
            <strong>Ship the sales order</strong> — finished goods leave the warehouse, the AR
            journal entry settles from POSTED to SETTLED in{' '}
            <Link to="/journal-entries" className="text-brand-600 hover:underline">Finance</Link>.
          </li>
        </ol>
      </div>
    </div>
  )
}

function DashboardCard({
  label,
  value,
  to,
  highlight = false,
}: {
  label: string
  value: number
  to: string
  highlight?: boolean
}) {
  return (
    <Link
      to={to}
      className={[
        'card flex flex-col gap-1 p-5 transition hover:shadow-md',
        highlight ? 'ring-2 ring-brand-200' : '',
      ].join(' ')}
    >
      <div className="text-xs font-semibold uppercase tracking-wide text-slate-500">{label}</div>
      <div className="text-3xl font-bold text-slate-900">{value}</div>
    </Link>
  )
}