@seedtactics/insight-client 17.0.0-beta.1 → 17.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import { ReactNode, ComponentType } from "react";
2
2
  import { ChooseModeItem } from "./ChooseMode.js";
3
3
  import * as serverSettings from "../network/server-settings.js";
4
4
  import type { SubmitBasketLoadStationCommand } from "./station-monitor/BasketLoadStationWork.js";
5
+ import type { SubmitBasketLocationCorrection, SubmitBasketMovementCompletion } from "./station-monitor/BasketMovementArrival.js";
5
6
  export declare function NavTabs({ children }: {
6
7
  children?: ReactNode;
7
8
  }): import("react").JSX.Element;
@@ -12,6 +13,8 @@ export interface AppProps {
12
13
  };
13
14
  readonly chooseModes?: (i: serverSettings.FMSInfoAndUser) => ReadonlyArray<ChooseModeItem> | null;
14
15
  readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
16
+ readonly submitBasketMovementCompletion?: SubmitBasketMovementCompletion;
17
+ readonly submitBasketLocationCorrection?: SubmitBasketLocationCorrection;
15
18
  }
16
19
  declare const App: import("react").MemoExoticComponent<(props: AppProps) => import("react").JSX.Element>;
17
20
  export default App;
@@ -310,7 +310,7 @@ const App = memo(function App(props) {
310
310
  else {
311
311
  switch (route.route) {
312
312
  case routes.RouteLocation.Station_LoadMonitor:
313
- page = (_jsx(LoadStation, { loadNum: route.loadNum, queues: route.queues, completed: route.completed, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }));
313
+ page = (_jsx(LoadStation, { loadNum: route.loadNum, queues: route.queues, completed: route.completed, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand, submitBasketMovementCompletion: props.submitBasketMovementCompletion, submitBasketLocationCorrection: props.submitBasketLocationCorrection }, route.loadNum));
314
314
  nav1 = StationToolbar;
315
315
  nav2 = StationToolbarOverviewButton;
316
316
  showOperator = true;
@@ -93,10 +93,22 @@ function logType(entry, fmsInfo) {
93
93
  else {
94
94
  return "Depart";
95
95
  }
96
- case api.LogType.BasketIdentityHint:
97
- return `${basketName} Identity Hint`;
96
+ case api.LogType.BasketIdentityAssociation:
97
+ return `${basketName} Identity Association`;
98
+ case api.LogType.BasketIdentityAssociationCorrection:
99
+ return `${basketName} Identity Correction`;
98
100
  case api.LogType.BasketContentSnapshot:
99
101
  return `${basketName} Content Snapshot`;
102
+ case api.LogType.BasketLocationObservation:
103
+ return `${basketName} Location Observation`;
104
+ case api.LogType.BasketLocationObservationCorrection:
105
+ return `${basketName} Location Correction`;
106
+ case api.LogType.BasketRegionSurvey:
107
+ return `${basketName} Region Survey`;
108
+ case api.LogType.BasketMisload:
109
+ return `${basketName} Inspection Hold`;
110
+ case api.LogType.BasketMisloadResolution:
111
+ return `${basketName} Inspection Resolution`;
100
112
  case api.LogType.MachineCycle:
101
113
  if (entry.startofcycle) {
102
114
  return "Start Cycle";
@@ -141,6 +153,12 @@ function logType(entry, fmsInfo) {
141
153
  return "Message";
142
154
  }
143
155
  }
156
+ function evidencePosition(entry) {
157
+ const title = entry.details?.locationTitle ?? entry.details?.location ?? entry.loc;
158
+ const locationNumber = entry.locnum > 0 ? ` ${entry.locnum}` : "";
159
+ const zone = entry.details?.zone === undefined ? "" : ` zone ${entry.details.zone}`;
160
+ return `${title}${locationNumber}${zone}`;
161
+ }
144
162
  export function isLogEntryInvalidated(e) {
145
163
  return e.details?.["PalletCycleInvalidated"] === "1";
146
164
  }
@@ -202,14 +220,57 @@ function display(props, fmsInfo) {
202
220
  return (_jsxs("span", { children: [basketContainerName(entry, basketName), " departed from ", entry.loc, entry.locnum > 0 ? ` position ${entry.locnum}` : ""] }));
203
221
  }
204
222
  }
205
- case api.LogType.BasketIdentityHint: {
223
+ case api.LogType.BasketIdentityAssociation: {
224
+ const basketName = basketDisplayName(fmsInfo.basketName);
225
+ const episodeCount = entry.details?.episodeCount;
226
+ const source = entry.details?.sourceName;
227
+ const note = entry.details?.note;
228
+ return (_jsxs("span", { children: [source === undefined ? "" : `${source} recorded `, episodeCount === undefined
229
+ ? `content episodes associated with ${basketName} ${entry.pal}`
230
+ : `${episodeCount} content episode${episodeCount === "1" ? "" : "s"} associated with ${basketName} ${entry.pal}`, entry.details?.location === undefined && entry.details?.locationTitle === undefined
231
+ ? ""
232
+ : ` at ${evidencePosition(entry)}`, note === undefined ? "" : `: ${note}`] }));
233
+ }
234
+ case api.LogType.BasketIdentityAssociationCorrection: {
206
235
  const basketName = basketDisplayName(fmsInfo.basketName);
207
- return (_jsxs("span", { children: [basketContainerName(entry, basketName), " presently associated with ", basketName, " ", entry.pal] }));
236
+ return (_jsxs("span", { children: ["Identity association for ", basketName, " ", entry.pal, " corrected"] }));
208
237
  }
209
238
  case api.LogType.BasketContentSnapshot: {
210
239
  const basketName = basketDisplayName(fmsInfo.basketName);
211
240
  return (_jsxs("span", { children: [basketContainerName(entry, basketName), " contained ", displayMat(entry.material)] }));
212
241
  }
242
+ case api.LogType.BasketLocationObservation: {
243
+ const basketName = basketDisplayName(fmsInfo.basketName);
244
+ const operator = entry.details?.sourceName ?? entry.details?.operator ?? "Operator";
245
+ return (_jsxs("span", { children: [operator, " observed ", basketName, " ", entry.pal, " at ", evidencePosition(entry)] }));
246
+ }
247
+ case api.LogType.BasketLocationObservationCorrection: {
248
+ const basketName = basketDisplayName(fmsInfo.basketName);
249
+ const operator = entry.details?.sourceName ?? entry.details?.operator ?? "Operator";
250
+ const location = evidencePosition(entry);
251
+ const replacementBasketId = entry.details?.replacementBasketId;
252
+ return replacementBasketId === undefined ? (_jsxs("span", { children: [operator, " retracted the observation of ", basketName, " ", entry.pal, " at ", location] })) : (_jsxs("span", { children: [operator, " changed the observation of ", basketName, " ", entry.pal, " at ", location, " to", " ", basketName, " ", replacementBasketId, " at ", location] }));
253
+ }
254
+ case api.LogType.BasketRegionSurvey: {
255
+ const basketName = basketDisplayName(fmsInfo.basketName);
256
+ const source = entry.details?.sourceName;
257
+ const observed = entry.details?.observedBasketCount;
258
+ const unidentified = entry.details?.unidentifiedBasketCount;
259
+ 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 ? "" : ")"] }));
260
+ }
261
+ case api.LogType.BasketMisload: {
262
+ const basketName = basketDisplayName(fmsInfo.basketName);
263
+ const source = entry.details?.sourceName;
264
+ const reason = entry.details?.reason;
265
+ const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
266
+ return (_jsxs("span", { children: [source === undefined ? subject : `${source} reported ${subject}`, " requires inspection at", " ", evidencePosition(entry), reason === undefined ? "" : `: ${reason}`] }));
267
+ }
268
+ case api.LogType.BasketMisloadResolution: {
269
+ const basketName = basketDisplayName(fmsInfo.basketName);
270
+ const source = entry.details?.sourceName;
271
+ const subject = entry.pal > 0 ? `${basketName} ${entry.pal}` : `Unnumbered ${basketName.toLowerCase()}`;
272
+ return (_jsxs("span", { children: [source === undefined ? "Inspection hold" : `${source} resolved inspection hold`, " for", " ", subject, " at ", evidencePosition(entry), ": ", entry.result] }));
273
+ }
213
274
  case api.LogType.MachineCycle:
214
275
  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] }));
215
276
  case api.LogType.PartMark:
@@ -1,6 +1,6 @@
1
1
  import * as api from "../network/api.js";
2
- type ContainerNameEntry = Readonly<Pick<api.ILogEntry, "pal" | "containerId">>;
3
- type BasketCycleEntry = ContainerNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle" | "containerIds">>;
4
- export declare function basketContainerName(entry: ContainerNameEntry, basketName: string): string;
2
+ type BasketNameEntry = Readonly<Pick<api.ILogEntry, "pal" | "basketContentEpisodeId">>;
3
+ type BasketCycleEntry = BasketNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle" | "basketCycleEndContentEpisodeIds">>;
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,6 @@
1
1
  export function basketContainerName(entry, basketName) {
2
- if (entry.containerId)
3
- return `${basketName} fragment ${entry.containerId.slice(0, 8)}`;
2
+ if (entry.basketContentEpisodeId)
3
+ return `${basketName} fragment ${entry.basketContentEpisodeId.slice(0, 8)}`;
4
4
  if (entry.pal > 0)
5
5
  return `${basketName} ${entry.pal}`;
6
6
  return basketName;
@@ -9,7 +9,7 @@ export function basketCycleDescription(entry, basketName) {
9
9
  const container = basketContainerName(entry, basketName);
10
10
  if (entry.startofcycle)
11
11
  return `${container} started cycle`;
12
- const fragmentCount = entry.containerIds?.length ?? 0;
12
+ const fragmentCount = entry.basketCycleEndContentEpisodeIds?.length ?? 0;
13
13
  const fragments = fragmentCount > 0
14
14
  ? ` from ${fragmentCount} UUID fragment${fragmentCount === 1 ? "" : "s"}`
15
15
  : "";
@@ -0,0 +1,38 @@
1
+ import * as api from "../../network/api.js";
2
+ export interface BasketMovementCompletionCommand {
3
+ readonly commandId: string;
4
+ readonly instructionId: string;
5
+ readonly observedBasketId: number;
6
+ }
7
+ export interface BasketMovementCompletionReceipt {
8
+ readonly stationNumber: number;
9
+ readonly instructionId: string;
10
+ readonly observationId: string;
11
+ readonly observedBasketId: number;
12
+ }
13
+ export type SubmitBasketMovementCompletion = (stationNumber: number, command: BasketMovementCompletionCommand) => Promise<BasketMovementCompletionReceipt | "conflict">;
14
+ export interface BasketLocationCorrectionCommand {
15
+ readonly correctionId: string;
16
+ readonly targetObservationId: string;
17
+ readonly replacementBasketId: number | null;
18
+ readonly replacementObservationId: string | null;
19
+ }
20
+ export type SubmitBasketLocationCorrection = (stationNumber: number, command: BasketLocationCorrectionCommand) => Promise<"accepted" | "conflict">;
21
+ export interface BasketArrivalReceipt {
22
+ readonly stationNumber: number;
23
+ readonly instruction: Readonly<api.IBasketMoveInstruction>;
24
+ readonly command: BasketMovementCompletionCommand;
25
+ readonly receipt: BasketMovementCompletionReceipt;
26
+ readonly status: "recorded" | "corrected" | "retracted";
27
+ }
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, }: {
30
+ readonly stationNumber: number;
31
+ readonly basketName: string;
32
+ readonly instruction: Readonly<api.IBasketMoveInstruction>;
33
+ readonly submitCommand: SubmitBasketMovementCompletion | undefined;
34
+ readonly submitCorrection: SubmitBasketLocationCorrection | undefined;
35
+ readonly receipt?: BasketArrivalReceipt;
36
+ readonly onAccepted?: (receipt: BasketArrivalReceipt) => void;
37
+ readonly onCorrected?: (command: BasketLocationCorrectionCommand) => void;
38
+ }): import("react").JSX.Element;
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* Copyright (c) 2026, John Lenz
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of John Lenz, Black Maple Software, SeedTactics,
15
+ nor the names of other contributors may be used to endorse or
16
+ promote products derived from this software without specific
17
+ prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ */
31
+ import { useEffect, useRef, useState } from "react";
32
+ import { Alert, Box, Button, Paper, Stack, TextField, Typography } from "@mui/material";
33
+ import * as api from "../../network/api.js";
34
+ export function loadStationArrivalInstruction(instructions, stationNumber) {
35
+ const activeIds = new Set(instructions.map((instruction) => instruction.instructionId));
36
+ return instructions.find((instruction) => instruction.basketId !== undefined &&
37
+ instruction.destination.location === api.BasketLocationEnum.LoadUnload &&
38
+ instruction.destination.locationNum === stationNumber &&
39
+ (instruction.prerequisiteInstructionId === undefined ||
40
+ !activeIds.has(instruction.prerequisiteInstructionId)));
41
+ }
42
+ export function BasketMovementArrival({ stationNumber, basketName, instruction, submitCommand, submitCorrection, receipt, onAccepted, onCorrected, }) {
43
+ const [chooseDifferent, setChooseDifferent] = useState(false);
44
+ const [differentBasket, setDifferentBasket] = useState("");
45
+ const [submission, setSubmission] = useState();
46
+ const [changingRecordedBasket, setChangingRecordedBasket] = useState(false);
47
+ 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
+ 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]);
71
+ const expectedBasketId = instruction.basketId;
72
+ if (expectedBasketId === undefined) {
73
+ return (_jsx(Alert, { severity: "warning", children: "This tracked basket must be handled through the recovery workflow." }));
74
+ }
75
+ async function record(command) {
76
+ if (submitCommand === undefined)
77
+ return;
78
+ setSubmission({ state: "submitting", command });
79
+ try {
80
+ const result = await submitCommand(stationNumber, command);
81
+ if (currentStationNumber.current !== stationNumber ||
82
+ currentInstructionId.current !== command.instructionId)
83
+ return;
84
+ if (result === "conflict") {
85
+ setSubmission({ state: result, command });
86
+ }
87
+ else {
88
+ setSubmission({ state: "accepted", command, receipt: result });
89
+ onAccepted?.({
90
+ stationNumber,
91
+ instruction,
92
+ command,
93
+ receipt: result,
94
+ status: "recorded",
95
+ });
96
+ }
97
+ }
98
+ catch (error) {
99
+ if (currentStationNumber.current !== stationNumber ||
100
+ currentInstructionId.current !== command.instructionId)
101
+ return;
102
+ setSubmission({
103
+ state: "error",
104
+ command,
105
+ message: error instanceof Error ? error.message : "Unable to record basket arrival",
106
+ });
107
+ }
108
+ }
109
+ async function correct(command) {
110
+ if (submitCorrection === undefined || activeCommand === undefined)
111
+ return;
112
+ setCorrectionSubmission({ state: "submitting", command });
113
+ try {
114
+ const result = await submitCorrection(stationNumber, command);
115
+ if (currentStationNumber.current !== stationNumber ||
116
+ currentCorrectionTargetId.current !== command.targetObservationId)
117
+ return;
118
+ setCorrectionSubmission({ state: result, command });
119
+ if (result === "accepted")
120
+ onCorrected?.(command);
121
+ }
122
+ catch {
123
+ if (currentStationNumber.current !== stationNumber ||
124
+ currentCorrectionTargetId.current !== command.targetObservationId)
125
+ return;
126
+ setCorrectionSubmission({ state: "error", command });
127
+ }
128
+ }
129
+ function createCommand(basketId) {
130
+ return {
131
+ commandId: crypto.randomUUID(),
132
+ instructionId: instruction.instructionId,
133
+ observedBasketId: basketId,
134
+ };
135
+ }
136
+ const parsedDifferentBasket = Number(differentBasket);
137
+ const activeCommand = receipt?.status === "retracted"
138
+ ? undefined
139
+ : (receipt?.command ??
140
+ (submission?.state === "accepted" &&
141
+ submission.command.instructionId === instruction.instructionId
142
+ ? submission.command
143
+ : undefined));
144
+ const activeObservationId = receipt?.status === "retracted"
145
+ ? undefined
146
+ : (receipt?.receipt.observationId ??
147
+ (submission?.state === "accepted" ? submission.receipt.observationId : undefined));
148
+ currentCorrectionTargetId.current = activeObservationId;
149
+ function createCorrection(replacementBasketId) {
150
+ return {
151
+ correctionId: crypto.randomUUID(),
152
+ targetObservationId: activeObservationId ?? "",
153
+ replacementBasketId,
154
+ replacementObservationId: replacementBasketId === null ? null : crypto.randomUUID(),
155
+ };
156
+ }
157
+ const validDifferentBasket = Number.isInteger(parsedDifferentBasket) &&
158
+ parsedDifferentBasket > 0 &&
159
+ parsedDifferentBasket !== (activeCommand?.observedBasketId ?? expectedBasketId);
160
+ const disabled = submitCommand === undefined ||
161
+ activeCommand !== undefined ||
162
+ (submission !== undefined && submission.state !== "error");
163
+ const receiptMessage = receipt?.status === "retracted"
164
+ ? "Arrival observation retracted."
165
+ : receipt?.status === "corrected"
166
+ ? `Arrival corrected to ${basketName.toLocaleLowerCase()} ${receipt.command.observedBasketId}.`
167
+ : "Arrival recorded. Waiting for updated status.";
168
+ return (_jsx(Paper, { component: "section", elevation: 2, sx: { margin: 1, padding: 2 }, children: _jsxs(Stack, { spacing: 2, children: [_jsxs(Box, { children: [_jsx(Typography, { variant: "overline", children: receipt === undefined ? "Basket arrival" : "Basket arrival receipt" }), _jsx(Typography, { variant: "h4", children: receipt?.status === "retracted"
169
+ ? "Retracted basket arrival"
170
+ : receipt === undefined
171
+ ? `Bring ${basketName.toLocaleLowerCase()} ${expectedBasketId} to this station`
172
+ : `${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
+ ? "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] }) }));
175
+ }
@@ -1,11 +1,14 @@
1
1
  import { ReactNode } from "react";
2
2
  import { SubmitBasketLoadStationCommand } from "./BasketLoadStationWork.js";
3
+ import { SubmitBasketLocationCorrection, SubmitBasketMovementCompletion } from "./BasketMovementArrival.js";
3
4
  interface LoadStationProps {
4
5
  readonly loadNum: number;
5
6
  readonly queues: ReadonlyArray<string>;
6
7
  readonly completed: boolean;
7
8
  readonly whiteBackground?: boolean;
8
9
  readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
10
+ readonly submitBasketMovementCompletion?: SubmitBasketMovementCompletion;
11
+ readonly submitBasketLocationCorrection?: SubmitBasketLocationCorrection;
9
12
  }
10
13
  export declare function LoadStation(props: LoadStationProps): import("react").JSX.Element;
11
14
  declare function LoadStationCheckWidth(props: LoadStationProps): ReactNode;
@@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
31
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  */
34
- import { useMemo, memo, useState, useCallback } from "react";
34
+ import { useMemo, memo, useState, useCallback, useEffect } from "react";
35
35
  import { Box, useMediaQuery, Button, Typography } from "@mui/material";
36
36
  import { LazySeq, mkCompareByProperties, OrderedMap } from "@seedtactics/immutable-collections";
37
37
  import { FolderOpen as FolderOpenIcon } from "@mui/icons-material";
@@ -64,6 +64,7 @@ import { hideNonLoadingMaterialOnLoadStation } from "../../data/queue-material.j
64
64
  import { basketDisplayName, loadStationDisplayName } from "../../cell-status/station-cycles.js";
65
65
  import { canAddOrMoveMaterialToQueue } from "../../data/material-operation-policy.js";
66
66
  import { BasketLoadStationWorkflow, } from "./BasketLoadStationWork.js";
67
+ import { BasketMovementArrival, loadStationArrivalInstruction, } from "./BasketMovementArrival.js";
67
68
  function encodeLoadStationRegionIdPart(value) {
68
69
  return encodeURIComponent(value.toString());
69
70
  }
@@ -663,6 +664,20 @@ export function LoadStation(props) {
663
664
  const currentSt = useAtomValue(currentStatus);
664
665
  const hideNonLoading = useAtomValue(hideNonLoadingMaterialOnLoadStation);
665
666
  const data = useMemo(() => selectLoadStationAndQueueProps(props.loadNum, props.queues, currentSt, hideNonLoading), [currentSt, props.loadNum, props.queues, hideNonLoading]);
667
+ const arrivalInstruction = loadStationArrivalInstruction(currentSt.basketMoveInstructions ?? [], props.loadNum);
668
+ const [recentArrivalReceipt, setRecentArrivalReceipt] = useState();
669
+ useEffect(() => {
670
+ if (recentArrivalReceipt !== undefined &&
671
+ (recentArrivalReceipt.stationNumber !== props.loadNum ||
672
+ (arrivalInstruction !== undefined &&
673
+ arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId)))
674
+ setRecentArrivalReceipt(undefined);
675
+ }, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
676
+ const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
677
+ const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
678
+ displayedArrivalInstruction?.instructionId === recentArrivalReceipt?.instruction.instructionId
679
+ ? recentArrivalReceipt
680
+ : undefined;
666
681
  const queueCols = LazySeq.of(data.queues)
667
682
  .sortBy(([q, _]) => q)
668
683
  .map(([q, mats]) => ({
@@ -709,31 +724,53 @@ export function LoadStation(props) {
709
724
  horizontal: fillViewPort,
710
725
  showMatInCompleted: props.completed,
711
726
  });
712
- return (_jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
713
- width: "100%",
714
- display: "grid",
715
- gridTemplate: grid,
716
- minHeight: {
717
- xs: "calc(100vh - 64px - 32px)",
718
- sm: "calc(100vh - 64px - 40px)",
719
- md: "calc(100vh - 64px)",
720
- },
721
- padding: fillViewPort ? undefined : "8px",
722
- }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
723
- gridArea: `mat${idx}`,
724
- padding: "8px",
725
- borderRight: fillViewPort ? "1px solid black" : undefined,
726
- borderBottom: !fillViewPort ? "1px solid black" : undefined,
727
- }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
728
- marginLeft: "15px",
729
- gridArea: `palface${idx}`,
730
- padding: "8px",
731
- borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
732
- }, children: _jsx(PalletFace, { data: data, faceNum: faceNum, loadNum: props.loadNum, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
733
- borderLeft: fillViewPort ? "1px solid black" : undefined,
734
- borderTop: !fillViewPort ? "1px solid black" : undefined,
735
- gridArea: "completed",
736
- }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) }));
727
+ return (_jsxs(_Fragment, { children: [displayedArrivalInstruction === undefined ? null : (_jsx(BasketMovementArrival, { basketName: basketName, instruction: displayedArrivalInstruction, stationNumber: props.loadNum, submitCommand: props.submitBasketMovementCompletion, submitCorrection: props.submitBasketLocationCorrection, receipt: displayedArrivalReceipt, onAccepted: setRecentArrivalReceipt, onCorrected: (command) => setRecentArrivalReceipt((current) => {
728
+ if (current === undefined ||
729
+ current.stationNumber !== props.loadNum ||
730
+ current.receipt.observationId !== command.targetObservationId)
731
+ return current;
732
+ return command.replacementBasketId === null ||
733
+ command.replacementObservationId === null
734
+ ? { ...current, status: "retracted" }
735
+ : {
736
+ ...current,
737
+ receipt: {
738
+ ...current.receipt,
739
+ observationId: command.replacementObservationId,
740
+ observedBasketId: command.replacementBasketId,
741
+ },
742
+ command: {
743
+ ...current.command,
744
+ commandId: command.replacementObservationId,
745
+ observedBasketId: command.replacementBasketId,
746
+ },
747
+ status: "corrected",
748
+ };
749
+ }) })), _jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
750
+ width: "100%",
751
+ display: "grid",
752
+ gridTemplate: grid,
753
+ minHeight: {
754
+ xs: "calc(100vh - 64px - 32px)",
755
+ sm: "calc(100vh - 64px - 40px)",
756
+ md: "calc(100vh - 64px)",
757
+ },
758
+ padding: fillViewPort ? undefined : "8px",
759
+ }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
760
+ gridArea: `mat${idx}`,
761
+ padding: "8px",
762
+ borderRight: fillViewPort ? "1px solid black" : undefined,
763
+ borderBottom: !fillViewPort ? "1px solid black" : undefined,
764
+ }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
765
+ marginLeft: "15px",
766
+ gridArea: `palface${idx}`,
767
+ padding: "8px",
768
+ borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
769
+ }, children: _jsx(PalletFace, { data: data, faceNum: faceNum, loadNum: props.loadNum, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
770
+ borderLeft: fillViewPort ? "1px solid black" : undefined,
771
+ borderTop: !fillViewPort ? "1px solid black" : undefined,
772
+ gridArea: "completed",
773
+ }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) })] }));
737
774
  }
738
775
  function LoadStationCheckWidth(props) {
739
776
  const fmsInfo = useAtomValue(fmsInformation);
@@ -9,3 +9,4 @@ export { defaultChooseModes } from "./components/ChooseMode.js";
9
9
  export type { ChooseModeItem } from "./components/ChooseMode.js";
10
10
  export { RouteLocation } from "./components/routes.js";
11
11
  export { authenticatedFetch } from "./network/backend.js";
12
+ export type { BasketMovementCompletionCommand, BasketMovementCompletionReceipt, BasketLocationCorrectionCommand, SubmitBasketLocationCorrection, SubmitBasketMovementCompletion, } from "./components/station-monitor/BasketMovementArrival.js";
package/dist/index.html CHANGED
@@ -43,7 +43,7 @@
43
43
  }
44
44
  }
45
45
  </style>
46
- <script type="module" crossorigin src="/assets/index-gLnatT5a.js"></script>
46
+ <script type="module" crossorigin src="/assets/index-CUu2qHWT.js"></script>
47
47
  <link rel="stylesheet" crossorigin href="/assets/index-DXfAwuj-.css">
48
48
  </head>
49
49
  <body>
@@ -223,8 +223,8 @@ export declare class LogEntry implements ILogEntry {
223
223
  loc: string;
224
224
  locnum: number;
225
225
  pal: number;
226
- containerId?: string | undefined;
227
- containerIds?: string[] | undefined;
226
+ basketContentEpisodeId?: string | undefined;
227
+ basketCycleEndContentEpisodeIds?: string[] | undefined;
228
228
  program: string;
229
229
  result: string;
230
230
  elapsed: string;
@@ -247,8 +247,8 @@ export interface ILogEntry {
247
247
  loc: string;
248
248
  locnum: number;
249
249
  pal: number;
250
- containerId?: string | undefined;
251
- containerIds?: string[] | undefined;
250
+ basketContentEpisodeId?: string | undefined;
251
+ basketCycleEndContentEpisodeIds?: string[] | undefined;
252
252
  program: string;
253
253
  result: string;
254
254
  elapsed: string;
@@ -308,8 +308,14 @@ export declare enum LogType {
308
308
  BasketLoadUnload = "BasketLoadUnload",
309
309
  BasketCycle = "BasketCycle",
310
310
  BasketInLocation = "BasketInLocation",
311
- BasketIdentityHint = "BasketIdentityHint",
312
- BasketContentSnapshot = "BasketContentSnapshot"
311
+ BasketIdentityAssociation = "BasketIdentityAssociation",
312
+ BasketContentSnapshot = "BasketContentSnapshot",
313
+ BasketLocationObservation = "BasketLocationObservation",
314
+ BasketLocationObservationCorrection = "BasketLocationObservationCorrection",
315
+ BasketIdentityAssociationCorrection = "BasketIdentityAssociationCorrection",
316
+ BasketRegionSurvey = "BasketRegionSurvey",
317
+ BasketMisload = "BasketMisload",
318
+ BasketMisloadResolution = "BasketMisloadResolution"
313
319
  }
314
320
  export declare class ToolUse implements IToolUse {
315
321
  tool: string;
@@ -1105,7 +1111,7 @@ export declare enum BasketLocationEnum {
1105
1111
  }
1106
1112
  export declare class BasketMoveInstruction implements IBasketMoveInstruction {
1107
1113
  instructionId: string;
1108
- basketId: number;
1114
+ basketId?: number | undefined;
1109
1115
  source: BasketPosition;
1110
1116
  destination: BasketPosition;
1111
1117
  reason: BasketMoveReason;
@@ -1118,7 +1124,7 @@ export declare class BasketMoveInstruction implements IBasketMoveInstruction {
1118
1124
  }
1119
1125
  export interface IBasketMoveInstruction {
1120
1126
  instructionId: string;
1121
- basketId: number;
1127
+ basketId?: number | undefined;
1122
1128
  source: BasketPosition;
1123
1129
  destination: BasketPosition;
1124
1130
  reason: BasketMoveReason;