"use client"; import { useState } from "react"; import useSWR from "swr"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Progress } from "@/components/ui/progress"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { Cpu, Package, AlertTriangle, Euro, ArrowRight, TrendingUp, FileText, ExternalLink, ClipboardList, ShoppingCart, } from "lucide-react"; import { useTableSort } from "@/hooks/use-table-sort"; import { SortableTableHead } from "@/components/sortable-table-head"; import { formatCurrency } from "@/lib/format"; import { toast } from "sonner"; import type { Component } from "@/lib/types"; const fetcher = (url: string) => fetch(url).then((r) => r.json()); interface OrderRow { id: number; po_number: string; status: string; order_date: string; due_date: string | null; customer_id: number; customer_name: string; customer_code: string; total_ordered: number; total_shipped: number; total_remaining: number; total_value: number; item_count: number; } interface DashboardData { stats: { total_components: number; total_inventory_value: number; low_stock_count: number; total_products: number; active_products: number; draft_products: number; active_orders: number; in_production_orders: number; confirmed_orders: number; shipped_orders: number; }; low_stock_items: Component[]; category_breakdown: { name: string; count: number; total_stock: number }[]; recent_orders: OrderRow[]; } const statusConfig: Record = { draft: { label: "Draft", variant: "secondary" }, confirmed: { label: "Confirmed", variant: "default" }, in_production: { label: "In Production", variant: "outline" }, shipped: { label: "Shipped", variant: "default" }, completed: { label: "Completed", variant: "default" }, cancelled: { label: "Cancelled", variant: "destructive" }, }; export default function DashboardPage() { const [selectedItems, setSelectedItems] = useState>(new Set()); const [poQuantities, setPoQuantities] = useState>({}); const [generatingPo, setGeneratingPo] = useState(false); const { data, isLoading } = useSWR("/api/dashboard", fetcher); const { sorted: sortedLowStock, sort: lowStockSort, toggleSort: toggleLowStockSort } = useTableSort(data?.low_stock_items ?? []); const { sorted: sortedOrders, sort: ordersSort, toggleSort: toggleOrdersSort } = useTableSort(data?.recent_orders ?? []); function toggleItem(id: number) { setSelectedItems((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); } function toggleAll(items: Component[]) { if (selectedItems.size === items.length) { setSelectedItems(new Set()); } else { setSelectedItems(new Set(items.map((i) => i.id))); } } async function handleGeneratePo(lowStockItems: Component[]) { const selected = lowStockItems.filter((i) => selectedItems.has(i.id)); if (selected.length === 0) return; setGeneratingPo(true); try { // Fetch suppliers to resolve IDs from names const suppRes = await fetch("/api/suppliers"); const suppliers = await suppRes.json(); const supplierMap: Record = {}; for (const s of Array.isArray(suppliers) ? suppliers : []) { supplierMap[s.name] = { id: s.id, name: s.name }; } // Group items by supplier const grouped: Record[] }> = {}; for (const item of selected) { const supplierName = item.supplier || "Unknown Supplier"; if (!grouped[supplierName]) { const sup = supplierMap[supplierName]; if (!sup) { toast.error(`Supplier "${supplierName}" not found. Please add it first.`); setGeneratingPo(false); return; } grouped[supplierName] = { supplier_id: sup.id, supplier_name: sup.name, items: [], }; } const qty = poQuantities[item.id] || Math.max(1, item.min_stock_level - item.stock_quantity); grouped[supplierName].items.push({ component_id: item.id, part_number: item.part_number, component_name: item.name, supplier_part_number: item.supplier_part_number || "", quantity: qty, unit_price: Number(item.unit_price) || 0, }); } const orders = Object.values(grouped); const res = await fetch("/api/supplier-pos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orders }), }); if (!res.ok) throw new Error("Failed to create POs"); const created = await res.json(); const count = Array.isArray(created) ? created.length : 1; toast.success(`${count} purchase order(s) created in Draft status`); setSelectedItems(new Set()); setPoQuantities({}); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to generate POs"); } finally { setGeneratingPo(false); } } const stats = data?.stats; if (isLoading || !data) { return (

Dashboard

Overview of your component inventory and products

{[...Array(4)].map((_, i) => (
))}
); } return (

Dashboard

Overview of your component inventory and products

{/* Stats Cards */}

Total Components

{stats.total_components}

Inventory Value

{formatCurrency(stats.total_inventory_value)}

Low Stock Alerts

{stats.low_stock_count}

Products

{stats.total_products} {stats.active_products > 0 && ( ({stats.active_products} active) )}

Active Orders

{stats.active_orders}

{/* Low Stock Alerts */} Low Stock Alerts {sortedLowStock.length === 0 ? (

All stock levels are healthy

No components are below their minimum stock level

) : (
{/* Generate PO action bar */} {selectedItems.size > 0 && (
{selectedItems.size} component(s) selected
)} 0 && selectedItems.size === sortedLowStock.slice(0, 8).length} onCheckedChange={() => toggleAll(sortedLowStock.slice(0, 8))} /> Level Order Qty {sortedLowStock.slice(0, 8).map((item) => { const pct = item.min_stock_level > 0 ? Math.min( (item.stock_quantity / item.min_stock_level) * 100, 100 ) : 0; const isSelected = selectedItems.has(item.id); const deficit = Math.max(1, item.min_stock_level - item.stock_quantity); return ( toggleItem(item.id)} /> {item.part_number} {item.name} {item.supplier || No supplier} {item.stock_quantity} {item.min_stock_level} {isSelected ? ( setPoQuantities((prev) => ({ ...prev, [item.id]: Math.max(1, Number(e.target.value) || 1), })) } /> ) : ( {deficit} )} ); })}
)}
{/* Purchase Orders Status */} Purchase Order Status {sortedOrders.length === 0 ? (

No active purchase orders

Purchase orders from customers will appear here

) : (
Fulfillment {sortedOrders.map((order) => { const ordered = Number(order.total_ordered); const shipped = Number(order.total_shipped); const remaining = Number(order.total_remaining); const pct = ordered > 0 ? Math.round((shipped / ordered) * 100) : 0; const sc = statusConfig[order.status] || statusConfig.draft; const isOverdue = order.due_date && new Date(order.due_date) < new Date() && pct < 100; return ( {order.po_number}
{order.customer_name} {order.customer_code}
{sc.label} {new Date(order.order_date).toLocaleDateString()} {order.due_date ? ( {new Date(order.due_date).toLocaleDateString()} {isOverdue && " (overdue)"} ) : ( - )} {formatCurrency(Number(order.total_value))} {ordered} {shipped} 0 ? "text-warning" : "text-accent" } > {remaining}
{shipped}/{ordered} {pct}%
); })}
)}
); }