@seedtactics/insight-client 17.0.1-beta.2 → 17.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/assets/ProgramHighlight-C6zSEXCX.js +3 -0
  2. package/dist/assets/index-C6EBFHuW.js +388 -0
  3. package/dist/components/LogEntry.js +0 -71
  4. package/dist/components/log-entry-container-name.d.ts +2 -2
  5. package/dist/components/log-entry-container-name.js +1 -7
  6. package/dist/components/operations/CompletedParts.js +10 -10
  7. package/dist/components/operations/Programs.js +24 -17
  8. package/dist/components/operations/Rebookings.js +1 -1
  9. package/dist/components/quality/RecentFailedInspections.js +1 -1
  10. package/dist/components/routes.js +1 -2
  11. package/dist/components/station-monitor/BasketMovementArrival.d.ts +4 -2
  12. package/dist/components/station-monitor/BasketMovementArrival.js +23 -33
  13. package/dist/components/station-monitor/BulkRawMaterial.js +25 -14
  14. package/dist/components/station-monitor/InvalidateCycle.js +1 -1
  15. package/dist/components/station-monitor/LoadStation.js +33 -13
  16. package/dist/components/station-monitor/Material.js +28 -28
  17. package/dist/components/station-monitor/PrintedLabel.js +2 -2
  18. package/dist/components/station-monitor/QuarantineButton.js +4 -4
  19. package/dist/components/station-monitor/SystemOverview.js +31 -27
  20. package/dist/data/all-material-bins.js +15 -11
  21. package/dist/data/current-cycles.js +6 -3
  22. package/dist/data/inspection-sankey.js +1 -1
  23. package/dist/data/results.completed-parts.js +1 -1
  24. package/dist/data/results.cycles.js +1 -1
  25. package/dist/data/results.schedules.js +1 -1
  26. package/dist/index.html +1 -1
  27. package/dist/network/api.d.ts +7 -18
  28. package/dist/network/api.js +2 -25
  29. package/package.json +20 -20
  30. package/dist/assets/ProgramHighlight-DPTeZ8Si.js +0 -3
  31. package/dist/assets/index-C4HZdhRu.js +0 -388
@@ -86,25 +86,6 @@ function logType(entry, fmsInfo) {
86
86
  }
87
87
  case api.LogType.BasketCycle:
88
88
  return `${basketName} Cycle`;
89
- case api.LogType.BasketInLocation:
90
- if (entry.startofcycle) {
91
- return "Arrive";
92
- }
93
- else {
94
- return "Depart";
95
- }
96
- case api.LogType.BasketObservation:
97
- return `${basketName} Observation`;
98
- case api.LogType.BasketObservationCorrection:
99
- return `${basketName} Observation Correction`;
100
- case api.LogType.BasketContentSnapshot:
101
- return `${basketName} Content Snapshot`;
102
- case api.LogType.BasketRegionSurvey:
103
- return `${basketName} Region Survey`;
104
- case api.LogType.BasketMisload:
105
- return `${basketName} Misload`;
106
- case api.LogType.BasketMisloadResolution:
107
- return `${basketName} Misload Resolution`;
108
89
  case api.LogType.MachineCycle:
109
90
  if (entry.startofcycle) {
110
91
  return "Start Cycle";
@@ -149,12 +130,6 @@ function logType(entry, fmsInfo) {
149
130
  return "Message";
150
131
  }
151
132
  }
152
- function evidencePosition(entry) {
153
- const title = entry.details?.locationTitle ?? entry.details?.location ?? entry.loc;
154
- const locationNumber = entry.locnum > 0 ? ` ${entry.locnum}` : "";
155
- const zone = entry.details?.zone === undefined ? "" : ` zone ${entry.details.zone}`;
156
- return `${title}${locationNumber}${zone}`;
157
- }
158
133
  export function isLogEntryInvalidated(e) {
159
134
  return e.details?.["PalletCycleInvalidated"] === "1";
160
135
  }
@@ -207,52 +182,6 @@ function display(props, fmsInfo) {
207
182
  const basketName = basketDisplayName(fmsInfo.basketName);
208
183
  return _jsx("span", { children: basketCycleDescription(entry, basketName) });
209
184
  }
210
- case api.LogType.BasketInLocation: {
211
- const basketName = basketDisplayName(fmsInfo.basketName);
212
- if (entry.startofcycle) {
213
- return (_jsxs("span", { children: [basketContainerName(entry, basketName), " arrived at ", entry.loc, entry.locnum > 0 ? ` position ${entry.locnum}` : ""] }));
214
- }
215
- else {
216
- return (_jsxs("span", { children: [basketContainerName(entry, basketName), " departed from ", entry.loc, entry.locnum > 0 ? ` position ${entry.locnum}` : ""] }));
217
- }
218
- }
219
- case api.LogType.BasketObservation: {
220
- const basketName = basketDisplayName(fmsInfo.basketName);
221
- const episodeCount = entry.details?.episodeCount;
222
- const source = entry.details?.sourceName;
223
- const note = entry.details?.note;
224
- return (_jsxs("span", { children: [source ?? "Source", " observed ", basketName, " ", entry.pal, " at ", evidencePosition(entry), episodeCount === undefined || episodeCount === "0"
225
- ? ""
226
- : ` with direct continuity to ${episodeCount} tracked content episode${episodeCount === "1" ? "" : "s"}`, note === undefined ? "" : `: ${note}`] }));
227
- }
228
- case api.LogType.BasketObservationCorrection: {
229
- const basketName = basketDisplayName(fmsInfo.basketName);
230
- return (_jsxs("span", { children: ["Observation of ", basketName, " ", entry.pal, " corrected"] }));
231
- }
232
- case api.LogType.BasketContentSnapshot: {
233
- const basketName = basketDisplayName(fmsInfo.basketName);
234
- return (_jsxs("span", { children: [basketContainerName(entry, basketName), " contained ", displayMat(entry.material)] }));
235
- }
236
- case api.LogType.BasketRegionSurvey: {
237
- const basketName = basketDisplayName(fmsInfo.basketName);
238
- const source = entry.details?.sourceName;
239
- const observed = entry.details?.observedBasketCount;
240
- const unidentified = entry.details?.unidentifiedBasketCount;
241
- return (_jsxs("span", { children: [source === undefined ? "Operator" : source, " recorded a ", entry.result.toLowerCase(), " ", basketName.toLowerCase(), " survey at ", evidencePosition(entry), observed === undefined ? "" : ` (${observed} identified`, unidentified === undefined ? "" : `, ${unidentified} unidentified`, observed === undefined && unidentified === undefined ? "" : ")"] }));
242
- }
243
- case api.LogType.BasketMisload: {
244
- const basketName = basketDisplayName(fmsInfo.basketName);
245
- const source = entry.details?.sourceName;
246
- const reason = entry.details?.reason;
247
- const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
248
- return (_jsxs("span", { children: [source === undefined ? subject : `${source} reported ${subject}`, " requires inspection at", " ", evidencePosition(entry), reason === undefined ? "" : `: ${reason}`] }));
249
- }
250
- case api.LogType.BasketMisloadResolution: {
251
- const basketName = basketDisplayName(fmsInfo.basketName);
252
- const source = entry.details?.sourceName;
253
- const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
254
- return (_jsxs("span", { children: [source === undefined ? "Inspection hold" : `${source} resolved inspection hold`, " for", " ", subject, " at ", evidencePosition(entry), ": ", entry.result] }));
255
- }
256
185
  case api.LogType.MachineCycle:
257
186
  return (_jsxs("span", { children: [displayMat(entry.material), " on", " ", _jsxs(ColoredSpan, { $type: "pallet", children: ["pallet ", entry.pal] }), " at", " ", _jsxs(ColoredSpan, { $type: "machine", children: [entry.loc, " ", entry.locnum.toString()] }), entry.program && entry.program !== "" ? (_jsxs("span", { children: [" with program ", entry.program] })) : undefined] }));
258
187
  case api.LogType.PartMark:
@@ -1,6 +1,6 @@
1
1
  import * as api from "../network/api.js";
2
- type BasketNameEntry = Readonly<Pick<api.ILogEntry, "pal" | "basketContentEpisodeId">>;
3
- type BasketCycleEntry = BasketNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle" | "basketCycleEndContentEpisodeIds">>;
2
+ type BasketNameEntry = Readonly<Pick<api.ILogEntry, "pal">>;
3
+ type BasketCycleEntry = BasketNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle">>;
4
4
  export declare function basketContainerName(entry: BasketNameEntry, basketName: string): string;
5
5
  export declare function basketCycleDescription(entry: BasketCycleEntry, basketName: string): string;
6
6
  export {};
@@ -1,6 +1,4 @@
1
1
  export function basketContainerName(entry, basketName) {
2
- if (entry.basketContentEpisodeId)
3
- return `${basketName} fragment ${entry.basketContentEpisodeId.slice(0, 8)}`;
4
2
  if (entry.pal > 0)
5
3
  return `${basketName} ${entry.pal}`;
6
4
  return basketName;
@@ -9,9 +7,5 @@ export function basketCycleDescription(entry, basketName) {
9
7
  const container = basketContainerName(entry, basketName);
10
8
  if (entry.startofcycle)
11
9
  return `${container} started cycle`;
12
- const fragmentCount = entry.basketCycleEndContentEpisodeIds?.length ?? 0;
13
- const fragments = fragmentCount > 0
14
- ? ` from ${fragmentCount} UUID fragment${fragmentCount === 1 ? "" : "s"}`
15
- : "";
16
- return `${container} completed cycle${fragments}`;
10
+ return `${container} completed cycle`;
17
11
  }
@@ -64,20 +64,20 @@ function sortPartSummary(parts, col, order) {
64
64
  case "Elapsed":
65
65
  sortCol = (j) => j.stationMins
66
66
  .toAscLazySeq()
67
- .filter(([_, stat]) => !stat.isLoadUnload)
67
+ .filter(([, stat]) => !stat.isLoadUnload)
68
68
  .sumBy(([, t]) => t.elapsed);
69
69
  break;
70
70
  case "Active":
71
71
  sortCol = (j) => j.stationMins
72
72
  .toAscLazySeq()
73
- .filter(([_, stat]) => !stat.isLoadUnload)
74
- .sumBy(([_, t]) => t.active);
73
+ .filter(([, stat]) => !stat.isLoadUnload)
74
+ .sumBy(([, t]) => t.active);
75
75
  break;
76
76
  case "Median":
77
77
  sortCol = (j) => j.stationMins
78
78
  .toAscLazySeq()
79
- .filter(([_, stat]) => !stat.isLoadUnload)
80
- .sumBy(([_, t]) => t.medianElapsed);
79
+ .filter(([, stat]) => !stat.isLoadUnload)
80
+ .sumBy(([, t]) => t.medianElapsed);
81
81
  break;
82
82
  }
83
83
  const sorted = [...parts];
@@ -183,13 +183,13 @@ const PartRow = memo(function PartRow({ part }) {
183
183
  display: "block",
184
184
  }, children: part.part }) })] }) }), _jsx(TableCell, { align: "right", children: part.completedQty }), _jsx(TableCell, { align: "right", children: part.abnormalQty }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
185
185
  .toAscLazySeq()
186
- .filter(([_, stat]) => !stat.isLoadUnload)
187
- .sumBy(([_, t]) => t.active) / 60) }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
186
+ .filter(([, stat]) => !stat.isLoadUnload)
187
+ .sumBy(([, t]) => t.active) / 60) }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
188
188
  .toAscLazySeq()
189
- .filter(([_, stat]) => !stat.isLoadUnload)
189
+ .filter(([, stat]) => !stat.isLoadUnload)
190
190
  .sumBy(([, t]) => t.elapsed) / 60) }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
191
191
  .toAscLazySeq()
192
- .filter(([_, stat]) => !stat.isLoadUnload)
192
+ .filter(([, stat]) => !stat.isLoadUnload)
193
193
  .sumBy(([, t]) => t.medianElapsed) / 60) }), _jsx(TableCell, { align: "left", children: part.workorders.size <= 1 ? (_jsx(WorkorderLink, { workorderId: part.workorders.lookupMin() ?? null })) : part.workorders.size === 2 ? (_jsxs("span", { children: [_jsx(WorkorderLink, { workorderId: part.workorders.lookupMin() }), " &", " ", _jsx(WorkorderLink, { workorderId: part.workorders.lookupMax() })] })) : (`${part.workorders.size} workorders`) }), _jsx(TableCell, { children: _jsx(Tooltip, { title: "Show Details", children: _jsx(IconButton, { size: "small", onClick: () => setOpen(!open), children: open ? _jsx(KeyboardArrowUpIcon, {}) : _jsx(KeyboardArrowDownIcon, {}) }) }) })] }), _jsx(TableRow, { children: _jsx(TableCell, { sx: { pb: "0", pt: "0" }, colSpan: partColCount + 1, children: _jsx(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: _jsx(PartDetails, { part: part }) }) }) })] }));
194
194
  });
195
195
  function CopyToClipboardBtn({ partsAtom }) {
@@ -275,7 +275,7 @@ function copyPartsToClipboard(parts) {
275
275
  for (const lul of [false, true]) {
276
276
  const stats = p.stationMins
277
277
  .toAscLazySeq()
278
- .filter(([_, stat]) => stat.isLoadUnload === lul)
278
+ .filter(([, stat]) => stat.isLoadUnload === lul)
279
279
  .toRArray();
280
280
  if (stats.length === 0) {
281
281
  table += `<td>0</td><td>0</td><td>0</td>`;
@@ -297,23 +297,30 @@ export function ProgramHistoryDialog() {
297
297
  const [loading, setLoading] = useState(false);
298
298
  const [error, setError] = useState(null);
299
299
  useEffect(() => {
300
- if (program === null) {
301
- setRevisions(null);
302
- setPage(0);
303
- setLastLoadedPage({ page: 0, hasMore: false });
304
- }
305
- else if (program !== null && revisions === null) {
306
- // load initial
307
- setLoading(true);
308
- setError(null);
309
- MachineBackend.getProgramRevisionsInDescendingOrderOfRevision(program.programName, revisionsPerPage, undefined)
310
- .then((revs) => {
311
- setRevisions(revs);
312
- setLastLoadedPage({ page: 0, hasMore: revs.length === revisionsPerPage });
313
- })
314
- .catch(setError)
315
- .finally(() => setLoading(false));
316
- }
300
+ const loadProgramDetails = async () => {
301
+ if (program === null) {
302
+ setRevisions(null);
303
+ setPage(0);
304
+ setLastLoadedPage({ page: 0, hasMore: false });
305
+ }
306
+ else if (program !== null && revisions === null) {
307
+ // load initial
308
+ setLoading(true);
309
+ setError(null);
310
+ try {
311
+ const revs = await MachineBackend.getProgramRevisionsInDescendingOrderOfRevision(program.programName, revisionsPerPage, undefined);
312
+ setRevisions(revs);
313
+ setLastLoadedPage({ page: 0, hasMore: revs.length === revisionsPerPage });
314
+ }
315
+ catch (err) {
316
+ setError(err instanceof Error ? err : String(err));
317
+ }
318
+ finally {
319
+ setLoading(false);
320
+ }
321
+ }
322
+ };
323
+ loadProgramDetails().catch(console.error);
317
324
  }, [program, revisions]);
318
325
  function advancePage() {
319
326
  if (page < lastLoadedPage.page) {
@@ -169,7 +169,7 @@ const RebookingDialog = memo(function RebookingDialog({ rebooking, close, }) {
169
169
  return (_jsxs(Dialog, { open: rebooking !== undefined, onClose: () => close(undefined), children: [_jsx(DialogTitle, { children: rebooking?.canceled ? (_jsx(Typography, { sx: { textDecoration: "line-through" }, children: rebooking?.bookingId })) : (rebooking?.bookingId) }), _jsx(DialogContent, { children: _jsxs(Stack, { direction: "column", spacing: 1, children: [rebooking?.canceled && (_jsxs(Typography, { variant: "h5", children: ["Canceled at ", longDateFormat.format(rebooking.canceled)] })), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx("span", { children: "Part:" }), rebooking?.partName && _jsx(PartIdenticon, { part: rebooking?.partName, size: 25 }), _jsx("span", { children: rebooking?.partName })] }), _jsxs(Typography, { children: ["Quantity: ", rebooking?.quantity] }), _jsxs(Typography, { children: ["Request Time: ", longDateFormat.format(rebooking?.timeUTC)] }), _jsxs(Typography, { children: ["Priority: ", rebooking?.priority] }), rebooking?.workorder && _jsxs(Typography, { children: ["Workorder: ", rebooking?.workorder] }), _jsxs(Typography, { children: ["Note: ", rebooking?.notes] }), rebooking?.job && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsxs(Typography, { children: ["Scheduled Job: ", rebooking?.job] }), _jsxs(Typography, { children: ["Scheduled Time: ", rebooking?.schTime?.toLocaleString()] })] }))] }) }), _jsxs(DialogActions, { children: [!rebooking?.canceled && !rebooking?.job && (_jsxs(Button, { onClick: cancel, color: "secondary", disabled: canceling, children: [canceling ? _jsx(CircularProgress, { size: 24 }) : undefined, "Cancel Rebooking Request"] })), _jsx(Button, { onClick: () => close(undefined), children: "Close" })] })] }));
170
170
  });
171
171
  const partNamesAtom = atom((get) => LazySeq.ofObject(get(currentStatus).jobs)
172
- .map(([_, j]) => j.partName)
172
+ .map(([, j]) => j.partName)
173
173
  .concat(get(last30Jobs)
174
174
  .valuesToLazySeq()
175
175
  .map((j) => j.partName))
@@ -111,7 +111,7 @@ export function RecentFailedInspectionsTable() {
111
111
  const inspections = useAtomValue(last30Inspections);
112
112
  const failed = useMemo(() => {
113
113
  const today = startOfToday();
114
- const allEvts = LazySeq.of(inspections).flatMap(([_, evts]) => evts.valuesToLazySeq());
114
+ const allEvts = LazySeq.of(inspections).flatMap(([, evts]) => evts.valuesToLazySeq());
115
115
  return extractFailedInspections(allEvts, addDays(today, -4), addDays(today, 1));
116
116
  }, [inspections]);
117
117
  return (_jsxs("div", { children: [_jsxs(Box, { component: "nav", sx: {
@@ -231,10 +231,9 @@ export function useIsDemo() {
231
231
  return useAtomValue(isDemoAtom);
232
232
  }
233
233
  export function useSetTitle(title) {
234
- const demo = useIsDemo();
235
234
  useEffect(() => {
236
235
  document.title = title + " - FMS Insight";
237
- }, [demo, title]);
236
+ }, [title]);
238
237
  }
239
238
  export function helpUrl(r) {
240
239
  switch (r.route) {
@@ -26,7 +26,7 @@ export interface BasketArrivalReceipt {
26
26
  readonly status: "recorded" | "corrected" | "retracted";
27
27
  }
28
28
  export declare function loadStationArrivalInstruction(instructions: ReadonlyArray<Readonly<api.IBasketMoveInstruction>>, stationNumber: number): Readonly<api.IBasketMoveInstruction> | undefined;
29
- export declare function BasketMovementArrival({ stationNumber, basketName, instruction, submitCommand, submitCorrection, receipt, onAccepted, onCorrected, }: {
29
+ interface BasketMovementArrivalProps {
30
30
  readonly stationNumber: number;
31
31
  readonly basketName: string;
32
32
  readonly instruction: Readonly<api.IBasketMoveInstruction>;
@@ -35,4 +35,6 @@ export declare function BasketMovementArrival({ stationNumber, basketName, instr
35
35
  readonly receipt?: BasketArrivalReceipt;
36
36
  readonly onAccepted?: (receipt: BasketArrivalReceipt) => void;
37
37
  readonly onCorrected?: (command: BasketLocationCorrectionCommand) => void;
38
- }): import("react").JSX.Element;
38
+ }
39
+ export declare function BasketMovementArrival(props: BasketMovementArrivalProps): import("react").JSX.Element;
40
+ export {};
@@ -39,35 +39,30 @@ export function loadStationArrivalInstruction(instructions, stationNumber) {
39
39
  (instruction.prerequisiteInstructionId === undefined ||
40
40
  !activeIds.has(instruction.prerequisiteInstructionId)));
41
41
  }
42
- export function BasketMovementArrival({ stationNumber, basketName, instruction, submitCommand, submitCorrection, receipt, onAccepted, onCorrected, }) {
42
+ function basketMovementArrivalKey({ stationNumber, instruction, receipt, }) {
43
+ return [
44
+ stationNumber,
45
+ instruction.instructionId,
46
+ receipt?.status ?? "pending",
47
+ receipt?.receipt.observationId ?? "",
48
+ ].join(":");
49
+ }
50
+ export function BasketMovementArrival(props) {
51
+ return _jsx(BasketMovementArrivalContent, { ...props }, basketMovementArrivalKey(props));
52
+ }
53
+ function BasketMovementArrivalContent({ stationNumber, basketName, instruction, submitCommand, submitCorrection, receipt, onAccepted, onCorrected, }) {
54
+ const mounted = useRef(true);
43
55
  const [chooseDifferent, setChooseDifferent] = useState(false);
44
56
  const [differentBasket, setDifferentBasket] = useState("");
45
57
  const [submission, setSubmission] = useState();
46
58
  const [changingRecordedBasket, setChangingRecordedBasket] = useState(false);
47
59
  const [correctionSubmission, setCorrectionSubmission] = useState();
48
- const currentInstructionId = useRef(instruction.instructionId);
49
- const currentStationNumber = useRef(stationNumber);
50
- const currentCorrectionTargetId = useRef(undefined);
51
- currentInstructionId.current = instruction.instructionId;
52
- currentStationNumber.current = stationNumber;
53
60
  useEffect(() => {
54
- setChooseDifferent(false);
55
- setDifferentBasket("");
56
- setSubmission((current) => current?.state === "error" && current.command.instructionId === instruction.instructionId
57
- ? current
58
- : undefined);
59
- setChangingRecordedBasket(false);
60
- setCorrectionSubmission(undefined);
61
- }, [instruction.instructionId, stationNumber]);
62
- useEffect(() => {
63
- setChangingRecordedBasket(false);
64
- setDifferentBasket("");
65
- setCorrectionSubmission((current) => current?.state === "error" &&
66
- receipt?.status !== "retracted" &&
67
- current.command.targetObservationId === receipt?.receipt.observationId
68
- ? current
69
- : undefined);
70
- }, [receipt?.receipt.observationId, receipt?.status]);
61
+ mounted.current = true;
62
+ return () => {
63
+ mounted.current = false;
64
+ };
65
+ }, []);
71
66
  const expectedBasketId = instruction.basketId;
72
67
  if (expectedBasketId === undefined) {
73
68
  return (_jsx(Alert, { severity: "warning", children: "This tracked basket must be handled through the recovery workflow." }));
@@ -78,8 +73,7 @@ export function BasketMovementArrival({ stationNumber, basketName, instruction,
78
73
  setSubmission({ state: "submitting", command });
79
74
  try {
80
75
  const result = await submitCommand(stationNumber, command);
81
- if (currentStationNumber.current !== stationNumber ||
82
- currentInstructionId.current !== command.instructionId)
76
+ if (!mounted.current)
83
77
  return;
84
78
  if (result === "conflict") {
85
79
  setSubmission({ state: result, command });
@@ -96,8 +90,7 @@ export function BasketMovementArrival({ stationNumber, basketName, instruction,
96
90
  }
97
91
  }
98
92
  catch (error) {
99
- if (currentStationNumber.current !== stationNumber ||
100
- currentInstructionId.current !== command.instructionId)
93
+ if (!mounted.current)
101
94
  return;
102
95
  setSubmission({
103
96
  state: "error",
@@ -112,16 +105,14 @@ export function BasketMovementArrival({ stationNumber, basketName, instruction,
112
105
  setCorrectionSubmission({ state: "submitting", command });
113
106
  try {
114
107
  const result = await submitCorrection(stationNumber, command);
115
- if (currentStationNumber.current !== stationNumber ||
116
- currentCorrectionTargetId.current !== command.targetObservationId)
108
+ if (!mounted.current)
117
109
  return;
118
110
  setCorrectionSubmission({ state: result, command });
119
111
  if (result === "accepted")
120
112
  onCorrected?.(command);
121
113
  }
122
114
  catch {
123
- if (currentStationNumber.current !== stationNumber ||
124
- currentCorrectionTargetId.current !== command.targetObservationId)
115
+ if (!mounted.current)
125
116
  return;
126
117
  setCorrectionSubmission({ state: "error", command });
127
118
  }
@@ -145,7 +136,6 @@ export function BasketMovementArrival({ stationNumber, basketName, instruction,
145
136
  ? undefined
146
137
  : (receipt?.receipt.observationId ??
147
138
  (submission?.state === "accepted" ? submission.receipt.observationId : undefined));
148
- currentCorrectionTargetId.current = activeObservationId;
149
139
  function createCorrection(replacementBasketId) {
150
140
  return {
151
141
  correctionId: crypto.randomUUID(),
@@ -171,5 +161,5 @@ export function BasketMovementArrival({ stationNumber, basketName, instruction,
171
161
  ? `Bring ${basketName.toLocaleLowerCase()} ${expectedBasketId} to this station`
172
162
  : `${basketName} ${receipt.command.observedBasketId} at this station` }), receipt === undefined ? (_jsx(Typography, { children: "Record arrival after the basket is physically in place." })) : null] }), receipt === undefined ? (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(Button, { disabled: disabled, onClick: () => void record(createCommand(expectedBasketId)), size: "large", variant: "contained", children: submission?.state === "submitting"
173
163
  ? "Recording arrival…"
174
- : `${basketName} ${expectedBasketId} arrived` }), _jsxs(Button, { disabled: disabled, onClick: () => setChooseDifferent((visible) => !visible), size: "large", variant: "outlined", children: ["Different ", basketName.toLocaleLowerCase()] })] })) : null, receipt === undefined && chooseDifferent && !disabled ? (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(TextField, { label: `${basketName} number`, onChange: (event) => setDifferentBasket(event.target.value), type: "number", value: differentBasket, slotProps: { htmlInput: { inputMode: "numeric", min: 1 } } }), _jsxs(Button, { disabled: !validDifferentBasket, onClick: () => void record(createCommand(parsedDifferentBasket)), variant: "contained", children: ["Record ", basketName.toLocaleLowerCase(), " arrival"] })] })) : null, submitCommand === undefined ? (_jsx(Alert, { severity: "warning", children: "Basket arrival completion is not configured." })) : activeCommand !== undefined || receipt?.status === "retracted" ? (_jsxs(Stack, { spacing: 1, children: [_jsx(Alert, { severity: "success", children: receiptMessage }), submitCorrection === undefined || activeCommand === undefined ? null : (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(Button, { disabled: correctionSubmission !== undefined, onClick: () => setChangingRecordedBasket((visible) => !visible), variant: "outlined", children: "Change" }), _jsx(Button, { color: "error", disabled: correctionSubmission !== undefined, onClick: () => void correct(createCorrection(null)), variant: "text", children: "Undo" })] })), changingRecordedBasket && correctionSubmission === undefined ? (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(TextField, { label: `Correct ${basketName.toLocaleLowerCase()} number`, onChange: (event) => setDifferentBasket(event.target.value), type: "number", value: differentBasket, slotProps: { htmlInput: { inputMode: "numeric", min: 1 } } }), _jsx(Button, { disabled: !validDifferentBasket, onClick: () => void correct(createCorrection(parsedDifferentBasket)), variant: "contained", children: "Save correction" })] })) : null, correctionSubmission?.state === "accepted" ? (_jsx(Alert, { severity: "success", children: "Correction recorded." })) : correctionSubmission?.state === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket evidence changed. Review the refreshed status." })) : correctionSubmission?.state === "error" ? (_jsx(Alert, { action: _jsx(Button, { color: "inherit", onClick: () => void correct(correctionSubmission.command), children: "Retry correction" }), severity: "error", children: "Unable to correct the recorded arrival." })) : null] })) : submission?.state === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket movement changed. Review the refreshed instruction." })) : submission?.state === "error" ? (_jsx(Alert, { action: _jsx(Button, { onClick: () => void record(submission.command), color: "inherit", children: "Retry arrival" }), severity: "error", children: submission.message })) : null] }) }));
164
+ : `${basketName} ${expectedBasketId} arrived` }), _jsxs(Button, { disabled: disabled, onClick: () => setChooseDifferent((visible) => !visible), size: "large", variant: "outlined", children: ["Different ", basketName.toLocaleLowerCase()] })] })) : null, receipt === undefined && chooseDifferent && !disabled ? (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(TextField, { label: `${basketName} number`, onChange: (event) => setDifferentBasket(event.target.value), type: "number", value: differentBasket, slotProps: { htmlInput: { inputMode: "numeric", min: 1 } } }), _jsxs(Button, { disabled: !validDifferentBasket, onClick: () => void record(createCommand(parsedDifferentBasket)), variant: "contained", children: ["Record ", basketName.toLocaleLowerCase(), " arrival"] })] })) : null, submitCommand === undefined ? (_jsx(Alert, { severity: "warning", children: "Basket arrival completion is not configured." })) : activeCommand !== undefined || receipt?.status === "retracted" ? (_jsxs(Stack, { spacing: 1, children: [_jsx(Alert, { severity: "success", children: receiptMessage }), submitCorrection === undefined || activeCommand === undefined ? null : (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(Button, { disabled: correctionSubmission !== undefined, onClick: () => setChangingRecordedBasket((visible) => !visible), variant: "outlined", children: "Change" }), _jsx(Button, { color: "error", disabled: correctionSubmission !== undefined, onClick: () => void correct(createCorrection(null)), variant: "text", children: "Undo" })] })), changingRecordedBasket && correctionSubmission === undefined ? (_jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1, children: [_jsx(TextField, { label: `Correct ${basketName.toLocaleLowerCase()} number`, onChange: (event) => setDifferentBasket(event.target.value), type: "number", value: differentBasket, slotProps: { htmlInput: { inputMode: "numeric", min: 1 } } }), _jsx(Button, { disabled: !validDifferentBasket, onClick: () => void correct(createCorrection(parsedDifferentBasket)), variant: "contained", children: "Save correction" })] })) : null, correctionSubmission?.state === "accepted" ? (_jsx(Alert, { severity: "success", children: "Correction recorded." })) : correctionSubmission?.state === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket status changed. Review the refreshed status." })) : correctionSubmission?.state === "error" ? (_jsx(Alert, { action: _jsx(Button, { color: "inherit", onClick: () => void correct(correctionSubmission.command), children: "Retry correction" }), severity: "error", children: "Unable to correct the recorded arrival." })) : null] })) : submission?.state === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket movement changed. Review the refreshed instruction." })) : submission?.state === "error" ? (_jsx(Alert, { action: _jsx(Button, { onClick: () => void record(submission.command), color: "inherit", children: "Retry arrival" }), severity: "error", children: submission.message })) : null] }) }));
175
165
  }
@@ -172,7 +172,9 @@ export const BulkAddCastingWithoutSerialDialog = memo(function BulkAddCastingWit
172
172
  enteredQty === null ||
173
173
  isNaN(enteredQty) ||
174
174
  (fmsInfo.requireOperatorNamePromptWhenAddingMaterial &&
175
- (enteredOperator === null || enteredOperator === "")), onClick: addAndPrint, children: [adding ? _jsx(CircularProgress, { size: 10 }) : undefined, "Add ", enteredQty !== null && !isNaN(enteredQty) ? enteredQty.toString() + " " : "", "to", " ", queue] })) : (_jsxs(Button, { color: "primary", disabled: selectedCasting === null ||
175
+ (enteredOperator === null || enteredOperator === "")), onClick: addAndPrint, children: [adding ? _jsx(CircularProgress, { size: 10 }) : undefined, "Add ", enteredQty !== null && !isNaN(enteredQty)
176
+ ? enteredQty.toString() + " "
177
+ : "", "to", " ", queue] })) : (_jsxs(Button, { color: "primary", disabled: selectedCasting === null ||
176
178
  enteredQty === null ||
177
179
  isNaN(enteredQty) ||
178
180
  (fmsInfo.requireOperatorNamePromptWhenAddingMaterial &&
@@ -191,22 +193,31 @@ export const MultiMaterialDialog = memo(function MultiMaterialDialog(props) {
191
193
  if (props.material === null)
192
194
  return;
193
195
  let isSubscribed = true;
194
- setLoading(true);
195
- loadRawMaterialEvents(props.material)
196
- .then((loadedEvents) => {
197
- if (isSubscribed) {
198
- setEvents(loadedEvents);
199
- let operator;
200
- for (const e of loadedEvents) {
201
- if (e.type === api.LogType.AddToQueue && e.details?.["operator"] !== undefined) {
202
- operator = e.details["operator"];
196
+ const fetchData = async () => {
197
+ if (props.material === null)
198
+ return;
199
+ setLoading(true);
200
+ try {
201
+ const loadedEvents = await loadRawMaterialEvents(props.material);
202
+ if (isSubscribed) {
203
+ setEvents(loadedEvents);
204
+ let operator;
205
+ for (const e of loadedEvents) {
206
+ if (e.type === api.LogType.AddToQueue && e.details?.["operator"] !== undefined) {
207
+ operator = e.details["operator"];
208
+ }
203
209
  }
210
+ setLastOperator(operator);
204
211
  }
205
- setLastOperator(operator);
206
212
  }
207
- })
208
- .catch(console.error)
209
- .finally(() => setLoading(false));
213
+ catch (e) {
214
+ console.error(e);
215
+ }
216
+ finally {
217
+ setLoading(false);
218
+ }
219
+ };
220
+ fetchData().catch(console.error);
210
221
  return () => {
211
222
  isSubscribed = false;
212
223
  };
@@ -150,7 +150,7 @@ export function InvalidateCycleDialogContent(props) {
150
150
  behavior: "smooth",
151
151
  block: "end",
152
152
  });
153
- }, [boxRef, show]);
153
+ }, [boxRef]);
154
154
  if (!show)
155
155
  return _jsx("div", {});
156
156
  const hasSelection = props.st.process !== null ||
@@ -33,7 +33,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  */
34
34
  import { useMemo, memo, useState, useCallback, useEffect } from "react";
35
35
  import { Box, useMediaQuery, Button, Typography } from "@mui/material";
36
- import { LazySeq, mkCompareByProperties, OrderedMap } from "@seedtactics/immutable-collections";
36
+ import { LazySeq, OrderedMap } from "@seedtactics/immutable-collections";
37
37
  import { FolderOpen as FolderOpenIcon } from "@mui/icons-material";
38
38
  import { MaterialDialog, InProcMaterial, SortableInProcMaterial, DragOverlayInProcMaterial, MatSummary, } from "./Material.js";
39
39
  import { SortableRegion } from "./Whiteboard.js";
@@ -103,12 +103,13 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
103
103
  }
104
104
  }
105
105
  for (const basket of Object.values(curSt.baskets ?? {})) {
106
- if (basket.position.locationNum !== loadNum)
106
+ const position = basket.position;
107
+ if (position === undefined || position.locationNum !== loadNum)
107
108
  continue;
108
- if (basket.position.location === api.BasketLocationEnum.LoadUnload) {
109
+ if (position.location === api.BasketLocationEnum.LoadUnload) {
109
110
  activeBasket = basket;
110
111
  }
111
- else if (basket.position.location === api.BasketLocationEnum.LoadStationStaging) {
112
+ else if (position.location === api.BasketLocationEnum.LoadStationStaging) {
112
113
  basketsToShow.add(basket.basketId);
113
114
  }
114
115
  }
@@ -282,8 +283,24 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
282
283
  }
283
284
  }
284
285
  }
285
- queueMat.forEach((queue) => queue.mats.sort(mkCompareByProperties((mat) => mat.location.queuePosition ?? 0)));
286
- basketMat.forEach((basket) => basket.mats.sort(mkCompareByProperties((mat) => mat.location.basketSlot ?? 0)));
286
+ const sortedQueueMat = new Map([...queueMat].map(([queueName, queue]) => [
287
+ queueName,
288
+ {
289
+ ...queue,
290
+ mats: LazySeq.of(queue.mats)
291
+ .sortBy((mat) => mat.location.queuePosition ?? 0)
292
+ .toRArray(),
293
+ },
294
+ ]));
295
+ const sortedBasketMat = new Map([...basketMat].map(([basketId, basket]) => [
296
+ basketId,
297
+ {
298
+ ...basket,
299
+ mats: LazySeq.of(basket.mats)
300
+ .sortBy((mat) => mat.location.basketSlot ?? 0)
301
+ .toRArray(),
302
+ },
303
+ ]));
287
304
  const matCount = freeLoading.length + palFaces.valuesToAscLazySeq().sumBy((x) => x.length);
288
305
  const activeBasketSlotCount = activeBasket
289
306
  ? LazySeq.of(activeBasket.emptySlots ?? [])
@@ -306,8 +323,8 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
306
323
  allMaterial: curSt.material,
307
324
  face: palFaces,
308
325
  freeLoadingMaterial: freeLoading,
309
- queues: queueMat,
310
- baskets: basketMat,
326
+ queues: sortedQueueMat,
327
+ baskets: sortedBasketMat,
311
328
  elapsedLoadingTime,
312
329
  fsize: layoutCount <= 2 ? "x-large" : layoutCount <= 6 ? "large" : "normal",
313
330
  };
@@ -320,7 +337,7 @@ function MultiInstructionButton({ loadData }) {
320
337
  if (pal) {
321
338
  return LazySeq.of(loadData.face.values())
322
339
  .append(loadData.freeLoadingMaterial)
323
- .concat(LazySeq.of(loadData.queues).collect(([_, v]) => v.mats))
340
+ .concat(LazySeq.of(loadData.queues).collect(([, v]) => v.mats))
324
341
  .flatMap((x) => x)
325
342
  .collect((mat) => {
326
343
  if (mat.action.type === api.ActionType.Loading &&
@@ -670,8 +687,11 @@ export function LoadStation(props) {
670
687
  if (recentArrivalReceipt !== undefined &&
671
688
  (recentArrivalReceipt.stationNumber !== props.loadNum ||
672
689
  (arrivalInstruction !== undefined &&
673
- arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId)))
690
+ arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId))) {
691
+ // This state is an ephemeral receipt; discard it permanently when a new instruction supersedes it.
692
+ // oxlint-disable-next-line react/set-state-in-effect -- intentional invalidation of stale UI state.
674
693
  setRecentArrivalReceipt(undefined);
694
+ }
675
695
  }, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
676
696
  const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
677
697
  const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
@@ -679,7 +699,7 @@ export function LoadStation(props) {
679
699
  ? recentArrivalReceipt
680
700
  : undefined;
681
701
  const queueCols = LazySeq.of(data.queues)
682
- .sortBy(([q, _]) => q)
702
+ .sortBy(([q]) => q)
683
703
  .map(([q, mats]) => ({
684
704
  kind: "queue",
685
705
  label: q,
@@ -689,8 +709,8 @@ export function LoadStation(props) {
689
709
  const matCols = [...queueCols];
690
710
  if (data.baskets.size > 0) {
691
711
  const baskets = LazySeq.of(data.baskets)
692
- .filter(([b, _]) => b !== data.activeBasket?.basketId)
693
- .sortBy(([b, _]) => b)
712
+ .filter(([b]) => b !== data.activeBasket?.basketId)
713
+ .sortBy(([b]) => b)
694
714
  .map(([b, mats]) => ({
695
715
  basketId: b,
696
716
  label: `${basketName} ${b}`,