This commit is contained in:
2026-03-18 20:36:30 -05:00
parent 69dfec98ad
commit 1e408d5316
28 changed files with 346 additions and 389 deletions

View File

@@ -200,19 +200,19 @@ export function AppShell() {
const { user, logout } = useAuth();
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-[22px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur md:flex 2xl:w-80">
<div className="min-h-screen px-3 py-3 xl:px-4 2xl:px-5">
<div className="mx-auto flex w-full max-w-[1760px] gap-2.5 2xl:gap-3">
<aside className="hidden w-72 shrink-0 flex-col rounded-[20px] border border-line/70 bg-surface/90 p-3 shadow-panel backdrop-blur md:flex 2xl:w-80">
<div>
<h1 className="text-xl font-extrabold uppercase tracking-[0.24em] text-text">CODEXIUM</h1>
</div>
<nav className="mt-6 space-y-2">
<nav className="mt-4 space-y-1.5">
{links.map((link) => (
<NavLink
key={link.to}
to={link.to}
className={({ isActive }) =>
`flex items-center gap-2 rounded-2xl px-2 py-2 text-sm font-semibold transition ${
`flex items-center gap-2 rounded-xl px-2.5 py-2 text-[12px] font-semibold uppercase tracking-[0.12em] transition ${
isActive ? "bg-brand text-white" : "text-text hover:bg-page"
}`
}
@@ -222,12 +222,12 @@ export function AppShell() {
</NavLink>
))}
</nav>
<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>
<div className="mt-auto space-y-2.5">
<div className="rounded-[16px] border border-line/70 bg-page/70 p-2.5">
<p className="mb-2 text-[11px] 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">
<div className="rounded-[16px] border border-line/70 bg-page/70 p-3">
<p className="text-sm font-semibold text-text">{user?.firstName} {user?.lastName}</p>
<p className="text-xs text-muted">{user?.email}</p>
<button
@@ -235,7 +235,7 @@ export function AppShell() {
onClick={() => {
void logout();
}}
className="mt-4 rounded-xl bg-text px-4 py-2 text-sm font-semibold text-page"
className="mt-3 rounded-xl bg-text px-3 py-2 text-sm font-semibold text-page"
>
Sign out
</button>
@@ -243,13 +243,13 @@ export function AppShell() {
</div>
</aside>
<main className="min-w-0 flex-1">
<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">
<nav className="mb-3 flex gap-2 overflow-x-auto rounded-[18px] border border-line/70 bg-surface/85 p-2.5 shadow-panel backdrop-blur md:hidden">
{links.map((link) => (
<NavLink
key={link.to}
to={link.to}
className={({ isActive }) =>
`inline-flex whitespace-nowrap items-center gap-2 rounded-2xl px-4 py-2 text-sm font-semibold transition ${
`inline-flex whitespace-nowrap items-center gap-2 rounded-xl px-3 py-2 text-[12px] font-semibold uppercase tracking-[0.12em] transition ${
isActive ? "bg-brand text-white" : "bg-page/70 text-text"
}`
}
@@ -259,7 +259,7 @@ export function AppShell() {
</NavLink>
))}
</nav>
<div className="mb-4 md:hidden">
<div className="mb-3 md:hidden">
<ThemeToggle />
</div>
<Outlet />

View File

@@ -4,6 +4,32 @@
@tailwind components;
@tailwind utilities;
@layer components {
.page-stack {
@apply space-y-3;
}
.surface-panel {
@apply rounded-[18px] border border-line/70 bg-surface/90 p-3 shadow-panel;
}
.surface-panel-tight {
@apply rounded-[16px] border border-line/70 bg-page/60 px-3 py-2.5;
}
.section-kicker {
@apply text-[11px] font-semibold uppercase tracking-[0.24em] text-muted;
}
.metric-kicker {
@apply text-[11px] font-semibold uppercase tracking-[0.18em] text-muted;
}
.module-title {
@apply mt-1 text-xl font-bold uppercase tracking-[0.08em] text-text;
}
}
:root {
color-scheme: light;
--font-family: "Manrope";

View File

@@ -111,21 +111,19 @@ export function CrmDetailPage({ entity }: CrmDetailPageProps) {
}
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM Detail</p>
<h3 className="mt-2 text-2xl font-bold text-text">{record.name}</h3>
<div className="mt-4">
<div className="flex flex-wrap gap-3">
<p className="section-kicker">CRM DETAIL</p>
<h3 className="module-title">{record.name}</h3>
<div className="mt-2.5">
<div className="flex flex-wrap gap-2">
<CrmStatusBadge status={record.status} />
{record.lifecycleStage ? <CrmLifecycleBadge stage={record.lifecycleStage} /> : null}
</div>
</div>
<p className="mt-2 text-sm text-muted">
{config.singularLabel} record last updated {new Date(record.updatedAt).toLocaleString()}.
</p>
<p className="mt-2 text-sm text-muted">UPDATED {new Date(record.updatedAt).toLocaleString()}</p>
</div>
<div className="flex flex-wrap gap-3">
<Link
@@ -146,8 +144,8 @@ export function CrmDetailPage({ entity }: CrmDetailPageProps) {
</div>
</div>
<div className="grid gap-3 2xl:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)]">
<article className="min-w-0 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">Contact</p>
<article className="surface-panel min-w-0">
<p className="section-kicker">CONTACT</p>
<dl className="mt-5 grid gap-3 xl:grid-cols-2">
<div>
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Email</dt>
@@ -176,8 +174,8 @@ export function CrmDetailPage({ entity }: CrmDetailPageProps) {
</div>
</dl>
</article>
<article className="min-w-0 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">Internal Notes</p>
<article className="surface-panel min-w-0">
<p className="section-kicker">INTERNAL NOTES</p>
<p className="mt-3 whitespace-pre-line text-sm leading-6 text-text">
{record.notes || "No internal notes recorded for this account yet."}
</p>

View File

@@ -110,17 +110,14 @@ export function CrmFormPage({ entity, mode }: CrmFormPageProps) {
}
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<form className="page-stack" onSubmit={handleSubmit}>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM Editor</p>
<h3 className="mt-2 text-xl font-bold text-text">
<p className="section-kicker">CRM EDITOR</p>
<h3 className="module-title">
{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}
</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">
Capture the operational contact and address details needed for quoting, purchasing, and shipping workflows.
</p>
</div>
<Link
to={mode === "create" ? config.routeBase : `${config.routeBase}/${recordId}`}
@@ -130,9 +127,9 @@ export function CrmFormPage({ entity, mode }: CrmFormPageProps) {
</Link>
</div>
</section>
<section className="space-y-4 rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="space-y-3 surface-panel">
<CrmRecordForm entity={entity} form={form} hierarchyOptions={hierarchyOptions} onChange={updateField} />
<div className="flex flex-col gap-3 rounded-2xl border border-line/70 bg-page/70 px-2 py-2 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-col gap-2.5 rounded-[16px] border border-line/70 bg-page/70 px-3 py-2 sm:flex-row sm:items-center sm:justify-between">
<span className="min-w-0 text-sm text-muted">{status}</span>
<button
type="submit"

View File

@@ -55,14 +55,11 @@ export function CrmListPage({ entity }: CrmListPageProps) {
}, [config.collectionLabel, entity, lifecycleFilter, operationalFilter, searchTerm, stateFilter, statusFilter, token]);
return (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM</p>
<h3 className="mt-2 text-lg font-bold text-text">{config.collectionLabel}</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">
Operational contact records, shipping addresses, and account context for active {config.collectionLabel.toLowerCase()}.
</p>
<p className="section-kicker">CRM</p>
<h3 className="module-title">{config.collectionLabel}</h3>
</div>
{canManage ? (
<Link
@@ -73,7 +70,7 @@ export function CrmListPage({ entity }: CrmListPageProps) {
</Link>
) : null}
</div>
<div className="mt-6 grid gap-3 rounded-[18px] border border-line/70 bg-page/60 p-3 xl:grid-cols-[1.35fr_0.8fr_0.8fr_0.9fr_0.9fr]">
<div className="mt-4 grid gap-2.5 rounded-[16px] border border-line/70 bg-page/60 p-2.5 xl:grid-cols-[1.35fr_0.8fr_0.8fr_0.9fr_0.9fr]">
<label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input
@@ -137,13 +134,13 @@ export function CrmListPage({ entity }: CrmListPageProps) {
</select>
</label>
</div>
<div className="mt-6 rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-4 rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-sm text-muted">{status}</div>
{records.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">
{config.emptyMessage}
</div>
) : (
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>

View File

@@ -82,19 +82,16 @@ function StackedBar({
function DashboardCard({
eyebrow,
title,
children,
className = "",
}: {
eyebrow: string;
title: string;
children: ReactNode;
className?: string;
}) {
return (
<article className={`rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5 ${className}`.trim()}>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{eyebrow}</p>
<h3 className="mt-2 text-lg font-bold text-text">{title}</h3>
<article className={`surface-panel ${className}`.trim()}>
<p className="section-kicker">{eyebrow}</p>
{children}
</article>
);
@@ -290,30 +287,30 @@ export function DashboardPage() {
];
return (
<div className="space-y-4">
{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="page-stack">
{error ? <div className="rounded-[16px] border border-amber-400/30 bg-amber-500/12 px-3 py-2.5 text-sm text-amber-700 dark:text-amber-300">{error}</div> : null}
<section className="grid gap-3 xl:grid-cols-6">
{metricCards.map((card) => (
<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 text-xl font-extrabold text-text">{isLoading ? "Loading..." : card.value}</div>
<div className="mt-2 flex items-center gap-3">
<article key={card.label} className="surface-panel-tight">
<p className="metric-kicker">{card.label}</p>
<div className="mt-1.5 text-xl font-extrabold text-text">{isLoading ? "Loading..." : card.value}</div>
<div className="mt-1.5 flex items-center gap-2.5">
<div className="h-2 flex-1 overflow-hidden rounded-full bg-page/80">
<div className={`h-full rounded-full ${card.tone}`} style={{ width: isLoading ? "35%" : "100%" }} />
</div>
<span className="text-xs text-muted">Live</span>
</div>
{card.secondary ? <div className="mt-2 text-xs text-muted">{card.secondary}</div> : null}
{card.secondary ? <div className="mt-1.5 text-xs text-muted">{card.secondary}</div> : null}
</article>
))}
</section>
<section className="grid gap-3 xl:grid-cols-[1.2fr_0.8fr]">
<DashboardCard eyebrow="Commercial Surface" title="Revenue and document mix">
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Quotes</div>
<div className="mt-2 text-2xl font-bold text-text">{snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access"}</div>
<div className="mt-3 space-y-2">
<DashboardCard eyebrow="COMMERCIAL">
<div className="mt-3 grid gap-2.5 sm:grid-cols-2">
<div className="surface-panel-tight">
<div className="metric-kicker">Quotes</div>
<div className="mt-1.5 text-2xl font-bold text-text">{snapshot?.quotes !== null ? formatCurrency(quoteValue) : "No access"}</div>
<div className="mt-2.5 space-y-2">
<div className="flex items-center justify-between text-xs text-muted">
<span>Draft</span>
<span>{draftQuoteCount}</span>
@@ -326,10 +323,10 @@ export function DashboardPage() {
<ProgressBar value={approvedQuoteCount} total={Math.max(quoteCount, 1)} tone="bg-emerald-500" />
</div>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Orders</div>
<div className="mt-2 text-2xl font-bold text-text">{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}</div>
<div className="mt-3 grid gap-3 sm:grid-cols-2">
<div className="surface-panel-tight">
<div className="metric-kicker">Orders</div>
<div className="mt-1.5 text-2xl font-bold text-text">{snapshot?.orders !== null ? formatCurrency(orderValue) : "No access"}</div>
<div className="mt-2.5 grid gap-2.5 sm:grid-cols-2">
<div>
<div className="text-xs text-muted">Issued / approved</div>
<div className="mt-1 text-lg font-semibold text-text">{issuedOrderCount}</div>
@@ -339,14 +336,14 @@ export function DashboardPage() {
<div className="mt-1 text-lg font-semibold text-text">{orderCount}</div>
</div>
</div>
<div className="mt-3">
<div className="mt-2.5">
<ProgressBar value={issuedOrderCount} total={Math.max(orderCount, 1)} tone="bg-brand" />
</div>
</div>
</div>
</DashboardCard>
<DashboardCard eyebrow="CRM Footprint" title="Customer and vendor balance">
<div className="mt-4 space-y-4">
<DashboardCard eyebrow="CRM">
<div className="mt-3 space-y-3">
<div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted">Active customers</span>
@@ -356,21 +353,21 @@ export function DashboardPage() {
<ProgressBar value={activeCustomerCount} total={Math.max(customerCount, 1)} tone="bg-emerald-500" />
</div>
</div>
<div className="grid gap-3 sm:grid-cols-3">
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Customers</div>
<div className="grid gap-2.5 sm:grid-cols-3">
<div className="surface-panel-tight">
<div className="metric-kicker">Customers</div>
<div className="mt-1 text-lg font-bold text-text">{customerCount}</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Resellers</div>
<div className="surface-panel-tight">
<div className="metric-kicker">Resellers</div>
<div className="mt-1 text-lg font-bold text-text">{resellerCount}</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Vendors</div>
<div className="surface-panel-tight">
<div className="metric-kicker">Vendors</div>
<div className="mt-1 text-lg font-bold text-text">{vendorCount}</div>
</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-3">
<div className="surface-panel-tight">
<div className="flex items-center justify-between text-sm">
<span className="text-muted">Strategic accounts</span>
<span className="font-semibold text-text">{strategicCustomerCount}</span>
@@ -383,11 +380,11 @@ export function DashboardPage() {
</DashboardCard>
</section>
<section className="grid gap-3 xl:grid-cols-3">
<DashboardCard eyebrow="Inventory and Supply" title="Stock posture">
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Item mix</div>
<div className="mt-3 space-y-3">
<DashboardCard eyebrow="INVENTORY">
<div className="mt-3 grid gap-2.5 sm:grid-cols-2">
<div className="surface-panel-tight">
<div className="metric-kicker">Item Mix</div>
<div className="mt-2.5 space-y-2.5">
<div>
<div className="flex items-center justify-between text-xs text-muted">
<span>Active items</span>
@@ -417,14 +414,14 @@ export function DashboardPage() {
</div>
</div>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Storage surface</div>
<div className="mt-3 grid gap-3">
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="surface-panel-tight">
<div className="metric-kicker">Storage</div>
<div className="mt-2.5 grid gap-2.5">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Warehouses</div>
<div className="mt-1 text-lg font-bold text-text">{warehouseCount}</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Locations</div>
<div className="mt-1 text-lg font-bold text-text">{locationCount}</div>
</div>
@@ -432,10 +429,10 @@ export function DashboardPage() {
</div>
</div>
</DashboardCard>
<DashboardCard eyebrow="Supply Execution" title="Purchasing and manufacturing flow">
<div className="mt-4 rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Open workload split</div>
<div className="mt-3">
<DashboardCard eyebrow="SUPPLY">
<div className="mt-3 surface-panel-tight">
<div className="metric-kicker">Open Workload</div>
<div className="mt-2.5">
<StackedBar
segments={[
{ value: openPurchaseOrderCount, tone: "bg-teal-500" },
@@ -445,31 +442,31 @@ export function DashboardPage() {
]}
/>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="mt-3 grid gap-2.5 sm:grid-cols-2">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Open PO queue</div>
<div className="mt-1 text-lg font-bold text-text">{openPurchaseOrderCount}</div>
<div className="mt-1 text-xs text-muted">{formatCurrency(purchaseOrderValue)} committed</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Active work orders</div>
<div className="mt-1 text-lg font-bold text-text">{activeWorkOrderCount}</div>
<div className="mt-1 text-xs text-muted">{overdueWorkOrderCount} overdue</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Issued / approved POs</div>
<div className="mt-1 text-lg font-bold text-text">{issuedPurchaseOrderCount}</div>
</div>
<div className="rounded-[16px] border border-line/70 bg-surface/80 px-3 py-3">
<div className="surface-panel-tight bg-surface/80">
<div className="text-xs text-muted">Released WOs</div>
<div className="mt-1 text-lg font-bold text-text">{releasedWorkOrderCount}</div>
</div>
</div>
</div>
</DashboardCard>
<DashboardCard eyebrow="Readiness" title="Planning pressure">
<div className="mt-4 space-y-3">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<DashboardCard eyebrow="READINESS">
<div className="mt-3 space-y-2.5">
<div className="surface-panel-tight">
<div className="flex items-center justify-between text-sm">
<span className="text-muted">Shortage items</span>
<span className="font-semibold text-text">{planningRollup ? shortageItemCount : "No access"}</span>
@@ -478,9 +475,9 @@ export function DashboardPage() {
<ProgressBar value={shortageItemCount} total={Math.max(planningItemCount, 1)} tone="bg-rose-500" />
</div>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Build vs buy</div>
<div className="mt-3">
<div className="surface-panel-tight">
<div className="metric-kicker">Build Vs Buy</div>
<div className="mt-2.5">
<StackedBar
segments={[
{ value: buildRecommendationCount, tone: "bg-indigo-500" },
@@ -488,7 +485,7 @@ export function DashboardPage() {
]}
/>
</div>
<div className="mt-3 grid gap-3 sm:grid-cols-2">
<div className="mt-2.5 grid gap-2.5 sm:grid-cols-2">
<div>
<div className="text-xs text-muted">Build recommendations</div>
<div className="mt-1 text-lg font-bold text-text">{planningRollup ? buildRecommendationCount : "No access"}</div>
@@ -499,7 +496,7 @@ export function DashboardPage() {
</div>
</div>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="surface-panel-tight">
<div className="text-xs text-muted">Uncovered quantity</div>
<div className="mt-1 text-lg font-bold text-text">{planningRollup ? totalUncoveredQuantity : "No access"}</div>
</div>
@@ -507,11 +504,11 @@ export function DashboardPage() {
</DashboardCard>
</section>
<section className="grid gap-3 xl:grid-cols-[0.95fr_1.05fr]">
<DashboardCard eyebrow="Programs" title="Project and shipment execution">
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Projects</div>
<div className="mt-3">
<DashboardCard eyebrow="PROGRAMS">
<div className="mt-3 grid gap-2.5 sm:grid-cols-2">
<div className="surface-panel-tight">
<div className="metric-kicker">Projects</div>
<div className="mt-2.5">
<StackedBar
segments={[
{ value: activeProjectCount, tone: "bg-violet-500" },
@@ -520,7 +517,7 @@ export function DashboardPage() {
]}
/>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-3">
<div className="mt-3 grid gap-2.5 sm:grid-cols-3">
<div>
<div className="text-xs text-muted">Active</div>
<div className="mt-1 font-semibold text-text">{activeProjectCount}</div>
@@ -535,9 +532,9 @@ export function DashboardPage() {
</div>
</div>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Shipping</div>
<div className="mt-3">
<div className="surface-panel-tight">
<div className="metric-kicker">Shipping</div>
<div className="mt-2.5">
<StackedBar
segments={[
{ value: activeShipmentCount, tone: "bg-brand" },
@@ -546,7 +543,7 @@ export function DashboardPage() {
]}
/>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-3">
<div className="mt-3 grid gap-2.5 sm:grid-cols-3">
<div>
<div className="text-xs text-muted">Open</div>
<div className="mt-1 font-semibold text-text">{activeShipmentCount}</div>
@@ -563,8 +560,8 @@ export function DashboardPage() {
</div>
</div>
</DashboardCard>
<DashboardCard eyebrow="Operations Mix" title="Cross-module volume">
<div className="mt-4 space-y-3">
<DashboardCard eyebrow="OPERATIONS">
<div className="mt-3 space-y-2.5">
{[
{ 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" },
@@ -574,7 +571,7 @@ export function DashboardPage() {
{ 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) => (
<div key={row.label} className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div key={row.label} className="surface-panel-tight">
<div className="flex items-center justify-between text-sm">
<span className="text-muted">{row.label}</span>
<span className="font-semibold text-text">{row.value}</span>
@@ -585,7 +582,7 @@ export function DashboardPage() {
</div>
))}
</div>
{snapshot ? <div className="mt-4 text-xs text-muted">Refreshed {new Date(snapshot.refreshedAt).toLocaleString()}</div> : null}
{snapshot ? <div className="mt-3 text-xs text-muted">REFRESHED {new Date(snapshot.refreshedAt).toLocaleString()}</div> : null}
</DashboardCard>
</section>
</div>

View File

@@ -192,18 +192,15 @@ export function FinancePage() {
const currencyCode = profile.currencyCode || "USD";
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Finance</p>
<h2 className="mt-2 text-2xl font-bold text-text">Cash, spend, and CapEx control</h2>
<p className="mt-2 max-w-3xl text-sm text-muted">
Track customer payments against sales orders, compare them to linked purchasing and manufacturing spend, and manage capital purchases for equipment, tooling, and consumables.
</p>
<p className="section-kicker">FINANCE</p>
<h2 className="module-title">CASH SPEND CAPEX</h2>
</div>
<div className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3 text-sm text-muted">
Live snapshot generated {new Date(dashboard.generatedAt).toLocaleString()}
<div className="surface-panel-tight text-sm text-muted">
SNAPSHOT {new Date(dashboard.generatedAt).toLocaleString()}
</div>
</div>
</div>
@@ -217,22 +214,19 @@ export function FinancePage() {
{ label: "Mfg Cost", value: formatCurrency(summary.manufacturingTotalCost, currencyCode) },
{ label: "CapEx Actual", value: formatCurrency(summary.capexActual, currencyCode) },
].map((card) => (
<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 text-xl font-extrabold text-text">{card.value}</div>
<article key={card.label} className="surface-panel-tight bg-surface/90 shadow-panel">
<p className="metric-kicker">{card.label}</p>
<div className="mt-1.5 text-xl font-extrabold text-text">{card.value}</div>
</article>
))}
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.15fr)_minmax(360px,0.85fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Sales Order Ledger</p>
<p className="mt-2 text-sm text-muted">Revenue, receipts, purchasing, and manufacturing cost by order.</p>
</div>
<p className="section-kicker">SALES ORDER LEDGER</p>
</div>
<div className="mt-5 overflow-x-auto">
<div className="mt-3 overflow-x-auto">
<table className="min-w-full divide-y divide-line/60 text-sm">
<thead>
<tr className="text-left text-xs uppercase tracking-[0.16em] text-muted">
@@ -290,9 +284,9 @@ export function FinancePage() {
</article>
<div className="space-y-3">
<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">Costing Assumptions</p>
<div className="mt-4 grid gap-3">
<article className="surface-panel">
<p className="section-kicker">COSTING ASSUMPTIONS</p>
<div className="mt-3 grid gap-2.5">
<label className="text-sm text-text">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Currency</span>
<input value={profileForm.currencyCode} onChange={(event) => setProfileForm((current) => ({ ...current, currencyCode: event.target.value }))} className="w-full rounded-2xl border border-line/70 bg-page px-3 py-2 outline-none" />
@@ -307,15 +301,15 @@ export function FinancePage() {
</label>
</div>
{canManage ? (
<button type="button" onClick={() => void handleSaveProfile()} disabled={isSavingProfile} className="mt-4 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
<button type="button" onClick={() => void handleSaveProfile()} disabled={isSavingProfile} className="mt-3 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
{isSavingProfile ? "Saving..." : "Save assumptions"}
</button>
) : null}
</article>
<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">Post Payment</p>
<div className="mt-4 grid gap-3">
<article className="surface-panel">
<p className="section-kicker">POST PAYMENT</p>
<div className="mt-3 grid gap-2.5">
<select value={paymentForm.salesOrderId} onChange={(event) => setPaymentForm((current) => ({ ...current, salesOrderId: event.target.value }))} className="rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none">
{salesOrders.map((order) => (
<option key={order.id} value={order.id}>
@@ -339,7 +333,7 @@ export function FinancePage() {
<textarea value={paymentForm.notes} onChange={(event) => setPaymentForm((current) => ({ ...current, notes: event.target.value }))} placeholder="Notes" rows={3} className="rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none" />
</div>
{canManage ? (
<button type="button" onClick={() => void handlePostPayment()} disabled={isPostingPayment || !paymentForm.salesOrderId || paymentForm.amount <= 0} className="mt-4 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
<button type="button" onClick={() => void handlePostPayment()} disabled={isPostingPayment || !paymentForm.salesOrderId || paymentForm.amount <= 0} className="mt-3 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
{isPostingPayment ? "Posting..." : "Post payment"}
</button>
) : null}
@@ -348,19 +342,16 @@ export function FinancePage() {
</div>
<div className="grid gap-3 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Recent Payments</p>
<p className="mt-2 text-sm text-muted">Posted receipts linked directly to sales orders.</p>
</div>
<div><p className="section-kicker">RECENT PAYMENTS</p></div>
</div>
<div className="mt-5 space-y-3">
<div className="mt-3 space-y-2.5">
{payments.length === 0 ? (
<div className="rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No payments posted yet.</div>
) : (
payments.map((payment) => (
<div key={payment.id} className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div key={payment.id} className="surface-panel-tight">
<div className="flex items-start justify-between gap-3">
<div>
<Link to={`/sales/orders/${payment.salesOrderId}`} className="font-semibold text-brand hover:underline">
@@ -381,12 +372,9 @@ export function FinancePage() {
</div>
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CapEx Tracker</p>
<p className="mt-2 text-sm text-muted">Manage equipment, tooling, and consumable capital plans with optional PO linkage.</p>
</div>
<div><p className="section-kicker">CAPEX TRACKER</p></div>
{editingCapexId ? (
<button type="button" onClick={resetCapexForm} className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
Clear edit
@@ -394,7 +382,7 @@ export function FinancePage() {
) : null}
</div>
<div className="mt-5 grid gap-3 lg:grid-cols-2">
<div className="mt-3 grid gap-2.5 lg:grid-cols-2">
<input value={capexForm.title} onChange={(event) => setCapexForm((current) => ({ ...current, title: event.target.value }))} placeholder="CapEx title" className="rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none" />
<select value={capexForm.category} onChange={(event) => setCapexForm((current) => ({ ...current, category: event.target.value as CapexCategory }))} className="rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none">
{capexCategories.map((category) => <option key={category} value={category}>{category}</option>)}
@@ -421,12 +409,12 @@ export function FinancePage() {
</div>
{canManage ? (
<button type="button" onClick={() => void handleSaveCapex()} disabled={isSavingCapex || !capexForm.title.trim()} className="mt-4 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
<button type="button" onClick={() => void handleSaveCapex()} disabled={isSavingCapex || !capexForm.title.trim()} className="mt-3 inline-flex rounded-2xl bg-brand px-4 py-2 text-sm font-semibold text-white disabled:opacity-60">
{isSavingCapex ? "Saving..." : editingCapexId ? "Update CapEx" : "Create CapEx"}
</button>
) : null}
<div className="mt-5 space-y-3">
<div className="mt-3 space-y-2.5">
{capex.length === 0 ? (
<div className="rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No CapEx entries yet.</div>
) : (
@@ -450,7 +438,7 @@ export function FinancePage() {
notes: entry.notes,
});
}}
className="block w-full rounded-[18px] border border-line/70 bg-page/60 p-3 text-left transition hover:bg-page/80"
className="block w-full rounded-[16px] border border-line/70 bg-page/60 px-3 py-2.5 text-left transition hover:bg-page/80"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
@@ -473,7 +461,7 @@ export function FinancePage() {
</article>
</div>
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">
<div className="surface-panel text-sm text-muted">
{status}
</div>
</section>

View File

@@ -308,18 +308,18 @@ export function InventoryDetailPage() {
}
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Inventory Detail</p>
<h3 className="mt-2 text-xl font-bold text-text">{item.sku}</h3>
<p className="section-kicker">INVENTORY DETAIL</p>
<h3 className="module-title">{item.sku}</h3>
<p className="mt-1 text-sm text-text">{item.name}</p>
<div className="mt-4 flex flex-wrap gap-3">
<div className="mt-2.5 flex flex-wrap gap-2">
<InventoryTypeBadge type={item.type} />
<InventoryStatusBadge status={item.status} />
</div>
<p className="mt-3 text-sm text-muted">Last updated {new Date(item.updatedAt).toLocaleString()}.</p>
<p className="mt-2 text-sm text-muted">UPDATED {new Date(item.updatedAt).toLocaleString()}</p>
</div>
<div className="flex flex-wrap gap-3">
<Link to="/inventory/items" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
@@ -366,8 +366,8 @@ export function InventoryDetailPage() {
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.05fr)_minmax(340px,0.95fr)]">
<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">Item Definition</p>
<article className="surface-panel">
<p className="section-kicker">ITEM DEFINITION</p>
<dl className="mt-5 grid gap-3 xl:grid-cols-2">
<div>
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Description</dt>
@@ -397,9 +397,9 @@ export function InventoryDetailPage() {
</div>
</dl>
</article>
<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">Thumbnail</p>
<div className="mt-4 overflow-hidden rounded-[18px] border border-line/70 bg-page/70">
<article className="surface-panel">
<p className="section-kicker">THUMBNAIL</p>
<div className="mt-3 overflow-hidden rounded-[18px] border border-line/70 bg-page/70">
{thumbnailPreviewUrl ? (
<img src={thumbnailPreviewUrl} alt={`${item.sku} thumbnail`} className="aspect-square w-full object-cover" />
) : (
@@ -408,19 +408,19 @@ export function InventoryDetailPage() {
</div>
)}
</div>
<div className="mt-3 text-xs text-muted">
<div className="mt-2 text-xs text-muted">
{thumbnailAttachment ? `Current file: ${thumbnailAttachment.originalName}` : "Add or replace the thumbnail from the item edit page."}
</div>
</article>
</div>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.05fr)_minmax(340px,0.95fr)]">
<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">Stock By Location</p>
<article className="surface-panel">
<p className="section-kicker">STOCK BY LOCATION</p>
{item.stockBalances.length === 0 ? (
<p className="mt-4 text-sm text-muted">No stock or reservation balances have been posted for this item yet.</p>
<p className="mt-3 text-sm text-muted">No stock or reservation balances have been posted for this item yet.</p>
) : (
<div className="mt-4 space-y-2">
<div className="mt-3 space-y-2">
{item.stockBalances.map((balance) => (
<div key={`${balance.warehouseId}-${balance.locationId}`} className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm">
<div className="min-w-0">

View File

@@ -41,14 +41,11 @@ export function InventoryListPage() {
}, [searchTerm, statusFilter, token, typeFilter]);
return (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Inventory</p>
<h3 className="mt-2 text-lg font-bold text-text">Item Master</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">
Core item and BOM definitions for purchased parts, manufactured items, assemblies, and service SKUs.
</p>
<p className="section-kicker">INVENTORY</p>
<h3 className="module-title">ITEM MASTER</h3>
</div>
{canManage ? (
<div className="flex flex-wrap gap-2">
@@ -61,7 +58,7 @@ export function InventoryListPage() {
</div>
) : null}
</div>
<div className="mt-6 grid gap-3 rounded-[18px] border border-line/70 bg-page/60 p-3 xl:grid-cols-[1.3fr_0.8fr_0.8fr]">
<div className="mt-4 grid gap-2.5 rounded-[16px] border border-line/70 bg-page/60 p-2.5 xl:grid-cols-[1.3fr_0.8fr_0.8fr]">
<label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input
@@ -100,13 +97,13 @@ export function InventoryListPage() {
</select>
</label>
</div>
<div className="mt-6 rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-4 rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-sm text-muted">{status}</div>
{items.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">
No inventory items have been added yet.
</div>
) : (
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>

View File

@@ -391,17 +391,17 @@ export function WorkOrderDetailPage() {
}
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Work Order</p>
<h3 className="mt-2 text-xl font-bold text-text">{workOrder.workOrderNumber}</h3>
<p className="section-kicker">WORK ORDER</p>
<h3 className="module-title">{workOrder.workOrderNumber}</h3>
<p className="mt-1 text-sm text-text">{workOrder.itemSku} - {workOrder.itemName}</p>
<div className="mt-3"><WorkOrderStatusBadge status={workOrder.status} /></div>
<div className="mt-2.5"><WorkOrderStatusBadge status={workOrder.status} /></div>
{workOrder.status === "ON_HOLD" && workOrder.holdReason ? (
<div className="mt-3 max-w-2xl rounded-[18px] border border-amber-300/60 bg-amber-50 px-3 py-3 text-sm text-amber-900">
<div className="text-xs font-semibold uppercase tracking-[0.18em]">Current Hold Reason</div>
<div className="mt-2.5 max-w-2xl rounded-[16px] border border-amber-300/60 bg-amber-50 px-3 py-2.5 text-sm text-amber-900">
<div className="metric-kicker text-amber-900">Current Hold Reason</div>
<div className="mt-2 whitespace-pre-line">{workOrder.holdReason}</div>
</div>
) : null}
@@ -416,11 +416,10 @@ export function WorkOrderDetailPage() {
</div>
</div>
{canManage ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p>
<p className="mt-2 text-sm text-muted">Release, hold, or close administrative status from the work-order record.</p>
<p className="section-kicker">QUICK ACTIONS</p>
</div>
<div className="flex flex-wrap gap-2">
{workOrderStatusOptions.map((option) => (

View File

@@ -35,13 +35,12 @@ export function WorkOrderListPage() {
}, [query, statusFilter, token]);
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing</p>
<h3 className="mt-2 text-xl font-bold text-text">Work Orders</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">Release and execute build work against manufactured or assembly inventory items, with project linkage and real inventory posting.</p>
<p className="section-kicker">MANUFACTURING</p>
<h3 className="module-title">WORK ORDERS</h3>
</div>
{canManage ? (
<Link to="/manufacturing/work-orders/new" className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">
@@ -50,8 +49,8 @@ export function WorkOrderListPage() {
) : null}
</div>
</div>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="grid gap-3 xl:grid-cols-[minmax(0,1fr)_240px]">
<section className="surface-panel">
<div className="grid gap-2.5 xl:grid-cols-[minmax(0,1fr)_240px]">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Search</span>
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search work order, item, or project" className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
@@ -63,7 +62,7 @@ export function WorkOrderListPage() {
</select>
</label>
</div>
<div className="mt-4 rounded-2xl border border-line/70 bg-page/70 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-3 rounded-[16px] border border-line/70 bg-page/70 px-3 py-2 text-sm text-muted">{status}</div>
</section>
{workOrders.length === 0 ? (
<div className="rounded-[20px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No work orders are available yet.</div>

View File

@@ -153,14 +153,14 @@ export function ProjectDetailPage() {
}
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Project</p>
<h3 className="mt-2 text-xl font-bold text-text">{project.projectNumber}</h3>
<p className="section-kicker">PROJECT</p>
<h3 className="module-title">{project.projectNumber}</h3>
<p className="mt-1 text-sm text-text">{project.name}</p>
<div className="mt-3 flex flex-wrap gap-2">
<div className="mt-2.5 flex flex-wrap gap-2">
<ProjectStatusBadge status={project.status} />
<ProjectPriorityBadge priority={project.priority} />
</div>
@@ -183,36 +183,34 @@ export function ProjectDetailPage() {
<article 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">Linked Work Orders</p><div className="mt-2 text-base font-bold text-text">{project.rollups.workOrderCount}</div><div className="mt-1 text-xs text-muted">{project.rollups.activeWorkOrderCount} active</div></article>
<article 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">Overdue Work Orders</p><div className="mt-2 text-base font-bold text-text">{project.rollups.overdueWorkOrderCount}</div><div className="mt-1 text-xs text-muted">{project.rollups.completedWorkOrderCount} complete</div></article>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Project Cockpit</p>
<h4 className="mt-2 text-lg font-bold text-text">Cross-functional execution view</h4>
<p className="mt-2 text-sm text-muted">Commercial, supply, execution, purchasing, and delivery signals for this program in one place.</p>
<p className="section-kicker">PROJECT COCKPIT</p>
</div>
<div className="rounded-2xl border border-line/70 bg-page/60 px-3 py-2 text-right">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Milestone Progress</div>
<div className="surface-panel-tight text-right">
<div className="metric-kicker">Milestone Progress</div>
<div className="mt-1 text-2xl font-bold text-text">{completionPercent}%</div>
</div>
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-4">
<div className="mt-3 grid gap-2.5 xl:grid-cols-4">
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Commercial</p><div className="mt-2 text-base font-bold text-text">{formatCurrency(project.cockpit.commercial.activeDocumentTotal)}</div><div className="mt-1 text-xs text-muted">{project.cockpit.commercial.activeDocumentNumber ? `${project.cockpit.commercial.activeDocumentNumber} - ${project.cockpit.commercial.activeDocumentStatus}` : "Link a quote or sales order"}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Supply</p><div className="mt-2 text-base font-bold text-text">{planning ? planning.summary.uncoveredItemCount : project.cockpit.risk.shortageItemCount} shortage items</div><div className="mt-1 text-xs text-muted">{planning ? `Build ${planning.summary.totalBuildQuantity} - Buy ${planning.summary.totalPurchaseQuantity}` : `Uncovered qty ${project.cockpit.risk.totalUncoveredQuantity}`}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Execution</p><div className="mt-2 text-base font-bold text-text">{project.rollups.activeWorkOrderCount} active work orders</div><div className="mt-1 text-xs text-muted">{nextWorkOrder ? `${nextWorkOrder.workOrderNumber} due ${nextWorkOrder.dueDate ? new Date(nextWorkOrder.dueDate).toLocaleDateString() : "unscheduled"}` : "No active work order due date"}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Delivery</p><div className="mt-2 text-base font-bold text-text">{project.cockpit.delivery.shipmentStatus ? project.cockpit.delivery.shipmentStatus.replaceAll("_", " ") : "Not linked"}</div><div className="mt-1 text-xs text-muted">{project.cockpit.delivery.shipmentNumber ? `${project.cockpit.delivery.shipmentNumber} - ${project.cockpit.delivery.packageCount} package(s)` : "Link a shipment to track delivery"}</div></article>
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-3">
<div className="mt-3 grid gap-2.5 xl:grid-cols-3">
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Purchasing Coverage</p><div className="mt-2 text-base font-bold text-text">{project.cockpit.purchasing.totalReceivedQuantity}/{project.cockpit.purchasing.totalOrderedQuantity} received</div><div className="mt-1 text-xs text-muted">{project.cockpit.purchasing.linkedPurchaseOrderCount} linked PO(s) - {project.cockpit.purchasing.totalOutstandingQuantity} outstanding</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Readiness Score</p><div className={`mt-2 text-base font-bold ${riskTone}`}>{readinessScore}%</div><div className="mt-1 text-xs text-muted">{project.cockpit.risk.riskLevel} risk - {project.cockpit.risk.shortageItemCount} shortage item(s)</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Material Spend</p><div className="mt-2 text-base font-bold text-text">${project.cockpit.purchasing.linkedLineValue.toFixed(2)}</div><div className="mt-1 text-xs text-muted">{project.cockpit.purchasing.vendorCount} vendor(s) across {project.cockpit.purchasing.linkedLineCount} linked line(s)</div></article>
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-4">
<div className="mt-3 grid gap-2.5 xl:grid-cols-4">
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Booked Revenue</p><div className="mt-2 text-base font-bold text-text">{formatCurrency(project.cockpit.costs.bookedRevenue)}</div><div className="mt-1 text-xs text-muted">Quoted baseline {formatCurrency(project.cockpit.costs.quotedRevenue)}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Purchase Commitment</p><div className="mt-2 text-base font-bold text-text">${project.cockpit.costs.linkedPurchaseCommitment.toFixed(2)}</div><div className="mt-1 text-xs text-muted">Linked PO line value already committed</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Planned Material Cost</p><div className="mt-2 text-base font-bold text-text">${project.cockpit.costs.plannedMaterialCost.toFixed(2)}</div><div className="mt-1 text-xs text-muted">Issued so far ${project.cockpit.costs.issuedMaterialCost.toFixed(2)}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build Load</p><div className="mt-2 text-base font-bold text-text">{project.cockpit.costs.completedBuildQuantity}/{project.cockpit.costs.buildQuantity}</div><div className="mt-1 text-xs text-muted">{project.cockpit.costs.plannedOperationHours.toFixed(1)} planned operation hours</div></article>
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
<div className="mt-3 grid gap-2.5 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
<article className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Next Checkpoints</p>
<div className="mt-4 space-y-3">
@@ -227,17 +225,16 @@ export function ProjectDetailPage() {
</div>
</section>
<section className="grid gap-3 xl:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Actionable Cockpit</p>
<p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p>
<p className="section-kicker">ACTIONABLE COCKPIT</p>
</div>
<Link to="/planning/workbench" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Open workbench
</Link>
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-2">
<div className="mt-3 grid gap-2.5 xl:grid-cols-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build Follow-Through</p>
<div className="mt-2 text-base font-bold text-text">{topBuildRecommendation ? topBuildRecommendation.itemSku : "No build recommendation"}</div>
@@ -269,7 +266,7 @@ export function ProjectDetailPage() {
) : null}
</div>
</div>
<div className="mt-4 flex flex-wrap gap-3">
<div className="mt-3 flex flex-wrap gap-3">
<Link to={`/manufacturing/work-orders/new?projectId=${project.id}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
New project work order
</Link>
@@ -283,9 +280,9 @@ export function ProjectDetailPage() {
</Link>
</div>
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="flex items-center justify-between gap-3"><div><p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Linked Purchasing</p><p className="mt-2 text-sm text-muted">Purchase orders and receipts tied back to the project sales order.</p></div>{project.salesOrderId ? <Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open purchasing</Link> : null}</div>
{project.cockpit.purchasing.purchaseOrders.length === 0 ? <div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No linked purchase orders are tied to this project yet.</div> : <div className="mt-6 space-y-3">{project.cockpit.purchasing.purchaseOrders.slice(0, 5).map((purchaseOrder) => (<Link key={purchaseOrder.id} to={`/purchasing/orders/${purchaseOrder.id}`} className="block rounded-[18px] border border-line/70 bg-page/60 p-3 transition hover:bg-page/80"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{purchaseOrder.documentNumber}</div><div className="mt-1 text-xs text-muted">{purchaseOrder.vendorName} - {purchaseOrder.status.replaceAll("_", " ")}</div></div><div className="text-right text-xs text-muted"><div>${purchaseOrder.linkedLineValue.toFixed(2)} linked value</div><div>{purchaseOrder.totalReceivedQuantity}/{purchaseOrder.totalOrderedQuantity} received</div></div></div></Link>))}</div>}
<article className="surface-panel">
<div className="flex items-center justify-between gap-3"><div><p className="section-kicker">LINKED PURCHASING</p></div>{project.salesOrderId ? <Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open purchasing</Link> : null}</div>
{project.cockpit.purchasing.purchaseOrders.length === 0 ? <div className="mt-5 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No linked purchase orders are tied to this project yet.</div> : <div className="mt-4 space-y-2.5">{project.cockpit.purchasing.purchaseOrders.slice(0, 5).map((purchaseOrder) => (<Link key={purchaseOrder.id} to={`/purchasing/orders/${purchaseOrder.id}`} className="block rounded-[16px] border border-line/70 bg-page/60 px-3 py-2.5 transition hover:bg-page/80"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{purchaseOrder.documentNumber}</div><div className="mt-1 text-xs text-muted">{purchaseOrder.vendorName} - {purchaseOrder.status.replaceAll("_", " ")}</div></div><div className="text-right text-xs text-muted"><div>${purchaseOrder.linkedLineValue.toFixed(2)} linked value</div><div>{purchaseOrder.totalReceivedQuantity}/{purchaseOrder.totalOrderedQuantity} received</div></div></div></Link>))}</div>}
</article>
<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">Readiness Drivers</p>

View File

@@ -235,20 +235,19 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
}
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<form className="page-stack" onSubmit={handleSubmit}>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Projects Editor</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Project" : "Edit Project"}</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">Create a customer-linked program record that can anchor commercial documents, delivery work, and project files.</p>
<p className="section-kicker">PROJECTS EDITOR</p>
<h3 className="module-title">{mode === "create" ? "New Project" : "Edit Project"}</h3>
</div>
<Link to={mode === "create" ? "/projects" : `/projects/${projectId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Cancel
</Link>
</div>
</section>
<section className="space-y-4 rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="space-y-3 surface-panel">
<div className="grid gap-3 xl:grid-cols-2">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Project name</span>

View File

@@ -42,13 +42,12 @@ export function ProjectListPage() {
}, [priorityFilter, query, statusFilter, token]);
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="page-stack">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Projects</p>
<h3 className="mt-2 text-xl font-bold text-text">Program records</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">Track long-running customer programs across commercial commitments, shipment deliverables, ownership, and due dates.</p>
<p className="section-kicker">PROJECTS</p>
<h3 className="module-title">PROGRAM RECORDS</h3>
</div>
{canManage ? (
<Link to="/projects/new" className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">
@@ -57,8 +56,8 @@ export function ProjectListPage() {
) : null}
</div>
</div>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.2fr)_0.45fr_0.45fr]">
<section className="surface-panel">
<div className="grid gap-2.5 xl:grid-cols-[minmax(0,1.2fr)_0.45fr_0.45fr]">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Search</span>
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Project number, name, customer" className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
@@ -76,11 +75,11 @@ export function ProjectListPage() {
</select>
</label>
</div>
<div className="mt-5 rounded-2xl border border-line/70 bg-page/70 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-3 rounded-[16px] border border-line/70 bg-page/70 px-3 py-2 text-sm text-muted">{status}</div>
{projects.length === 0 ? (
<div className="mt-5 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No projects are available for the current filters.</div>
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">No projects are available for the current filters.</div>
) : (
<div className="mt-5 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>

View File

@@ -292,11 +292,11 @@ export function PurchaseDetailPage() {
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchase Order</p>
<h3 className="mt-2 text-xl font-bold text-text">{activeDocument.documentNumber}</h3>
<p className="section-kicker">PURCHASE ORDER</p>
<h3 className="module-title">{activeDocument.documentNumber}</h3>
<p className="mt-1 text-sm text-text">{activeDocument.vendorName}</p>
<div className="mt-3 flex flex-wrap gap-2">
<PurchaseStatusBadge status={activeDocument.status} />
@@ -326,11 +326,10 @@ export function PurchaseDetailPage() {
</div>
</div>
{canManage ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p>
<p className="mt-2 text-sm text-muted">Update purchase-order status without opening the full editor.</p>
<p className="section-kicker">QUICK ACTIONS</p>
</div>
<div className="flex flex-wrap gap-2">
{purchaseStatusOptions.map((option) => (
@@ -356,11 +355,10 @@ export function PurchaseDetailPage() {
<article 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">Payment Terms</p><div className="mt-2 text-base font-bold text-text">{activeDocument.paymentTerms || "N/A"}</div></article>
<article 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">Currency</p><div className="mt-2 text-base font-bold text-text">{activeDocument.currencyCode || "USD"}</div></article>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Revision History</p>
<p className="mt-2 text-sm text-muted">Automatic snapshots are recorded when the purchase order changes or receipts are posted.</p>
<p className="section-kicker">REVISION HISTORY</p>
</div>
</div>
{activeDocument.revisions.length === 0 ? (
@@ -499,10 +497,8 @@ export function PurchaseDetailPage() {
</section>
<section className="grid gap-3 2xl:grid-cols-[minmax(360px,0.82fr)_minmax(0,1.18fr)]">
{canReceive ? (
<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">Purchase Receiving</p>
<h4 className="mt-2 text-lg font-bold text-text">Receive material</h4>
<p className="mt-2 text-sm text-muted">Post received quantities to inventory and retain a receipt record against this order.</p>
<article className="surface-panel">
<p className="section-kicker">PURCHASE RECEIVING</p>
{openLines.length === 0 ? (
<div className="mt-5 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
All ordered quantities have been received for this purchase order.
@@ -594,9 +590,8 @@ export function PurchaseDetailPage() {
)}
</article>
) : null}
<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">Receipt History</p>
<h4 className="mt-2 text-lg font-bold text-text">Received material log</h4>
<article className="surface-panel">
<p className="section-kicker">RECEIPT HISTORY</p>
{activeDocument.receipts.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
No purchase receipts have been recorded for this order yet.

View File

@@ -264,19 +264,19 @@ export function PurchaseFormPage({ mode }: { mode: "create" | "edit" }) {
}).length;
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<form className="page-stack" onSubmit={handleSubmit}>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchasing Editor</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Purchase Order" : "Edit Purchase Order"}</h3>
<p className="section-kicker">PURCHASING EDITOR</p>
<h3 className="module-title">{mode === "create" ? "New Purchase Order" : "Edit Purchase Order"}</h3>
</div>
<Link to={mode === "create" ? "/purchasing/orders" : `/purchasing/orders/${orderId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Cancel
</Link>
</div>
</section>
<section className="space-y-4 rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="space-y-3 surface-panel">
<div className="grid gap-3 xl:grid-cols-4">
<label className="block xl:col-span-2">
<span className="mb-2 block text-sm font-semibold text-text">Vendor</span>

View File

@@ -34,12 +34,11 @@ export function PurchaseListPage() {
}, [searchTerm, statusFilter, token]);
return (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchasing</p>
<h3 className="mt-2 text-lg font-bold text-text">Purchase Orders</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">Vendor-facing procurement documents for material replenishment and bought-in components.</p>
<p className="section-kicker">PURCHASING</p>
<h3 className="module-title">PURCHASE ORDERS</h3>
</div>
{canManage ? (
<Link to="/purchasing/orders/new" className="inline-flex items-center justify-center rounded-2xl bg-brand px-3 py-2 text-sm font-semibold text-white">
@@ -47,7 +46,7 @@ export function PurchaseListPage() {
</Link>
) : null}
</div>
<div className="mt-6 grid gap-3 rounded-[18px] border border-line/70 bg-page/60 p-3 xl:grid-cols-[1.35fr_0.8fr]">
<div className="mt-4 grid gap-2.5 rounded-[16px] border border-line/70 bg-page/60 p-2.5 xl:grid-cols-[1.35fr_0.8fr]">
<label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input value={searchTerm} onChange={(event) => setSearchTerm(event.target.value)} placeholder="Search purchase orders by document number or vendor" className="w-full rounded-2xl border border-line/70 bg-surface px-2 py-2 text-text outline-none transition focus:border-brand" />
@@ -63,11 +62,11 @@ export function PurchaseListPage() {
</select>
</label>
</div>
<div className="mt-6 rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-4 rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-sm text-muted">{status}</div>
{documents.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No purchase orders have been added yet.</div>
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">No purchase orders have been added yet.</div>
) : (
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>

View File

@@ -335,11 +335,11 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.detailEyebrow}</p>
<h3 className="mt-2 text-xl font-bold text-text">{activeDocument.documentNumber}</h3>
<p className="section-kicker">{config.detailEyebrow.toUpperCase()}</p>
<h3 className="module-title">{activeDocument.documentNumber}</h3>
<p className="mt-1 text-sm text-text">{activeDocument.customerName}</p>
<div className="mt-3 flex flex-wrap gap-2">
<SalesStatusBadge status={activeDocument.status} />
@@ -396,11 +396,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
</div>
</div>
{canManage ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p>
<p className="mt-2 text-sm text-muted">Update document status without opening the full editor.</p>
<p className="section-kicker">QUICK ACTIONS</p>
</div>
<div className="flex flex-wrap gap-2">
{salesStatusOptions.map((option) => (
@@ -457,11 +456,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
<div className="mt-2 text-base font-bold text-text">${activeDocument.total.toFixed(2)}</div>
</article>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Revision History</p>
<p className="mt-2 text-sm text-muted">Automatic snapshots are recorded when the document changes status, content, or approval state.</p>
<p className="section-kicker">REVISION HISTORY</p>
</div>
</div>
{activeDocument.revisions.length === 0 ? (
@@ -587,14 +585,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
)}
</section>
{entity === "order" && planning ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Demand Planning</p>
<h3 className="mt-2 text-lg font-bold text-text">Net build and buy requirements</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">
Sales-order demand is netted against available stock, active reservations, open work orders, and open purchase orders before new build or buy quantities are recommended.
</p>
<p className="section-kicker">DEMAND PLANNING</p>
</div>
<div className="text-right text-xs text-muted">
<div>Generated {new Date(planning.generatedAt).toLocaleString()}</div>
@@ -706,11 +700,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
</section>
) : null}
{entity === "order" && canReadShipping ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping</p>
<p className="mt-2 text-sm text-muted">Shipment records currently tied to this sales order.</p>
<p className="section-kicker">SHIPPING</p>
</div>
{canManageShipping ? (
<Link to={`/shipping/shipments/new?orderId=${activeDocument.id}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">

View File

@@ -168,19 +168,19 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m
}
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<form className="page-stack" onSubmit={handleSubmit}>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.detailEyebrow} Editor</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}</h3>
<p className="section-kicker">{`${config.detailEyebrow} EDITOR`.toUpperCase()}</p>
<h3 className="module-title">{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}</h3>
</div>
<Link to={mode === "create" ? config.routeBase : `${config.routeBase}/${documentId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Cancel
</Link>
</div>
</section>
<section className="space-y-4 rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="space-y-3 surface-panel">
<div className="grid gap-3 xl:grid-cols-4">
<label className="block xl:col-span-2">
<span className="mb-2 block text-sm font-semibold text-text">Customer</span>

View File

@@ -40,14 +40,11 @@ export function SalesListPage({ entity }: { entity: SalesDocumentEntity }) {
}, [config.collectionLabel, entity, searchTerm, statusFilter, token]);
return (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.listEyebrow}</p>
<h3 className="mt-2 text-lg font-bold text-text">{config.collectionLabel}</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">
Customer-facing commercial documents for pricing, commitment, and downstream fulfillment planning.
</p>
<p className="section-kicker">{config.listEyebrow.toUpperCase()}</p>
<h3 className="module-title">{config.collectionLabel}</h3>
</div>
{canManage ? (
<Link to={`${config.routeBase}/new`} className="inline-flex items-center justify-center rounded-2xl bg-brand px-3 py-2 text-sm font-semibold text-white">
@@ -55,7 +52,7 @@ export function SalesListPage({ entity }: { entity: SalesDocumentEntity }) {
</Link>
) : null}
</div>
<div className="mt-6 grid gap-3 rounded-[18px] border border-line/70 bg-page/60 p-3 xl:grid-cols-[1.35fr_0.8fr]">
<div className="mt-4 grid gap-2.5 rounded-[16px] border border-line/70 bg-page/60 p-2.5 xl:grid-cols-[1.35fr_0.8fr]">
<label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input
@@ -80,13 +77,13 @@ export function SalesListPage({ entity }: { entity: SalesDocumentEntity }) {
</select>
</label>
</div>
<div className="mt-6 rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-4 rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-sm text-muted">{status}</div>
{documents.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">
No {config.collectionLabel.toLowerCase()} have been added yet.
</div>
) : (
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>

View File

@@ -157,14 +157,11 @@ export function AdminDiagnosticsPage() {
return (
<div className="space-y-6">
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Admin Diagnostics</p>
<h3 className="mt-2 text-lg font-bold text-text">Operational runtime and audit visibility</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">
This view surfaces environment footprint, record counts, and recent change activity so admin review does not require direct database access.
</p>
<p className="section-kicker">ADMIN DIAGNOSTICS</p>
<h3 className="module-title">RUNTIME AUDIT SUPPORT</h3>
</div>
<div className="flex flex-wrap gap-3">
<button
@@ -199,14 +196,10 @@ export function AdminDiagnosticsPage() {
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Backup And Restore</p>
<h3 className="mt-2 text-lg font-bold text-text">Operational backup workflow</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">
Use these paths and steps as the support baseline for manual backup and restore procedures.
</p>
<p className="section-kicker">BACKUP AND RESTORE</p>
</div>
<div className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3 text-sm text-muted">
<div>Data: {backupGuidance.dataPath}</div>
@@ -266,11 +259,10 @@ export function AdminDiagnosticsPage() {
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Startup Validation</p>
<h3 className="mt-2 text-lg font-bold text-text">Boot-time readiness checks</h3>
<p className="section-kicker">STARTUP VALIDATION</p>
</div>
<span className={`inline-flex rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${startupStatusTone}`}>
{diagnostics.startup.status}
@@ -297,8 +289,8 @@ export function AdminDiagnosticsPage() {
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">System Footprint</p>
<section className="surface-panel backdrop-blur">
<p className="section-kicker">SYSTEM FOOTPRINT</p>
<div className="mt-5 grid gap-3 xl:grid-cols-2">
{footprintCards.map(([label, value]) => (
<div key={label} className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3">
@@ -309,11 +301,10 @@ export function AdminDiagnosticsPage() {
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<section className="surface-panel backdrop-blur">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Support Logs</p>
<h3 className="mt-2 text-lg font-bold text-text">Recent runtime warnings and failures</h3>
<p className="section-kicker">SUPPORT LOGS</p>
</div>
<p className="text-sm text-muted">
{supportLogSummary ? `${supportLogSummary.filteredCount} of ${supportLogSummary.totalCount} entries` : "No entries loaded"}
@@ -404,11 +395,10 @@ export function AdminDiagnosticsPage() {
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<section className="surface-panel backdrop-blur">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Recent Audit Trail</p>
<h3 className="mt-2 text-lg font-bold text-text">Latest cross-module write activity</h3>
<p className="section-kicker">RECENT AUDIT TRAIL</p>
</div>
<p className="text-sm text-muted">{status}</p>
</div>

View File

@@ -273,14 +273,11 @@ export function UserManagementPage() {
return (
<div className="space-y-6">
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">User Management</p>
<h3 className="mt-2 text-lg font-bold text-text">Accounts, roles, and permission assignment</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">
Manage user accounts and the role-permission model from one admin surface so onboarding and access control stay tied together.
</p>
<p className="section-kicker">ADMIN</p>
<h3 className="module-title">USERS ROLES SESSIONS</h3>
</div>
<div className="flex flex-wrap gap-3">
<Link to="/settings/company" className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
@@ -294,11 +291,10 @@ export function UserManagementPage() {
</section>
<section className="grid gap-6 xl:grid-cols-2">
<form className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5" onSubmit={handleUserSave}>
<form className="surface-panel backdrop-blur" onSubmit={handleUserSave}>
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Users</p>
<h3 className="mt-2 text-lg font-bold text-text">Account generation and role assignment</h3>
<p className="section-kicker">USERS</p>
</div>
<select
value={selectedUserId}
@@ -387,11 +383,10 @@ export function UserManagementPage() {
</div>
</form>
<form className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5" onSubmit={handleRoleSave}>
<form className="surface-panel backdrop-blur" onSubmit={handleRoleSave}>
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Roles</p>
<h3 className="mt-2 text-lg font-bold text-text">Permission assignment administration</h3>
<p className="section-kicker">ROLES</p>
</div>
<select
value={selectedRoleId}
@@ -465,14 +460,10 @@ export function UserManagementPage() {
</form>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Sessions</p>
<h3 className="mt-2 text-lg font-bold text-text">Active sign-ins and revocation control</h3>
<p className="mt-2 max-w-3xl text-sm text-muted">
Review recent authenticated sessions, see their current state, and revoke stale or risky access without changing the user record.
</p>
<p className="section-kicker">SESSIONS</p>
</div>
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<label className="block">

View File

@@ -218,11 +218,11 @@ export function ShipmentDetailPage() {
return (
<section className="space-y-4">
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipment</p>
<h3 className="mt-2 text-xl font-bold text-text">{shipment.shipmentNumber}</h3>
<p className="section-kicker">SHIPMENT</p>
<h3 className="module-title">{shipment.shipmentNumber}</h3>
<p className="mt-1 text-sm text-text">{shipment.salesOrderNumber} / {shipment.customerName}</p>
<div className="mt-3 flex flex-wrap items-center gap-3">
<ShipmentStatusBadge status={shipment.status} />
@@ -249,11 +249,10 @@ export function ShipmentDetailPage() {
</div>
{canManage ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p>
<p className="mt-2 text-sm text-muted">Use inventory-backed picking before marking the shipment packed or shipped.</p>
<p className="section-kicker">QUICK ACTIONS</p>
</div>
<div className="flex flex-wrap gap-2">
{shipmentStatusOptions.map((option) => (
@@ -286,11 +285,10 @@ export function ShipmentDetailPage() {
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.3fr)_minmax(340px,0.9fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipment Lines</p>
<p className="mt-2 text-sm text-muted">Track ordered, picked, and remaining quantity before shipment closeout.</p>
<p className="section-kicker">SHIPMENT LINES</p>
</div>
</div>
<div className="mt-5 overflow-x-auto">
@@ -350,13 +348,10 @@ export function ShipmentDetailPage() {
</div>
{canManage ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Pick And Issue From Stock</p>
<p className="mt-2 max-w-2xl text-sm text-muted">
Posting a pick immediately creates an inventory issue transaction against the selected warehouse location and advances draft shipments into picking.
</p>
<p className="section-kicker">PICK AND ISSUE FROM STOCK</p>
</div>
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-xs text-muted">
Select the sales-order line, source location, and quantity you are physically picking.
@@ -468,11 +463,10 @@ export function ShipmentDetailPage() {
) : null}
<div className="grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(320px,0.9fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<article className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Pick History</p>
<p className="mt-2 text-sm text-muted">Every pick here already issued stock from a specific inventory location.</p>
<p className="section-kicker">PICK HISTORY</p>
</div>
</div>
{shipment.picks.length === 0 ? (
@@ -508,11 +502,10 @@ export function ShipmentDetailPage() {
</article>
</div>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Related Shipments</p>
<p className="mt-2 text-sm text-muted">Other shipments already tied to this sales order.</p>
<p className="section-kicker">RELATED SHIPMENTS</p>
</div>
{canManage ? (
<Link to={`/shipping/shipments/new?orderId=${shipment.salesOrderId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Add another shipment</Link>

View File

@@ -86,19 +86,19 @@ export function ShipmentFormPage({ mode }: { mode: "create" | "edit" }) {
}
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<form className="page-stack" onSubmit={handleSubmit}>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping Editor</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Shipment" : "Edit Shipment"}</h3>
<p className="section-kicker">SHIPPING EDITOR</p>
<h3 className="module-title">{mode === "create" ? "New Shipment" : "Edit Shipment"}</h3>
</div>
<Link to={mode === "create" ? "/shipping/shipments" : `/shipping/shipments/${shipmentId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Cancel
</Link>
</div>
</section>
<section className="space-y-4 rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="space-y-3 surface-panel">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Sales Order</span>
<div className="relative">

View File

@@ -38,12 +38,11 @@ export function ShipmentListPage() {
}, [searchTerm, statusFilter, token]);
return (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping</p>
<h3 className="mt-2 text-lg font-bold text-text">Shipments</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">Outbound shipment records tied to sales orders, carriers, and tracking details.</p>
<p className="section-kicker">SHIPPING</p>
<h3 className="module-title">SHIPMENTS</h3>
</div>
{canManage ? (
<Link to="/shipping/shipments/new" className="inline-flex items-center justify-center rounded-2xl bg-brand px-3 py-2 text-sm font-semibold text-white">
@@ -51,7 +50,7 @@ export function ShipmentListPage() {
</Link>
) : null}
</div>
<div className="mt-6 grid gap-3 rounded-[18px] border border-line/70 bg-page/60 p-3 xl:grid-cols-[1.35fr_0.8fr]">
<div className="mt-4 grid gap-2.5 rounded-[16px] border border-line/70 bg-page/60 p-2.5 xl:grid-cols-[1.35fr_0.8fr]">
<label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input
@@ -76,11 +75,11 @@ export function ShipmentListPage() {
</select>
</label>
</div>
<div className="mt-6 rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
<div className="mt-4 rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-sm text-muted">{status}</div>
{shipments.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No shipments have been added yet.</div>
<div className="mt-4 rounded-[16px] border border-dashed border-line/70 bg-page/60 px-4 py-7 text-center text-sm text-muted">No shipments have been added yet.</div>
) : (
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
<div className="mt-4 overflow-hidden rounded-[16px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted">
<tr>