"use client"; import { useState, useCallback, use } from "react"; import useSWR from "swr"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; 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, ArrowLeft, AlertTriangle, Euro, Layers, Hash, } from "lucide-react"; import { useTableSort } from "@/hooks/use-table-sort"; import { SortableTableHead } from "@/components/sortable-table-head"; import { BomItemDialog } from "@/components/bom-item-dialog"; import { toast } from "sonner"; import { formatCurrency, formatUnitPrice } from "@/lib/format"; import type { Product, BomItem } from "@/lib/types"; const fetcher = (url: string) => fetch(url).then((r) => r.json()); export default function BomDetailPage({ params, }: { params: Promise<{ productId: string }>; }) { const { productId } = use(params); const [dialogOpen, setDialogOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const { data: products = [] } = useSWR("/api/products", fetcher); const product = products.find((p) => p.id === Number(productId)); const { data: bomItems = [], mutate } = useSWR( `/api/bom/${productId}`, fetcher ); const { sorted: sortedBomItems, sort, toggleSort } = useTableSort(bomItems); const totalCost = bomItems.reduce( (sum, item) => sum + Number(item.line_cost || 0), 0 ); const totalParts = bomItems.reduce((sum, item) => sum + item.quantity, 0); const handleSave = useCallback( async (data: Partial) => { const url = editingItem ? `/api/bom/${productId}/${editingItem.id}` : `/api/bom/${productId}`; const method = editingItem ? "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(); toast.error(err.error || "Failed to save"); throw new Error(err.error || "Failed to save"); } toast.success(editingItem ? "BOM item updated" : "Component added to BOM"); mutate(); }, [editingItem, productId, mutate] ); const handleDelete = useCallback(async () => { if (!deleteTarget) return; await fetch(`/api/bom/${productId}/${deleteTarget.id}`, { method: "DELETE", }); toast.success("BOM item removed"); setDeleteTarget(null); mutate(); }, [deleteTarget, productId, mutate]); function openCreate() { setEditingItem(null); setDialogOpen(true); } function openEdit(item: BomItem) { setEditingItem(item); setDialogOpen(true); } return (
{/* Header */}

{product?.name || "Loading..."}

{product && ( <> {product.sku} {" "} · v{product.version} · Bill of Materials )}

{/* Summary Cards */}

Unique Items

{bomItems.length}

Total Parts

{totalParts}

Total BOM Cost

{formatCurrency(totalCost)}

{/* BOM Table */}
Actions {sortedBomItems.length === 0 ? ( No items in this BOM yet. Click "Add Item" to add components. ) : ( sortedBomItems.map((item) => { const stockShort = (item.stock_quantity || 0) < item.quantity; return ( {item.reference_designator || "-"} {item.part_number} {item.component_name} {item.category_name && ( {item.category_name} )} {item.quantity} {formatUnitPrice(Number(item.unit_price || 0))} {formatCurrency(Number(item.line_cost || 0))} {stockShort && ( )} {item.stock_quantity ?? 0}
); }) )}
setDeleteTarget(null)} > Remove BOM Item Are you sure you want to remove{" "} {deleteTarget?.component_name} from this BOM? Cancel Remove
); }