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 - 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 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 - 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 - 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 - 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 - 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. 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`. 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. 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. 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. Purchase-order item selection must be restricted to inventory items where `isPurchasable = true`. 10. When designing operational pages, bias toward information density: tighter panel padding, smaller stack gaps, and fewer explanatory filler blocks.
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. 11. Treat the landing page as `Dashboard`: a metric-oriented, modular command surface that should accumulate reusable operational panels over time.
12. Keep `Projects`, `Manufacturing`, and `Planning` distinct: projects are long-running program records, manufacturing is execution, and planning is scheduling/visibility. 12. Purchase-order item selection must be restricted to inventory items where `isPurchasable = true`.
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. 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 ## Operational notes

View File

@@ -89,6 +89,8 @@ Navigation direction:
- module navigation now uses inline SVG icons alongside labels - 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 - 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 - 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 ## Finance Direction

View File

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

View File

@@ -4,6 +4,32 @@
@tailwind components; @tailwind components;
@tailwind utilities; @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 { :root {
color-scheme: light; color-scheme: light;
--font-family: "Manrope"; --font-family: "Manrope";

View File

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

View File

@@ -110,17 +110,14 @@ export function CrmFormPage({ entity, mode }: CrmFormPageProps) {
} }
return ( return (
<form className="space-y-6" onSubmit={handleSubmit}> <form className="page-stack" onSubmit={handleSubmit}>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM Editor</p> <p className="section-kicker">CRM EDITOR</p>
<h3 className="mt-2 text-xl font-bold text-text"> <h3 className="module-title">
{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`} {mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}
</h3> </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> </div>
<Link <Link
to={mode === "create" ? config.routeBase : `${config.routeBase}/${recordId}`} to={mode === "create" ? config.routeBase : `${config.routeBase}/${recordId}`}
@@ -130,9 +127,9 @@ export function CrmFormPage({ entity, mode }: CrmFormPageProps) {
</Link> </Link>
</div> </div>
</section> </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} /> <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> <span className="min-w-0 text-sm text-muted">{status}</span>
<button <button
type="submit" type="submit"

View File

@@ -55,14 +55,11 @@ export function CrmListPage({ entity }: CrmListPageProps) {
}, [config.collectionLabel, entity, lifecycleFilter, operationalFilter, searchTerm, stateFilter, statusFilter, token]); }, [config.collectionLabel, entity, lifecycleFilter, operationalFilter, searchTerm, stateFilter, statusFilter, token]);
return ( 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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM</p> <p className="section-kicker">CRM</p>
<h3 className="mt-2 text-lg font-bold text-text">{config.collectionLabel}</h3> <h3 className="module-title">{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>
</div> </div>
{canManage ? ( {canManage ? (
<Link <Link
@@ -73,7 +70,7 @@ export function CrmListPage({ entity }: CrmListPageProps) {
</Link> </Link>
) : null} ) : null}
</div> </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"> <label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span> <span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input <input
@@ -137,13 +134,13 @@ export function CrmListPage({ entity }: CrmListPageProps) {
</select> </select>
</label> </label>
</div> </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 ? ( {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} {config.emptyMessage}
</div> </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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>

View File

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

View File

@@ -192,18 +192,15 @@ export function FinancePage() {
const currencyCode = profile.currencyCode || "USD"; const currencyCode = profile.currencyCode || "USD";
return ( return (
<section className="space-y-4"> <section className="page-stack">
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Finance</p> <p className="section-kicker">FINANCE</p>
<h2 className="mt-2 text-2xl font-bold text-text">Cash, spend, and CapEx control</h2> <h2 className="module-title">CASH SPEND CAPEX</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>
</div> </div>
<div className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3 text-sm text-muted"> <div className="surface-panel-tight text-sm text-muted">
Live snapshot generated {new Date(dashboard.generatedAt).toLocaleString()} SNAPSHOT {new Date(dashboard.generatedAt).toLocaleString()}
</div> </div>
</div> </div>
</div> </div>
@@ -217,22 +214,19 @@ export function FinancePage() {
{ label: "Mfg Cost", value: formatCurrency(summary.manufacturingTotalCost, currencyCode) }, { label: "Mfg Cost", value: formatCurrency(summary.manufacturingTotalCost, currencyCode) },
{ label: "CapEx Actual", value: formatCurrency(summary.capexActual, currencyCode) }, { label: "CapEx Actual", value: formatCurrency(summary.capexActual, currencyCode) },
].map((card) => ( ].map((card) => (
<article key={card.label} className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel"> <article key={card.label} className="surface-panel-tight bg-surface/90 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">{card.label}</p> <p className="metric-kicker">{card.label}</p>
<div className="mt-2 text-xl font-extrabold text-text">{card.value}</div> <div className="mt-1.5 text-xl font-extrabold text-text">{card.value}</div>
</article> </article>
))} ))}
</section> </section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.15fr)_minmax(360px,0.85fr)]"> <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 className="flex items-center justify-between gap-3">
<div> <p className="section-kicker">SALES ORDER LEDGER</p>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Sales Order Ledger</p>
<p className="mt-2 text-sm text-muted">Revenue, receipts, purchasing, and manufacturing cost by order.</p>
</div> </div>
</div> <div className="mt-3 overflow-x-auto">
<div className="mt-5 overflow-x-auto">
<table className="min-w-full divide-y divide-line/60 text-sm"> <table className="min-w-full divide-y divide-line/60 text-sm">
<thead> <thead>
<tr className="text-left text-xs uppercase tracking-[0.16em] text-muted"> <tr className="text-left text-xs uppercase tracking-[0.16em] text-muted">
@@ -290,9 +284,9 @@ export function FinancePage() {
</article> </article>
<div className="space-y-3"> <div className="space-y-3">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="surface-panel">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Costing Assumptions</p> <p className="section-kicker">COSTING ASSUMPTIONS</p>
<div className="mt-4 grid gap-3"> <div className="mt-3 grid gap-2.5">
<label className="text-sm text-text"> <label className="text-sm text-text">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Currency</span> <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" /> <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> </label>
</div> </div>
{canManage ? ( {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"} {isSavingProfile ? "Saving..." : "Save assumptions"}
</button> </button>
) : null} ) : null}
</article> </article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="surface-panel">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Post Payment</p> <p className="section-kicker">POST PAYMENT</p>
<div className="mt-4 grid gap-3"> <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"> <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) => ( {salesOrders.map((order) => (
<option key={order.id} value={order.id}> <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" /> <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> </div>
{canManage ? ( {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"} {isPostingPayment ? "Posting..." : "Post payment"}
</button> </button>
) : null} ) : null}
@@ -348,19 +342,16 @@ export function FinancePage() {
</div> </div>
<div className="grid gap-3 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]"> <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 className="flex items-center justify-between gap-3">
<div> <div><p className="section-kicker">RECENT PAYMENTS</p></div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Recent Payments</p>
<p className="mt-2 text-sm text-muted">Posted receipts linked directly to sales orders.</p>
</div> </div>
</div> <div className="mt-3 space-y-2.5">
<div className="mt-5 space-y-3">
{payments.length === 0 ? ( {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> <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) => ( 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 className="flex items-start justify-between gap-3">
<div> <div>
<Link to={`/sales/orders/${payment.salesOrderId}`} className="font-semibold text-brand hover:underline"> <Link to={`/sales/orders/${payment.salesOrderId}`} className="font-semibold text-brand hover:underline">
@@ -381,12 +372,9 @@ export function FinancePage() {
</div> </div>
</article> </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 className="flex items-center justify-between gap-3">
<div> <div><p className="section-kicker">CAPEX TRACKER</p></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>
{editingCapexId ? ( {editingCapexId ? (
<button type="button" onClick={resetCapexForm} className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text"> <button type="button" onClick={resetCapexForm} className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
Clear edit Clear edit
@@ -394,7 +382,7 @@ export function FinancePage() {
) : null} ) : null}
</div> </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" /> <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"> <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>)} {capexCategories.map((category) => <option key={category} value={category}>{category}</option>)}
@@ -421,12 +409,12 @@ export function FinancePage() {
</div> </div>
{canManage ? ( {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"} {isSavingCapex ? "Saving..." : editingCapexId ? "Update CapEx" : "Create CapEx"}
</button> </button>
) : null} ) : null}
<div className="mt-5 space-y-3"> <div className="mt-3 space-y-2.5">
{capex.length === 0 ? ( {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> <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, 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 className="flex flex-wrap items-start justify-between gap-3">
<div> <div>
@@ -473,7 +461,7 @@ export function FinancePage() {
</article> </article>
</div> </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} {status}
</div> </div>
</section> </section>

View File

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

View File

@@ -41,14 +41,11 @@ export function InventoryListPage() {
}, [searchTerm, statusFilter, token, typeFilter]); }, [searchTerm, statusFilter, token, typeFilter]);
return ( 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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Inventory</p> <p className="section-kicker">INVENTORY</p>
<h3 className="mt-2 text-lg font-bold text-text">Item Master</h3> <h3 className="module-title">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>
</div> </div>
{canManage ? ( {canManage ? (
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
@@ -61,7 +58,7 @@ export function InventoryListPage() {
</div> </div>
) : null} ) : null}
</div> </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"> <label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span> <span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input <input
@@ -100,13 +97,13 @@ export function InventoryListPage() {
</select> </select>
</label> </label>
</div> </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 ? ( {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. No inventory items have been added yet.
</div> </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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>

View File

@@ -391,17 +391,17 @@ export function WorkOrderDetailPage() {
} }
return ( return (
<section className="space-y-4"> <section className="page-stack">
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Work Order</p> <p className="section-kicker">WORK ORDER</p>
<h3 className="mt-2 text-xl font-bold text-text">{workOrder.workOrderNumber}</h3> <h3 className="module-title">{workOrder.workOrderNumber}</h3>
<p className="mt-1 text-sm text-text">{workOrder.itemSku} - {workOrder.itemName}</p> <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 ? ( {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="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="text-xs font-semibold uppercase tracking-[0.18em]">Current Hold Reason</div> <div className="metric-kicker text-amber-900">Current Hold Reason</div>
<div className="mt-2 whitespace-pre-line">{workOrder.holdReason}</div> <div className="mt-2 whitespace-pre-line">{workOrder.holdReason}</div>
</div> </div>
) : null} ) : null}
@@ -416,11 +416,10 @@ export function WorkOrderDetailPage() {
</div> </div>
</div> </div>
{canManage ? ( {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 className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p> <p className="section-kicker">QUICK ACTIONS</p>
<p className="mt-2 text-sm text-muted">Release, hold, or close administrative status from the work-order record.</p>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{workOrderStatusOptions.map((option) => ( {workOrderStatusOptions.map((option) => (

View File

@@ -35,13 +35,12 @@ export function WorkOrderListPage() {
}, [query, statusFilter, token]); }, [query, statusFilter, token]);
return ( return (
<section className="space-y-4"> <section className="page-stack">
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing</p> <p className="section-kicker">MANUFACTURING</p>
<h3 className="mt-2 text-xl font-bold text-text">Work Orders</h3> <h3 className="module-title">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>
</div> </div>
{canManage ? ( {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"> <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} ) : null}
</div> </div>
</div> </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="grid gap-3 xl:grid-cols-[minmax(0,1fr)_240px]"> <div className="grid gap-2.5 xl:grid-cols-[minmax(0,1fr)_240px]">
<label className="block"> <label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Search</span> <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" /> <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> </select>
</label> </label>
</div> </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> </section>
{workOrders.length === 0 ? ( {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> <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 ( return (
<section className="space-y-4"> <section className="page-stack">
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Project</p> <p className="section-kicker">PROJECT</p>
<h3 className="mt-2 text-xl font-bold text-text">{project.projectNumber}</h3> <h3 className="module-title">{project.projectNumber}</h3>
<p className="mt-1 text-sm text-text">{project.name}</p> <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} /> <ProjectStatusBadge status={project.status} />
<ProjectPriorityBadge priority={project.priority} /> <ProjectPriorityBadge priority={project.priority} />
</div> </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">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> <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>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Project Cockpit</p> <p className="section-kicker">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>
</div> </div>
<div className="rounded-2xl border border-line/70 bg-page/60 px-3 py-2 text-right"> <div className="surface-panel-tight text-right">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Milestone Progress</div> <div className="metric-kicker">Milestone Progress</div>
<div className="mt-1 text-2xl font-bold text-text">{completionPercent}%</div> <div className="mt-1 text-2xl font-bold text-text">{completionPercent}%</div>
</div> </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">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">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">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> <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>
<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">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">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> <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>
<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">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">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">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> <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>
<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"> <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> <p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Next Checkpoints</p>
<div className="mt-4 space-y-3"> <div className="mt-4 space-y-3">
@@ -227,17 +225,16 @@ export function ProjectDetailPage() {
</div> </div>
</section> </section>
<section className="grid gap-3 xl:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]"> <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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Actionable Cockpit</p> <p className="section-kicker">ACTIONABLE COCKPIT</p>
<p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p>
</div> </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"> <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 Open workbench
</Link> </Link>
</div> </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"> <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> <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> <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} ) : null}
</div> </div>
</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"> <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 New project work order
</Link> </Link>
@@ -283,9 +280,9 @@ export function ProjectDetailPage() {
</Link> </Link>
</div> </div>
</article> </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">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> <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-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>} {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>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5"> <article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Readiness Drivers</p> <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 ( return (
<form className="space-y-6" onSubmit={handleSubmit}> <form className="page-stack" onSubmit={handleSubmit}>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Projects Editor</p> <p className="section-kicker">PROJECTS EDITOR</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Project" : "Edit Project"}</h3> <h3 className="module-title">{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>
</div> </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"> <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 Cancel
</Link> </Link>
</div> </div>
</section> </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"> <div className="grid gap-3 xl:grid-cols-2">
<label className="block"> <label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Project name</span> <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]); }, [priorityFilter, query, statusFilter, token]);
return ( return (
<section className="space-y-4"> <section className="page-stack">
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Projects</p> <p className="section-kicker">PROJECTS</p>
<h3 className="mt-2 text-xl font-bold text-text">Program records</h3> <h3 className="module-title">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>
</div> </div>
{canManage ? ( {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"> <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} ) : null}
</div> </div>
</div> </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="grid gap-3 xl:grid-cols-[minmax(0,1.2fr)_0.45fr_0.45fr]"> <div className="grid gap-2.5 xl:grid-cols-[minmax(0,1.2fr)_0.45fr_0.45fr]">
<label className="block"> <label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Search</span> <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" /> <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> </select>
</label> </label>
</div> </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 ? ( {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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>

View File

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

View File

@@ -264,19 +264,19 @@ export function PurchaseFormPage({ mode }: { mode: "create" | "edit" }) {
}).length; }).length;
return ( return (
<form className="space-y-6" onSubmit={handleSubmit}> <form className="page-stack" onSubmit={handleSubmit}>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchasing Editor</p> <p className="section-kicker">PURCHASING EDITOR</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Purchase Order" : "Edit Purchase Order"}</h3> <h3 className="module-title">{mode === "create" ? "New Purchase Order" : "Edit Purchase Order"}</h3>
</div> </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"> <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 Cancel
</Link> </Link>
</div> </div>
</section> </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"> <div className="grid gap-3 xl:grid-cols-4">
<label className="block xl:col-span-2"> <label className="block xl:col-span-2">
<span className="mb-2 block text-sm font-semibold text-text">Vendor</span> <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]); }, [searchTerm, statusFilter, token]);
return ( 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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Purchasing</p> <p className="section-kicker">PURCHASING</p>
<h3 className="mt-2 text-lg font-bold text-text">Purchase Orders</h3> <h3 className="module-title">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>
</div> </div>
{canManage ? ( {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"> <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> </Link>
) : null} ) : null}
</div> </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"> <label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span> <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" /> <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> </select>
</label> </label>
</div> </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 ? ( {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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>

View File

@@ -335,11 +335,11 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
return ( return (
<section className="space-y-4"> <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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.detailEyebrow}</p> <p className="section-kicker">{config.detailEyebrow.toUpperCase()}</p>
<h3 className="mt-2 text-xl font-bold text-text">{activeDocument.documentNumber}</h3> <h3 className="module-title">{activeDocument.documentNumber}</h3>
<p className="mt-1 text-sm text-text">{activeDocument.customerName}</p> <p className="mt-1 text-sm text-text">{activeDocument.customerName}</p>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
<SalesStatusBadge status={activeDocument.status} /> <SalesStatusBadge status={activeDocument.status} />
@@ -396,11 +396,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
</div> </div>
</div> </div>
{canManage ? ( {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 className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p> <p className="section-kicker">QUICK ACTIONS</p>
<p className="mt-2 text-sm text-muted">Update document status without opening the full editor.</p>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{salesStatusOptions.map((option) => ( {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> <div className="mt-2 text-base font-bold text-text">${activeDocument.total.toFixed(2)}</div>
</article> </article>
</section> </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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Revision History</p> <p className="section-kicker">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>
</div> </div>
</div> </div>
{activeDocument.revisions.length === 0 ? ( {activeDocument.revisions.length === 0 ? (
@@ -587,14 +585,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
)} )}
</section> </section>
{entity === "order" && planning ? ( {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 className="flex flex-wrap items-start justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Demand Planning</p> <p className="section-kicker">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>
</div> </div>
<div className="text-right text-xs text-muted"> <div className="text-right text-xs text-muted">
<div>Generated {new Date(planning.generatedAt).toLocaleString()}</div> <div>Generated {new Date(planning.generatedAt).toLocaleString()}</div>
@@ -706,11 +700,10 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
</section> </section>
) : null} ) : null}
{entity === "order" && canReadShipping ? ( {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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping</p> <p className="section-kicker">SHIPPING</p>
<p className="mt-2 text-sm text-muted">Shipment records currently tied to this sales order.</p>
</div> </div>
{canManageShipping ? ( {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"> <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 ( return (
<form className="space-y-6" onSubmit={handleSubmit}> <form className="page-stack" onSubmit={handleSubmit}>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.detailEyebrow} Editor</p> <p className="section-kicker">{`${config.detailEyebrow} EDITOR`.toUpperCase()}</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}</h3> <h3 className="module-title">{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}</h3>
</div> </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"> <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 Cancel
</Link> </Link>
</div> </div>
</section> </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"> <div className="grid gap-3 xl:grid-cols-4">
<label className="block xl:col-span-2"> <label className="block xl:col-span-2">
<span className="mb-2 block text-sm font-semibold text-text">Customer</span> <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]); }, [config.collectionLabel, entity, searchTerm, statusFilter, token]);
return ( 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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">{config.listEyebrow}</p> <p className="section-kicker">{config.listEyebrow.toUpperCase()}</p>
<h3 className="mt-2 text-lg font-bold text-text">{config.collectionLabel}</h3> <h3 className="module-title">{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>
</div> </div>
{canManage ? ( {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"> <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> </Link>
) : null} ) : null}
</div> </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"> <label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span> <span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input <input
@@ -80,13 +77,13 @@ export function SalesListPage({ entity }: { entity: SalesDocumentEntity }) {
</select> </select>
</label> </label>
</div> </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 ? ( {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. No {config.collectionLabel.toLowerCase()} have been added yet.
</div> </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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>

View File

@@ -157,14 +157,11 @@ export function AdminDiagnosticsPage() {
return ( return (
<div className="space-y-6"> <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"> <section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Admin Diagnostics</p> <p className="section-kicker">ADMIN DIAGNOSTICS</p>
<h3 className="mt-2 text-lg font-bold text-text">Operational runtime and audit visibility</h3> <h3 className="module-title">RUNTIME AUDIT SUPPORT</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>
</div> </div>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<button <button
@@ -199,14 +196,10 @@ export function AdminDiagnosticsPage() {
</div> </div>
</section> </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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Backup And Restore</p> <p className="section-kicker">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>
</div> </div>
<div className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3 text-sm text-muted"> <div className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3 text-sm text-muted">
<div>Data: {backupGuidance.dataPath}</div> <div>Data: {backupGuidance.dataPath}</div>
@@ -266,11 +259,10 @@ export function AdminDiagnosticsPage() {
</div> </div>
</section> </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 className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Startup Validation</p> <p className="section-kicker">STARTUP VALIDATION</p>
<h3 className="mt-2 text-lg font-bold text-text">Boot-time readiness checks</h3>
</div> </div>
<span className={`inline-flex rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${startupStatusTone}`}> <span className={`inline-flex rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${startupStatusTone}`}>
{diagnostics.startup.status} {diagnostics.startup.status}
@@ -297,8 +289,8 @@ export function AdminDiagnosticsPage() {
</div> </div>
</section> </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">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">System Footprint</p> <p className="section-kicker">SYSTEM FOOTPRINT</p>
<div className="mt-5 grid gap-3 xl:grid-cols-2"> <div className="mt-5 grid gap-3 xl:grid-cols-2">
{footprintCards.map(([label, value]) => ( {footprintCards.map(([label, value]) => (
<div key={label} className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3"> <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> </div>
</section> </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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Support Logs</p> <p className="section-kicker">SUPPORT LOGS</p>
<h3 className="mt-2 text-lg font-bold text-text">Recent runtime warnings and failures</h3>
</div> </div>
<p className="text-sm text-muted"> <p className="text-sm text-muted">
{supportLogSummary ? `${supportLogSummary.filteredCount} of ${supportLogSummary.totalCount} entries` : "No entries loaded"} {supportLogSummary ? `${supportLogSummary.filteredCount} of ${supportLogSummary.totalCount} entries` : "No entries loaded"}
@@ -404,11 +395,10 @@ export function AdminDiagnosticsPage() {
</div> </div>
</section> </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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Recent Audit Trail</p> <p className="section-kicker">RECENT AUDIT TRAIL</p>
<h3 className="mt-2 text-lg font-bold text-text">Latest cross-module write activity</h3>
</div> </div>
<p className="text-sm text-muted">{status}</p> <p className="text-sm text-muted">{status}</p>
</div> </div>

View File

@@ -273,14 +273,11 @@ export function UserManagementPage() {
return ( return (
<div className="space-y-6"> <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"> <section className="surface-panel backdrop-blur">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">User Management</p> <p className="section-kicker">ADMIN</p>
<h3 className="mt-2 text-lg font-bold text-text">Accounts, roles, and permission assignment</h3> <h3 className="module-title">USERS ROLES SESSIONS</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>
</div> </div>
<div className="flex flex-wrap gap-3"> <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"> <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>
<section className="grid gap-6 xl:grid-cols-2"> <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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Users</p> <p className="section-kicker">USERS</p>
<h3 className="mt-2 text-lg font-bold text-text">Account generation and role assignment</h3>
</div> </div>
<select <select
value={selectedUserId} value={selectedUserId}
@@ -387,11 +383,10 @@ export function UserManagementPage() {
</div> </div>
</form> </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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Roles</p> <p className="section-kicker">ROLES</p>
<h3 className="mt-2 text-lg font-bold text-text">Permission assignment administration</h3>
</div> </div>
<select <select
value={selectedRoleId} value={selectedRoleId}
@@ -465,14 +460,10 @@ export function UserManagementPage() {
</form> </form>
</section> </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-4 lg:flex-row lg:items-start lg:justify-between"> <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Sessions</p> <p className="section-kicker">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>
</div> </div>
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4"> <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<label className="block"> <label className="block">

View File

@@ -218,11 +218,11 @@ export function ShipmentDetailPage() {
return ( return (
<section className="space-y-4"> <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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipment</p> <p className="section-kicker">SHIPMENT</p>
<h3 className="mt-2 text-xl font-bold text-text">{shipment.shipmentNumber}</h3> <h3 className="module-title">{shipment.shipmentNumber}</h3>
<p className="mt-1 text-sm text-text">{shipment.salesOrderNumber} / {shipment.customerName}</p> <p className="mt-1 text-sm text-text">{shipment.salesOrderNumber} / {shipment.customerName}</p>
<div className="mt-3 flex flex-wrap items-center gap-3"> <div className="mt-3 flex flex-wrap items-center gap-3">
<ShipmentStatusBadge status={shipment.status} /> <ShipmentStatusBadge status={shipment.status} />
@@ -249,11 +249,10 @@ export function ShipmentDetailPage() {
</div> </div>
{canManage ? ( {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 className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Quick Actions</p> <p className="section-kicker">QUICK ACTIONS</p>
<p className="mt-2 text-sm text-muted">Use inventory-backed picking before marking the shipment packed or shipped.</p>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{shipmentStatusOptions.map((option) => ( {shipmentStatusOptions.map((option) => (
@@ -286,11 +285,10 @@ export function ShipmentDetailPage() {
</section> </section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.3fr)_minmax(340px,0.9fr)]"> <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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipment Lines</p> <p className="section-kicker">SHIPMENT LINES</p>
<p className="mt-2 text-sm text-muted">Track ordered, picked, and remaining quantity before shipment closeout.</p>
</div> </div>
</div> </div>
<div className="mt-5 overflow-x-auto"> <div className="mt-5 overflow-x-auto">
@@ -350,13 +348,10 @@ export function ShipmentDetailPage() {
</div> </div>
{canManage ? ( {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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Pick And Issue From Stock</p> <p className="section-kicker">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>
</div> </div>
<div className="rounded-[16px] border border-line/70 bg-page/60 px-3 py-2 text-xs text-muted"> <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. Select the sales-order line, source location, and quantity you are physically picking.
@@ -468,11 +463,10 @@ export function ShipmentDetailPage() {
) : null} ) : null}
<div className="grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(320px,0.9fr)]"> <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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Pick History</p> <p className="section-kicker">PICK HISTORY</p>
<p className="mt-2 text-sm text-muted">Every pick here already issued stock from a specific inventory location.</p>
</div> </div>
</div> </div>
{shipment.picks.length === 0 ? ( {shipment.picks.length === 0 ? (
@@ -508,11 +502,10 @@ export function ShipmentDetailPage() {
</article> </article>
</div> </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 className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Related Shipments</p> <p className="section-kicker">RELATED SHIPMENTS</p>
<p className="mt-2 text-sm text-muted">Other shipments already tied to this sales order.</p>
</div> </div>
{canManage ? ( {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> <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 ( return (
<form className="space-y-6" onSubmit={handleSubmit}> <form className="page-stack" onSubmit={handleSubmit}>
<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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping Editor</p> <p className="section-kicker">SHIPPING EDITOR</p>
<h3 className="mt-2 text-xl font-bold text-text">{mode === "create" ? "New Shipment" : "Edit Shipment"}</h3> <h3 className="module-title">{mode === "create" ? "New Shipment" : "Edit Shipment"}</h3>
</div> </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"> <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 Cancel
</Link> </Link>
</div> </div>
</section> </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"> <label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Sales Order</span> <span className="mb-2 block text-sm font-semibold text-text">Sales Order</span>
<div className="relative"> <div className="relative">

View File

@@ -38,12 +38,11 @@ export function ShipmentListPage() {
}, [searchTerm, statusFilter, token]); }, [searchTerm, statusFilter, token]);
return ( 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 className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Shipping</p> <p className="section-kicker">SHIPPING</p>
<h3 className="mt-2 text-lg font-bold text-text">Shipments</h3> <h3 className="module-title">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>
</div> </div>
{canManage ? ( {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"> <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> </Link>
) : null} ) : null}
</div> </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"> <label className="block">
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span> <span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Search</span>
<input <input
@@ -76,11 +75,11 @@ export function ShipmentListPage() {
</select> </select>
</label> </label>
</div> </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 ? ( {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"> <table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/80 text-left text-muted"> <thead className="bg-page/80 text-left text-muted">
<tr> <tr>