diff --git a/apps/Frontend/src/App.tsx b/apps/Frontend/src/App.tsx index c38e41ac..f0aeb062 100755 --- a/apps/Frontend/src/App.tsx +++ b/apps/Frontend/src/App.tsx @@ -28,6 +28,7 @@ const DatabaseManagementPage = lazy( const ReportsPage = lazy(() => import("./pages/reports-page")); const CloudStoragePage = lazy(() => import("./pages/cloud-storage-page")); const JobMonitorPage = lazy(() => import("./pages/job-monitor-page")); +const ChartPage = lazy(() => import("./pages/chart-page")); const NotFound = lazy(() => import("./pages/not-found")); function Router() { @@ -42,6 +43,8 @@ function Router() { component={() => } /> } /> + } /> + } /> } adminOnly /> } /> = { + pending: "bg-yellow-100 text-yellow-700 border-yellow-200", + "in-lab": "bg-blue-100 text-blue-700 border-blue-200", + received: "bg-green-100 text-green-700 border-green-200", + delivered: "bg-gray-100 text-gray-600 border-gray-200", + cancelled: "bg-red-100 text-red-600 border-red-200", +}; + +const STATUS_LABELS: Record = { + pending: "Pending", + "in-lab": "In Lab", + received: "Received", + delivered: "Delivered", + cancelled: "Cancelled", +}; + +const CASE_TYPES = [ + "Crown – PFM", + "Crown – All Ceramic", + "Crown – Zirconia", + "Crown – Gold", + "Bridge – PFM", + "Bridge – Zirconia", + "Implant Crown", + "Implant Abutment", + "Veneer", + "Inlay / Onlay", + "Full Denture (Upper)", + "Full Denture (Lower)", + "Partial Denture", + "Night Guard", + "Bleaching Tray", + "Retainer", + "Diagnostic Model", + "Other", +]; + +const COMMON_LABS = [ + "Dental Arts Lab", + "National Dentex", + "Glidewell Dental", + "Henry Schein Lab", + "Affordable Dentures Lab", + "Local Lab", +]; + +const SHADES = [ + "A1", "A2", "A3", "A3.5", "A4", + "B1", "B2", "B3", "B4", + "C1", "C2", "C3", "C4", + "D2", "D3", "D4", + "BL1", "BL2", "BL3", "BL4", + "Custom", +]; + +let nextId = 1; +const newOrder = (): LabOrder => ({ + id: nextId++, + orderDate: new Date().toISOString().substring(0, 10), + dueDate: "", + tooth: "", + caseType: "", + lab: "", + shade: "", + status: "pending", + rush: false, + notes: "", + trackingNumber: "", +}); + +export function LabManagementTab() { + const [orders, setOrders] = useState([]); + const [dialogOpen, setDialogOpen] = useState(false); + const [editing, setEditing] = useState(newOrder()); + + const openAdd = () => { + setEditing(newOrder()); + setDialogOpen(true); + }; + + const openEdit = (order: LabOrder) => { + setEditing({ ...order }); + setDialogOpen(true); + }; + + const handleSave = () => { + setOrders((prev) => { + const idx = prev.findIndex((o) => o.id === editing.id); + if (idx >= 0) { + const next = [...prev]; + next[idx] = editing; + return next; + } + return [...prev, editing]; + }); + setDialogOpen(false); + }; + + const handleDelete = (id: number) => { + setOrders((prev) => prev.filter((o) => o.id !== id)); + }; + + const pending = orders.filter((o) => o.status === "pending" || o.status === "in-lab").length; + const rush = orders.filter((o) => o.rush && o.status !== "delivered" && o.status !== "cancelled").length; + + return ( +
+
+
+ {pending > 0 && Open orders: {pending}} + {rush > 0 && Rush: {rush}} + {orders.length === 0 && No lab orders yet} +
+ +
+ +
+ + + + Order Date + Tooth + Case Type + Lab + Shade + Due + Status + Actions + + + + {orders.length === 0 ? ( + + +
+ + No lab orders yet. Click "New Lab Order" to add one. +
+
+
+ ) : ( + orders.map((order) => ( + + {order.orderDate} + {order.tooth || "—"} + +
+ {order.caseType} + {order.rush && ( + + RUSH + + )} +
+ {order.notes && ( +

{order.notes}

+ )} +
+ {order.lab || "—"} + {order.shade || "—"} + {order.dueDate || "—"} + + + {STATUS_LABELS[order.status]} + + + +
+ + +
+
+
+ )) + )} +
+
+
+ + + + + Lab Order + +
+
+ + setEditing((d) => ({ ...d, orderDate: e.target.value }))} + className="h-9 text-sm" + /> +
+
+ + setEditing((d) => ({ ...d, dueDate: e.target.value }))} + className="h-9 text-sm" + /> +
+
+ + setEditing((d) => ({ ...d, tooth: e.target.value }))} + className="h-9 text-sm" + /> +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + setEditing((d) => ({ ...d, trackingNumber: e.target.value }))} + className="h-9 text-sm" + /> +
+
+ setEditing((d) => ({ ...d, rush: e.target.checked }))} + className="h-4 w-4 rounded border-gray-300" + /> + +
+
+ +