import { permissions } from "@mrp/shared"; import type { DemandPlanningRollupDto } from "@mrp/shared/dist/sales/types.js"; import { useEffect, useState } from "react"; import type { ReactNode } from "react"; import { useAuth } from "../../auth/AuthProvider"; import { ApiError, api } from "../../lib/api"; interface DashboardSnapshot { customers: Awaited> | null; vendors: Awaited> | null; items: Awaited> | null; warehouses: Awaited> | null; purchaseOrders: Awaited> | null; workOrders: Awaited> | null; quotes: Awaited> | null; orders: Awaited> | null; shipments: Awaited> | null; projects: Awaited> | null; planningRollup: DemandPlanningRollupDto | null; refreshedAt: string; } function hasPermission(userPermissions: string[] | undefined, permission: string) { return Boolean(userPermissions?.includes(permission)); } function formatCurrency(value: number) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(value); } function sumNumber(values: number[]) { return values.reduce((total, value) => total + value, 0); } function formatPercent(value: number, total: number) { if (total <= 0) { return "0%"; } return `${Math.round((value / total) * 100)}%`; } function ProgressBar({ value, total, tone, }: { value: number; total: number; tone: string; }) { const width = total > 0 ? Math.max(6, Math.round((value / total) * 100)) : 0; return (
); } function StackedBar({ segments, }: { segments: Array<{ value: number; tone: string }>; }) { const total = segments.reduce((sum, segment) => sum + segment.value, 0); return (
{segments.map((segment, index) => { const width = total > 0 ? (segment.value / total) * 100 : 0; return
; })}
); } function DashboardCard({ eyebrow, title, children, className = "", }: { eyebrow: string; title: string; children: ReactNode; className?: string; }) { return (

{eyebrow}

{title}

{children}
); } export function DashboardPage() { const { token, user } = useAuth(); const [snapshot, setSnapshot] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!token || !user) { setSnapshot(null); setIsLoading(false); return; } const authToken = token; let isMounted = true; setIsLoading(true); setError(null); const canReadCrm = hasPermission(user.permissions, permissions.crmRead); const canReadInventory = hasPermission(user.permissions, permissions.inventoryRead); const canReadPurchasing = hasPermission(user.permissions, permissions.purchasingRead); const canReadManufacturing = hasPermission(user.permissions, permissions.manufacturingRead); const canReadSales = hasPermission(user.permissions, permissions.salesRead); const canReadShipping = hasPermission(user.permissions, permissions.shippingRead); const canReadProjects = hasPermission(user.permissions, permissions.projectsRead); async function loadSnapshot() { const results = await Promise.allSettled([ canReadCrm ? api.getCustomers(authToken) : Promise.resolve(null), canReadCrm ? api.getVendors(authToken) : Promise.resolve(null), canReadInventory ? api.getInventoryItems(authToken) : Promise.resolve(null), canReadInventory ? api.getWarehouses(authToken) : Promise.resolve(null), canReadPurchasing ? api.getPurchaseOrders(authToken) : Promise.resolve(null), canReadManufacturing ? api.getWorkOrders(authToken) : Promise.resolve(null), canReadSales ? api.getQuotes(authToken) : Promise.resolve(null), canReadSales ? api.getSalesOrders(authToken) : Promise.resolve(null), canReadShipping ? api.getShipments(authToken) : Promise.resolve(null), canReadProjects ? api.getProjects(authToken) : Promise.resolve(null), canReadSales ? api.getDemandPlanningRollup(authToken) : Promise.resolve(null), ]); if (!isMounted) { return; } const firstRejected = results.find((result) => result.status === "rejected"); if (firstRejected?.status === "rejected") { const reason = firstRejected.reason; setError(reason instanceof ApiError ? reason.message : "Unable to load dashboard data."); } setSnapshot({ customers: results[0].status === "fulfilled" ? results[0].value : null, vendors: results[1].status === "fulfilled" ? results[1].value : null, items: results[2].status === "fulfilled" ? results[2].value : null, warehouses: results[3].status === "fulfilled" ? results[3].value : null, purchaseOrders: results[4].status === "fulfilled" ? results[4].value : null, workOrders: results[5].status === "fulfilled" ? results[5].value : null, quotes: results[6].status === "fulfilled" ? results[6].value : null, orders: results[7].status === "fulfilled" ? results[7].value : null, shipments: results[8].status === "fulfilled" ? results[8].value : null, projects: results[9].status === "fulfilled" ? results[9].value : null, planningRollup: results[10].status === "fulfilled" ? results[10].value : null, refreshedAt: new Date().toISOString(), }); setIsLoading(false); } loadSnapshot().catch((loadError) => { if (!isMounted) { return; } setError(loadError instanceof ApiError ? loadError.message : "Unable to load dashboard data."); setSnapshot(null); setIsLoading(false); }); return () => { isMounted = false; }; }, [token, user]); const customers = snapshot?.customers ?? []; const vendors = snapshot?.vendors ?? []; const items = snapshot?.items ?? []; const warehouses = snapshot?.warehouses ?? []; const purchaseOrders = snapshot?.purchaseOrders ?? []; const workOrders = snapshot?.workOrders ?? []; const quotes = snapshot?.quotes ?? []; const orders = snapshot?.orders ?? []; const shipments = snapshot?.shipments ?? []; const projects = snapshot?.projects ?? []; const planningRollup = snapshot?.planningRollup; const customerCount = customers.length; const activeCustomerCount = customers.filter((customer) => customer.lifecycleStage === "ACTIVE").length; const resellerCount = customers.filter((customer) => customer.isReseller).length; const strategicCustomerCount = customers.filter((customer) => customer.strategicAccount).length; const vendorCount = vendors.length; const itemCount = items.length; const activeItemCount = items.filter((item) => item.status === "ACTIVE").length; const assemblyCount = items.filter((item) => item.type === "ASSEMBLY" || item.type === "MANUFACTURED").length; const obsoleteItemCount = items.filter((item) => item.status === "OBSOLETE").length; const warehouseCount = warehouses.length; const locationCount = sumNumber(warehouses.map((warehouse) => warehouse.locationCount)); const purchaseOrderCount = purchaseOrders.length; const openPurchaseOrderCount = purchaseOrders.filter((order) => order.status !== "CLOSED").length; const issuedPurchaseOrderCount = purchaseOrders.filter((order) => order.status === "ISSUED" || order.status === "APPROVED").length; const closedPurchaseOrderCount = purchaseOrders.filter((order) => order.status === "CLOSED").length; const purchaseOrderValue = sumNumber(purchaseOrders.map((order) => order.total)); const workOrderCount = workOrders.length; const activeWorkOrderCount = workOrders.filter((workOrder) => workOrder.status === "RELEASED" || workOrder.status === "IN_PROGRESS" || workOrder.status === "ON_HOLD").length; const releasedWorkOrderCount = workOrders.filter((workOrder) => workOrder.status === "RELEASED").length; const inProgressWorkOrderCount = workOrders.filter((workOrder) => workOrder.status === "IN_PROGRESS").length; const overdueWorkOrderCount = workOrders.filter((workOrder) => workOrder.dueDate && workOrder.status !== "COMPLETE" && workOrder.status !== "CANCELLED" && new Date(workOrder.dueDate).getTime() < Date.now()).length; const quoteCount = quotes.length; const orderCount = orders.length; const draftQuoteCount = quotes.filter((quote) => quote.status === "DRAFT").length; const approvedQuoteCount = quotes.filter((quote) => quote.status === "APPROVED").length; const issuedOrderCount = orders.filter((order) => order.status === "ISSUED" || order.status === "APPROVED").length; const quoteValue = sumNumber(quotes.map((quote) => quote.total)); const orderValue = sumNumber(orders.map((order) => order.total)); const shipmentCount = shipments.length; const activeShipmentCount = shipments.filter((shipment) => shipment.status !== "DELIVERED").length; const inTransitCount = shipments.filter((shipment) => shipment.status === "SHIPPED").length; const deliveredCount = shipments.filter((shipment) => shipment.status === "DELIVERED").length; const projectCount = projects.length; const activeProjectCount = projects.filter((project) => project.status === "ACTIVE").length; const atRiskProjectCount = projects.filter((project) => project.status === "AT_RISK").length; const overdueProjectCount = projects.filter((project) => { if (!project.dueDate || project.status === "COMPLETE") { return false; } return new Date(project.dueDate).getTime() < Date.now(); }).length; const shortageItemCount = planningRollup?.summary.uncoveredItemCount ?? 0; const buyRecommendationCount = planningRollup?.summary.purchaseRecommendationCount ?? 0; const buildRecommendationCount = planningRollup?.summary.buildRecommendationCount ?? 0; const totalUncoveredQuantity = planningRollup?.summary.totalUncoveredQuantity ?? 0; const planningItemCount = planningRollup?.summary.itemCount ?? 0; const metricCards = [ { label: "Accounts", value: snapshot?.customers !== null ? `${customerCount + vendorCount}` : "No access", secondary: snapshot?.customers !== null ? `${activeCustomerCount} active customers` : "", tone: "bg-emerald-500", }, { label: "Inventory", value: snapshot?.items !== null ? `${itemCount}` : "No access", secondary: snapshot?.items !== null ? `${assemblyCount} buildable items` : "", tone: "bg-sky-500", }, { label: "Open Supply", value: snapshot?.purchaseOrders !== null || snapshot?.workOrders !== null ? `${openPurchaseOrderCount + activeWorkOrderCount}` : "No access", secondary: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount} PO | ${activeWorkOrderCount} WO` : "", tone: "bg-teal-500", }, { label: "Commercial", value: snapshot?.quotes !== null || snapshot?.orders !== null ? formatCurrency(quoteValue + orderValue) : "No access", secondary: snapshot?.orders !== null ? `${orderCount} orders live` : "", tone: "bg-amber-500", }, { label: "Projects", value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access", secondary: snapshot?.projects !== null ? `${atRiskProjectCount} at risk` : "", tone: "bg-violet-500", }, { label: "Readiness", value: planningRollup ? `${shortageItemCount}` : "No access", secondary: planningRollup ? `${totalUncoveredQuantity} units uncovered` : "", tone: "bg-rose-500", }, ]; return (
{error ?
{error}
: null}
{metricCards.map((card) => (

{card.label}

{isLoading ? "Loading..." : card.value}
Live
{card.secondary ?
{card.secondary}
: null}
))}
Quotes
{snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access"}
Draft {draftQuoteCount}
Approved {approvedQuoteCount}
Orders
{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}
Issued / approved
{issuedOrderCount}
Total orders
{orderCount}
Active customers {snapshot?.customers !== null ? formatPercent(activeCustomerCount, Math.max(customerCount, 1)) : "No access"}
Customers
{customerCount}
Resellers
{resellerCount}
Vendors
{vendorCount}
Strategic accounts {strategicCustomerCount}
Item mix
Active items {activeItemCount}
Buildable items {assemblyCount}
Obsolete items {obsoleteItemCount}
Storage surface
Warehouses
{warehouseCount}
Locations
{locationCount}
Open workload split
Open PO queue
{openPurchaseOrderCount}
{formatCurrency(purchaseOrderValue)} committed
Active work orders
{activeWorkOrderCount}
{overdueWorkOrderCount} overdue
Issued / approved POs
{issuedPurchaseOrderCount}
Released WOs
{releasedWorkOrderCount}
Shortage items {planningRollup ? shortageItemCount : "No access"}
Build vs buy
Build recommendations
{planningRollup ? buildRecommendationCount : "No access"}
Buy recommendations
{planningRollup ? buyRecommendationCount : "No access"}
Uncovered quantity
{planningRollup ? totalUncoveredQuantity : "No access"}
Projects
Active
{activeProjectCount}
At risk
{atRiskProjectCount}
Overdue
{overdueProjectCount}
Shipping
Open
{activeShipmentCount}
In transit
{inTransitCount}
Delivered
{deliveredCount}
{[ { label: "Customers", value: customerCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-emerald-500" }, { label: "Inventory items", value: itemCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-sky-500" }, { label: "Sales orders", value: orderCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-amber-500" }, { label: "Purchase orders", value: purchaseOrderCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-teal-500" }, { label: "Work orders", value: workOrderCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-indigo-500" }, { label: "Shipments", value: shipmentCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-brand" }, { label: "Projects", value: projectCount, total: Math.max(customerCount, vendorCount, itemCount, orderCount, purchaseOrderCount, workOrderCount, shipmentCount, projectCount, 1), tone: "bg-violet-500" }, ].map((row) => (
{row.label} {row.value}
))}
{snapshot ?
Refreshed {new Date(snapshot.refreshedAt).toLocaleString()}
: null}
); }