diff --git a/src/app/(app)/stock-orders/StockOrdersTable.tsx b/src/app/(app)/stock-orders/StockOrdersTable.tsx new file mode 100644 index 0000000..0d26750 --- /dev/null +++ b/src/app/(app)/stock-orders/StockOrdersTable.tsx @@ -0,0 +1,302 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { Card, CardContent } from "@/components/ui/card"; +import { ChevronRight, Package, Pencil } from "lucide-react"; +import { QuickDraftModal } from "@/components/stock-orders/quick-draft-modal"; +import { + STOCK_PURCHASE_STATUS_VALUES, + type StockPurchase, + type StockPurchaseStatus, + type Supplier, +} from "@/lib/types"; + +// `formatCurrency` lives in `pb-helpers.ts` but that file is the +// legacy data-access layer (AGENTS.md forbids importing it from +// new components). Inlined here — same ZAR / en-ZA shape used +// elsewhere in the app. +function formatCurrency(amount: number): string { + return new Intl.NumberFormat("en-ZA", { style: "currency", currency: "ZAR" }).format(amount); +} + +/** + * Client-side table view for the `/stock-orders` list. Holds the + * QuickDraftModal open state + the per-row "edit draft" target. + * The page itself is a Server Component that fetches rows; this + * component receives them as a prop and renders tabs + table + + * pagination as ``s so the server re-renders on filter/page + * change. + */ + +type ListRow = StockPurchase & { + item_count: number; + ordered_value: number; + received_value: number; + expand?: { supplier?: Supplier }; +}; + +const STATUS_TABS: Array<{ value: StockPurchaseStatus | ""; label: string }> = [ + { value: "", label: "All" }, + ...STOCK_PURCHASE_STATUS_VALUES.filter((s) => + ["draft", "sent", "received", "cancelled"].includes(s), + ).map((value) => ({ + value: value as StockPurchaseStatus, + label: value.charAt(0).toUpperCase() + value.slice(1), + })), +]; + +const STATUS_VARIANT: Record< + StockPurchaseStatus, + "default" | "secondary" | "destructive" | "outline" +> = { + draft: "outline", + sending: "secondary", + sent: "secondary", + mail_failed: "destructive", + received: "default", + cancelled: "destructive", +}; + +const STATUS_LABEL: Record = { + draft: "Draft", + sending: "Sending", + sent: "Sent", + mail_failed: "Mail failed", + received: "Received", + cancelled: "Cancelled", +}; + +function formatDate(s: string): string { + if (!s) return ""; + const d = new Date(s); + if (Number.isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + }); +} + +export interface StockOrdersTableProps { + rows: ListRow[]; + currentStatus: StockPurchaseStatus | ""; + page: number; + totalPages: number; +} + +export default function StockOrdersTable({ + rows, + currentStatus, + page, + totalPages, +}: StockOrdersTableProps) { + const [quickDraftOpen, setQuickDraftOpen] = useState(false); + const [editingPurchaseId, setEditingPurchaseId] = useState(null); + + const openEditForDraft = (purchaseId: string) => { + setEditingPurchaseId(purchaseId); + setQuickDraftOpen(true); + }; + + const tabHref = (value: StockPurchaseStatus | "") => { + const params = new URLSearchParams(); + if (value) params.set("status", value); + const qs = params.toString(); + return qs ? `/stock-orders?${qs}` : "/stock-orders"; + }; + + const pageHref = (target: number) => { + const params = new URLSearchParams(); + if (currentStatus) params.set("status", currentStatus); + if (target > 1) params.set("page", String(target)); + const qs = params.toString(); + return qs ? `/stock-orders?${qs}` : "/stock-orders"; + }; + + return ( + <> + {/* Status filter — server-rendered tabs. */} +
+ {STATUS_TABS.map((tab) => { + const active = currentStatus === tab.value; + return ( + + {tab.label} + + ); + })} +
+ +
+ +
+ + {rows.length === 0 ? ( + + + +

+ {currentStatus + ? `No ${STATUS_LABEL[currentStatus].toLowerCase()} stock orders.` + : "No stock orders yet."} +

+ + Create your first stock order + +
+
+ ) : ( + + + + + + Number + Supplier + Status + Ordered (R) + Received (R) + Items + Created + + + + + {rows.map((row) => { + const supplier = row.expand?.supplier; + return ( + { + window.location.href = `/stock-orders/${row.id}`; + }} + > + + {row.purchase_number} + + + {supplier?.suppliername ?? ( + + Unknown supplier + + )} + + + + {STATUS_LABEL[row.status]} + + + + {formatCurrency(row.ordered_value)} + + + {formatCurrency(row.received_value)} + + + {row.item_count} + + + {formatDate(row.created)} + + +
+ {row.status === "draft" && ( + + )} + +
+
+
+ ); + })} +
+
+
+
+ )} + + {totalPages > 1 && ( +
+ + Previous + + + Page {page} of {totalPages} + + = totalPages} + className={cn( + buttonVariants({ variant: "outline", size: "sm" }), + page >= totalPages && "pointer-events-none opacity-50", + )} + > + Next + +
+ )} + + { + setQuickDraftOpen(next); + if (!next) setEditingPurchaseId(null); + }} + editPurchaseId={editingPurchaseId ?? undefined} + /> + + ); +}