"use client"; import { useState, useCallback } from "react"; import useSWR from "swr"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Plus, Pencil, Trash2, ClipboardList } from "lucide-react"; import { useTableSort } from "@/hooks/use-table-sort"; import { SortableTableHead } from "@/components/sortable-table-head"; import { ProductDialog } from "@/components/product-dialog"; import { toast } from "sonner"; import { formatCurrency } from "@/lib/format"; import type { Product } from "@/lib/types"; const fetcher = (url: string) => fetch(url).then((r) => r.json()); const statusStyles: Record = { draft: "bg-muted text-muted-foreground", active: "bg-accent/15 text-accent border-accent/30", deprecated: "bg-warning/15 text-warning border-warning/30", archived: "bg-muted text-muted-foreground", }; export default function ProductsPage() { const [dialogOpen, setDialogOpen] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const { data: products = [], mutate } = useSWR( "/api/products", fetcher ); const { sorted: sortedProducts, sort, toggleSort } = useTableSort(products); const handleSave = useCallback( async (data: Partial) => { const url = editingProduct ? `/api/products/${editingProduct.id}` : "/api/products"; const method = editingProduct ? "PUT" : "POST"; const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Failed to save"); } toast.success( editingProduct ? "Product updated" : "Product created" ); mutate(); }, [editingProduct, mutate] ); const handleDelete = useCallback(async () => { if (!deleteTarget) return; await fetch(`/api/products/${deleteTarget.id}`, { method: "DELETE" }); toast.success("Product deleted"); setDeleteTarget(null); mutate(); }, [deleteTarget, mutate]); function openCreate() { setEditingProduct(null); setDialogOpen(true); } function openEdit(prod: Product) { setEditingProduct(prod); setDialogOpen(true); } return (

Products

Manage your products and their bill of materials

Actions {sortedProducts.length === 0 ? ( No products yet. Click "Add Product" to get started. ) : ( sortedProducts.map((prod) => ( {prod.sku} {prod.name} v{prod.version} {prod.status} {prod.bom_count || 0} {formatCurrency(Number(prod.total_cost || 0))}
)) )}
setDeleteTarget(null)} > Delete Product Are you sure you want to delete{" "} {deleteTarget?.name} ({deleteTarget?.sku})? This will also delete all associated BOM items. Cancel Delete
); }