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

@@ -23,6 +23,9 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
- Work-order `On Hold` quick status changes now require a recorded hold reason and persist the active blocker on the work-order record and audit trail
- Project milestone cards now support inline quick status actions for start, block, complete, reset, and reopen flows directly from the project detail view
- Project milestones with status, due dates, notes, and edit-time sequencing inside the project workflow
- UI density standardization pass across app shell, dashboard, finance, project detail, manufacturing detail, and admin surfaces, including tighter panel spacing, more compact shell/navigation spacing, and removal of redundant explanatory subcopy in favor of concise uppercase section labels
- Continued density standardization across CRM, inventory, sales, purchasing, and shipping list/detail surfaces so module headers, filter bars, and status panels follow the same tighter uppercase operational pattern
- Continued density standardization across CRM, sales, purchasing, shipping, manufacturing, and project form/detail headers so editor and record surfaces now follow the same compact uppercase pattern with less redundant helper copy
- Project-side milestone and work-order rollups surfaced on project list and detail pages
- Inventory SKU master builder with family-level sequence codes, branch-aware taxonomy management, and generated SKU previews on the item form
- Thumbnail image attachment staging on inventory item create/edit pages, with upload-on-save and replacement/removal support

View File

@@ -55,11 +55,13 @@ This repository implements the platform foundation milestone:
6. Any non-filter UI that looks up records or items must use a searchable picker/autocomplete, not a long static dropdown.
7. Inventory items must carry both `defaultCost` and `defaultPrice`; sales documents should default line pricing from the selected item `defaultPrice`.
8. Maintain the denser UI baseline on active screens; avoid reintroducing oversized `px-4 py-3` style controls, tall action bars, or overly loose card spacing without a specific reason.
9. Treat the landing page as `Dashboard`: a metric-oriented, modular command surface that should accumulate reusable operational panels over time.
10. Purchase-order item selection must be restricted to inventory items where `isPurchasable = true`.
11. Treat `Projects` as a first-class cross-module domain tying together CRM, sales, inventory, purchasing, shipping, and planning; do not bury it as a one-off manufacturing subfeature.
12. Keep `Projects`, `Manufacturing`, and `Planning` distinct: projects are long-running program records, manufacturing is execution, and planning is scheduling/visibility.
13. New top-level modules added to the app shell should include a matching SVG icon in navigation so the module list remains visually scannable.
9. Prefer concise uppercase module and section labels in the live interface, and avoid redundant descriptive subcopy when the surrounding data already makes the purpose clear.
10. When designing operational pages, bias toward information density: tighter panel padding, smaller stack gaps, and fewer explanatory filler blocks.
11. Treat the landing page as `Dashboard`: a metric-oriented, modular command surface that should accumulate reusable operational panels over time.
12. Purchase-order item selection must be restricted to inventory items where `isPurchasable = true`.
13. Treat `Projects` as a first-class cross-module domain tying together CRM, sales, inventory, purchasing, shipping, and planning; do not bury it as a one-off manufacturing subfeature.
14. Keep `Projects`, `Manufacturing`, and `Planning` distinct: projects are long-running program records, manufacturing is execution, and planning is scheduling/visibility.
15. New top-level modules added to the app shell should include a matching SVG icon in navigation so the module list remains visually scannable.
## Operational notes

View File

@@ -89,6 +89,8 @@ Navigation direction:
- module navigation now uses inline SVG icons alongside labels
- new modules should add a clear, domain-appropriate SVG icon when they are added to the shell
- icons should stay lightweight, theme-aware, and dependency-free unless there is a strong reason to introduce a shared icon package
- active operational screens should default to a denser layout baseline with tighter card padding, smaller inter-panel gaps, and less decorative negative space
- module headers and section labels should prefer uppercase naming and concise operational wording instead of redundant explanatory subcopy inside the working interface
## Finance Direction

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>
<p className="section-kicker">SALES ORDER LEDGER</p>
</div>
</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><p className="section-kicker">RECENT PAYMENTS</p></div>
</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>