manufacturing
This commit is contained in:
@@ -10,6 +10,7 @@ interface DashboardSnapshot {
|
||||
vendors: Awaited<ReturnType<typeof api.getVendors>> | null;
|
||||
items: Awaited<ReturnType<typeof api.getInventoryItems>> | null;
|
||||
warehouses: Awaited<ReturnType<typeof api.getWarehouses>> | null;
|
||||
workOrders: Awaited<ReturnType<typeof api.getWorkOrders>> | null;
|
||||
quotes: Awaited<ReturnType<typeof api.getQuotes>> | null;
|
||||
orders: Awaited<ReturnType<typeof api.getSalesOrders>> | null;
|
||||
shipments: Awaited<ReturnType<typeof api.getShipments>> | null;
|
||||
@@ -51,6 +52,7 @@ export function DashboardPage() {
|
||||
const [snapshot, setSnapshot] = useState<DashboardSnapshot | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const canWriteManufacturing = hasPermission(user?.permissions, permissions.manufacturingWrite);
|
||||
const canWriteProjects = hasPermission(user?.permissions, permissions.projectsWrite);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +69,7 @@ export function DashboardPage() {
|
||||
|
||||
const canReadCrm = hasPermission(user.permissions, permissions.crmRead);
|
||||
const canReadInventory = hasPermission(user.permissions, permissions.inventoryRead);
|
||||
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);
|
||||
@@ -77,6 +80,7 @@ export function DashboardPage() {
|
||||
canReadCrm ? api.getVendors(authToken) : Promise.resolve(null),
|
||||
canReadInventory ? api.getInventoryItems(authToken) : Promise.resolve(null),
|
||||
canReadInventory ? api.getWarehouses(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),
|
||||
@@ -98,10 +102,11 @@ export function DashboardPage() {
|
||||
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,
|
||||
quotes: results[4].status === "fulfilled" ? results[4].value : null,
|
||||
orders: results[5].status === "fulfilled" ? results[5].value : null,
|
||||
shipments: results[6].status === "fulfilled" ? results[6].value : null,
|
||||
projects: results[7].status === "fulfilled" ? results[7].value : null,
|
||||
workOrders: results[4].status === "fulfilled" ? results[4].value : null,
|
||||
quotes: results[5].status === "fulfilled" ? results[5].value : null,
|
||||
orders: results[6].status === "fulfilled" ? results[6].value : null,
|
||||
shipments: results[7].status === "fulfilled" ? results[7].value : null,
|
||||
projects: results[8].status === "fulfilled" ? results[8].value : null,
|
||||
refreshedAt: new Date().toISOString(),
|
||||
});
|
||||
setIsLoading(false);
|
||||
@@ -126,6 +131,7 @@ export function DashboardPage() {
|
||||
const vendors = snapshot?.vendors ?? [];
|
||||
const items = snapshot?.items ?? [];
|
||||
const warehouses = snapshot?.warehouses ?? [];
|
||||
const workOrders = snapshot?.workOrders ?? [];
|
||||
const quotes = snapshot?.quotes ?? [];
|
||||
const orders = snapshot?.orders ?? [];
|
||||
const shipments = snapshot?.shipments ?? [];
|
||||
@@ -134,6 +140,7 @@ export function DashboardPage() {
|
||||
const accessibleModules = [
|
||||
snapshot?.customers !== null || snapshot?.vendors !== null,
|
||||
snapshot?.items !== null || snapshot?.warehouses !== null,
|
||||
snapshot?.workOrders !== null,
|
||||
snapshot?.quotes !== null || snapshot?.orders !== null,
|
||||
snapshot?.shipments !== null,
|
||||
snapshot?.projects !== null,
|
||||
@@ -152,6 +159,11 @@ export function DashboardPage() {
|
||||
const warehouseCount = warehouses.length;
|
||||
const locationCount = sumNumber(warehouses.map((warehouse) => warehouse.locationCount));
|
||||
|
||||
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 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;
|
||||
@@ -180,6 +192,7 @@ export function DashboardPage() {
|
||||
...vendors.map((vendor) => vendor.updatedAt),
|
||||
...items.map((item) => item.updatedAt),
|
||||
...warehouses.map((warehouse) => warehouse.updatedAt),
|
||||
...workOrders.map((workOrder) => workOrder.updatedAt),
|
||||
...quotes.map((quote) => quote.updatedAt),
|
||||
...orders.map((order) => order.updatedAt),
|
||||
...shipments.map((shipment) => shipment.updatedAt),
|
||||
@@ -207,6 +220,15 @@ export function DashboardPage() {
|
||||
: "Inventory metrics are permission-gated.",
|
||||
tone: "border-sky-400/30 bg-sky-500/12 text-sky-700 dark:text-sky-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",
|
||||
@@ -271,6 +293,23 @@ export function DashboardPage() {
|
||||
{ label: "Open warehouses", to: "/inventory/warehouses" },
|
||||
],
|
||||
},
|
||||
{
|
||||
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" },
|
||||
{ label: "Overdue", value: snapshot?.workOrders !== null ? `${overdueWorkOrderCount}` : "No access" },
|
||||
],
|
||||
links: [
|
||||
{ label: "Open work orders", to: "/manufacturing/work-orders" },
|
||||
...(canWriteManufacturing ? [{ label: "New work order", to: "/manufacturing/work-orders/new" }] : []),
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Sales",
|
||||
eyebrow: "Revenue Flow",
|
||||
@@ -327,7 +366,6 @@ export function DashboardPage() {
|
||||
const futureModules = [
|
||||
"Vendor invoice attachments and supplier exception queues",
|
||||
"Stock transfers, allocations, and cycle counts",
|
||||
"Manufacturing work orders, routings, and bottleneck metrics",
|
||||
"Planning timeline, milestones, and dependency views",
|
||||
"Audit trails, diagnostics, and system health checks",
|
||||
];
|
||||
@@ -350,8 +388,8 @@ export function DashboardPage() {
|
||||
</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, sales, shipping, and project data. It is intentionally modular so future
|
||||
purchasing, manufacturing, and audit slices can slot into the same command surface without a redesign.
|
||||
This landing page now reads directly from live CRM, inventory, manufacturing, sales, shipping, and project data. It is intentionally
|
||||
modular so future purchasing, planning, 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">
|
||||
@@ -380,6 +418,9 @@ export function DashboardPage() {
|
||||
<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>
|
||||
</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>
|
||||
@@ -396,7 +437,7 @@ export function DashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="grid gap-3 xl:grid-cols-5">
|
||||
<section className="grid gap-3 xl:grid-cols-6">
|
||||
{metricCards.map((card) => (
|
||||
<article key={card.label} className="rounded-[24px] 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>
|
||||
@@ -408,7 +449,7 @@ export function DashboardPage() {
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
<section className="grid gap-3 xl:grid-cols-2 2xl:grid-cols-5">
|
||||
<section className="grid gap-3 xl:grid-cols-2 2xl:grid-cols-6">
|
||||
{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>
|
||||
@@ -432,7 +473,7 @@ export function DashboardPage() {
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
<section className="grid gap-3 xl:grid-cols-4">
|
||||
<section className="grid gap-3 xl:grid-cols-5">
|
||||
<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>
|
||||
@@ -469,6 +510,24 @@ export function DashboardPage() {
|
||||
</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>
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user