This commit is contained in:
2026-03-15 16:40:25 -05:00
parent 15116807ce
commit 59754c7657
33 changed files with 1620 additions and 49 deletions

View File

@@ -141,6 +141,7 @@ export function WorkOrderDetailPage() {
<div className="flex flex-wrap gap-3">
<Link to="/manufacturing/work-orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Back to work orders</Link>
{workOrder.projectId ? <Link to={`/projects/${workOrder.projectId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open project</Link> : null}
{workOrder.salesOrderId ? <Link to={`/sales/orders/${workOrder.salesOrderId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open sales order</Link> : null}
<Link to={`/inventory/items/${workOrder.itemId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open item</Link>
{canManage ? <Link to={`/manufacturing/work-orders/${workOrder.id}/edit`} className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">Edit work order</Link> : null}
</div>
@@ -170,6 +171,7 @@ export function WorkOrderDetailPage() {
<article className="rounded-[24px] 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-[24px] 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-[24px] 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-[24px] 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>
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(360px,0.9fr)]">
<article className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
@@ -179,6 +181,7 @@ export function WorkOrderDetailPage() {
<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>
<div><dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Project customer</dt><dd className="mt-1 text-sm text-text">{workOrder.projectCustomerName || "Not linked"}</dd></div>
<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-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
@@ -299,6 +302,8 @@ export function WorkOrderDetailPage() {
<th className="px-3 py-3">Required</th>
<th className="px-3 py-3">Issued</th>
<th className="px-3 py-3">Remaining</th>
<th className="px-3 py-3">Available</th>
<th className="px-3 py-3">Shortage</th>
</tr>
</thead>
<tbody className="divide-y divide-line/70">
@@ -309,6 +314,8 @@ export function WorkOrderDetailPage() {
<td className="px-3 py-3 text-text">{requirement.requiredQuantity}</td>
<td className="px-3 py-3 text-text">{requirement.issuedQuantity}</td>
<td className="px-3 py-3 text-text">{requirement.remainingQuantity}</td>
<td className="px-3 py-3 text-text">{requirement.availableQuantity}</td>
<td className="px-3 py-3 text-text">{requirement.shortageQuantity}</td>
</tr>
))}
</tbody>

View File

@@ -17,6 +17,13 @@ export function WorkOrderFormPage({ mode }: { mode: "create" | "edit" }) {
const { workOrderId } = useParams();
const [searchParams] = useSearchParams();
const seededProjectId = searchParams.get("projectId");
const seededItemId = searchParams.get("itemId");
const seededSalesOrderId = searchParams.get("salesOrderId");
const seededSalesOrderLineId = searchParams.get("salesOrderLineId");
const seededQuantity = searchParams.get("quantity");
const seededStatus = searchParams.get("status");
const seededDueDate = searchParams.get("dueDate");
const seededNotes = searchParams.get("notes");
const [form, setForm] = useState<WorkOrderInput>(emptyWorkOrderInput);
const [itemOptions, setItemOptions] = useState<ManufacturingItemOptionDto[]>([]);
const [projectOptions, setProjectOptions] = useState<ManufacturingProjectOptionDto[]>([]);
@@ -33,7 +40,25 @@ export function WorkOrderFormPage({ mode }: { mode: "create" | "edit" }) {
return;
}
api.getManufacturingItemOptions(token).then(setItemOptions).catch(() => setItemOptions([]));
api.getManufacturingItemOptions(token).then((options) => {
setItemOptions(options);
if (mode === "create" && seededItemId) {
const seededItem = options.find((option) => option.id === seededItemId);
if (seededItem) {
setForm((current) => ({
...current,
itemId: seededItem.id,
salesOrderId: seededSalesOrderId || current.salesOrderId,
salesOrderLineId: seededSalesOrderLineId || current.salesOrderLineId,
quantity: seededQuantity ? Number.parseInt(seededQuantity, 10) || current.quantity : current.quantity,
status: seededStatus && workOrderStatusOptions.some((option) => option.value === seededStatus) ? (seededStatus as WorkOrderInput["status"]) : current.status,
dueDate: seededDueDate || current.dueDate,
notes: seededNotes || current.notes,
}));
setItemSearchTerm(`${seededItem.sku} - ${seededItem.name}`);
}
}
}).catch(() => setItemOptions([]));
api.getManufacturingProjectOptions(token).then((options) => {
setProjectOptions(options);
if (mode === "create" && seededProjectId) {
@@ -45,7 +70,7 @@ export function WorkOrderFormPage({ mode }: { mode: "create" | "edit" }) {
}
}).catch(() => setProjectOptions([]));
api.getWarehouseLocationOptions(token).then(setLocationOptions).catch(() => setLocationOptions([]));
}, [mode, seededProjectId, token]);
}, [mode, seededDueDate, seededItemId, seededNotes, seededProjectId, seededQuantity, seededSalesOrderId, seededSalesOrderLineId, seededStatus, token]);
useEffect(() => {
if (!token || mode !== "edit" || !workOrderId) {
@@ -57,6 +82,8 @@ export function WorkOrderFormPage({ mode }: { mode: "create" | "edit" }) {
setForm({
itemId: workOrder.itemId,
projectId: workOrder.projectId,
salesOrderId: workOrder.salesOrderId,
salesOrderLineId: workOrder.salesOrderLineId,
status: workOrder.status,
quantity: workOrder.quantity,
warehouseId: workOrder.warehouseId,

View File

@@ -26,6 +26,8 @@ export const workOrderStatusPalette: Record<WorkOrderStatus, string> = {
export const emptyWorkOrderInput: WorkOrderInput = {
itemId: "",
projectId: null,
salesOrderId: null,
salesOrderLineId: null,
status: "DRAFT",
quantity: 1,
warehouseId: "",