"use client"; import { useState, type FormEvent } from "react"; import { useApiList } from "@/hooks/use-api-list"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; 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/requests — snake_case per this repo's // API-boundary convention (see src/lib/requests.ts's toRecord()). `status` // is always "Draft" right now (no state-machine transitions built yet), see // src/lib/requests.ts's RequestStatus comment. interface RequestRecord { id: string; employee_id: string; type: "asset" | "license"; reason: string; status: string; purchase_order_id: string | null; created_at: string; } // Shape returned by GET /api/employees — only the fields used to populate // the employeeId label since PurchaseOrder has no human-friendly name field (see // src/app/purchase-orders/page.tsx's PurchaseOrderRecord for the full // shape). interface PurchaseOrderRecord { id: string; } // The exact enum literal values from RequestType (src/lib/requests.ts) — // don't invent labels not accepted by the Zod schema. const REQUEST_TYPES = ["asset", "license"] as const; // Radix can't take an empty-string value, so "no purchase // order" 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 / purchase-orders page's NOT_RECEIVED_VALUE. const NO_PURCHASE_ORDER_VALUE = "none"; export default function RequestsPage() { const { data: requests, loading, error, refetch, } = useApiList("/api/requests"); const { data: employees } = useApiList("/api/employees"); const { data: purchaseOrders } = useApiList( "/api/purchase-orders" ); const [employeeId, setEmployeeId] = useState(""); const [type, setType] = useState<(typeof REQUEST_TYPES)[number] | "">(""); const [reason, setReason] = useState(""); const [purchaseOrderId, setPurchaseOrderId] = useState(NO_PURCHASE_ORDER_VALUE); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); async function handleSubmit(event: FormEvent) { event.preventDefault(); setSubmitting(true); setSubmitError(null); const hasPurchaseOrderId = purchaseOrderId !== NO_PURCHASE_ORDER_VALUE; try { const response = await fetch("/api/requests", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ employeeId, type, reason, // Omit the key entirely when left unset, rather than sending // null — matches the create-form pattern used on other pages // (e.g. employees page's managerId). ...(hasPurchaseOrderId ? { purchaseOrderId } : {}), }), }); 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; } setEmployeeId(""); setType(""); setReason(""); setPurchaseOrderId(NO_PURCHASE_ORDER_VALUE); refetch(); } catch { setSubmitError("Request failed"); } finally { setSubmitting(false); } } return (

Requests

{loading ? (

Loading...

) : error ? (

{error}

) : ( ID Employee ID Type Reason Status Purchase Order ID Created At {requests.map((request) => ( {request.id} {request.employee_id} {request.type} {request.reason} {request.status} {request.purchase_order_id ?? "-"} {request.created_at} ))}
)}

New Request