Compare commits

...

2 Commits

Author SHA1 Message Date
afad00bf46 1 2026-03-18 23:17:44 -05:00
28ea1ee6b9 cleanup 2026-03-18 23:14:47 -05:00
4 changed files with 120 additions and 120 deletions

View File

@@ -33,6 +33,8 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
- Continued density standardization across shared attachment and revision-comparison surfaces, and changed inventory item-editor exit actions to hard navigation so SKU master and cancel transitions no longer depend on client-side router state
- Continued density standardization across the SKU master builder and planning workbench, including tighter tree and board panels, denser exception and focus surfaces, shorter empty states, and less helper copy on those operational screens
- Continued density standardization across warehouse list/detail/editor screens and the manufacturing station surface, including tighter status blocks, denser location/station cards, and removal of older roomy header patterns
- Continued density standardization across company settings and deeper manufacturing detail surfaces, including tighter admin/profile/theme sections, denser work-order execution panels, and compact issue/completion history cards
- Continued density standardization across project cockpit/detail internals, including tighter cockpit cards, denser purchasing and readiness panels, and compact milestone, manufacturing-link, and activity-timeline surfaces
- Project-side milestone and work-order rollups surfaced on project list and detail pages
- Inventory SKU master builder with family-level sequence codes, branch-aware taxonomy management, and generated SKU previews on the item form
- Thumbnail image attachment staging on inventory item create/edit pages, with upload-on-save and replacement/removal support

View File

@@ -433,18 +433,18 @@ export function WorkOrderDetailPage() {
) : null}
<section className="grid gap-2 xl:grid-cols-6">
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Planned</p><div className="mt-2 text-base font-bold text-text">{workOrder.quantity}</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">Completed</p><div className="mt-2 text-base font-bold text-text">{workOrder.completedQuantity}</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">Remaining</p><div className="mt-2 text-base font-bold text-text">{workOrder.dueQuantity}</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">Project</p><div className="mt-2 text-base font-bold text-text">{workOrder.projectNumber || "Unlinked"}</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">Operations</p><div className="mt-2 text-base font-bold text-text">{workOrder.operations.length}</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">Due Date</p><div className="mt-2 text-base font-bold text-text">{workOrder.dueDate ? new Date(workOrder.dueDate).toLocaleDateString() : "Not set"}</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">Material Shortage</p><div className="mt-2 text-base font-bold text-text">{workOrder.materialRequirements.reduce((sum, requirement) => sum + requirement.shortageQuantity, 0)}</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">Actual Hours</p><div className="mt-2 text-base font-bold text-text">{(workOrder.totalActualMinutes / 60).toFixed(1)}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Completed</p><div className="mt-1 text-base font-bold text-text">{workOrder.completedQuantity}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Remaining</p><div className="mt-1 text-base font-bold text-text">{workOrder.dueQuantity}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Project</p><div className="mt-1 text-base font-bold text-text">{workOrder.projectNumber || "Unlinked"}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Operations</p><div className="mt-1 text-base font-bold text-text">{workOrder.operations.length}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Due Date</p><div className="mt-1 text-base font-bold text-text">{workOrder.dueDate ? new Date(workOrder.dueDate).toLocaleDateString() : "Not set"}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Material Shortage</p><div className="mt-1 text-base font-bold text-text">{workOrder.materialRequirements.reduce((sum, requirement) => sum + requirement.shortageQuantity, 0)}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Actual Hours</p><div className="mt-1 text-base font-bold text-text">{(workOrder.totalActualMinutes / 60).toFixed(1)}</div></article>
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(360px,0.9fr)]">
<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">Execution Context</p>
<dl className="mt-5 grid gap-3">
<article className="surface-panel">
<p className="section-kicker">EXECUTION CONTEXT</p>
<dl className="mt-3 grid gap-3">
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build item</dt><dd className="mt-1 text-sm text-text">{workOrder.itemSku} - {workOrder.itemName}</dd></div>
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Item type</dt><dd className="mt-1 text-sm text-text">{workOrder.itemType}</dd></div>
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Output location</dt><dd className="mt-1 text-sm text-text">{workOrder.warehouseCode} / {workOrder.locationCode}</dd></div>
@@ -452,17 +452,17 @@ export function WorkOrderDetailPage() {
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Demand source</dt><dd className="mt-1 text-sm text-text">{workOrder.salesOrderNumber ?? "Not linked"}</dd></div>
</dl>
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Work Instructions</p>
<article className="surface-panel">
<p className="section-kicker">WORK INSTRUCTIONS</p>
<p className="mt-3 whitespace-pre-line text-sm leading-6 text-text">{workOrder.notes || "No work-order notes recorded."}</p>
</article>
</div>
<section 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">Operation Plan</p>
<section className="surface-panel">
<p className="section-kicker">OPERATION PLAN</p>
{workOrder.operations.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">This work order has no inherited station operations. Add routing steps on the item record to automate planning.</div>
<div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">This work order has no inherited station operations. Add routing steps on the item record to automate planning.</div>
) : (
<div className="mt-5 overflow-hidden rounded-[18px] border border-line/70">
<div className="mt-3 overflow-hidden rounded-[18px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/70">
<tr className="text-left text-xs font-semibold uppercase tracking-[0.18em] text-muted">
@@ -647,11 +647,11 @@ export function WorkOrderDetailPage() {
</section>
{canManage ? (
<section className="grid gap-3 xl:grid-cols-2">
<form onSubmit={handleIssueSubmit} 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">Material Issue</p>
<div className="mt-4 grid gap-3">
<form onSubmit={handleIssueSubmit} className="surface-panel">
<p className="section-kicker">MATERIAL ISSUE</p>
<div className="mt-3 grid gap-3">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Component</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Component</span>
<select value={issueForm.componentItemId} onChange={(event) => setIssueForm((current) => ({ ...current, componentItemId: event.target.value }))} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand">
<option value="">Select component</option>
{workOrder.materialRequirements.map((requirement) => (
@@ -661,7 +661,7 @@ export function WorkOrderDetailPage() {
</label>
<div className="grid gap-3 sm:grid-cols-3">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Warehouse</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Warehouse</span>
<select value={issueForm.warehouseId} onChange={(event) => setIssueForm((current) => ({ ...current, warehouseId: event.target.value, locationId: "" }))} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand">
{[...new Map(locationOptions.map((option) => [option.warehouseId, option])).values()].map((option) => (
<option key={option.warehouseId} value={option.warehouseId}>{option.warehouseCode} - {option.warehouseName}</option>
@@ -669,7 +669,7 @@ export function WorkOrderDetailPage() {
</select>
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Location</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Location</span>
<select value={issueForm.locationId} onChange={(event) => setIssueForm((current) => ({ ...current, locationId: event.target.value }))} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand">
<option value="">Select location</option>
{filteredLocationOptions.map((option) => (
@@ -678,12 +678,12 @@ export function WorkOrderDetailPage() {
</select>
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Quantity</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Quantity</span>
<input type="number" min={1} step={1} value={issueForm.quantity} onChange={(event) => setIssueForm((current) => ({ ...current, quantity: Number.parseInt(event.target.value, 10) || 1 }))} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
</label>
</div>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Notes</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Notes</span>
<textarea value={issueForm.notes} onChange={(event) => setIssueForm((current) => ({ ...current, notes: event.target.value }))} rows={3} className="w-full rounded-[18px] border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
</label>
<button type="submit" disabled={isPostingIssue} className="rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-60">
@@ -691,15 +691,15 @@ export function WorkOrderDetailPage() {
</button>
</div>
</form>
<form onSubmit={handleCompletionSubmit} 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">Production Completion</p>
<div className="mt-4 grid gap-3">
<form onSubmit={handleCompletionSubmit} className="surface-panel">
<p className="section-kicker">PRODUCTION COMPLETION</p>
<div className="mt-3 grid gap-3">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Quantity</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Quantity</span>
<input type="number" min={1} step={1} value={completionForm.quantity} onChange={(event) => setCompletionForm((current) => ({ ...current, quantity: Number.parseInt(event.target.value, 10) || 1 }))} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Notes</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Notes</span>
<textarea value={completionForm.notes} onChange={(event) => setCompletionForm((current) => ({ ...current, notes: event.target.value }))} rows={3} className="w-full rounded-[18px] border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
</label>
<div className="rounded-2xl border border-line/70 bg-page/70 px-2 py-2 text-sm text-muted">Finished goods receipt posts back to {workOrder.warehouseCode} / {workOrder.locationCode}.</div>
@@ -710,12 +710,12 @@ export function WorkOrderDetailPage() {
</form>
</section>
) : null}
<section 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">Material Requirements</p>
<section className="surface-panel">
<p className="section-kicker">MATERIAL REQUIREMENTS</p>
{workOrder.materialRequirements.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">This build item does not currently have BOM material requirements.</div>
<div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">This build item does not currently have BOM material requirements.</div>
) : (
<div className="mt-5 overflow-hidden rounded-[18px] border border-line/70">
<div className="mt-3 overflow-hidden rounded-[18px] border border-line/70">
<table className="min-w-full divide-y divide-line/70 text-sm">
<thead className="bg-page/70">
<tr className="text-left text-xs font-semibold uppercase tracking-[0.18em] text-muted">
@@ -746,14 +746,14 @@ export function WorkOrderDetailPage() {
)}
</section>
<section className="grid gap-3 xl:grid-cols-2">
<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">Issue History</p>
<article className="surface-panel">
<p className="section-kicker">ISSUE HISTORY</p>
{workOrder.materialIssues.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 material issues have been posted yet.</div>
<div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No material issues have been posted yet.</div>
) : (
<div className="mt-5 space-y-3">
<div className="mt-3 space-y-2">
{workOrder.materialIssues.map((issue) => (
<div key={issue.id} className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3">
<div key={issue.id} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="font-semibold text-text">{issue.componentSku} - {issue.componentName}</div>
@@ -768,14 +768,14 @@ export function WorkOrderDetailPage() {
</div>
)}
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Completion History</p>
<article className="surface-panel">
<p className="section-kicker">COMPLETION HISTORY</p>
{workOrder.completions.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 production completions have been posted yet.</div>
<div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No production completions have been posted yet.</div>
) : (
<div className="mt-5 space-y-3">
<div className="mt-3 space-y-2">
{workOrder.completions.map((completion) => (
<div key={completion.id} className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3">
<div key={completion.id} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="font-semibold text-text">{completion.quantity} completed</div>
<div className="text-xs text-muted">{completion.createdByName}</div>

View File

@@ -51,7 +51,7 @@ export function ProjectDetailPage() {
}, [projectId, token]);
if (!project) {
return <div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">{status}</div>;
return <div className="surface-panel text-sm text-muted">{status}</div>;
}
const sortedMilestones = [...project.milestones].sort((left, right) => {
@@ -172,16 +172,16 @@ export function ProjectDetailPage() {
</div>
</div>
<section className="grid gap-3 xl:grid-cols-4">
<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">Customer</p><div className="mt-2 text-base font-bold text-text">{project.customerName}</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">Owner</p><div className="mt-2 text-base font-bold text-text">{project.ownerName || "Unassigned"}</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">Due Date</p><div className="mt-2 text-base font-bold text-text">{project.dueDate ? new Date(project.dueDate).toLocaleDateString() : "Not set"}</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">Created</p><div className="mt-2 text-base font-bold text-text">{new Date(project.createdAt).toLocaleDateString()}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Customer</p><div className="mt-1 text-base font-bold text-text">{project.customerName}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Owner</p><div className="mt-1 text-base font-bold text-text">{project.ownerName || "Unassigned"}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Due Date</p><div className="mt-1 text-base font-bold text-text">{project.dueDate ? new Date(project.dueDate).toLocaleDateString() : "Not set"}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Created</p><div className="mt-1 text-base font-bold text-text">{new Date(project.createdAt).toLocaleDateString()}</div></article>
</section>
<section className="grid gap-3 xl:grid-cols-4">
<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">Milestones</p><div className="mt-2 text-base font-bold text-text">{project.rollups.completedMilestoneCount}/{project.rollups.milestoneCount}</div><div className="mt-1 text-xs text-muted">{project.rollups.openMilestoneCount} open</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 Milestones</p><div className="mt-2 text-base font-bold text-text">{project.rollups.overdueMilestoneCount}</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="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Milestones</p><div className="mt-1 text-base font-bold text-text">{project.rollups.completedMilestoneCount}/{project.rollups.milestoneCount}</div><div className="mt-1 text-xs text-muted">{project.rollups.openMilestoneCount} open</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Overdue Milestones</p><div className="mt-1 text-base font-bold text-text">{project.rollups.overdueMilestoneCount}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Linked Work Orders</p><div className="mt-1 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="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Overdue Work Orders</p><div className="mt-1 text-base font-bold text-text">{project.rollups.overdueWorkOrderCount}</div><div className="mt-1 text-xs text-muted">{project.rollups.completedWorkOrderCount} complete</div></article>
</section>
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
@@ -284,88 +284,88 @@ export function ProjectDetailPage() {
<div className="flex items-center justify-between gap-3"><div><p className="section-kicker">LINKED PURCHASING</p></div>{project.salesOrderId ? <Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open purchasing</Link> : null}</div>
{project.cockpit.purchasing.purchaseOrders.length === 0 ? <div className="mt-5 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No linked purchase orders are tied to this project yet.</div> : <div className="mt-4 space-y-2.5">{project.cockpit.purchasing.purchaseOrders.slice(0, 5).map((purchaseOrder) => (<Link key={purchaseOrder.id} to={`/purchasing/orders/${purchaseOrder.id}`} className="block rounded-[16px] border border-line/70 bg-page/60 px-3 py-2.5 transition hover:bg-page/80"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{purchaseOrder.documentNumber}</div><div className="mt-1 text-xs text-muted">{purchaseOrder.vendorName} - {purchaseOrder.status.replaceAll("_", " ")}</div></div><div className="text-right text-xs text-muted"><div>${purchaseOrder.linkedLineValue.toFixed(2)} linked value</div><div>{purchaseOrder.totalReceivedQuantity}/{purchaseOrder.totalOrderedQuantity} received</div></div></div></Link>))}</div>}
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Readiness Drivers</p>
<div className="mt-5 space-y-3">
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Risk posture</div><div className={`mt-2 text-lg font-bold ${riskTone}`}>{project.cockpit.risk.riskLevel}</div><div className="mt-1 text-xs text-muted">{project.cockpit.risk.outstandingPurchaseOrderCount} PO(s) still waiting on receipts.</div></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3 text-sm text-text">Blocked milestones: <span className="font-semibold">{project.cockpit.risk.blockedMilestoneCount}</span></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3 text-sm text-text">Overdue execution items: <span className="font-semibold">{project.cockpit.risk.overdueMilestoneCount + project.cockpit.risk.overdueWorkOrderCount}</span></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3 text-sm text-text">Uncovered material quantity: <span className="font-semibold">{project.cockpit.risk.totalUncoveredQuantity}</span></div>
<article className="surface-panel">
<p className="section-kicker">READINESS DRIVERS</p>
<div className="mt-3 space-y-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Risk posture</div><div className={`mt-2 text-lg font-bold ${riskTone}`}>{project.cockpit.risk.riskLevel}</div><div className="mt-1 text-xs text-muted">{project.cockpit.risk.outstandingPurchaseOrderCount} PO(s) still waiting on receipts.</div></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2 text-sm text-text">Blocked milestones: <span className="font-semibold">{project.cockpit.risk.blockedMilestoneCount}</span></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2 text-sm text-text">Overdue execution items: <span className="font-semibold">{project.cockpit.risk.overdueMilestoneCount + project.cockpit.risk.overdueWorkOrderCount}</span></div>
<div className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2 text-sm text-text">Uncovered material quantity: <span className="font-semibold">{project.cockpit.risk.totalUncoveredQuantity}</span></div>
</div>
</article>
</section>
<section 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">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Vendor Exposure</p>
{project.cockpit.purchasing.vendors.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 supplier exposure exists until purchasing is linked.</div> : <div className="mt-5 space-y-3">{project.cockpit.purchasing.vendors.slice(0, 4).map((vendor) => (<div key={vendor.vendorId} className="rounded-[18px] border border-line/70 bg-page/60 p-3"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{vendor.vendorName}</div><div className="mt-1 text-xs text-muted">{vendor.orderCount} linked order(s)</div></div><div className="text-right text-xs text-muted"><div>${vendor.linkedLineValue.toFixed(2)}</div><div>{vendor.outstandingQuantity} outstanding qty</div></div></div></div>))}</div>}
<article className="surface-panel">
<p className="section-kicker">VENDOR EXPOSURE</p>
{project.cockpit.purchasing.vendors.length === 0 ? <div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No supplier exposure exists until purchasing is linked.</div> : <div className="mt-3 space-y-2">{project.cockpit.purchasing.vendors.slice(0, 4).map((vendor) => (<div key={vendor.vendorId} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{vendor.vendorName}</div><div className="mt-1 text-xs text-muted">{vendor.orderCount} linked order(s)</div></div><div className="text-right text-xs text-muted"><div>${vendor.linkedLineValue.toFixed(2)}</div><div>{vendor.outstandingQuantity} outstanding qty</div></div></div></div>))}</div>}
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Recent Receipts</p>
{project.cockpit.purchasing.recentReceipts.length === 0 ? <div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">No purchase receipts have been posted against linked project supply.</div> : <div className="mt-5 space-y-3">{project.cockpit.purchasing.recentReceipts.map((receipt) => (<div key={receipt.receiptId} className="rounded-[18px] border border-line/70 bg-page/60 p-3"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{receipt.receiptNumber}</div><div className="mt-1 text-xs text-muted">{receipt.vendorName} - {receipt.purchaseOrderNumber}</div></div><div className="text-right text-xs text-muted"><div>{new Date(receipt.receivedAt).toLocaleDateString()}</div><div>{receipt.totalQuantity} units received</div></div></div></div>))}</div>}
<article className="surface-panel">
<p className="section-kicker">RECENT RECEIPTS</p>
{project.cockpit.purchasing.recentReceipts.length === 0 ? <div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No purchase receipts have been posted against linked project supply.</div> : <div className="mt-3 space-y-2">{project.cockpit.purchasing.recentReceipts.map((receipt) => (<div key={receipt.receiptId} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{receipt.receiptNumber}</div><div className="mt-1 text-xs text-muted">{receipt.vendorName} - {receipt.purchaseOrderNumber}</div></div><div className="text-right text-xs text-muted"><div>{new Date(receipt.receivedAt).toLocaleDateString()}</div><div>{receipt.totalQuantity} units received</div></div></div></div>))}</div>}
</article>
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.05fr)_minmax(320px,0.95fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Customer Linkage</p>
<dl className="mt-5 grid gap-3">
<article className="surface-panel">
<p className="section-kicker">CUSTOMER LINKAGE</p>
<dl className="mt-3 grid gap-3">
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Account</dt><dd className="mt-1 text-sm text-text"><Link to={`/crm/customers/${project.customerId}`} className="hover:text-brand">{project.customerName}</Link></dd></div>
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Email</dt><dd className="mt-1 text-sm text-text">{project.customerEmail}</dd></div>
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Phone</dt><dd className="mt-1 text-sm text-text">{project.customerPhone}</dd></div>
</dl>
</article>
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Program Notes</p>
<article className="surface-panel">
<p className="section-kicker">PROGRAM NOTES</p>
<p className="mt-3 whitespace-pre-line text-sm leading-6 text-text">{project.notes || "No project notes recorded."}</p>
</article>
</div>
<section 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">Commercial + Delivery Links</p>
<div className="mt-5 grid gap-3 xl:grid-cols-3">
<section className="surface-panel">
<p className="section-kicker">COMMERCIAL + DELIVERY LINKS</p>
<div className="mt-3 grid gap-3 xl:grid-cols-3">
<div className="rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Quote</div><div className="mt-2 font-semibold text-text">{project.salesQuoteNumber ? <Link to={`/sales/quotes/${project.salesQuoteId}`} className="hover:text-brand">{project.salesQuoteNumber}</Link> : "Not linked"}</div></div>
<div className="rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Sales Order</div><div className="mt-2 font-semibold text-text">{project.salesOrderNumber ? <Link to={`/sales/orders/${project.salesOrderId}`} className="hover:text-brand">{project.salesOrderNumber}</Link> : "Not linked"}</div></div>
<div className="rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">Shipment</div><div className="mt-2 font-semibold text-text">{project.shipmentNumber ? <Link to={`/shipping/shipments/${project.shipmentId}`} className="hover:text-brand">{project.shipmentNumber}</Link> : "Not linked"}</div></div>
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div><p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Milestones</p><p className="mt-2 text-sm text-muted">Track project checkpoints, blockers, and completion progress.</p></div>
<div><p className="section-kicker">MILESTONES</p></div>
{canManage ? <Link to={`/projects/${project.id}/edit`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Edit milestones</Link> : null}
</div>
{project.milestones.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 milestones are defined for this project yet.</div> : <div className="mt-6 space-y-3">{project.milestones.map((milestone) => (<div key={milestone.id} className="rounded-[18px] border border-line/70 bg-page/60 p-3"><div className="flex flex-wrap items-start justify-between gap-3"><div className="min-w-0"><div className="font-semibold text-text">{milestone.title}</div><div className="mt-2 flex flex-wrap items-center gap-2"><span className={`inline-flex rounded-full px-2 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${projectMilestoneStatusPalette[milestone.status]}`}>{milestone.status.replace("_", " ")}</span><span className="text-xs text-muted">Due {milestone.dueDate ? new Date(milestone.dueDate).toLocaleDateString() : "not scheduled"}</span>{milestone.completedAt ? <span className="text-xs text-muted">Completed {new Date(milestone.completedAt).toLocaleDateString()}</span> : null}</div>{milestone.notes ? <div className="mt-3 whitespace-pre-line text-sm text-text">{milestone.notes}</div> : null}</div>{canManage ? <div className="flex flex-wrap gap-2">{milestoneQuickActions(milestone.status).map((action) => (<button key={action.status} type="button" onClick={() => void updateMilestoneStatus(milestone.id, action.status)} disabled={updatingMilestoneId === milestone.id} className="rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">{updatingMilestoneId === milestone.id ? "Saving..." : action.label}</button>))}</div> : null}</div></div>))}</div>}
{project.milestones.length === 0 ? <div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No milestones are defined for this project yet.</div> : <div className="mt-3 space-y-2">{project.milestones.map((milestone) => (<div key={milestone.id} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2"><div className="flex flex-wrap items-start justify-between gap-3"><div className="min-w-0"><div className="font-semibold text-text">{milestone.title}</div><div className="mt-2 flex flex-wrap items-center gap-2"><span className={`inline-flex rounded-full px-2 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${projectMilestoneStatusPalette[milestone.status]}`}>{milestone.status.replace("_", " ")}</span><span className="text-xs text-muted">Due {milestone.dueDate ? new Date(milestone.dueDate).toLocaleDateString() : "not scheduled"}</span>{milestone.completedAt ? <span className="text-xs text-muted">Completed {new Date(milestone.completedAt).toLocaleDateString()}</span> : null}</div>{milestone.notes ? <div className="mt-3 whitespace-pre-line text-sm text-text">{milestone.notes}</div> : null}</div>{canManage ? <div className="flex flex-wrap gap-2">{milestoneQuickActions(milestone.status).map((action) => (<button key={action.status} type="button" onClick={() => void updateMilestoneStatus(milestone.id, action.status)} disabled={updatingMilestoneId === milestone.id} className="rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">{updatingMilestoneId === milestone.id ? "Saving..." : action.label}</button>))}</div> : null}</div></div>))}</div>}
</section>
{planning ? (
<section 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">Material Readiness</p>
<div className="mt-5 grid gap-3 xl:grid-cols-4">
<article className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build Qty</p><div className="mt-2 text-base font-bold text-text">{planning.summary.totalBuildQuantity}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Buy Qty</p><div className="mt-2 text-base font-bold text-text">{planning.summary.totalPurchaseQuantity}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Uncovered Qty</p><div className="mt-2 text-base font-bold text-text">{planning.summary.totalUncoveredQuantity}</div></article>
<article className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Shortage Items</p><div className="mt-2 text-base font-bold text-text">{planning.summary.uncoveredItemCount}</div></article>
<section className="surface-panel">
<p className="section-kicker">MATERIAL READINESS</p>
<div className="mt-3 grid gap-3 xl:grid-cols-4">
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build Qty</p><div className="mt-1 text-base font-bold text-text">{planning.summary.totalBuildQuantity}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Buy Qty</p><div className="mt-1 text-base font-bold text-text">{planning.summary.totalPurchaseQuantity}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Uncovered Qty</p><div className="mt-1 text-base font-bold text-text">{planning.summary.totalUncoveredQuantity}</div></article>
<article className="surface-panel-tight"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Shortage Items</p><div className="mt-1 text-base font-bold text-text">{planning.summary.uncoveredItemCount}</div></article>
</div>
<div className="mt-5 space-y-3">
<div className="mt-3 space-y-2">
{planning.items.filter((item) => item.recommendedBuildQuantity > 0 || item.recommendedPurchaseQuantity > 0 || item.uncoveredQuantity > 0).slice(0, 8).map((item) => (
<div key={item.itemId} className="rounded-[18px] border border-line/70 bg-page/60 p-3"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{item.itemSku}</div><div className="mt-1 text-xs text-muted">{item.itemName}</div></div><div className="text-sm text-muted">Build {item.recommendedBuildQuantity} - Buy {item.recommendedPurchaseQuantity} - Uncovered {item.uncoveredQuantity}</div></div></div>
<div key={item.itemId} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{item.itemSku}</div><div className="mt-1 text-xs text-muted">{item.itemName}</div></div><div className="text-sm text-muted">Build {item.recommendedBuildQuantity} - Buy {item.recommendedPurchaseQuantity} - Uncovered {item.uncoveredQuantity}</div></div></div>
))}
</div>
</section>
) : null}
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div><p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing Links</p><p className="mt-2 text-sm text-muted">Work orders already linked to this project.</p></div>
<div><p className="section-kicker">MANUFACTURING LINKS</p></div>
{canManage ? <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 work order</Link> : null}
</div>
{workOrders.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 work orders are linked to this project yet.</div> : <div className="mt-6 space-y-3">{workOrders.map((workOrder) => (<Link key={workOrder.id} to={`/manufacturing/work-orders/${workOrder.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-center justify-between gap-3"><div><div className="font-semibold text-text">{workOrder.workOrderNumber}</div><div className="mt-1 text-xs text-muted">{workOrder.itemSku} - {workOrder.completedQuantity}/{workOrder.quantity} complete</div></div><div className="text-sm font-semibold text-text">{workOrder.status.replace("_", " ")}</div></div></Link>))}</div>}
{workOrders.length === 0 ? <div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No work orders are linked to this project yet.</div> : <div className="mt-3 space-y-2">{workOrders.map((workOrder) => (<Link key={workOrder.id} to={`/manufacturing/work-orders/${workOrder.id}`} className="block rounded-[18px] border border-line/70 bg-page/60 px-2 py-2 transition hover:bg-page/80"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{workOrder.workOrderNumber}</div><div className="mt-1 text-xs text-muted">{workOrder.itemSku} - {workOrder.completedQuantity}/{workOrder.quantity} complete</div></div><div className="text-sm font-semibold text-text">{workOrder.status.replace("_", " ")}</div></div></Link>))}</div>}
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<section className="surface-panel">
<div className="flex items-center justify-between gap-3">
<div><p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Activity Timeline</p><p className="mt-2 text-sm text-muted">Chronological project, milestone, purchasing, manufacturing, sales, and shipping history.</p></div>
<div><p className="section-kicker">ACTIVITY TIMELINE</p></div>
</div>
{project.timeline.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 timeline activity is available for this project yet.</div>
<div className="mt-3 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-3 py-5 text-center text-sm text-muted">No timeline activity is available for this project yet.</div>
) : (
<div className="mt-6 space-y-3">
<div className="mt-3 space-y-2">
{project.timeline.map((entry) => (
<div key={entry.id} className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div key={entry.id} className="rounded-[18px] border border-line/70 bg-page/60 px-2 py-2">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<div className="text-xs font-semibold uppercase tracking-[0.16em] text-muted">{entry.sourceType}</div>

View File

@@ -93,7 +93,7 @@ export function CompanySettingsPage() {
}, [logoUrl]);
if (!form || !token) {
return <div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">{status}</div>;
return <div className="surface-panel text-sm text-muted">{status}</div>;
}
async function handleSave(event: React.FormEvent<HTMLFormElement>) {
@@ -145,14 +145,13 @@ export function CompanySettingsPage() {
}
return (
<form className="space-y-6" onSubmit={handleSave}>
<form className="page-stack" onSubmit={handleSave}>
{user?.permissions.includes("admin.manage") ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Admin</p>
<h3 className="mt-2 text-lg font-bold text-text">Admin access and diagnostics</h3>
<p className="mt-2 text-sm text-muted">Manage users, roles, and system diagnostics from the linked admin surfaces.</p>
<p className="section-kicker">ADMIN</p>
<h3 className="module-title">ADMIN SURFACES</h3>
</div>
<div className="flex flex-wrap gap-3">
<Link to="/settings/users" className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
@@ -165,14 +164,13 @@ export function CompanySettingsPage() {
</div>
</section>
) : null}
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<div className="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
<section className="surface-panel">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Company Profile</p>
<h3 className="mt-2 text-lg font-bold text-text">Branding and legal identity</h3>
<p className="mt-2 max-w-2xl text-sm text-muted">Every internal document and PDF template will inherit its company identity from this profile.</p>
<p className="section-kicker">COMPANY PROFILE</p>
<h3 className="module-title">BRANDING AND LEGAL IDENTITY</h3>
</div>
<div className="rounded-[18px] border border-dashed border-line/70 bg-page/80 p-4">
<div className="rounded-[18px] border border-dashed border-line/70 bg-page/80 px-3 py-3">
{logoUrl ? <img alt="Company logo" className="h-20 w-20 rounded-2xl object-cover" src={logoUrl} /> : <div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-brand text-sm font-bold text-white">LOGO</div>}
<label className="mt-3 block cursor-pointer text-sm font-semibold text-brand">
Upload logo
@@ -180,7 +178,7 @@ export function CompanySettingsPage() {
</label>
</div>
</div>
<div className="mt-6 grid gap-4 xl:grid-cols-2 2xl:grid-cols-3">
<div className="mt-3 grid gap-3 xl:grid-cols-2 2xl:grid-cols-3">
{[
["companyName", "Company name"],
["legalName", "Legal name"],
@@ -196,37 +194,37 @@ export function CompanySettingsPage() {
["country", "Country"],
].map(([key, label]) => (
<label key={key} className="block">
<span className="mb-2 block text-sm font-semibold text-text">{label}</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">{label}</span>
<input
value={String(form[key as keyof CompanyProfileInput])}
onChange={(event) => updateField(key as keyof CompanyProfileInput, event.target.value as never)}
className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand"
className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand"
/>
</label>
))}
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Theme</p>
<div className="mt-5 grid gap-4 md:grid-cols-2 2xl:grid-cols-4">
<section className="surface-panel">
<p className="section-kicker">THEME</p>
<div className="mt-3 grid gap-3 md:grid-cols-2 2xl:grid-cols-4">
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Primary color</span>
<input type="color" value={form.theme.primaryColor} onChange={(event) => updateField("theme", { ...form.theme, primaryColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Primary color</span>
<input type="color" value={form.theme.primaryColor} onChange={(event) => updateField("theme", { ...form.theme, primaryColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Accent color</span>
<input type="color" value={form.theme.accentColor} onChange={(event) => updateField("theme", { ...form.theme, accentColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Accent color</span>
<input type="color" value={form.theme.accentColor} onChange={(event) => updateField("theme", { ...form.theme, accentColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Surface color</span>
<input type="color" value={form.theme.surfaceColor} onChange={(event) => updateField("theme", { ...form.theme, surfaceColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Surface color</span>
<input type="color" value={form.theme.surfaceColor} onChange={(event) => updateField("theme", { ...form.theme, surfaceColor: event.target.value })} className="h-10 w-full rounded-2xl border border-line/70 bg-page p-2" />
</label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Font family</span>
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-muted">Font family</span>
<input value={form.theme.fontFamily} onChange={(event) => updateField("theme", { ...form.theme, fontFamily: event.target.value })} className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" />
</label>
</div>
<div className="mt-5 flex flex-col gap-3 rounded-2xl border border-line/70 bg-page/70 px-2 py-2 lg:flex-row lg:items-center lg:justify-between">
<div className="mt-3 flex flex-col gap-3 rounded-2xl border border-line/70 bg-page/70 px-2 py-2 lg:flex-row lg:items-center lg:justify-between">
<span className="min-w-0 text-sm text-muted">{status}</span>
<div className="flex flex-wrap gap-3">
<button