@seedtactics/insight-client 17.0.1-beta.2 → 17.0.1-beta.4
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-C6EBFHuW.js +388 -0
- 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/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/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/InvalidateCycle.js +1 -1
- package/dist/components/station-monitor/LoadStation.js +33 -13
- package/dist/components/station-monitor/Material.js +28 -28
- 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 +31 -27
- 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 +7 -18
- package/dist/network/api.js +2 -25
- package/package.json +20 -20
- package/dist/assets/ProgramHighlight-DPTeZ8Si.js +0 -3
- package/dist/assets/index-C4HZdhRu.js +0 -388
|
@@ -86,25 +86,6 @@ function logType(entry, fmsInfo) {
|
|
|
86
86
|
}
|
|
87
87
|
case api.LogType.BasketCycle:
|
|
88
88
|
return `${basketName} Cycle`;
|
|
89
|
-
case api.LogType.BasketInLocation:
|
|
90
|
-
if (entry.startofcycle) {
|
|
91
|
-
return "Arrive";
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
return "Depart";
|
|
95
|
-
}
|
|
96
|
-
case api.LogType.BasketObservation:
|
|
97
|
-
return `${basketName} Observation`;
|
|
98
|
-
case api.LogType.BasketObservationCorrection:
|
|
99
|
-
return `${basketName} Observation Correction`;
|
|
100
|
-
case api.LogType.BasketContentSnapshot:
|
|
101
|
-
return `${basketName} Content Snapshot`;
|
|
102
|
-
case api.LogType.BasketRegionSurvey:
|
|
103
|
-
return `${basketName} Region Survey`;
|
|
104
|
-
case api.LogType.BasketMisload:
|
|
105
|
-
return `${basketName} Misload`;
|
|
106
|
-
case api.LogType.BasketMisloadResolution:
|
|
107
|
-
return `${basketName} Misload Resolution`;
|
|
108
89
|
case api.LogType.MachineCycle:
|
|
109
90
|
if (entry.startofcycle) {
|
|
110
91
|
return "Start Cycle";
|
|
@@ -149,12 +130,6 @@ function logType(entry, fmsInfo) {
|
|
|
149
130
|
return "Message";
|
|
150
131
|
}
|
|
151
132
|
}
|
|
152
|
-
function evidencePosition(entry) {
|
|
153
|
-
const title = entry.details?.locationTitle ?? entry.details?.location ?? entry.loc;
|
|
154
|
-
const locationNumber = entry.locnum > 0 ? ` ${entry.locnum}` : "";
|
|
155
|
-
const zone = entry.details?.zone === undefined ? "" : ` zone ${entry.details.zone}`;
|
|
156
|
-
return `${title}${locationNumber}${zone}`;
|
|
157
|
-
}
|
|
158
133
|
export function isLogEntryInvalidated(e) {
|
|
159
134
|
return e.details?.["PalletCycleInvalidated"] === "1";
|
|
160
135
|
}
|
|
@@ -207,52 +182,6 @@ function display(props, fmsInfo) {
|
|
|
207
182
|
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
208
183
|
return _jsx("span", { children: basketCycleDescription(entry, basketName) });
|
|
209
184
|
}
|
|
210
|
-
case api.LogType.BasketInLocation: {
|
|
211
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
212
|
-
if (entry.startofcycle) {
|
|
213
|
-
return (_jsxs("span", { children: [basketContainerName(entry, basketName), " arrived at ", entry.loc, entry.locnum > 0 ? ` position ${entry.locnum}` : ""] }));
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
return (_jsxs("span", { children: [basketContainerName(entry, basketName), " departed from ", entry.loc, entry.locnum > 0 ? ` position ${entry.locnum}` : ""] }));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
case api.LogType.BasketObservation: {
|
|
220
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
221
|
-
const episodeCount = entry.details?.episodeCount;
|
|
222
|
-
const source = entry.details?.sourceName;
|
|
223
|
-
const note = entry.details?.note;
|
|
224
|
-
return (_jsxs("span", { children: [source ?? "Source", " observed ", basketName, " ", entry.pal, " at ", evidencePosition(entry), episodeCount === undefined || episodeCount === "0"
|
|
225
|
-
? ""
|
|
226
|
-
: ` with direct continuity to ${episodeCount} tracked content episode${episodeCount === "1" ? "" : "s"}`, note === undefined ? "" : `: ${note}`] }));
|
|
227
|
-
}
|
|
228
|
-
case api.LogType.BasketObservationCorrection: {
|
|
229
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
230
|
-
return (_jsxs("span", { children: ["Observation of ", basketName, " ", entry.pal, " corrected"] }));
|
|
231
|
-
}
|
|
232
|
-
case api.LogType.BasketContentSnapshot: {
|
|
233
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
234
|
-
return (_jsxs("span", { children: [basketContainerName(entry, basketName), " contained ", displayMat(entry.material)] }));
|
|
235
|
-
}
|
|
236
|
-
case api.LogType.BasketRegionSurvey: {
|
|
237
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
238
|
-
const source = entry.details?.sourceName;
|
|
239
|
-
const observed = entry.details?.observedBasketCount;
|
|
240
|
-
const unidentified = entry.details?.unidentifiedBasketCount;
|
|
241
|
-
return (_jsxs("span", { children: [source === undefined ? "Operator" : source, " recorded a ", entry.result.toLowerCase(), " ", basketName.toLowerCase(), " survey at ", evidencePosition(entry), observed === undefined ? "" : ` (${observed} identified`, unidentified === undefined ? "" : `, ${unidentified} unidentified`, observed === undefined && unidentified === undefined ? "" : ")"] }));
|
|
242
|
-
}
|
|
243
|
-
case api.LogType.BasketMisload: {
|
|
244
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
245
|
-
const source = entry.details?.sourceName;
|
|
246
|
-
const reason = entry.details?.reason;
|
|
247
|
-
const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
|
|
248
|
-
return (_jsxs("span", { children: [source === undefined ? subject : `${source} reported ${subject}`, " requires inspection at", " ", evidencePosition(entry), reason === undefined ? "" : `: ${reason}`] }));
|
|
249
|
-
}
|
|
250
|
-
case api.LogType.BasketMisloadResolution: {
|
|
251
|
-
const basketName = basketDisplayName(fmsInfo.basketName);
|
|
252
|
-
const source = entry.details?.sourceName;
|
|
253
|
-
const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
|
|
254
|
-
return (_jsxs("span", { children: [source === undefined ? "Inspection hold" : `${source} resolved inspection hold`, " for", " ", subject, " at ", evidencePosition(entry), ": ", entry.result] }));
|
|
255
|
-
}
|
|
256
185
|
case api.LogType.MachineCycle:
|
|
257
186
|
return (_jsxs("span", { children: [displayMat(entry.material), " on", " ", _jsxs(ColoredSpan, { $type: "pallet", children: ["pallet ", entry.pal] }), " at", " ", _jsxs(ColoredSpan, { $type: "machine", children: [entry.loc, " ", entry.locnum.toString()] }), entry.program && entry.program !== "" ? (_jsxs("span", { children: [" with program ", entry.program] })) : undefined] }));
|
|
258
187
|
case api.LogType.PartMark:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as api from "../network/api.js";
|
|
2
|
-
type BasketNameEntry = Readonly<Pick<api.ILogEntry, "pal"
|
|
3
|
-
type BasketCycleEntry = BasketNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle"
|
|
2
|
+
type BasketNameEntry = Readonly<Pick<api.ILogEntry, "pal">>;
|
|
3
|
+
type BasketCycleEntry = BasketNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle">>;
|
|
4
4
|
export declare function basketContainerName(entry: BasketNameEntry, basketName: string): string;
|
|
5
5
|
export declare function basketCycleDescription(entry: BasketCycleEntry, basketName: string): string;
|
|
6
6
|
export {};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export function basketContainerName(entry, basketName) {
|
|
2
|
-
if (entry.basketContentEpisodeId)
|
|
3
|
-
return `${basketName} fragment ${entry.basketContentEpisodeId.slice(0, 8)}`;
|
|
4
2
|
if (entry.pal > 0)
|
|
5
3
|
return `${basketName} ${entry.pal}`;
|
|
6
4
|
return basketName;
|
|
@@ -9,9 +7,5 @@ export function basketCycleDescription(entry, basketName) {
|
|
|
9
7
|
const container = basketContainerName(entry, basketName);
|
|
10
8
|
if (entry.startofcycle)
|
|
11
9
|
return `${container} started cycle`;
|
|
12
|
-
|
|
13
|
-
const fragments = fragmentCount > 0
|
|
14
|
-
? ` from ${fragmentCount} UUID fragment${fragmentCount === 1 ? "" : "s"}`
|
|
15
|
-
: "";
|
|
16
|
-
return `${container} completed cycle${fragments}`;
|
|
10
|
+
return `${container} completed cycle`;
|
|
17
11
|
}
|
|
@@ -64,20 +64,20 @@ function sortPartSummary(parts, col, order) {
|
|
|
64
64
|
case "Elapsed":
|
|
65
65
|
sortCol = (j) => j.stationMins
|
|
66
66
|
.toAscLazySeq()
|
|
67
|
-
.filter(([
|
|
67
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
68
68
|
.sumBy(([, t]) => t.elapsed);
|
|
69
69
|
break;
|
|
70
70
|
case "Active":
|
|
71
71
|
sortCol = (j) => j.stationMins
|
|
72
72
|
.toAscLazySeq()
|
|
73
|
-
.filter(([
|
|
74
|
-
.sumBy(([
|
|
73
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
74
|
+
.sumBy(([, t]) => t.active);
|
|
75
75
|
break;
|
|
76
76
|
case "Median":
|
|
77
77
|
sortCol = (j) => j.stationMins
|
|
78
78
|
.toAscLazySeq()
|
|
79
|
-
.filter(([
|
|
80
|
-
.sumBy(([
|
|
79
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
80
|
+
.sumBy(([, t]) => t.medianElapsed);
|
|
81
81
|
break;
|
|
82
82
|
}
|
|
83
83
|
const sorted = [...parts];
|
|
@@ -183,13 +183,13 @@ const PartRow = memo(function PartRow({ part }) {
|
|
|
183
183
|
display: "block",
|
|
184
184
|
}, children: part.part }) })] }) }), _jsx(TableCell, { align: "right", children: part.completedQty }), _jsx(TableCell, { align: "right", children: part.abnormalQty }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
|
|
185
185
|
.toAscLazySeq()
|
|
186
|
-
.filter(([
|
|
187
|
-
.sumBy(([
|
|
186
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
187
|
+
.sumBy(([, t]) => t.active) / 60) }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
|
|
188
188
|
.toAscLazySeq()
|
|
189
|
-
.filter(([
|
|
189
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
190
190
|
.sumBy(([, t]) => t.elapsed) / 60) }), _jsx(TableCell, { align: "right", children: numFormat.format(part.stationMins
|
|
191
191
|
.toAscLazySeq()
|
|
192
|
-
.filter(([
|
|
192
|
+
.filter(([, stat]) => !stat.isLoadUnload)
|
|
193
193
|
.sumBy(([, t]) => t.medianElapsed) / 60) }), _jsx(TableCell, { align: "left", children: part.workorders.size <= 1 ? (_jsx(WorkorderLink, { workorderId: part.workorders.lookupMin() ?? null })) : part.workorders.size === 2 ? (_jsxs("span", { children: [_jsx(WorkorderLink, { workorderId: part.workorders.lookupMin() }), " &", " ", _jsx(WorkorderLink, { workorderId: part.workorders.lookupMax() })] })) : (`${part.workorders.size} workorders`) }), _jsx(TableCell, { children: _jsx(Tooltip, { title: "Show Details", children: _jsx(IconButton, { size: "small", onClick: () => setOpen(!open), children: open ? _jsx(KeyboardArrowUpIcon, {}) : _jsx(KeyboardArrowDownIcon, {}) }) }) })] }), _jsx(TableRow, { children: _jsx(TableCell, { sx: { pb: "0", pt: "0" }, colSpan: partColCount + 1, children: _jsx(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: _jsx(PartDetails, { part: part }) }) }) })] }));
|
|
194
194
|
});
|
|
195
195
|
function CopyToClipboardBtn({ partsAtom }) {
|
|
@@ -275,7 +275,7 @@ function copyPartsToClipboard(parts) {
|
|
|
275
275
|
for (const lul of [false, true]) {
|
|
276
276
|
const stats = p.stationMins
|
|
277
277
|
.toAscLazySeq()
|
|
278
|
-
.filter(([
|
|
278
|
+
.filter(([, stat]) => stat.isLoadUnload === lul)
|
|
279
279
|
.toRArray();
|
|
280
280
|
if (stats.length === 0) {
|
|
281
281
|
table += `<td>0</td><td>0</td><td>0</td>`;
|
|
@@ -297,23 +297,30 @@ export function ProgramHistoryDialog() {
|
|
|
297
297
|
const [loading, setLoading] = useState(false);
|
|
298
298
|
const [error, setError] = useState(null);
|
|
299
299
|
useEffect(() => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
const loadProgramDetails = async () => {
|
|
301
|
+
if (program === null) {
|
|
302
|
+
setRevisions(null);
|
|
303
|
+
setPage(0);
|
|
304
|
+
setLastLoadedPage({ page: 0, hasMore: false });
|
|
305
|
+
}
|
|
306
|
+
else if (program !== null && revisions === null) {
|
|
307
|
+
// load initial
|
|
308
|
+
setLoading(true);
|
|
309
|
+
setError(null);
|
|
310
|
+
try {
|
|
311
|
+
const revs = await MachineBackend.getProgramRevisionsInDescendingOrderOfRevision(program.programName, revisionsPerPage, undefined);
|
|
312
|
+
setRevisions(revs);
|
|
313
|
+
setLastLoadedPage({ page: 0, hasMore: revs.length === revisionsPerPage });
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
setError(err instanceof Error ? err : String(err));
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
setLoading(false);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
loadProgramDetails().catch(console.error);
|
|
317
324
|
}, [program, revisions]);
|
|
318
325
|
function advancePage() {
|
|
319
326
|
if (page < lastLoadedPage.page) {
|
|
@@ -169,7 +169,7 @@ const RebookingDialog = memo(function RebookingDialog({ rebooking, close, }) {
|
|
|
169
169
|
return (_jsxs(Dialog, { open: rebooking !== undefined, onClose: () => close(undefined), children: [_jsx(DialogTitle, { children: rebooking?.canceled ? (_jsx(Typography, { sx: { textDecoration: "line-through" }, children: rebooking?.bookingId })) : (rebooking?.bookingId) }), _jsx(DialogContent, { children: _jsxs(Stack, { direction: "column", spacing: 1, children: [rebooking?.canceled && (_jsxs(Typography, { variant: "h5", children: ["Canceled at ", longDateFormat.format(rebooking.canceled)] })), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx("span", { children: "Part:" }), rebooking?.partName && _jsx(PartIdenticon, { part: rebooking?.partName, size: 25 }), _jsx("span", { children: rebooking?.partName })] }), _jsxs(Typography, { children: ["Quantity: ", rebooking?.quantity] }), _jsxs(Typography, { children: ["Request Time: ", longDateFormat.format(rebooking?.timeUTC)] }), _jsxs(Typography, { children: ["Priority: ", rebooking?.priority] }), rebooking?.workorder && _jsxs(Typography, { children: ["Workorder: ", rebooking?.workorder] }), _jsxs(Typography, { children: ["Note: ", rebooking?.notes] }), rebooking?.job && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsxs(Typography, { children: ["Scheduled Job: ", rebooking?.job] }), _jsxs(Typography, { children: ["Scheduled Time: ", rebooking?.schTime?.toLocaleString()] })] }))] }) }), _jsxs(DialogActions, { children: [!rebooking?.canceled && !rebooking?.job && (_jsxs(Button, { onClick: cancel, color: "secondary", disabled: canceling, children: [canceling ? _jsx(CircularProgress, { size: 24 }) : undefined, "Cancel Rebooking Request"] })), _jsx(Button, { onClick: () => close(undefined), children: "Close" })] })] }));
|
|
170
170
|
});
|
|
171
171
|
const partNamesAtom = atom((get) => LazySeq.ofObject(get(currentStatus).jobs)
|
|
172
|
-
.map(([
|
|
172
|
+
.map(([, j]) => j.partName)
|
|
173
173
|
.concat(get(last30Jobs)
|
|
174
174
|
.valuesToLazySeq()
|
|
175
175
|
.map((j) => j.partName))
|
|
@@ -111,7 +111,7 @@ export function RecentFailedInspectionsTable() {
|
|
|
111
111
|
const inspections = useAtomValue(last30Inspections);
|
|
112
112
|
const failed = useMemo(() => {
|
|
113
113
|
const today = startOfToday();
|
|
114
|
-
const allEvts = LazySeq.of(inspections).flatMap(([
|
|
114
|
+
const allEvts = LazySeq.of(inspections).flatMap(([, evts]) => evts.valuesToLazySeq());
|
|
115
115
|
return extractFailedInspections(allEvts, addDays(today, -4), addDays(today, 1));
|
|
116
116
|
}, [inspections]);
|
|
117
117
|
return (_jsxs("div", { children: [_jsxs(Box, { component: "nav", sx: {
|
|
@@ -231,10 +231,9 @@ export function useIsDemo() {
|
|
|
231
231
|
return useAtomValue(isDemoAtom);
|
|
232
232
|
}
|
|
233
233
|
export function useSetTitle(title) {
|
|
234
|
-
const demo = useIsDemo();
|
|
235
234
|
useEffect(() => {
|
|
236
235
|
document.title = title + " - FMS Insight";
|
|
237
|
-
}, [
|
|
236
|
+
}, [title]);
|
|
238
237
|
}
|
|
239
238
|
export function helpUrl(r) {
|
|
240
239
|
switch (r.route) {
|
|
@@ -26,7 +26,7 @@ export interface BasketArrivalReceipt {
|
|
|
26
26
|
readonly status: "recorded" | "corrected" | "retracted";
|
|
27
27
|
}
|
|
28
28
|
export declare function loadStationArrivalInstruction(instructions: ReadonlyArray<Readonly<api.IBasketMoveInstruction>>, stationNumber: number): Readonly<api.IBasketMoveInstruction> | undefined;
|
|
29
|
-
|
|
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
|
};
|
|
@@ -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";
|
|
@@ -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 &&
|
|
@@ -670,8 +687,11 @@ export function LoadStation(props) {
|
|
|
670
687
|
if (recentArrivalReceipt !== undefined &&
|
|
671
688
|
(recentArrivalReceipt.stationNumber !== props.loadNum ||
|
|
672
689
|
(arrivalInstruction !== undefined &&
|
|
673
|
-
arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId)))
|
|
690
|
+
arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId))) {
|
|
691
|
+
// This state is an ephemeral receipt; discard it permanently when a new instruction supersedes it.
|
|
692
|
+
// oxlint-disable-next-line react/set-state-in-effect -- intentional invalidation of stale UI state.
|
|
674
693
|
setRecentArrivalReceipt(undefined);
|
|
694
|
+
}
|
|
675
695
|
}, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
|
|
676
696
|
const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
|
|
677
697
|
const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
|
|
@@ -679,7 +699,7 @@ export function LoadStation(props) {
|
|
|
679
699
|
? recentArrivalReceipt
|
|
680
700
|
: undefined;
|
|
681
701
|
const queueCols = LazySeq.of(data.queues)
|
|
682
|
-
.sortBy(([q
|
|
702
|
+
.sortBy(([q]) => q)
|
|
683
703
|
.map(([q, mats]) => ({
|
|
684
704
|
kind: "queue",
|
|
685
705
|
label: q,
|
|
@@ -689,8 +709,8 @@ export function LoadStation(props) {
|
|
|
689
709
|
const matCols = [...queueCols];
|
|
690
710
|
if (data.baskets.size > 0) {
|
|
691
711
|
const baskets = LazySeq.of(data.baskets)
|
|
692
|
-
.filter(([b
|
|
693
|
-
.sortBy(([b
|
|
712
|
+
.filter(([b]) => b !== data.activeBasket?.basketId)
|
|
713
|
+
.sortBy(([b]) => b)
|
|
694
714
|
.map(([b, mats]) => ({
|
|
695
715
|
basketId: b,
|
|
696
716
|
label: `${basketName} ${b}`,
|