@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.
- package/dist/assets/ProgramHighlight-C6zSEXCX.js +3 -0
- package/dist/assets/index-kk-GRa5i.js +388 -0
- package/dist/cell-status/inspections.js +0 -20
- package/dist/cell-status/material-details.js +7 -1
- package/dist/cell-status/material-summary.js +1 -87
- package/dist/cell-status/station-cycles.d.ts +1 -0
- package/dist/cell-status/station-cycles.js +11 -14
- package/dist/components/LogEntry.js +0 -71
- package/dist/components/log-entry-container-name.d.ts +2 -2
- package/dist/components/log-entry-container-name.js +1 -7
- package/dist/components/operations/AllMaterial.js +2 -4
- package/dist/components/operations/CompletedParts.js +10 -10
- package/dist/components/operations/Programs.js +24 -17
- package/dist/components/operations/Rebookings.js +1 -1
- package/dist/components/quality/RecentFailedInspections.js +1 -1
- package/dist/components/routes.js +1 -2
- package/dist/components/station-monitor/BasketLoadStationWork.js +66 -16
- package/dist/components/station-monitor/BasketMovementArrival.d.ts +4 -2
- package/dist/components/station-monitor/BasketMovementArrival.js +23 -33
- package/dist/components/station-monitor/BulkRawMaterial.js +25 -14
- package/dist/components/station-monitor/CancelLoadButton.js +3 -1
- package/dist/components/station-monitor/InvalidateCycle.d.ts +1 -17
- package/dist/components/station-monitor/InvalidateCycle.js +2 -92
- package/dist/components/station-monitor/LoadStation.js +36 -18
- package/dist/components/station-monitor/Material.js +33 -31
- package/dist/components/station-monitor/PrintedLabel.js +2 -2
- package/dist/components/station-monitor/QuarantineButton.js +4 -4
- package/dist/components/station-monitor/SystemOverview.js +33 -31
- package/dist/data/all-material-bins.js +15 -11
- package/dist/data/current-cycles.js +6 -3
- package/dist/data/inspection-sankey.js +1 -1
- package/dist/data/results.completed-parts.js +1 -1
- package/dist/data/results.cycles.js +1 -1
- package/dist/data/results.schedules.js +1 -1
- package/dist/index.html +1 -1
- package/dist/network/api.d.ts +33 -50
- package/dist/network/api.js +57 -141
- package/dist/network/backend-mock.js +0 -3
- package/dist/network/backend.d.ts +0 -1
- package/docs/mazak.md +13 -0
- package/docs/operator-procedures.md +6 -6
- package/package.json +20 -20
- package/dist/assets/ProgramHighlight-DPTeZ8Si.js +0 -3
- 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
|
|
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: "
|
|
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(() =>
|
|
109
|
-
const
|
|
110
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
180
|
+
setSubmission((current) => (current === pending ? { ...pending, state } : current));
|
|
137
181
|
}
|
|
138
182
|
catch {
|
|
139
|
-
setSubmission((current) => current
|
|
183
|
+
setSubmission((current) => (current === pending ? { ...pending, state: "error" } : current));
|
|
140
184
|
}
|
|
141
185
|
}
|
|
142
|
-
const submissionDisabled =
|
|
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] }),
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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)
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
106
|
+
const position = basket.position;
|
|
107
|
+
if (position === undefined || position.locationNum !== loadNum)
|
|
107
108
|
continue;
|
|
108
|
-
if (
|
|
109
|
+
if (position.location === api.BasketLocationEnum.LoadUnload) {
|
|
109
110
|
activeBasket = basket;
|
|
110
111
|
}
|
|
111
|
-
else if (
|
|
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.
|
|
286
|
-
|
|
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:
|
|
310
|
-
baskets:
|
|
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(([
|
|
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
|
-
}, [
|
|
622
|
-
return (_jsx(MaterialDialog, { onClose: onClose, allowNote: true, highlightProcsGreaterOrEqualTo: invalidateSt?.process ?? undefined, extraDialogElements: _jsxs(_Fragment, { children: [
|
|
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
|
|
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
|
|
693
|
-
.sortBy(([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}`,
|