From cf54e4ba58ebbeb43376391bb14cbfbcbc409b6f Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 18 Mar 2026 23:48:14 -0500 Subject: [PATCH] usability workbench --- CHANGELOG.md | 1 + .../src/modules/workbench/WorkbenchPage.tsx | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d6a2c4..2155d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh - Workbench usability depth with keyboard row navigation, enter-to-open behavior, escape-to-clear, and inline readiness/shortage/hold signal pills across planner rows and day-detail cards - Workbench dispatch workflow depth with saved planner views, a release queue for visible ready work, queued-record visibility in the sticky control bar, and batch release directly from the workbench - Workbench batch operation rebalance with multi-operation selection, sticky-bar batch reschedule controls, station reassignment across selected operations, and selected-operation visibility in row signals and focus context +- Workbench conflict-intelligence pass with projected batch target load, overload warnings before batch station moves, and best-alternate-station suggestions inside the sticky rebalance controls - 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 diff --git a/client/src/modules/workbench/WorkbenchPage.tsx b/client/src/modules/workbench/WorkbenchPage.tsx index 3850522..5e28934 100644 --- a/client/src/modules/workbench/WorkbenchPage.tsx +++ b/client/src/modules/workbench/WorkbenchPage.tsx @@ -377,6 +377,46 @@ export function WorkbenchPage() { const stationLoadById = useMemo(() => new Map(stationLoads.map((station) => [station.stationId, station])), [stationLoads]); const selectedRescheduleStation = rescheduleStationId ? stations.find((station) => station.id === rescheduleStationId) ?? null : null; const selectedRescheduleLoad = selectedRescheduleStation ? stationLoadById.get(selectedRescheduleStation.id) ?? null : null; + const selectedOperationLoadMinutes = useMemo(() => selectedOperations.reduce((sum, record) => sum + Math.max(record.loadMinutes, 1), 0), [selectedOperations]); + const batchTargetLoad = useMemo(() => { + if (!batchStationId) { + return null; + } + const station = stations.find((candidate) => candidate.id === batchStationId) ?? null; + const load = stationLoadById.get(batchStationId) ?? null; + if (!station || !load) { + return null; + } + const projectedMinutes = load.totalPlannedMinutes + selectedOperationLoadMinutes; + const projectedUtilizationPercent = Math.round((projectedMinutes / Math.max(load.capacityMinutes, 1)) * 100); + return { + station, + load, + projectedMinutes, + projectedUtilizationPercent, + overloaded: projectedUtilizationPercent > 100, + }; + }, [batchStationId, selectedOperationLoadMinutes, stationLoadById, stations]); + const batchStationSuggestions = useMemo(() => { + if (selectedOperations.length === 0) { + return []; + } + return stations + .filter((station) => station.isActive) + .map((station) => { + const load = stationLoadById.get(station.id); + const projectedMinutes = (load?.totalPlannedMinutes ?? 0) + selectedOperationLoadMinutes; + const capacityMinutes = Math.max(load?.capacityMinutes ?? station.dailyCapacityMinutes * station.parallelCapacity, 1); + const projectedUtilizationPercent = Math.round((projectedMinutes / capacityMinutes) * 100); + return { + station, + projectedUtilizationPercent, + overloaded: projectedUtilizationPercent > 100, + }; + }) + .sort((left, right) => left.projectedUtilizationPercent - right.projectedUtilizationPercent) + .slice(0, 3); + }, [selectedOperationLoadMinutes, selectedOperations.length, stationLoadById, stations]); const agendaItems = useMemo( () => [...focusRecords] .filter((record) => record.kind !== "OPERATION") @@ -819,6 +859,37 @@ export function WorkbenchPage() { {isBatchRescheduling ? "Applying..." : "Apply batch"} +
+
+
TARGET LOAD
+ {batchTargetLoad ? ( +
+
{batchTargetLoad.station.code} - {batchTargetLoad.station.name}
+
Projected util: {batchTargetLoad.projectedUtilizationPercent}%
+
Projected minutes: {batchTargetLoad.projectedMinutes}
+
{batchTargetLoad.overloaded ? "This batch move will overload the target station." : "This batch move stays within summarized station capacity."}
+
+ ) : ( +
Keeping current stations. Pick a station to preview the batch landing load.
+ )} +
+
+
BEST ALTERNATES
+
+ {batchStationSuggestions.map((suggestion) => ( + + ))} +
+
+
{selectedOperations.slice(0, 6).map((record) => (