ui cleanup

This commit is contained in:
2026-03-15 19:58:26 -05:00
parent dcac4f135d
commit e88d949a59
3 changed files with 67 additions and 263 deletions

View File

@@ -189,7 +189,7 @@ export function AppShell() {
return ( return (
<div className="min-h-screen px-4 py-5 xl:px-6 2xl:px-8"> <div className="min-h-screen px-4 py-5 xl:px-6 2xl:px-8">
<div className="mx-auto flex w-full max-w-[1760px] gap-3 2xl:gap-4"> <div className="mx-auto flex w-full max-w-[1760px] gap-3 2xl:gap-4">
<aside className="hidden w-72 shrink-0 flex-col rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur md:flex 2xl:w-80"> <aside className="hidden w-72 shrink-0 flex-col rounded-[22px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur md:flex 2xl:w-80">
<div> <div>
<div className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">MRP Codex</div> <div className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">MRP Codex</div>
<h1 className="mt-2 text-xl font-extrabold text-text">Manufacturing foundation</h1> <h1 className="mt-2 text-xl font-extrabold text-text">Manufacturing foundation</h1>
@@ -211,29 +211,28 @@ export function AppShell() {
</NavLink> </NavLink>
))} ))}
</nav> </nav>
<div className="mt-auto rounded-2xl border border-line/70 bg-page/70 p-4"> <div className="mt-auto space-y-3">
<p className="text-sm font-semibold text-text">{user?.firstName} {user?.lastName}</p> <div className="rounded-[18px] border border-line/70 bg-page/70 p-3">
<p className="text-xs text-muted">{user?.email}</p> <p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted">Theme</p>
<button <ThemeToggle />
type="button" </div>
onClick={() => { <div className="rounded-[18px] border border-line/70 bg-page/70 p-4">
void logout(); <p className="text-sm font-semibold text-text">{user?.firstName} {user?.lastName}</p>
}} <p className="text-xs text-muted">{user?.email}</p>
className="mt-4 rounded-xl bg-text px-4 py-2 text-sm font-semibold text-page" <button
> type="button"
Sign out onClick={() => {
</button> void logout();
}}
className="mt-4 rounded-xl bg-text px-4 py-2 text-sm font-semibold text-page"
>
Sign out
</button>
</div>
</div> </div>
</aside> </aside>
<main className="min-w-0 flex-1"> <main className="min-w-0 flex-1">
<div className="mb-4 flex items-center justify-between rounded-[28px] border border-line/70 bg-surface/90 px-2 py-2 shadow-panel backdrop-blur 2xl:px-3"> <nav className="mb-4 flex gap-3 overflow-x-auto rounded-[20px] border border-line/70 bg-surface/85 p-3 shadow-panel backdrop-blur md:hidden">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">Operations Command</p>
<h2 className="text-lg font-bold text-text">Foundation Console</h2>
</div>
<ThemeToggle />
</div>
<nav className="mb-4 flex gap-3 overflow-x-auto rounded-[24px] border border-line/70 bg-surface/85 p-3 shadow-panel backdrop-blur md:hidden">
{links.map((link) => ( {links.map((link) => (
<NavLink <NavLink
key={link.to} key={link.to}
@@ -249,6 +248,9 @@ export function AppShell() {
</NavLink> </NavLink>
))} ))}
</nav> </nav>
<div className="mb-4 md:hidden">
<ThemeToggle />
</div>
<Outlet /> <Outlet />
</main> </main>
</div> </div>

View File

@@ -7,10 +7,9 @@ export function ThemeToggle() {
<button <button
type="button" type="button"
onClick={toggleMode} onClick={toggleMode}
className="rounded-full border border-line/70 bg-surface px-4 py-2 text-sm font-semibold text-text transition hover:border-brand/60" className="w-full rounded-xl border border-line/70 bg-page/70 px-3 py-2 text-sm font-semibold text-text transition hover:border-brand/60"
> >
{mode === "light" ? "Dark mode" : "Light mode"} {mode === "light" ? "Dark mode" : "Light mode"}
</button> </button>
); );
} }

View File

@@ -33,19 +33,6 @@ function formatCurrency(value: number) {
}).format(value); }).format(value);
} }
function formatDateTime(value: string | null) {
if (!value) {
return "No recent activity";
}
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
}).format(new Date(value));
}
function sumNumber(values: number[]) { function sumNumber(values: number[]) {
return values.reduce((total, value) => total + value, 0); return values.reduce((total, value) => total + value, 0);
} }
@@ -148,17 +135,6 @@ export function DashboardPage() {
const projects = snapshot?.projects ?? []; const projects = snapshot?.projects ?? [];
const planningRollup = snapshot?.planningRollup; const planningRollup = snapshot?.planningRollup;
const accessibleModules = [
snapshot?.customers !== null || snapshot?.vendors !== null,
snapshot?.items !== null || snapshot?.warehouses !== null,
snapshot?.purchaseOrders !== null,
snapshot?.workOrders !== null,
snapshot?.quotes !== null || snapshot?.orders !== null,
snapshot?.shipments !== null,
snapshot?.projects !== null,
canReadPlanning,
].filter(Boolean).length;
const customerCount = customers.length; const customerCount = customers.length;
const resellerCount = customers.filter((customer) => customer.isReseller).length; const resellerCount = customers.filter((customer) => customer.isReseller).length;
const activeCustomerCount = customers.filter((customer) => customer.lifecycleStage === "ACTIVE").length; const activeCustomerCount = customers.filter((customer) => customer.lifecycleStage === "ACTIVE").length;
@@ -209,91 +185,45 @@ export function DashboardPage() {
const buildRecommendationCount = planningRollup?.summary.buildRecommendationCount ?? 0; const buildRecommendationCount = planningRollup?.summary.buildRecommendationCount ?? 0;
const totalUncoveredQuantity = planningRollup?.summary.totalUncoveredQuantity ?? 0; const totalUncoveredQuantity = planningRollup?.summary.totalUncoveredQuantity ?? 0;
const lastActivityAt = [
...customers.map((customer) => customer.updatedAt),
...vendors.map((vendor) => vendor.updatedAt),
...items.map((item) => item.updatedAt),
...warehouses.map((warehouse) => warehouse.updatedAt),
...purchaseOrders.map((order) => order.updatedAt),
...workOrders.map((workOrder) => workOrder.updatedAt),
...quotes.map((quote) => quote.updatedAt),
...orders.map((order) => order.updatedAt),
...shipments.map((shipment) => shipment.updatedAt),
...projects.map((project) => project.updatedAt),
]
.sort()
.at(-1) ?? null;
const metricCards = [ const metricCards = [
{ {
label: "CRM Accounts", label: "CRM Accounts",
value: snapshot?.customers !== null ? `${customerCount}` : "No access", value: snapshot?.customers !== null ? `${customerCount}` : "No access",
detail:
snapshot?.customers !== null
? `${vendorCount} vendors, ${resellerCount} resellers, ${activeCustomerCount} active`
: "CRM metrics are permission-gated.",
tone: "border-emerald-400/30 bg-emerald-500/12 text-emerald-700 dark:text-emerald-300", tone: "border-emerald-400/30 bg-emerald-500/12 text-emerald-700 dark:text-emerald-300",
}, },
{ {
label: "Inventory Footprint", label: "Inventory Footprint",
value: snapshot?.items !== null ? `${itemCount}` : "No access", value: snapshot?.items !== null ? `${itemCount}` : "No access",
detail:
snapshot?.items !== null
? `${assemblyCount} buildable items across ${warehouseCount} warehouses`
: "Inventory metrics are permission-gated.",
tone: "border-sky-400/30 bg-sky-500/12 text-sky-700 dark:text-sky-300", tone: "border-sky-400/30 bg-sky-500/12 text-sky-700 dark:text-sky-300",
}, },
{ {
label: "Purchasing Queue", label: "Purchasing Queue",
value: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access", value: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access",
detail:
snapshot?.purchaseOrders !== null
? `${issuedPurchaseOrderCount} issued/approved and ${formatCurrency(purchaseOrderValue)} committed`
: "Purchasing metrics are permission-gated.",
tone: "border-teal-400/30 bg-teal-500/12 text-teal-700 dark:text-teal-300", tone: "border-teal-400/30 bg-teal-500/12 text-teal-700 dark:text-teal-300",
}, },
{ {
label: "Manufacturing Load", label: "Manufacturing Load",
value: snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access", value: snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access",
detail:
snapshot?.workOrders !== null
? `${releasedWorkOrderCount} released and ${overdueWorkOrderCount} overdue`
: "Manufacturing metrics are permission-gated.",
tone: "border-indigo-400/30 bg-indigo-500/12 text-indigo-700 dark:text-indigo-300", tone: "border-indigo-400/30 bg-indigo-500/12 text-indigo-700 dark:text-indigo-300",
}, },
{ {
label: "Commercial Value", label: "Commercial Value",
value: snapshot?.quotes !== null || snapshot?.orders !== null ? formatCurrency(quoteValue + orderValue) : "No access", value: snapshot?.quotes !== null || snapshot?.orders !== null ? formatCurrency(quoteValue + orderValue) : "No access",
detail:
snapshot?.quotes !== null || snapshot?.orders !== null
? `${quoteCount} quotes and ${orderCount} orders in the pipeline`
: "Sales metrics are permission-gated.",
tone: "border-amber-400/30 bg-amber-500/12 text-amber-700 dark:text-amber-300", tone: "border-amber-400/30 bg-amber-500/12 text-amber-700 dark:text-amber-300",
}, },
{ {
label: "Shipping Queue", label: "Shipping Queue",
value: snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access", value: snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access",
detail:
snapshot?.shipments !== null
? `${inTransitCount} in transit, ${deliveredCount} delivered`
: "Shipping metrics are permission-gated.",
tone: "border-brand/30 bg-brand/10 text-brand", tone: "border-brand/30 bg-brand/10 text-brand",
}, },
{ {
label: "Project Load", label: "Project Load",
value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access", value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access",
detail:
snapshot?.projects !== null
? `${atRiskProjectCount} at risk and ${overdueProjectCount} overdue`
: "Project metrics are permission-gated.",
tone: "border-violet-400/30 bg-violet-500/12 text-violet-700 dark:text-violet-300", tone: "border-violet-400/30 bg-violet-500/12 text-violet-700 dark:text-violet-300",
}, },
{ {
label: "Material Readiness", label: "Material Readiness",
value: planningRollup ? `${shortageItemCount}` : "No access", value: planningRollup ? `${shortageItemCount}` : "No access",
detail: planningRollup
? `${buildRecommendationCount} build and ${buyRecommendationCount} buy recommendations`
: "Sales read permission is required to surface shortage rollups.",
tone: "border-rose-400/30 bg-rose-500/12 text-rose-700 dark:text-rose-300", tone: "border-rose-400/30 bg-rose-500/12 text-rose-700 dark:text-rose-300",
}, },
]; ];
@@ -301,11 +231,6 @@ export function DashboardPage() {
const modulePanels = [ const modulePanels = [
{ {
title: "CRM", title: "CRM",
eyebrow: "Account Health",
summary:
snapshot?.customers !== null
? "Live account counts, reseller coverage, and strategic-account concentration from the current CRM records."
: "CRM read permission is required to surface customer and vendor metrics here.",
metrics: [ metrics: [
{ label: "Customers", value: snapshot?.customers !== null ? `${customerCount}` : "No access" }, { label: "Customers", value: snapshot?.customers !== null ? `${customerCount}` : "No access" },
{ label: "Strategic", value: snapshot?.customers !== null ? `${strategicCustomerCount}` : "No access" }, { label: "Strategic", value: snapshot?.customers !== null ? `${strategicCustomerCount}` : "No access" },
@@ -318,11 +243,6 @@ export function DashboardPage() {
}, },
{ {
title: "Inventory", title: "Inventory",
eyebrow: "Master + Stock",
summary:
snapshot?.items !== null
? "Item master, BOM-capable parts, and warehouse footprint are now feeding the dashboard directly."
: "Inventory read permission is required to surface item and warehouse metrics here.",
metrics: [ metrics: [
{ label: "Active items", value: snapshot?.items !== null ? `${activeItemCount}` : "No access" }, { label: "Active items", value: snapshot?.items !== null ? `${activeItemCount}` : "No access" },
{ label: "Assemblies", value: snapshot?.items !== null ? `${assemblyCount}` : "No access" }, { label: "Assemblies", value: snapshot?.items !== null ? `${assemblyCount}` : "No access" },
@@ -335,11 +255,6 @@ export function DashboardPage() {
}, },
{ {
title: "Purchasing", title: "Purchasing",
eyebrow: "Inbound Supply",
summary:
snapshot?.purchaseOrders !== null
? "Purchase orders, open commitments, and current inbound procurement load are now visible from the dashboard."
: "Purchasing read permission is required to surface procurement metrics here.",
metrics: [ metrics: [
{ label: "Open POs", value: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access" }, { label: "Open POs", value: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access" },
{ label: "Issued", value: snapshot?.purchaseOrders !== null ? `${issuedPurchaseOrderCount}` : "No access" }, { label: "Issued", value: snapshot?.purchaseOrders !== null ? `${issuedPurchaseOrderCount}` : "No access" },
@@ -351,11 +266,6 @@ export function DashboardPage() {
}, },
{ {
title: "Manufacturing", title: "Manufacturing",
eyebrow: "Execution Load",
summary:
snapshot?.workOrders !== null
? "Work orders, released load, and overdue build pressure are now visible from the dashboard."
: "Manufacturing read permission is required to surface work-order metrics here.",
metrics: [ metrics: [
{ label: "Open work", value: snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access" }, { label: "Open work", value: snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access" },
{ label: "Released", value: snapshot?.workOrders !== null ? `${releasedWorkOrderCount}` : "No access" }, { label: "Released", value: snapshot?.workOrders !== null ? `${releasedWorkOrderCount}` : "No access" },
@@ -368,11 +278,6 @@ export function DashboardPage() {
}, },
{ {
title: "Sales", title: "Sales",
eyebrow: "Revenue Flow",
summary:
snapshot?.quotes !== null || snapshot?.orders !== null
? "Quotes and sales orders now contribute real commercial value, open-document counts, and pipeline visibility."
: "Sales read permission is required to surface commercial metrics here.",
metrics: [ metrics: [
{ label: "Quote value", value: snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access" }, { label: "Quote value", value: snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access" },
{ label: "Order value", value: snapshot?.orders !== null ? formatCurrency(orderValue) : "No access" }, { label: "Order value", value: snapshot?.orders !== null ? formatCurrency(orderValue) : "No access" },
@@ -385,11 +290,6 @@ export function DashboardPage() {
}, },
{ {
title: "Shipping", title: "Shipping",
eyebrow: "Execution Queue",
summary:
snapshot?.shipments !== null
? "Shipment records, in-transit volume, and completed deliveries are now visible from the landing page."
: "Shipping read permission is required to surface shipment metrics here.",
metrics: [ metrics: [
{ label: "Open shipments", value: snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access" }, { label: "Open shipments", value: snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access" },
{ label: "In transit", value: snapshot?.shipments !== null ? `${inTransitCount}` : "No access" }, { label: "In transit", value: snapshot?.shipments !== null ? `${inTransitCount}` : "No access" },
@@ -402,11 +302,6 @@ export function DashboardPage() {
}, },
{ {
title: "Projects", title: "Projects",
eyebrow: "Program Control",
summary:
snapshot?.projects !== null
? "Project records now tie customers, commercial documents, shipment context, and delivery ownership into one operational surface."
: "Project read permission is required to surface program metrics here.",
metrics: [ metrics: [
{ label: "Active", value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access" }, { label: "Active", value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access" },
{ label: "At risk", value: snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access" }, { label: "At risk", value: snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access" },
@@ -419,10 +314,6 @@ export function DashboardPage() {
}, },
{ {
title: "Planning", title: "Planning",
eyebrow: "Schedule Visibility",
summary: canReadPlanning
? "Live gantt planning now pulls directly from active projects and open manufacturing work orders, with shared shortage/readiness rollups alongside schedule pressure."
: "Planning read permission is required to surface the live gantt schedule.",
metrics: [ metrics: [
{ label: "At risk projects", value: canReadPlanning ? `${atRiskProjectCount}` : "No access" }, { label: "At risk projects", value: canReadPlanning ? `${atRiskProjectCount}` : "No access" },
{ label: "Shortage items", value: canReadPlanning && planningRollup ? `${shortageItemCount}` : "No access" }, { label: "Shortage items", value: canReadPlanning && planningRollup ? `${shortageItemCount}` : "No access" },
@@ -432,108 +323,27 @@ export function DashboardPage() {
}, },
]; ];
const futureModules = [
"Stock transfers, allocations, and cycle counts",
"Revision comparison and document restore tooling",
"Audit trails, diagnostics, and system health checks",
];
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<section className="overflow-hidden rounded-[30px] border border-line/70 bg-surface/90 shadow-panel backdrop-blur"> {error ? <div className="rounded-[18px] border border-amber-400/30 bg-amber-500/12 px-3 py-3 text-sm text-amber-700 dark:text-amber-300">{error}</div> : null}
<div className="grid gap-0 xl:grid-cols-[1.35fr_0.65fr]">
<div className="relative overflow-hidden p-5 2xl:p-6">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(24,90,219,0.16),_transparent_44%),linear-gradient(135deg,rgba(255,255,255,0.06),transparent)]" />
<div className="relative">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Dashboard</p>
<h3 className="mt-2 max-w-3xl text-2xl font-extrabold text-text">Operational command surface for metrics, movement, and next actions.</h3>
</div>
<div className="rounded-2xl border border-line/70 bg-surface/80 px-2 py-2 text-right">
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-muted">Last Refresh</p>
<p className="mt-1 text-sm font-semibold text-text">{snapshot ? formatDateTime(snapshot.refreshedAt) : "Waiting"}</p>
</div>
</div>
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted">
This landing page now reads directly from live CRM, inventory, purchasing, manufacturing, sales, shipping, and project data. It is
intentionally modular so future planning, approvals, and audit slices can slot into the same command surface without a redesign.
</p>
<div className="mt-5 grid gap-2 sm:grid-cols-3">
<div className="rounded-2xl border border-line/70 bg-surface/80 px-2 py-2">
<p className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted">Modules Live</p>
<p className="mt-1 text-lg font-extrabold text-text">{accessibleModules}</p>
</div>
<div className="rounded-2xl border border-line/70 bg-surface/80 px-2 py-2">
<p className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted">Recent Activity</p>
<p className="mt-1 text-sm font-semibold text-text">{formatDateTime(lastActivityAt)}</p>
</div>
<div className="rounded-2xl border border-line/70 bg-surface/80 px-2 py-2">
<p className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted">Loading State</p>
<p className="mt-1 text-sm font-semibold text-text">{isLoading ? "Refreshing data" : "Live snapshot loaded"}</p>
</div>
</div>
<div className="mt-5 flex flex-wrap gap-3">
<Link className="rounded-2xl bg-brand px-3 py-2 text-sm font-semibold text-white" to="/sales/orders">
Open sales orders
</Link>
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/shipping/shipments">
Open shipments
</Link>
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/inventory/items">
Open inventory
</Link>
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/purchasing/orders">
Open purchasing
</Link>
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/projects">
Open projects
</Link>
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/manufacturing/work-orders">
Open manufacturing
</Link>
{canReadPlanning ? (
<Link className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text" to="/planning/gantt">
Open planning
</Link>
) : null}
</div>
{error ? <div className="mt-4 rounded-2xl border border-amber-400/30 bg-amber-500/12 px-2 py-2 text-sm text-amber-700 dark:text-amber-300">{error}</div> : null}
</div>
</div>
<div className="border-l border-line/70 bg-page/40 p-5 2xl:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Upgrade Path</p>
<div className="mt-4 space-y-3">
{futureModules.map((item) => (
<div key={item} className="rounded-2xl border border-line/70 bg-surface/80 px-2 py-2 text-sm text-text">
{item}
</div>
))}
</div>
</div>
</div>
</section>
<section className="grid gap-3 xl:grid-cols-7"> <section className="grid gap-3 xl:grid-cols-7">
{metricCards.map((card) => ( {metricCards.map((card) => (
<article key={card.label} className="rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel"> <article key={card.label} className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">{card.label}</p> <p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">{card.label}</p>
<div className="mt-2 flex items-center justify-between gap-3"> <div className="mt-2 flex items-center justify-between gap-3">
<div className="text-xl font-extrabold text-text">{card.value}</div> <div className="text-xl font-extrabold text-text">{card.value}</div>
<span className={`rounded-full px-2 py-1 text-xs font-semibold ${card.tone}`}>Live</span> <span className={`rounded-lg px-2 py-1 text-xs font-semibold ${card.tone}`}>Live</span>
</div> </div>
<p className="mt-2 text-sm text-muted">{card.detail}</p>
</article> </article>
))} ))}
</section> </section>
<section className="grid gap-3 xl:grid-cols-2 2xl:grid-cols-7"> <section className="grid gap-3 xl:grid-cols-2 2xl:grid-cols-7">
{modulePanels.map((panel) => ( {modulePanels.map((panel) => (
<article key={panel.title} className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article key={panel.title} className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{panel.eyebrow}</p> <h4 className="text-lg font-bold text-text">{panel.title}</h4>
<h4 className="mt-2 text-lg font-bold text-text">{panel.title}</h4> <div className="mt-4 grid gap-2">
<p className="mt-3 text-sm leading-6 text-muted">{panel.summary}</p>
<div className="mt-5 grid gap-2">
{panel.metrics.map((metric) => ( {panel.metrics.map((metric) => (
<div key={metric.label} className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div key={metric.label} className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">{metric.label}</span> <span className="text-muted">{metric.label}</span>
<span className="font-semibold text-text">{metric.value}</span> <span className="font-semibold text-text">{metric.value}</span>
</div> </div>
@@ -550,131 +360,124 @@ export function DashboardPage() {
))} ))}
</section> </section>
<section className="grid gap-3 xl:grid-cols-6"> <section className="grid gap-3 xl:grid-cols-6">
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Planning Watch</p> <h4 className="text-lg font-bold text-text">Planning</h4>
<h4 className="mt-2 text-lg font-bold text-text">Shared shortage and readiness</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Shortage items</span> <span className="text-muted">Shortage items</span>
<span className="font-semibold text-text">{planningRollup ? `${shortageItemCount}` : "No access"}</span> <span className="font-semibold text-text">{planningRollup ? `${shortageItemCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Build recommendations</span> <span className="text-muted">Build recommendations</span>
<span className="font-semibold text-text">{planningRollup ? `${buildRecommendationCount}` : "No access"}</span> <span className="font-semibold text-text">{planningRollup ? `${buildRecommendationCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Buy recommendations</span> <span className="text-muted">Buy recommendations</span>
<span className="font-semibold text-text">{planningRollup ? `${buyRecommendationCount}` : "No access"}</span> <span className="font-semibold text-text">{planningRollup ? `${buyRecommendationCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Uncovered qty</span> <span className="text-muted">Uncovered qty</span>
<span className="font-semibold text-text">{planningRollup ? `${totalUncoveredQuantity}` : "No access"}</span> <span className="font-semibold text-text">{planningRollup ? `${totalUncoveredQuantity}` : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Inventory Watch</p> <h4 className="text-lg font-bold text-text">Inventory</h4>
<h4 className="mt-2 text-lg font-bold text-text">Master data pressure points</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Obsolete items</span> <span className="text-muted">Obsolete items</span>
<span className="font-semibold text-text">{snapshot?.items !== null ? `${obsoleteItemCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.items !== null ? `${obsoleteItemCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Warehouse count</span> <span className="text-muted">Warehouse count</span>
<span className="font-semibold text-text">{snapshot?.warehouses !== null ? `${warehouseCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.warehouses !== null ? `${warehouseCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Stock locations</span> <span className="text-muted">Stock locations</span>
<span className="font-semibold text-text">{snapshot?.warehouses !== null ? `${locationCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.warehouses !== null ? `${locationCount}` : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Sales Watch</p> <h4 className="text-lg font-bold text-text">Sales</h4>
<h4 className="mt-2 text-lg font-bold text-text">Commercial flow snapshot</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Issued orders</span> <span className="text-muted">Issued orders</span>
<span className="font-semibold text-text">{snapshot?.orders !== null ? `${issuedOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.orders !== null ? `${issuedOrderCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Draft quotes</span> <span className="text-muted">Draft quotes</span>
<span className="font-semibold text-text">{snapshot?.quotes !== null ? `${draftQuoteCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.quotes !== null ? `${draftQuoteCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Order backlog</span> <span className="text-muted">Order backlog</span>
<span className="font-semibold text-text">{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}</span> <span className="font-semibold text-text">{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchasing Watch</p> <h4 className="text-lg font-bold text-text">Purchasing</h4>
<h4 className="mt-2 text-lg font-bold text-text">Inbound supply and commitment load</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Total purchase orders</span> <span className="text-muted">Total purchase orders</span>
<span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${purchaseOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${purchaseOrderCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Open queue</span> <span className="text-muted">Open queue</span>
<span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Committed value</span> <span className="text-muted">Committed value</span>
<span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? formatCurrency(purchaseOrderValue) : "No access"}</span> <span className="font-semibold text-text">{snapshot?.purchaseOrders !== null ? formatCurrency(purchaseOrderValue) : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing Watch</p> <h4 className="text-lg font-bold text-text">Manufacturing</h4>
<h4 className="mt-2 text-lg font-bold text-text">Build execution and due-date pressure</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Total work orders</span> <span className="text-muted">Total work orders</span>
<span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${workOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${workOrderCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Active queue</span> <span className="text-muted">Active queue</span>
<span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Overdue</span> <span className="text-muted">Overdue</span>
<span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${overdueWorkOrderCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.workOrders !== null ? `${overdueWorkOrderCount}` : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Project Watch</p> <h4 className="text-lg font-bold text-text">Projects</h4>
<h4 className="mt-2 text-lg font-bold text-text">Program status and delivery pressure</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Total projects</span> <span className="text-muted">Total projects</span>
<span className="font-semibold text-text">{snapshot?.projects !== null ? `${projectCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.projects !== null ? `${projectCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">At risk</span> <span className="text-muted">At risk</span>
<span className="font-semibold text-text">{snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Overdue</span> <span className="text-muted">Overdue</span>
<span className="font-semibold text-text">{snapshot?.projects !== null ? `${overdueProjectCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.projects !== null ? `${overdueProjectCount}` : "No access"}</span>
</div> </div>
</div> </div>
</article> </article>
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping Watch</p> <h4 className="text-lg font-bold text-text">Shipping</h4>
<h4 className="mt-2 text-lg font-bold text-text">Execution and delivery status</h4>
<div className="mt-4 grid gap-2"> <div className="mt-4 grid gap-2">
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Total shipments</span> <span className="text-muted">Total shipments</span>
<span className="font-semibold text-text">{snapshot?.shipments !== null ? `${shipmentCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.shipments !== null ? `${shipmentCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Open queue</span> <span className="text-muted">Open queue</span>
<span className="font-semibold text-text">{snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access"}</span>
</div> </div>
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"> <div className="flex items-center justify-between rounded-xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<span className="text-muted">Delivered</span> <span className="text-muted">Delivered</span>
<span className="font-semibold text-text">{snapshot?.shipments !== null ? `${deliveredCount}` : "No access"}</span> <span className="font-semibold text-text">{snapshot?.shipments !== null ? `${deliveredCount}` : "No access"}</span>
</div> </div>