ui cleanup
This commit is contained in:
@@ -189,7 +189,7 @@ export function AppShell() {
|
||||
return (
|
||||
<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">
|
||||
<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 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>
|
||||
@@ -211,29 +211,28 @@ export function AppShell() {
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
<div className="mt-auto rounded-2xl border border-line/70 bg-page/70 p-4">
|
||||
<p className="text-sm font-semibold text-text">{user?.firstName} {user?.lastName}</p>
|
||||
<p className="text-xs text-muted">{user?.email}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void logout();
|
||||
}}
|
||||
className="mt-4 rounded-xl bg-text px-4 py-2 text-sm font-semibold text-page"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
<div className="mt-auto space-y-3">
|
||||
<div className="rounded-[18px] border border-line/70 bg-page/70 p-3">
|
||||
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted">Theme</p>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<div className="rounded-[18px] border border-line/70 bg-page/70 p-4">
|
||||
<p className="text-sm font-semibold text-text">{user?.firstName} {user?.lastName}</p>
|
||||
<p className="text-xs text-muted">{user?.email}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void logout();
|
||||
}}
|
||||
className="mt-4 rounded-xl bg-text px-4 py-2 text-sm font-semibold text-page"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
{links.map((link) => (
|
||||
<NavLink
|
||||
key={link.to}
|
||||
@@ -249,6 +248,9 @@ export function AppShell() {
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
<div className="mb-4 md:hidden">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,9 @@ export function ThemeToggle() {
|
||||
<button
|
||||
type="button"
|
||||
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"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,19 +33,6 @@ function formatCurrency(value: number) {
|
||||
}).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[]) {
|
||||
return values.reduce((total, value) => total + value, 0);
|
||||
}
|
||||
@@ -148,17 +135,6 @@ export function DashboardPage() {
|
||||
const projects = snapshot?.projects ?? [];
|
||||
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 resellerCount = customers.filter((customer) => customer.isReseller).length;
|
||||
const activeCustomerCount = customers.filter((customer) => customer.lifecycleStage === "ACTIVE").length;
|
||||
@@ -209,91 +185,45 @@ export function DashboardPage() {
|
||||
const buildRecommendationCount = planningRollup?.summary.buildRecommendationCount ?? 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 = [
|
||||
{
|
||||
label: "CRM Accounts",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Inventory Footprint",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Purchasing Queue",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Manufacturing Load",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Commercial Value",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Shipping Queue",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Project Load",
|
||||
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",
|
||||
},
|
||||
{
|
||||
label: "Material Readiness",
|
||||
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",
|
||||
},
|
||||
];
|
||||
@@ -301,11 +231,6 @@ export function DashboardPage() {
|
||||
const modulePanels = [
|
||||
{
|
||||
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: [
|
||||
{ label: "Customers", value: snapshot?.customers !== null ? `${customerCount}` : "No access" },
|
||||
{ label: "Strategic", value: snapshot?.customers !== null ? `${strategicCustomerCount}` : "No access" },
|
||||
@@ -318,11 +243,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Active items", value: snapshot?.items !== null ? `${activeItemCount}` : "No access" },
|
||||
{ label: "Assemblies", value: snapshot?.items !== null ? `${assemblyCount}` : "No access" },
|
||||
@@ -335,11 +255,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Open POs", value: snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access" },
|
||||
{ label: "Issued", value: snapshot?.purchaseOrders !== null ? `${issuedPurchaseOrderCount}` : "No access" },
|
||||
@@ -351,11 +266,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Open work", value: snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access" },
|
||||
{ label: "Released", value: snapshot?.workOrders !== null ? `${releasedWorkOrderCount}` : "No access" },
|
||||
@@ -368,11 +278,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Quote value", value: snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access" },
|
||||
{ label: "Order value", value: snapshot?.orders !== null ? formatCurrency(orderValue) : "No access" },
|
||||
@@ -385,11 +290,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Open shipments", value: snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access" },
|
||||
{ label: "In transit", value: snapshot?.shipments !== null ? `${inTransitCount}` : "No access" },
|
||||
@@ -402,11 +302,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "Active", value: snapshot?.projects !== null ? `${activeProjectCount}` : "No access" },
|
||||
{ label: "At risk", value: snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access" },
|
||||
@@ -419,10 +314,6 @@ export function DashboardPage() {
|
||||
},
|
||||
{
|
||||
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: [
|
||||
{ label: "At risk projects", value: canReadPlanning ? `${atRiskProjectCount}` : "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 (
|
||||
<div className="space-y-4">
|
||||
<section className="overflow-hidden rounded-[30px] border border-line/70 bg-surface/90 shadow-panel backdrop-blur">
|
||||
<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>
|
||||
{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}
|
||||
<section className="grid gap-3 xl:grid-cols-7">
|
||||
{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>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
<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>
|
||||
<p className="mt-2 text-sm text-muted">{card.detail}</p>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
<section className="grid gap-3 xl:grid-cols-2 2xl:grid-cols-7">
|
||||
{modulePanels.map((panel) => (
|
||||
<article key={panel.title} className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">{panel.title}</h4>
|
||||
<p className="mt-3 text-sm leading-6 text-muted">{panel.summary}</p>
|
||||
<div className="mt-5 grid gap-2">
|
||||
<article key={panel.title} className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">{panel.title}</h4>
|
||||
<div className="mt-4 grid gap-2">
|
||||
{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="font-semibold text-text">{metric.value}</span>
|
||||
</div>
|
||||
@@ -550,131 +360,124 @@ export function DashboardPage() {
|
||||
))}
|
||||
</section>
|
||||
<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">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Planning Watch</p>
|
||||
<h4 className="mt-2 text-lg font-bold text-text">Shared shortage and readiness</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Planning</h4>
|
||||
<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="font-semibold text-text">{planningRollup ? `${shortageItemCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{planningRollup ? `${buildRecommendationCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{planningRollup ? `${buyRecommendationCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{planningRollup ? `${totalUncoveredQuantity}` : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Master data pressure points</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Inventory</h4>
|
||||
<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="font-semibold text-text">{snapshot?.items !== null ? `${obsoleteItemCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.warehouses !== null ? `${warehouseCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.warehouses !== null ? `${locationCount}` : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Commercial flow snapshot</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Sales</h4>
|
||||
<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="font-semibold text-text">{snapshot?.orders !== null ? `${issuedOrderCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.quotes !== null ? `${draftQuoteCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Inbound supply and commitment load</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Purchasing</h4>
|
||||
<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="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${purchaseOrderCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.purchaseOrders !== null ? `${openPurchaseOrderCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.purchaseOrders !== null ? formatCurrency(purchaseOrderValue) : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Build execution and due-date pressure</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Manufacturing</h4>
|
||||
<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="font-semibold text-text">{snapshot?.workOrders !== null ? `${workOrderCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.workOrders !== null ? `${activeWorkOrderCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.workOrders !== null ? `${overdueWorkOrderCount}` : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Program status and delivery pressure</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Projects</h4>
|
||||
<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="font-semibold text-text">{snapshot?.projects !== null ? `${projectCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.projects !== null ? `${atRiskProjectCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.projects !== null ? `${overdueProjectCount}` : "No access"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article className="rounded-[28px] 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="mt-2 text-lg font-bold text-text">Execution and delivery status</h4>
|
||||
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<h4 className="text-lg font-bold text-text">Shipping</h4>
|
||||
<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="font-semibold text-text">{snapshot?.shipments !== null ? `${shipmentCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.shipments !== null ? `${activeShipmentCount}` : "No access"}</span>
|
||||
</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="font-semibold text-text">{snapshot?.shipments !== null ? `${deliveredCount}` : "No access"}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user