@seedtactics/insight-client 17.0.1-beta.1 → 17.0.1-beta.11

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 (44) hide show
  1. package/dist/assets/ProgramHighlight-C6zSEXCX.js +3 -0
  2. package/dist/assets/index-kk-GRa5i.js +388 -0
  3. package/dist/cell-status/inspections.js +0 -20
  4. package/dist/cell-status/material-details.js +7 -1
  5. package/dist/cell-status/material-summary.js +1 -87
  6. package/dist/cell-status/station-cycles.d.ts +1 -0
  7. package/dist/cell-status/station-cycles.js +11 -14
  8. package/dist/components/LogEntry.js +0 -71
  9. package/dist/components/log-entry-container-name.d.ts +2 -2
  10. package/dist/components/log-entry-container-name.js +1 -7
  11. package/dist/components/operations/AllMaterial.js +2 -4
  12. package/dist/components/operations/CompletedParts.js +10 -10
  13. package/dist/components/operations/Programs.js +24 -17
  14. package/dist/components/operations/Rebookings.js +1 -1
  15. package/dist/components/quality/RecentFailedInspections.js +1 -1
  16. package/dist/components/routes.js +1 -2
  17. package/dist/components/station-monitor/BasketLoadStationWork.js +66 -16
  18. package/dist/components/station-monitor/BasketMovementArrival.d.ts +4 -2
  19. package/dist/components/station-monitor/BasketMovementArrival.js +23 -33
  20. package/dist/components/station-monitor/BulkRawMaterial.js +25 -14
  21. package/dist/components/station-monitor/CancelLoadButton.js +3 -1
  22. package/dist/components/station-monitor/InvalidateCycle.d.ts +1 -17
  23. package/dist/components/station-monitor/InvalidateCycle.js +2 -92
  24. package/dist/components/station-monitor/LoadStation.js +36 -18
  25. package/dist/components/station-monitor/Material.js +33 -31
  26. package/dist/components/station-monitor/PrintedLabel.js +2 -2
  27. package/dist/components/station-monitor/QuarantineButton.js +4 -4
  28. package/dist/components/station-monitor/SystemOverview.js +33 -31
  29. package/dist/data/all-material-bins.js +15 -11
  30. package/dist/data/current-cycles.js +6 -3
  31. package/dist/data/inspection-sankey.js +1 -1
  32. package/dist/data/results.completed-parts.js +1 -1
  33. package/dist/data/results.cycles.js +1 -1
  34. package/dist/data/results.schedules.js +1 -1
  35. package/dist/index.html +1 -1
  36. package/dist/network/api.d.ts +33 -50
  37. package/dist/network/api.js +57 -141
  38. package/dist/network/backend-mock.js +0 -3
  39. package/dist/network/backend.d.ts +0 -1
  40. package/docs/mazak.md +13 -0
  41. package/docs/operator-procedures.md +6 -6
  42. package/package.json +20 -20
  43. package/dist/assets/ProgramHighlight-DPTeZ8Si.js +0 -3
  44. package/dist/assets/index-C4HZdhRu.js +0 -388
@@ -31,6 +31,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
31
  import { useMemo, useState } from "react";
32
32
  import { Alert, Box, Button, Stack, Typography } from "@mui/material";
33
33
  import { LazySeq } from "@seedtactics/immutable-collections";
34
+ import { basketSlotLabel } from "../../cell-status/station-cycles.js";
34
35
  import * as api from "../../network/api.js";
35
36
  import { InProcMaterial } from "./Material.js";
36
37
  import { MoveMaterialArrowNode } from "./MoveMaterialArrows.js";
@@ -69,14 +70,48 @@ function basketAction(mat, basketId) {
69
70
  }
70
71
  return undefined;
71
72
  }
72
- function confirmableWork(material, basketId) {
73
+ function stationWork(material, basket) {
73
74
  const classifiedActions = LazySeq.of(material)
74
- .collect((mat) => basketAction(mat, basketId))
75
+ .collect((mat) => basketAction(mat, basket.basketId))
75
76
  .toRArray();
76
77
  if (classifiedActions.some((action) => action.type === "invalid")) {
77
78
  return { type: "inconsistent" };
78
79
  }
79
80
  const validActions = classifiedActions.filter((action) => action.type === "valid");
81
+ const descriptor = basket.loadStationWork;
82
+ if (descriptor !== undefined) {
83
+ if (!descriptor ||
84
+ !isNonBlank(descriptor.workId) ||
85
+ typeof descriptor.readyToConfirm !== "boolean")
86
+ return { type: "inconsistent" };
87
+ const awaiting = descriptor.awaitingMaterialSlots ?? [];
88
+ if (!Array.isArray(awaiting) ||
89
+ !awaiting.every(isPositiveInteger) ||
90
+ new Set(awaiting).size !== awaiting.length ||
91
+ (descriptor.readyToConfirm && awaiting.length > 0) ||
92
+ validActions.some((action) => action.workId !== descriptor.workId))
93
+ return { type: "inconsistent" };
94
+ const empty = descriptor.type === api.BasketLoadStationWorkType.ConfirmEmptyBasket;
95
+ if (empty) {
96
+ if (validActions.length > 0 ||
97
+ awaiting.length > 0 ||
98
+ material.some((mat) => mat.location.type === api.LocType.InBasket && mat.location.basketId === basket.basketId))
99
+ return { type: "inconsistent" };
100
+ }
101
+ else if (descriptor.type !== api.BasketLoadStationWorkType.Material ||
102
+ (descriptor.readyToConfirm && validActions.length === 0)) {
103
+ return { type: "inconsistent" };
104
+ }
105
+ return {
106
+ type: "active",
107
+ work: {
108
+ workId: descriptor.workId,
109
+ ready: descriptor.readyToConfirm,
110
+ confirmEmpty: empty,
111
+ awaitingSlots: awaiting,
112
+ },
113
+ };
114
+ }
80
115
  if (validActions.length === 0)
81
116
  return { type: "none" };
82
117
  const tagged = validActions.filter((action) => isNonBlank(action.workId));
@@ -85,8 +120,8 @@ function confirmableWork(material, basketId) {
85
120
  return { type: "inconsistent" };
86
121
  }
87
122
  return {
88
- type: "confirmable",
89
- work: { workId: tagged[0].workId },
123
+ type: "active",
124
+ work: { workId: tagged[0].workId, ready: true, confirmEmpty: false, awaitingSlots: [] },
90
125
  };
91
126
  }
92
127
  function loadingInstruction(mat) {
@@ -105,21 +140,29 @@ function SlotMaterial({ material, fsize, }) {
105
140
  }
106
141
  export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }) {
107
142
  const [submission, setSubmission] = useState();
108
- const workState = useMemo(() => confirmableWork(material, basket.basketId), [basket.basketId, material]);
109
- const work = workState.type === "confirmable" ? workState.work : undefined;
110
- const submissionState = submission !== undefined && submission.workId === work?.workId ? submission.state : "idle";
143
+ const workState = useMemo(() => stationWork(material, basket), [basket, material]);
144
+ const blockedReason = basket.loadStationWork?.confirmationBlockedReason?.trim();
145
+ const work = workState.type === "active" ? workState.work : undefined;
146
+ const submissionState = submission !== undefined &&
147
+ submission.workId === work?.workId &&
148
+ // A conflict rejects the submitted status snapshot, not the preserved occurrence.
149
+ // Compare the captured snapshot even when refresh precedes the response.
150
+ (submission.state !== "conflict" ||
151
+ (submission.basket === basket && submission.material === material))
152
+ ? submission.state
153
+ : "idle";
111
154
  const materialBySlot = useMemo(() => LazySeq.of(material)
112
155
  .filter((mat) => mat.location.type === api.LocType.InBasket &&
113
156
  mat.location.basketId === basket.basketId &&
114
157
  mat.location.basketSlot !== undefined)
115
158
  .groupBy((mat) => mat.location.basketSlot ?? 0)
116
- .toHashMap((entry) => entry), [basket.basketId, material]);
159
+ .toHashMap((entry) => entry), [basket, material]);
117
160
  const loadsBySlot = useMemo(() => LazySeq.of(material)
118
161
  .filter((mat) => mat.action.type === api.ActionType.LoadingToBasket &&
119
162
  mat.action.loadToBasketId === basket.basketId &&
120
163
  mat.action.loadToBasketSlot !== undefined)
121
164
  .groupBy((mat) => mat.action.loadToBasketSlot ?? 0)
122
- .toHashMap((entry) => entry), [basket.basketId, material]);
165
+ .toHashMap((entry) => entry), [basket, material]);
123
166
  const slots = useMemo(() => LazySeq.of(basket.emptySlots ?? [])
124
167
  .concat(basket.unknownSlots ?? [])
125
168
  .concat(materialBySlot.keysToLazySeq())
@@ -127,19 +170,21 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
127
170
  .distinctBy((slot) => slot)
128
171
  .toSortedArray((slot) => slot), [basket.emptySlots, basket.unknownSlots, materialBySlot, loadsBySlot]);
129
172
  async function submit() {
130
- if (work === undefined || submitCommand === undefined)
173
+ if (work === undefined || !work.ready || submitCommand === undefined)
131
174
  return;
132
175
  const submittedWorkId = work.workId;
133
- setSubmission({ workId: submittedWorkId, state: "submitting" });
176
+ const pending = { workId: submittedWorkId, basket, material, state: "submitting" };
177
+ setSubmission(pending);
134
178
  try {
135
179
  const state = await submitCommand(stationNumber, { workId: submittedWorkId });
136
- setSubmission((current) => current?.workId === submittedWorkId ? { workId: submittedWorkId, state } : current);
180
+ setSubmission((current) => (current === pending ? { ...pending, state } : current));
137
181
  }
138
182
  catch {
139
- setSubmission((current) => current?.workId === submittedWorkId ? { workId: submittedWorkId, state: "error" } : current);
183
+ setSubmission((current) => (current === pending ? { ...pending, state: "error" } : current));
140
184
  }
141
185
  }
142
- const submissionDisabled = submissionState === "submitting" ||
186
+ const submissionDisabled = !work?.ready ||
187
+ submissionState === "submitting" ||
143
188
  submissionState === "accepted" ||
144
189
  submissionState === "conflict";
145
190
  return (_jsxs(Stack, { spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { variant: "h6", children: "Basket slots" }), _jsx(Box, { sx: {
@@ -154,6 +199,11 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
154
199
  type: MoveMaterialNodeKindType.BasketSlotZone,
155
200
  basketId: basket.basketId,
156
201
  slot,
157
- }, children: _jsxs(Box, { component: "section", "data-testid": `basket-load-station-slot-${slot}`, sx: { border: "1px solid", borderColor: "text.primary", p: 2 }, children: [_jsxs(Typography, { variant: "h6", children: ["Slot ", slot] }), slotMaterial.length > 0 ? (_jsx(SlotMaterial, { material: slotMaterial, fsize: fsize })) : (_jsx(Typography, { color: isUnknown ? "warning.main" : "text.secondary", children: isUnknown ? "Unknown" : "Empty" })), loads.map((mat, index) => (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { children: loadingInstruction(mat) }), _jsxs(Typography, { variant: "body2", color: "text.secondary", children: [mat.partName, " \u00B7 Process ", mat.action.processAfterLoad ?? mat.process] })] }, `${mat.jobUnique}-${mat.process}-${index}`)))] }) }, slot));
158
- }) }), work && submitCommand ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: "Confirm" }) })) : work ? (_jsx(Alert, { severity: "warning", children: "Basket work confirmation is not configured." })) : workState.type === "inconsistent" ? (_jsx(Alert, { severity: "warning", children: "Basket work is inconsistent. Wait for refreshed material actions before confirming." })) : null, submissionState === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submissionState === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submissionState === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
202
+ }, children: _jsxs(Box, { component: "section", "data-testid": `basket-load-station-slot-${slot}`, sx: { border: "1px solid", borderColor: "text.primary", p: 2 }, children: [_jsxs(Typography, { variant: "h6", children: ["Slot ", basketSlotLabel(slot)] }), loads.length > 0 &&
203
+ slotMaterial.some((mat) => mat.action.type === api.ActionType.UnloadToInProcess ||
204
+ mat.action.type === api.ActionType.UnloadToCompletedMaterial) && (_jsx(Typography, { children: "Unload the current material before preparing the replacement." })), slotMaterial.length > 0 ? (_jsx(SlotMaterial, { material: slotMaterial, fsize: fsize })) : (_jsx(Typography, { color: isUnknown ? "warning.main" : "text.secondary", children: isUnknown ? "Unknown" : "Empty" })), loads.map((mat, index) => (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { children: loadingInstruction(mat) }), _jsxs(Typography, { variant: "body2", color: "text.secondary", children: [mat.partName, " \u00B7 Process ", mat.action.processAfterLoad ?? mat.process] })] }, `${mat.jobUnique}-${mat.process}-${index}`)))] }) }, slot));
205
+ }) }), work?.confirmEmpty && _jsxs(Typography, { children: ["Confirm basket ", basket.basketId, " is empty."] }), work && !work.ready && (_jsx(Alert, { severity: "info", children: blockedReason ||
206
+ (work.awaitingSlots.length > 0
207
+ ? `Waiting for material for slots ${work.awaitingSlots.map(basketSlotLabel).join(", ")}.`
208
+ : "Basket work is not ready for confirmation.") })), work && submitCommand ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: "Confirm" }) })) : work ? (_jsx(Alert, { severity: "warning", children: "Basket work confirmation is not configured." })) : workState.type === "inconsistent" ? (_jsx(Alert, { severity: "warning", children: "Basket work is inconsistent. Wait for refreshed material actions before confirming." })) : null, submissionState === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submissionState === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submissionState === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
159
209
  }
@@ -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
  };
@@ -56,5 +56,7 @@ export function CancelLoadButton({ onClose, ignoreOperator = false, }) {
56
56
  setReason("");
57
57
  setError(null);
58
58
  setOpen(true);
59
- }, children: "Cancel Load" }) })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: () => setOpen(false), children: [_jsx(DialogTitle, { children: "Cancel Load" }), _jsxs(DialogContent, { children: [_jsx("p", { children: "The displayed load instruction will be cancelled and recalculated for:" }), _jsx("ul", { children: snapshot?.group.map((candidate) => (_jsx("li", { children: candidate.serial || `Material ID ${candidate.materialID}` }, candidate.materialID))) }), _jsx(TextField, { label: "Reason (optional)", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: updating, onClick: cancelLoad, children: "Cancel Load" }), _jsx(Button, { color: "secondary", disabled: updating, onClick: () => setOpen(false), children: "Keep Load" })] })] })] }));
59
+ }, children: "Cancel Load" }) })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: () => setOpen(false), children: [_jsx(DialogTitle, { children: "Cancel Load" }), _jsxs(DialogContent, { children: [_jsx("p", { children: "The displayed load instruction will be cancelled and recalculated for:" }), _jsx("ul", { children: snapshot?.group.map((candidate, index) => (_jsx("li", { children: candidate.materialID < 0
60
+ ? `${candidate.partName} raw material`
61
+ : candidate.serial || `Material ID ${candidate.materialID}` }, candidate.materialID >= 0 ? candidate.materialID : `raw-${index}`))) }), _jsx(TextField, { label: "Reason (optional)", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: updating, onClick: cancelLoad, children: "Cancel Load" }), _jsx(Button, { color: "secondary", disabled: updating, onClick: () => setOpen(false), children: "Keep Load" })] })] })] }));
60
62
  }
@@ -1,6 +1,5 @@
1
- import { IInProcessMaterial, ILogEntry } from "../../network/api.js";
1
+ import { ILogEntry } from "../../network/api.js";
2
2
  import { HashMap } from "@seedtactics/immutable-collections";
3
- import { ReactNode } from "react";
4
3
  export type InvalidateCycleState = {
5
4
  readonly process: number | null;
6
5
  readonly changeRawMat: string | null;
@@ -19,18 +18,3 @@ export declare function InvalidateCycleDialogButton(props: InvalidateCycleProps
19
18
  readonly ignoreOperator?: boolean;
20
19
  readonly loadStation?: boolean;
21
20
  }): import("react").JSX.Element | null;
22
- interface SwapMaterial {
23
- readonly selectedMatToSwap: Readonly<IInProcessMaterial> | null;
24
- readonly updating: boolean;
25
- }
26
- export type SwapMaterialState = SwapMaterial | null;
27
- export interface SwapMaterialProps {
28
- readonly st: SwapMaterialState;
29
- readonly setState: (s: SwapMaterialState) => void;
30
- }
31
- export declare function SwapMaterialDialogContent(props: SwapMaterialProps): ReactNode;
32
- export declare function SwapMaterialButtons(props: SwapMaterialProps & {
33
- readonly onClose: () => void;
34
- readonly ignoreOperator?: boolean;
35
- }): import("react").JSX.Element | null;
36
- export {};
@@ -47,14 +47,12 @@ import { last30Jobs } from "../../cell-status/scheduled-jobs.js";
47
47
  import { PartIdenticon } from "./Material.js";
48
48
  import { isLogEntryInvalidated } from "../LogEntry.js";
49
49
  import { ApiException } from "../../network/api.js";
50
- import { canInvalidateMaterial, isActiveLoadStationOperation, } from "../../data/material-operation-policy.js";
50
+ import { canInvalidateMaterial } from "../../data/material-operation-policy.js";
51
51
  const invalidatableEventTypes = LazySeq.of([
52
52
  LogType.AddToQueue,
53
53
  LogType.RemoveFromQueue,
54
54
  LogType.LoadUnloadCycle,
55
55
  LogType.MachineCycle,
56
- LogType.BasketLoadUnload,
57
- LogType.BasketCycle,
58
56
  ]).toRSet((eventType) => eventType);
59
57
  export function affectedMaterialForInvalidation(events, materialId, process) {
60
58
  let affected = HashMap.empty();
@@ -150,7 +148,7 @@ export function InvalidateCycleDialogContent(props) {
150
148
  behavior: "smooth",
151
149
  block: "end",
152
150
  });
153
- }, [boxRef, show]);
151
+ }, [boxRef]);
154
152
  if (!show)
155
153
  return _jsx("div", {});
156
154
  const hasSelection = props.st.process !== null ||
@@ -245,91 +243,3 @@ export function InvalidateCycleDialogButton(props) {
245
243
  : "Invalidate Process " + props.st.process.toString()
246
244
  : "Invalidate Cycle" })) : undefined] }));
247
245
  }
248
- function isNullOrEmpty(s) {
249
- return s === undefined || s === null || s == "";
250
- }
251
- function matCanSwap(curMat, job) {
252
- return (newMat) => {
253
- if (isNullOrEmpty(newMat.serial))
254
- return false;
255
- if (newMat.location.type === LocType.OnPallet)
256
- return false;
257
- if (newMat.process !== curMat.process - 1)
258
- return false;
259
- if (isNullOrEmpty(newMat.jobUnique)) {
260
- // if part name is wrong, check casting
261
- if (isNullOrEmpty(newMat.partName))
262
- return false;
263
- if (newMat.partName !== curMat.partName) {
264
- if (!job)
265
- return false;
266
- if (!LazySeq.of(job.procsAndPaths)
267
- .flatMap((p) => p.paths)
268
- .some((p) => p.casting === newMat.partName)) {
269
- return false;
270
- }
271
- }
272
- }
273
- else {
274
- // check path
275
- if (newMat.jobUnique !== curMat.jobUnique)
276
- return false;
277
- if (newMat.path !== curMat.path)
278
- return false;
279
- }
280
- return true;
281
- };
282
- }
283
- export function SwapMaterialDialogContent(props) {
284
- const status = useAtomValue(currentStatus);
285
- const curMat = useAtomValue(inProcessMaterialInDialog);
286
- if (curMat === null || props.st === null)
287
- return _jsx("div", {});
288
- const curMatJob = status.jobs[curMat.jobUnique];
289
- const availMats = status.material.filter(matCanSwap(curMat, curMatJob));
290
- if (availMats.length === 0) {
291
- return (_jsx("p", { style: { margin: "2em" }, children: "No material with the same job is available for swapping. You must edit the pallet using the cell controller software to remove the material from the pallet. Insight will automatically refresh once the cell controller software is updated." }));
292
- }
293
- else {
294
- return (_jsxs("div", { style: { margin: "2em" }, children: [_jsx("p", { children: "Swap serial on pallet with material from the same job." }), _jsx("p", { children: "If material on the pallet is from a different job, you cannot use this screen. Instead, the material must first be removed from the pallet using the cell controller software. Insight will automatically refresh when this occurs." }), _jsx(TextField, { value: props.st?.selectedMatToSwap?.serial ?? "", select: true, onChange: (e) => props.st &&
295
- props.setState({
296
- ...props.st,
297
- selectedMatToSwap: availMats.find((m) => m.serial === e.target.value) ?? null,
298
- }), style: { width: "20em" }, variant: "outlined", label: "Select serial to swap with " + (curMat.serial ?? ""), children: availMats.map((m) => (_jsx(MenuItem, { value: m.serial, children: m.serial }, m.materialID))) })] }));
299
- }
300
- }
301
- export function SwapMaterialButtons(props) {
302
- const fmsInfo = useAtomValue(fmsInformation);
303
- const curMat = useAtomValue(inProcessMaterialInDialog);
304
- const closeMatDialog = useSetAtom(materialDialogOpen);
305
- let operator = useAtomValue(currentOperator);
306
- if (!fmsInfo.allowSwapSerialAtLoadStation)
307
- return null;
308
- if (props.ignoreOperator)
309
- operator = null;
310
- if (!curMat ||
311
- curMat.location.type !== LocType.OnPallet ||
312
- isActiveLoadStationOperation(curMat)) {
313
- return null;
314
- }
315
- function swapMats() {
316
- if (curMat &&
317
- props.st &&
318
- props.st.selectedMatToSwap &&
319
- curMat.location.type === LocType.OnPallet) {
320
- props.setState({ selectedMatToSwap: props.st.selectedMatToSwap, updating: true });
321
- JobsBackend.swapMaterialOnPallet(curMat.materialID, operator, {
322
- pallet: curMat.location.palletNum ?? 0,
323
- materialIDToSetOnPallet: props.st.selectedMatToSwap.materialID,
324
- })
325
- .catch(console.log)
326
- .finally(() => {
327
- closeMatDialog(null);
328
- props.onClose();
329
- });
330
- }
331
- }
332
- return (_jsxs(_Fragment, { children: [props.st === null ? (_jsx(Button, { color: "primary", onClick: () => props.setState({ selectedMatToSwap: null, updating: false }), children: "Swap Serial" })) : undefined, props.st !== null ? (_jsx(Button, { color: "primary", onClick: swapMats, disabled: props.st.selectedMatToSwap === null || props.st.updating, children: props.st.selectedMatToSwap === null
333
- ? "Swap Serial"
334
- : "Swap with " + (props.st.selectedMatToSwap.serial ?? "") })) : undefined] }));
335
- }
@@ -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";
@@ -54,7 +54,7 @@ import { QuarantineMatButton } from "./QuarantineButton.js";
54
54
  import { CancelLoadButton } from "./CancelLoadButton.js";
55
55
  import { durationToSeconds } from "../../util/parseISODuration.js";
56
56
  import { formatSeconds } from "./SystemOverview.js";
57
- import { InvalidateCycleDialogButton, InvalidateCycleDialogContent, SwapMaterialButtons, SwapMaterialDialogContent, } from "./InvalidateCycle.js";
57
+ import { InvalidateCycleDialogButton, InvalidateCycleDialogContent, } from "./InvalidateCycle.js";
58
58
  import { last30MaterialSummary } from "../../cell-status/material-summary.js";
59
59
  import { addHours } from "date-fns";
60
60
  import { PromptForQueue } from "./QueuesAddMaterial.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 &&
@@ -608,18 +625,16 @@ function AssignWorkorderButton() {
608
625
  return (_jsx(Button, { color: "primary", onClick: () => setWorkorderDialogOpen(true), children: "Assign Workorder" }));
609
626
  }
610
627
  const LoadMatDialog = memo(function LoadMatDialog(props) {
611
- const [swapSt, setSwapSt] = useState(null);
612
628
  const [invalidateSt, setInvalidateSt] = useState(null);
613
629
  // add material state
614
630
  const [showAddMaterial, setShowAddMaterial] = useState(false);
615
631
  const [selectedQueue, setSelectedQueue] = useState(null);
616
632
  const onClose = useCallback(function onClose() {
617
- setSwapSt(null);
618
633
  setInvalidateSt(null);
619
634
  setShowAddMaterial(false);
620
635
  setSelectedQueue(null);
621
- }, [setSwapSt, setInvalidateSt]);
622
- return (_jsx(MaterialDialog, { onClose: onClose, allowNote: true, highlightProcsGreaterOrEqualTo: invalidateSt?.process ?? undefined, extraDialogElements: _jsxs(_Fragment, { children: [_jsx(SwapMaterialDialogContent, { st: swapSt, setState: setSwapSt }), invalidateSt !== null ? (_jsx(InvalidateCycleDialogContent, { st: invalidateSt, setState: setInvalidateSt })) : null, showAddMaterial ? (_jsx(PromptForQueue, { selectedQueue: selectedQueue, setSelectedQueue: setSelectedQueue, queueNames: props.queues })) : undefined] }), buttons: _jsxs(_Fragment, { children: [_jsx(InstructionButton, { pallet: props.pallet }), _jsx(PrintLabelButton, {}), _jsx(QuarantineMatButton, {}), _jsx(CancelLoadButton, { onClose: onClose }), _jsx(SignalInspectionButton, {}), _jsx(AddMatButton, { toQueue: selectedQueue, queues: props.queues, onClose: onClose, showAddToQueue: showAddMaterial, setShowAddToQueue: setShowAddMaterial }), _jsx(SwapMaterialButtons, { st: swapSt, setState: setSwapSt, onClose: onClose }), _jsx(InvalidateCycleDialogButton, { st: invalidateSt, setState: setInvalidateSt, onClose: onClose, loadStation: true }), _jsx(AssignWorkorderButton, {})] }) }));
636
+ }, [setInvalidateSt]);
637
+ return (_jsx(MaterialDialog, { onClose: onClose, allowNote: true, highlightProcsGreaterOrEqualTo: invalidateSt?.process ?? undefined, extraDialogElements: _jsxs(_Fragment, { children: [invalidateSt !== null ? (_jsx(InvalidateCycleDialogContent, { st: invalidateSt, setState: setInvalidateSt })) : null, showAddMaterial ? (_jsx(PromptForQueue, { selectedQueue: selectedQueue, setSelectedQueue: setSelectedQueue, queueNames: props.queues })) : undefined] }), buttons: _jsxs(_Fragment, { children: [_jsx(InstructionButton, { pallet: props.pallet }), _jsx(PrintLabelButton, {}), _jsx(QuarantineMatButton, {}), _jsx(CancelLoadButton, { onClose: onClose }), _jsx(SignalInspectionButton, {}), _jsx(AddMatButton, { toQueue: selectedQueue, queues: props.queues, onClose: onClose, showAddToQueue: showAddMaterial, setShowAddToQueue: setShowAddMaterial }), _jsx(InvalidateCycleDialogButton, { st: invalidateSt, setState: setInvalidateSt, onClose: onClose, loadStation: true }), _jsx(AssignWorkorderButton, {})] }) }));
623
638
  });
624
639
  function useGridLayout({ numMatCols, maxNumFaces, horizontal, showMatInCompleted, }) {
625
640
  let rows = "";
@@ -670,8 +685,11 @@ export function LoadStation(props) {
670
685
  if (recentArrivalReceipt !== undefined &&
671
686
  (recentArrivalReceipt.stationNumber !== props.loadNum ||
672
687
  (arrivalInstruction !== undefined &&
673
- arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId)))
688
+ arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId))) {
689
+ // This state is an ephemeral receipt; discard it permanently when a new instruction supersedes it.
690
+ // oxlint-disable-next-line react/set-state-in-effect -- intentional invalidation of stale UI state.
674
691
  setRecentArrivalReceipt(undefined);
692
+ }
675
693
  }, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
676
694
  const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
677
695
  const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
@@ -679,7 +697,7 @@ export function LoadStation(props) {
679
697
  ? recentArrivalReceipt
680
698
  : undefined;
681
699
  const queueCols = LazySeq.of(data.queues)
682
- .sortBy(([q, _]) => q)
700
+ .sortBy(([q]) => q)
683
701
  .map(([q, mats]) => ({
684
702
  kind: "queue",
685
703
  label: q,
@@ -689,8 +707,8 @@ export function LoadStation(props) {
689
707
  const matCols = [...queueCols];
690
708
  if (data.baskets.size > 0) {
691
709
  const baskets = LazySeq.of(data.baskets)
692
- .filter(([b, _]) => b !== data.activeBasket?.basketId)
693
- .sortBy(([b, _]) => b)
710
+ .filter(([b]) => b !== data.activeBasket?.basketId)
711
+ .sortBy(([b]) => b)
694
712
  .map(([b, mats]) => ({
695
713
  basketId: b,
696
714
  label: `${basketName} ${b}`,