"use client"; import { useState, type FormEvent } from "react"; import { useApiList } from "@/hooks/use-api-list"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; // Shape returned by GET /api/purchase-orders — snake_case per this repo's // API-boundary convention (see src/lib/purchase-orders.ts's toRecord()). interface PurchaseOrderRecord { id: string; vendor_id: string; order_date: string; expected_date: string; received_at: string | null; received_by: string | null; created_at: string; } // Shape returned by GET /api/vendors — only the fields used to populate the // vendorId (see src/app/employees/page.tsx's // EmployeeRecord for the full shape). interface EmployeeRecord { id: string; name: string; } // Radix can't take an empty-string value, so "not received // yet" is represented by this sentinel in the form state and translated to // "omit the key" on submit — same pattern as employees page's // NO_MANAGER_VALUE. const NOT_RECEIVED_VALUE = "none"; export default function PurchaseOrdersPage() { const { data: purchaseOrders, loading, error, refetch, } = useApiList("/api/purchase-orders"); const { data: vendors } = useApiList("/api/vendors"); const { data: employees } = useApiList("/api/employees"); const [vendorId, setVendorId] = useState(""); const [orderDate, setOrderDate] = useState(""); const [expectedDate, setExpectedDate] = useState(""); const [receivedAt, setReceivedAt] = useState(""); const [receivedById, setReceivedById] = useState(NOT_RECEIVED_VALUE); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); async function handleSubmit(event: FormEvent) { event.preventDefault(); setSubmitError(null); // Mirror the server's .refine(): receivedAt/receivedById must both be // empty or both be filled (src/lib/purchase-orders.ts's // createPurchaseOrderSchema). Server is still the source of truth if // this check somehow disagrees with it. const hasReceivedAt = receivedAt.trim() !== ""; const hasReceivedById = receivedById !== NOT_RECEIVED_VALUE; if (hasReceivedAt !== hasReceivedById) { setSubmitError("receivedAt and receivedById must both be set or both be unset"); return; } setSubmitting(true); try { const response = await fetch("/api/purchase-orders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ vendorId, orderDate, expectedDate, // Omit both keys entirely when left empty, rather than sending // null — matches the create-form pattern used on other pages // (e.g. employees page's managerId). ...(hasReceivedAt ? { receivedAt } : {}), ...(hasReceivedById ? { receivedById } : {}), }), }); let body: unknown; try { body = await response.json(); } catch { body = null; } if (!response.ok) { const message = body !== null && typeof body === "object" && "error" in body && typeof (body as { error: unknown }).error === "string" ? (body as { error: string }).error : "Request failed"; setSubmitError(message); return; } setVendorId(""); setOrderDate(""); setExpectedDate(""); setReceivedAt(""); setReceivedById(NOT_RECEIVED_VALUE); refetch(); } catch { setSubmitError("Request failed"); } finally { setSubmitting(false); } } return (

Purchase Orders

{loading ? (

Loading...

) : error ? (

{error}

) : ( ID Vendor ID Order Date Expected Date Received At Received By Created At {purchaseOrders.map((purchaseOrder) => ( {purchaseOrder.id} {purchaseOrder.vendor_id} {purchaseOrder.order_date} {purchaseOrder.expected_date} {purchaseOrder.received_at ?? "-"} {purchaseOrder.received_by ?? "-"} {purchaseOrder.created_at} ))}
)}

New Purchase Order

setOrderDate(event.target.value)} required />
setExpectedDate(event.target.value)} required />
setReceivedAt(event.target.value)} />
{submitError ?

{submitError}

: null}
); }