shipping label fix - codex
This commit is contained in:
@@ -46,6 +46,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
|
|||||||
- Workbench now surfaces day-level capacity directly in the planner, including hot-station day counts on heatmap cells, selected-day station load breakdowns, and per-station hot-day chips in station grouping mode
|
- Workbench now surfaces day-level capacity directly in the planner, including hot-station day counts on heatmap cells, selected-day station load breakdowns, and per-station hot-day chips in station grouping mode
|
||||||
- Workbench exception prioritization now scores and ranks projects, work orders, agenda rows, and dispatch exceptions by lateness, blockage, shortage, readiness, and overload pressure, with inline priority chips for faster triage
|
- Workbench exception prioritization now scores and ranks projects, work orders, agenda rows, and dispatch exceptions by lateness, blockage, shortage, readiness, and overload pressure, with inline priority chips for faster triage
|
||||||
- Workbench now surfaces top-priority action lanes for `DO NOW`, `UNBLOCK`, and `RELEASE READY` records so planners can jump straight into ranked dispatch queues before working deeper lists
|
- Workbench now surfaces top-priority action lanes for `DO NOW`, `UNBLOCK`, and `RELEASE READY` records so planners can jump straight into ranked dispatch queues before working deeper lists
|
||||||
|
- Workbench action lanes now support direct follow-through from the lane cards themselves, including queue-release and the first inline build/buy/open actions without requiring a second step into the focus drawer
|
||||||
- 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
|
||||||
@@ -102,6 +103,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Shipping-label PDFs now render inside an explicit single-page 4x6 canvas with tighter print-safe spacing and overflow-safe text wrapping to prevent second-sheet runover on label printers
|
||||||
- Project records now persist milestone plans directly on create/edit instead of treating schedule checkpoints as freeform notes only
|
- Project records now persist milestone plans directly on create/edit instead of treating schedule checkpoints as freeform notes only
|
||||||
- Company theme colors and font now persist correctly across refresh through startup brand-profile hydration in the frontend theme provider
|
- Company theme colors and font now persist correctly across refresh through startup brand-profile hydration in the frontend theme provider
|
||||||
- Demand-planning purchase-order draft generation now links sales-order lines only when the purchase item matches the originating sales item
|
- Demand-planning purchase-order draft generation now links sales-order lines only when the purchase item matches the originating sales item
|
||||||
|
|||||||
@@ -1083,6 +1083,9 @@ export function WorkbenchPage() {
|
|||||||
records={actionLanes.doNow}
|
records={actionLanes.doNow}
|
||||||
selectedId={selectedFocus?.id ?? null}
|
selectedId={selectedFocus?.id ?? null}
|
||||||
onSelect={setSelectedFocusId}
|
onSelect={setSelectedFocusId}
|
||||||
|
onTaskAction={handleTaskAction}
|
||||||
|
onQueueRelease={addRecordToQueue}
|
||||||
|
queuedWorkOrderIds={queuedWorkOrderIds}
|
||||||
/>
|
/>
|
||||||
<ActionLane
|
<ActionLane
|
||||||
title="UNBLOCK"
|
title="UNBLOCK"
|
||||||
@@ -1090,6 +1093,9 @@ export function WorkbenchPage() {
|
|||||||
records={actionLanes.unblock}
|
records={actionLanes.unblock}
|
||||||
selectedId={selectedFocus?.id ?? null}
|
selectedId={selectedFocus?.id ?? null}
|
||||||
onSelect={setSelectedFocusId}
|
onSelect={setSelectedFocusId}
|
||||||
|
onTaskAction={handleTaskAction}
|
||||||
|
onQueueRelease={addRecordToQueue}
|
||||||
|
queuedWorkOrderIds={queuedWorkOrderIds}
|
||||||
/>
|
/>
|
||||||
<ActionLane
|
<ActionLane
|
||||||
title="RELEASE READY"
|
title="RELEASE READY"
|
||||||
@@ -1097,6 +1103,9 @@ export function WorkbenchPage() {
|
|||||||
records={actionLanes.releaseReady}
|
records={actionLanes.releaseReady}
|
||||||
selectedId={selectedFocus?.id ?? null}
|
selectedId={selectedFocus?.id ?? null}
|
||||||
onSelect={setSelectedFocusId}
|
onSelect={setSelectedFocusId}
|
||||||
|
onTaskAction={handleTaskAction}
|
||||||
|
onQueueRelease={addRecordToQueue}
|
||||||
|
queuedWorkOrderIds={queuedWorkOrderIds}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1346,12 +1355,18 @@ function ActionLane({
|
|||||||
records,
|
records,
|
||||||
selectedId,
|
selectedId,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
onTaskAction,
|
||||||
|
onQueueRelease,
|
||||||
|
queuedWorkOrderIds,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
accent: string;
|
accent: string;
|
||||||
records: FocusRecord[];
|
records: FocusRecord[];
|
||||||
selectedId: string | null;
|
selectedId: string | null;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
|
onTaskAction: (action: PlanningTaskActionDto) => void | Promise<void>;
|
||||||
|
onQueueRelease: (record: FocusRecord) => void;
|
||||||
|
queuedWorkOrderIds: string[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className="surface-panel">
|
<section className="surface-panel">
|
||||||
@@ -1364,26 +1379,48 @@ function ActionLane({
|
|||||||
) : (
|
) : (
|
||||||
<div className="mt-3 space-y-2">
|
<div className="mt-3 space-y-2">
|
||||||
{records.map((record) => (
|
{records.map((record) => (
|
||||||
<button
|
<div
|
||||||
key={record.id}
|
key={record.id}
|
||||||
type="button"
|
className={`rounded-[16px] border px-2 py-2 ${selectedId === record.id ? "border-brand bg-brand/10" : "border-line/70 bg-page/60"}`}
|
||||||
onClick={() => onSelect(record.id)}
|
|
||||||
className={`block w-full rounded-[16px] border px-2 py-2 text-left transition hover:bg-page/80 ${selectedId === record.id ? "border-brand bg-brand/10" : "border-line/70 bg-page/60"}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<button type="button" onClick={() => onSelect(record.id)} className="block w-full text-left">
|
||||||
<div>
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="font-semibold text-text">{record.title}</div>
|
<div>
|
||||||
<div className="mt-1 text-xs text-muted">{record.kind} · {record.ownerLabel ?? "No context"}</div>
|
<div className="font-semibold text-text">{record.title}</div>
|
||||||
|
<div className="mt-1 text-xs text-muted">{record.kind} · {record.ownerLabel ?? "No context"}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right text-xs text-muted">
|
||||||
|
<div>{formatDate(record.end)}</div>
|
||||||
|
<div>P{priorityScore(record)}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right text-xs text-muted">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
<div>{formatDate(record.end)}</div>
|
<RecordSignals record={record} />
|
||||||
<div>P{priorityScore(record)}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
<RecordSignals record={record} />
|
{canQueueRelease(record) ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onQueueRelease(record)}
|
||||||
|
disabled={queuedWorkOrderIds.includes(record.workOrderId ?? record.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"
|
||||||
|
>
|
||||||
|
{queuedWorkOrderIds.includes(record.workOrderId ?? record.id) ? "Queued" : "Queue release"}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{record.actions.slice(0, 2).map((action, index) => (
|
||||||
|
<button
|
||||||
|
key={`${record.id}-${action.kind}-${index}`}
|
||||||
|
type="button"
|
||||||
|
onClick={() => void onTaskAction(action)}
|
||||||
|
className={`${index === 0 ? "bg-brand text-white" : "border border-line/70 text-text"} rounded-2xl px-2 py-2 text-xs font-semibold`}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -154,29 +154,38 @@ function buildShippingLabelPdf(options: {
|
|||||||
<style>
|
<style>
|
||||||
@page { size: 4in 6in; margin: 0; }
|
@page { size: 4in 6in; margin: 0; }
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
html, body { width: 4in; height: 6in; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; overflow: hidden; background: white; }
|
html, body { width: 4in; min-width: 4in; max-width: 4in; height: 6in; min-height: 6in; max-height: 6in; margin: 0; padding: 0; overflow: hidden; background: white; }
|
||||||
body { font-family: ${company.theme.fontFamily}, Arial, sans-serif; color: #111827; font-size: 10.5px; }
|
body { font-family: ${company.theme.fontFamily}, Arial, sans-serif; color: #111827; font-size: 10px; line-height: 1.2; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||||
.label { width: 3in; height: 5in; border: 2px solid #111827; border-radius: 6px; padding: 6px; display: flex; flex-direction: column; gap: 6px; overflow: hidden; }
|
.page { width: 4in; height: 6in; padding: 0.14in; overflow: hidden; page-break-after: avoid; break-after: avoid-page; }
|
||||||
.row { display: flex; justify-content: space-between; gap: 8px; }
|
.label { width: 100%; height: 100%; border: 2px solid #111827; border-radius: 10px; padding: 0.11in; display: flex; flex-direction: column; gap: 0.09in; overflow: hidden; }
|
||||||
|
.row { display: flex; justify-content: space-between; gap: 0.09in; }
|
||||||
.muted { font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; color: #4b5563; }
|
.muted { font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; color: #4b5563; }
|
||||||
.brand { border-bottom: 2px solid ${company.theme.primaryColor}; padding-bottom: 6px; }
|
.brand { border-bottom: 2px solid ${company.theme.primaryColor}; padding-bottom: 0.09in; }
|
||||||
.brand h1 { margin: 0; font-size: 18px; color: ${company.theme.primaryColor}; }
|
.brand-row { align-items: flex-start; }
|
||||||
.block { border: 1px solid #d1d5db; border-radius: 10px; padding: 10px; }
|
.brand-company { flex: 1; min-width: 0; padding-right: 0.06in; }
|
||||||
.stack { display: flex; flex-direction: column; gap: 4px; }
|
.brand h1 { margin: 0; font-size: 16px; line-height: 1.05; color: ${company.theme.primaryColor}; overflow-wrap: anywhere; }
|
||||||
.barcode { border: 2px solid #111827; border-radius: 6px; padding: 6px; text-align: center; font-family: monospace; font-size: 18px; letter-spacing: 0.18em; }
|
.shipment-number { width: 1.25in; flex: 0 0 1.25in; text-align: right; }
|
||||||
|
.block { border: 1px solid #d1d5db; border-radius: 10px; padding: 0.08in; min-width: 0; }
|
||||||
|
.stack { display: flex; flex-direction: column; gap: 3px; }
|
||||||
|
.barcode { border: 2px solid #111827; border-radius: 8px; padding: 0.08in; text-align: center; font-family: monospace; font-size: 16px; line-height: 1; letter-spacing: 0.15em; }
|
||||||
.strong { font-weight: 700; }
|
.strong { font-weight: 700; }
|
||||||
.big { font-size: 16px; font-weight: 700; }
|
.big { font-size: 15px; line-height: 1.05; font-weight: 700; }
|
||||||
|
.footer { text-align: center; font-size: 9px; color: #4b5563; overflow-wrap: anywhere; }
|
||||||
|
.reference-text { margin-top: 6px; overflow-wrap: anywhere; word-break: break-word; }
|
||||||
|
.block > div[style="margin-top:6px;"] { overflow-wrap: anywhere; word-break: break-word; }
|
||||||
|
div[style="text-align:center; font-size:10px; color:#4b5563;"] { text-align: center; font-size: 9px; color: #4b5563; overflow-wrap: anywhere; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="label">
|
<div class="page">
|
||||||
|
<div class="label">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<div class="row">
|
<div class="row brand-row">
|
||||||
<div>
|
<div class="brand-company">
|
||||||
<div class="muted">From</div>
|
<div class="muted">From</div>
|
||||||
<h1>${escapeHtml(company.companyName)}</h1>
|
<h1>${escapeHtml(company.companyName)}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:right;">
|
<div class="shipment-number">
|
||||||
<div class="muted">Shipment</div>
|
<div class="muted">Shipment</div>
|
||||||
<div class="big">${escapeHtml(shipment.shipmentNumber)}</div>
|
<div class="big">${escapeHtml(shipment.shipmentNumber)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user