@seedtactics/insight-client 16.8.4-beta.0 → 16.8.4-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/assets/index-DXfAwuj-.css +1 -0
  2. package/dist/assets/index-Y5yNHUvc.js +388 -0
  3. package/dist/assets/roboto-cyrillic-300-normal-D0x1uCa7.woff +0 -0
  4. package/dist/assets/roboto-cyrillic-400-normal-BepgbcUK.woff +0 -0
  5. package/dist/assets/roboto-cyrillic-500-normal-C8ZOEKN4.woff +0 -0
  6. package/dist/assets/roboto-cyrillic-700-normal-D8g9KsRf.woff +0 -0
  7. package/dist/assets/roboto-cyrillic-ext-300-normal-C0UYUNTV.woff +0 -0
  8. package/dist/assets/roboto-cyrillic-ext-400-normal-DhDk0Xwi.woff +0 -0
  9. package/dist/assets/roboto-cyrillic-ext-500-normal-SCgicl0l.woff +0 -0
  10. package/dist/assets/roboto-cyrillic-ext-700-normal-BWOtdzfy.woff +0 -0
  11. package/dist/assets/roboto-greek-300-normal-xS8CB6LN.woff +0 -0
  12. package/dist/assets/roboto-greek-400-normal-D904r64h.woff +0 -0
  13. package/dist/assets/roboto-greek-500-normal-XBIQOowA.woff +0 -0
  14. package/dist/assets/roboto-greek-700-normal-xQsEWSIA.woff +0 -0
  15. package/dist/assets/roboto-latin-300-normal-Dqn3NW0Y.woff +0 -0
  16. package/dist/assets/roboto-latin-400-normal-D27F5sTu.woff +0 -0
  17. package/dist/assets/roboto-latin-500-normal-fsTFPL3E.woff +0 -0
  18. package/dist/assets/roboto-latin-700-normal-YuyVweIx.woff +0 -0
  19. package/dist/assets/roboto-latin-ext-300-normal-B0-sTZUp.woff +0 -0
  20. package/dist/assets/roboto-latin-ext-400-normal-CIveymTr.woff +0 -0
  21. package/dist/assets/roboto-latin-ext-500-normal-C1cK9xmS.woff +0 -0
  22. package/dist/assets/roboto-latin-ext-700-normal-BDePAh3g.woff +0 -0
  23. package/dist/assets/roboto-math-300-normal-CkwMvXpj.woff +0 -0
  24. package/dist/assets/roboto-math-400-normal-D6sS4O5l.woff +0 -0
  25. package/dist/assets/roboto-math-500-normal-BNGXE_xU.woff +0 -0
  26. package/dist/assets/roboto-math-700-normal-DbhUef31.woff +0 -0
  27. package/dist/assets/roboto-symbols-300-normal-CBIeSvs3.woff +0 -0
  28. package/dist/assets/roboto-symbols-400-normal-C7tGlxgb.woff +0 -0
  29. package/dist/assets/roboto-symbols-500-normal-DFnofPUt.woff +0 -0
  30. package/dist/assets/roboto-symbols-700-normal-C59U6HqI.woff +0 -0
  31. package/dist/assets/roboto-vietnamese-300-normal-CYjuQheQ.woff +0 -0
  32. package/dist/assets/roboto-vietnamese-400-normal-Bf76hAzZ.woff +0 -0
  33. package/dist/assets/roboto-vietnamese-500-normal-BgoYVz-9.woff +0 -0
  34. package/dist/assets/roboto-vietnamese-700-normal-kpkdMgbf.woff +0 -0
  35. package/dist/bootstrap.d.ts +5 -0
  36. package/dist/bootstrap.js +56 -0
  37. package/dist/cell-status/custom-state.d.ts +9 -0
  38. package/dist/cell-status/custom-state.js +42 -0
  39. package/dist/cell-status/loading.js +5 -0
  40. package/dist/components/App.d.ts +2 -0
  41. package/dist/components/App.js +1 -1
  42. package/dist/components/station-monitor/BasketLoadStationWork.d.ts +15 -0
  43. package/dist/components/station-monitor/BasketLoadStationWork.js +147 -0
  44. package/dist/components/station-monitor/LoadStation.d.ts +2 -0
  45. package/dist/components/station-monitor/LoadStation.js +26 -17
  46. package/dist/components/station-monitor/Material.js +22 -3
  47. package/dist/components/station-monitor/MoveMaterialArrows.js +1 -1
  48. package/dist/components/station-monitor/QuarantineButton.js +5 -2
  49. package/dist/components/station-monitor/QueuesAddMaterial.js +2 -1
  50. package/dist/components/station-monitor/SystemOverview.js +7 -3
  51. package/dist/data/all-material-bins.js +10 -0
  52. package/dist/data/current-cycles.js +22 -20
  53. package/dist/data/move-arrows.d.ts +1 -0
  54. package/dist/data/move-arrows.js +18 -2
  55. package/dist/data/queue-material.js +2 -0
  56. package/dist/index.html +2 -2
  57. package/dist/index.js +7 -15
  58. package/dist/network/api.d.ts +10 -1
  59. package/dist/network/api.js +9 -0
  60. package/dist/network/backend.d.ts +2 -0
  61. package/dist/network/backend.js +14 -9
  62. package/package.json +8 -7
  63. package/dist/assets/index-4ivKxo2f.js +0 -388
  64. package/dist/assets/index-CbqW4-gR.css +0 -1
  65. package/dist/assets/roboto-cyrillic-300-normal-DEFNdjk5.woff +0 -0
  66. package/dist/assets/roboto-cyrillic-400-normal-Bjg-1-sg.woff +0 -0
  67. package/dist/assets/roboto-cyrillic-500-normal-CBKMylY4.woff +0 -0
  68. package/dist/assets/roboto-cyrillic-700-normal-DhZFXDSN.woff +0 -0
  69. package/dist/assets/roboto-cyrillic-ext-300-normal-D7ank4TF.woff +0 -0
  70. package/dist/assets/roboto-cyrillic-ext-400-normal-CaK1767H.woff +0 -0
  71. package/dist/assets/roboto-cyrillic-ext-500-normal-DqF2hftb.woff +0 -0
  72. package/dist/assets/roboto-cyrillic-ext-700-normal-CI7FH63F.woff +0 -0
  73. package/dist/assets/roboto-greek-300-normal-C_Dgaih9.woff +0 -0
  74. package/dist/assets/roboto-greek-400-normal-Bb5mj_fZ.woff +0 -0
  75. package/dist/assets/roboto-greek-500-normal-oCqhoyfc.woff +0 -0
  76. package/dist/assets/roboto-greek-700-normal-DjRqqLBV.woff +0 -0
  77. package/dist/assets/roboto-latin-300-normal-BARJ-h6h.woff +0 -0
  78. package/dist/assets/roboto-latin-400-normal-DyYNIH4P.woff +0 -0
  79. package/dist/assets/roboto-latin-500-normal-DQZyH_nt.woff +0 -0
  80. package/dist/assets/roboto-latin-700-normal-DLgJJpmK.woff +0 -0
  81. package/dist/assets/roboto-latin-ext-300-normal-CTCCHkZF.woff +0 -0
  82. package/dist/assets/roboto-latin-ext-400-normal-scX0fKtV.woff +0 -0
  83. package/dist/assets/roboto-latin-ext-500-normal-Cyc0AKLz.woff +0 -0
  84. package/dist/assets/roboto-latin-ext-700-normal-BUhwtWwy.woff +0 -0
  85. package/dist/assets/roboto-math-300-normal-Ds0YpBw2.woff +0 -0
  86. package/dist/assets/roboto-math-400-normal-C9RxBKAh.woff +0 -0
  87. package/dist/assets/roboto-math-500-normal-C-7mKPO3.woff +0 -0
  88. package/dist/assets/roboto-math-700-normal-DVoD5t2k.woff +0 -0
  89. package/dist/assets/roboto-symbols-300-normal-BCnjhQd_.woff +0 -0
  90. package/dist/assets/roboto-symbols-400-normal-DLYbZahX.woff +0 -0
  91. package/dist/assets/roboto-symbols-500-normal-F7c8nfcH.woff +0 -0
  92. package/dist/assets/roboto-symbols-700-normal-BoS6HWkc.woff +0 -0
  93. package/dist/assets/roboto-vietnamese-300-normal-INUupD3o.woff +0 -0
  94. package/dist/assets/roboto-vietnamese-400-normal-DnpnVwnf.woff +0 -0
  95. package/dist/assets/roboto-vietnamese-500-normal-D380IkQ8.woff +0 -0
  96. package/dist/assets/roboto-vietnamese-700-normal-DsFyXAL4.woff +0 -0
@@ -0,0 +1,5 @@
1
+ import type { AppProps } from "./components/App.js";
2
+ /**
3
+ * Starts one complete FMS Insight application with optional statically composed custom pages.
4
+ */
5
+ export declare function bootstrapInsight(appProps?: AppProps, rootElement?: HTMLElement): Promise<void>;
@@ -0,0 +1,56 @@
1
+ /* Copyright (c) 2026, John Lenz
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above
12
+ copyright notice, this list of conditions and the following
13
+ disclaimer in the documentation and/or other materials provided
14
+ with the distribution.
15
+
16
+ * Neither the name of John Lenz, Black Maple Software, SeedTactics,
17
+ nor the names of other contributors may be used to endorse or
18
+ promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ */
33
+ import { createStore } from "jotai";
34
+ import { registerNetworkBackend } from "./network/backend.js";
35
+ import { fmsInformation, loadInfo } from "./network/server-settings.js";
36
+ import { render } from "./renderer.js";
37
+ /**
38
+ * Starts one complete FMS Insight application with optional statically composed custom pages.
39
+ */
40
+ export async function bootstrapInsight(appProps = {}, rootElement) {
41
+ const root = rootElement ?? document.getElementById("root");
42
+ if (!root) {
43
+ throw new Error("Unable to start FMS Insight: no root element was provided or found");
44
+ }
45
+ registerNetworkBackend();
46
+ const store = createStore();
47
+ store.set(fmsInformation, await loadInfo());
48
+ render(appProps, root, store);
49
+ if ("serviceWorker" in navigator) {
50
+ void navigator.serviceWorker.getRegistrations().then((registrations) => {
51
+ for (const registration of registrations) {
52
+ void registration.unregister();
53
+ }
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,9 @@
1
+ import { Atom } from "jotai";
2
+ /**
3
+ * The latest complete application-specific snapshot received from the Insight server.
4
+ * Consumers must validate this opaque value before interpreting it.
5
+ */
6
+ export declare const customState: Atom<unknown>;
7
+ export declare const replaceCustomState: import("jotai").WritableAtom<null, [state: unknown], void> & {
8
+ init: null;
9
+ };
@@ -0,0 +1,42 @@
1
+ /* Copyright (c) 2026, John Lenz
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above
12
+ copyright notice, this list of conditions and the following
13
+ disclaimer in the documentation and/or other materials provided
14
+ with the distribution.
15
+
16
+ * Neither the name of John Lenz, Black Maple Software, SeedTactics,
17
+ nor the names of other contributors may be used to endorse or
18
+ promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ */
33
+ import { atom } from "jotai";
34
+ const customStateRW = atom(null);
35
+ /**
36
+ * The latest complete application-specific snapshot received from the Insight server.
37
+ * Consumers must validate this opaque value before interpreting it.
38
+ */
39
+ export const customState = customStateRW;
40
+ export const replaceCustomState = atom(null, (_, set, state) => {
41
+ set(customStateRW, state);
42
+ });
@@ -36,6 +36,7 @@ import * as simUse from "./sim-station-use.js";
36
36
  import * as schJobs from "./scheduled-jobs.js";
37
37
  import * as buffers from "./buffers.js";
38
38
  import * as currentSt from "./current-status.js";
39
+ import * as customSt from "./custom-state.js";
39
40
  import * as insp from "./inspections.js";
40
41
  import * as mats from "./material-summary.js";
41
42
  import * as names from "./names.js";
@@ -51,6 +52,9 @@ import { atom } from "jotai";
51
52
  const lastEventCounterRW = atom(null);
52
53
  export const lastEventCounter = lastEventCounterRW;
53
54
  export const onServerEvent = atom(null, (_, set, evt) => {
55
+ if (evt.evt.newCurrentStatus) {
56
+ set(customSt.replaceCustomState, evt.evt.newCurrentStatus.customState ?? null);
57
+ }
54
58
  set(simProd.updateLast30JobProduction, evt);
55
59
  set(simUse.updateLast30SimStatUse, evt);
56
60
  set(schJobs.updateLast30Jobs, evt);
@@ -97,6 +101,7 @@ export const onLoadLast30Log = atom(null, (_, set, log) => {
97
101
  });
98
102
  export const onLoadCurrentSt = atom(null, (_, set, curSt) => {
99
103
  set(currentSt.setCurrentStatus, curSt);
104
+ set(customSt.replaceCustomState, curSt.customState ?? null);
100
105
  set(names.setNamesFromCurrentStatus, curSt);
101
106
  });
102
107
  export const onLoadSpecificMonthJobs = atom(null, (_, set, historicData) => {
@@ -1,6 +1,7 @@
1
1
  import { ReactNode, ComponentType } from "react";
2
2
  import { ChooseModeItem } from "./ChooseMode.js";
3
3
  import * as serverSettings from "../network/server-settings.js";
4
+ import type { SubmitBasketLoadStationCommand } from "./station-monitor/BasketLoadStationWork.js";
4
5
  export declare function NavTabs({ children }: {
5
6
  children?: ReactNode;
6
7
  }): import("react").JSX.Element;
@@ -10,6 +11,7 @@ export interface AppProps {
10
11
  readonly page: ReactNode;
11
12
  };
12
13
  readonly chooseModes?: (i: serverSettings.FMSInfoAndUser) => ReadonlyArray<ChooseModeItem> | null;
14
+ readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
13
15
  }
14
16
  declare const App: import("react").MemoExoticComponent<(props: AppProps) => import("react").JSX.Element>;
15
17
  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 }));
313
+ page = (_jsx(LoadStation, { loadNum: route.loadNum, queues: route.queues, completed: route.completed, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }));
314
314
  nav1 = StationToolbar;
315
315
  nav2 = StationToolbarOverviewButton;
316
316
  showOperator = true;
@@ -0,0 +1,15 @@
1
+ import * as api from "../../network/api.js";
2
+ import { MatCardFontSize } from "./Material.js";
3
+ export interface BasketLoadStationCommand {
4
+ readonly workId: string;
5
+ }
6
+ export type SubmitBasketLoadStationCommand = (stationNumber: number, command: BasketLoadStationCommand) => Promise<"accepted" | "conflict">;
7
+ interface BasketLoadStationWorkflowProps {
8
+ readonly stationNumber: number;
9
+ readonly basket: Readonly<api.IBasketStatus>;
10
+ readonly material: ReadonlyArray<Readonly<api.IInProcessMaterial>>;
11
+ readonly fsize: MatCardFontSize;
12
+ readonly submitCommand: SubmitBasketLoadStationCommand | undefined;
13
+ }
14
+ export declare function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }: BasketLoadStationWorkflowProps): import("react").JSX.Element;
15
+ export {};
@@ -0,0 +1,147 @@
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, useMemo, useState } from "react";
32
+ import { Alert, Box, Button, Card, CardContent, Stack, Typography } from "@mui/material";
33
+ import { LazySeq } from "@seedtactics/immutable-collections";
34
+ import * as api from "../../network/api.js";
35
+ import { InProcMaterial } from "./Material.js";
36
+ import { MoveMaterialArrowNode } from "./MoveMaterialArrows.js";
37
+ import { MoveMaterialNodeKindType } from "../../data/move-arrows.js";
38
+ function isNonNegativeInteger(value) {
39
+ return Number.isInteger(value) && (value ?? -1) >= 0;
40
+ }
41
+ function isNonBlank(value) {
42
+ return typeof value === "string" && value.trim() !== "";
43
+ }
44
+ function actionPhase(mat, basketId) {
45
+ if (mat.location.type === api.LocType.InBasket &&
46
+ mat.location.basketId === basketId &&
47
+ (mat.action.type === api.ActionType.UnloadToInProcess ||
48
+ mat.action.type === api.ActionType.UnloadToCompletedMaterial)) {
49
+ if (!isNonNegativeInteger(mat.location.basketSlot)) {
50
+ return "invalid";
51
+ }
52
+ return "unload";
53
+ }
54
+ if (mat.action.type === api.ActionType.LoadingToBasket) {
55
+ if (!isNonNegativeInteger(mat.action.loadToBasketId))
56
+ return "invalid";
57
+ if (mat.action.loadToBasketId !== basketId)
58
+ return undefined;
59
+ if (!isNonNegativeInteger(mat.action.loadToBasketSlot) ||
60
+ (mat.location.type !== api.LocType.Free &&
61
+ (mat.location.type !== api.LocType.InQueue || !isNonBlank(mat.location.currentQueue)))) {
62
+ return "invalid";
63
+ }
64
+ return "load";
65
+ }
66
+ return undefined;
67
+ }
68
+ function confirmableWork(material, basketId) {
69
+ const classifiedActions = LazySeq.of(material)
70
+ .collect((mat) => {
71
+ const phase = actionPhase(mat, basketId);
72
+ return phase === undefined ? undefined : { phase, workId: mat.action.workId };
73
+ })
74
+ .toRArray();
75
+ if (classifiedActions.some(({ phase }) => phase === "invalid")) {
76
+ return { type: "inconsistent" };
77
+ }
78
+ const phaseActions = classifiedActions.filter((action) => action.phase !== "invalid");
79
+ if (phaseActions.length === 0)
80
+ return { type: "none" };
81
+ const tagged = phaseActions.filter((action) => isNonBlank(action.workId));
82
+ const workIds = LazySeq.of(tagged).toHashSet(({ workId }) => workId);
83
+ const phases = LazySeq.of(tagged).toHashSet(({ phase }) => phase);
84
+ if (tagged.length !== phaseActions.length || workIds.size !== 1 || phases.size !== 1) {
85
+ return { type: "inconsistent" };
86
+ }
87
+ return {
88
+ type: "confirmable",
89
+ work: { workId: tagged[0].workId, phase: tagged[0].phase },
90
+ };
91
+ }
92
+ function loadingSource(mat) {
93
+ if (mat.location.type === api.LocType.InQueue && mat.location.currentQueue) {
94
+ return mat.location.currentQueue;
95
+ }
96
+ return "raw material";
97
+ }
98
+ function SlotMaterial({ material, fsize, }) {
99
+ return material.map((mat) => (_jsx(MoveMaterialArrowNode, { kind: { type: MoveMaterialNodeKindType.Material, material: mat }, children: _jsx(InProcMaterial, { mat: mat, fsize: fsize }) }, mat.materialID)));
100
+ }
101
+ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }) {
102
+ const [submission, setSubmission] = useState("idle");
103
+ const workState = useMemo(() => confirmableWork(material, basket.basketId), [basket.basketId, material]);
104
+ const work = workState.type === "confirmable" ? workState.work : undefined;
105
+ useEffect(() => setSubmission("idle"), [work?.workId]);
106
+ const materialBySlot = useMemo(() => LazySeq.of(material)
107
+ .filter((mat) => mat.location.type === api.LocType.InBasket &&
108
+ mat.location.basketId === basket.basketId &&
109
+ mat.location.basketSlot !== undefined)
110
+ .groupBy((mat) => mat.location.basketSlot ?? 0)
111
+ .toHashMap((entry) => entry), [basket.basketId, material]);
112
+ const loadsBySlot = useMemo(() => LazySeq.of(material)
113
+ .filter((mat) => mat.action.type === api.ActionType.LoadingToBasket &&
114
+ mat.action.loadToBasketId === basket.basketId &&
115
+ mat.action.loadToBasketSlot !== undefined)
116
+ .groupBy((mat) => mat.action.loadToBasketSlot ?? 0)
117
+ .toHashMap((entry) => entry), [basket.basketId, material]);
118
+ const slots = useMemo(() => LazySeq.of(basket.emptySlots ?? [])
119
+ .concat(basket.unknownSlots ?? [])
120
+ .concat(materialBySlot.keysToLazySeq())
121
+ .concat(loadsBySlot.keysToLazySeq())
122
+ .distinctBy((slot) => slot)
123
+ .toSortedArray((slot) => slot), [basket.emptySlots, basket.unknownSlots, materialBySlot, loadsBySlot]);
124
+ async function submit() {
125
+ if (work === undefined || submitCommand === undefined)
126
+ return;
127
+ setSubmission("submitting");
128
+ try {
129
+ setSubmission(await submitCommand(stationNumber, { workId: work.workId }));
130
+ }
131
+ catch {
132
+ setSubmission("error");
133
+ }
134
+ }
135
+ const submissionDisabled = submission === "submitting" || submission === "accepted" || submission === "conflict";
136
+ const buttonLabel = work?.phase === "unload" ? "Unload Complete" : "Load Complete";
137
+ return (_jsxs(Stack, { spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { variant: "h6", children: "Basket slots" }), _jsx(Box, { sx: {
138
+ display: "grid",
139
+ gridTemplateColumns: "repeat(auto-fit, minmax(15em, 1fr))",
140
+ gap: 2,
141
+ }, children: slots.map((slot) => {
142
+ const slotMaterial = materialBySlot.get(slot) ?? [];
143
+ const loads = loadsBySlot.get(slot) ?? [];
144
+ const isUnknown = basket.unknownSlots?.includes(slot) === true;
145
+ return (_jsx(Card, { "data-testid": `basket-load-station-slot-${slot}`, variant: "outlined", children: _jsxs(CardContent, { children: [_jsxs(Typography, { variant: "h6", children: ["Slot ", slot + 1] }), 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: [_jsxs(Typography, { children: ["Load from ", loadingSource(mat)] }), _jsxs(Typography, { variant: "body2", color: "text.secondary", children: [mat.partName, " \u00B7 Process ", mat.action.processAfterLoad ?? mat.process] })] }, `${mat.jobUnique}-${mat.process}-${index}`)))] }) }, slot));
146
+ }) }), work && submitCommand ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: buttonLabel }) })) : 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, submission === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submission === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submission === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
147
+ }
@@ -1,9 +1,11 @@
1
1
  import { ReactNode } from "react";
2
+ import { SubmitBasketLoadStationCommand } from "./BasketLoadStationWork.js";
2
3
  interface LoadStationProps {
3
4
  readonly loadNum: number;
4
5
  readonly queues: ReadonlyArray<string>;
5
6
  readonly completed: boolean;
6
7
  readonly whiteBackground?: boolean;
8
+ readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
7
9
  }
8
10
  export declare function LoadStation(props: LoadStationProps): import("react").JSX.Element;
9
11
  declare function LoadStationCheckWidth(props: LoadStationProps): ReactNode;
@@ -61,6 +61,7 @@ import { useAtomValue, useSetAtom } from "jotai";
61
61
  import { PrintLabelButton } from "./PrintedLabel.js";
62
62
  import { hideNonLoadingMaterialOnLoadStation } from "../../data/queue-material.js";
63
63
  import { basketDisplayName, loadStationDisplayName } from "../../cell-status/station-cycles.js";
64
+ import { BasketLoadStationWorkflow, } from "./BasketLoadStationWork.js";
64
65
  function encodeLoadStationRegionIdPart(value) {
65
66
  return encodeURIComponent(value.toString());
66
67
  }
@@ -75,9 +76,15 @@ function materialColumnTestId(region) {
75
76
  }
76
77
  }
77
78
  function isLoadingToDisplayedTarget(mat, pallet, activeBasket) {
78
- return (mat.action.type === api.ActionType.Loading &&
79
- ((pallet !== undefined && mat.action.loadOntoPalletNum === pallet.palletNum) ||
80
- (activeBasket !== undefined && mat.action.loadFromBasketId === activeBasket.basketId)));
79
+ switch (mat.action.type) {
80
+ case api.ActionType.Loading:
81
+ return ((pallet !== undefined && mat.action.loadOntoPalletNum === pallet.palletNum) ||
82
+ (activeBasket !== undefined && mat.action.loadFromBasketId === activeBasket.basketId));
83
+ case api.ActionType.LoadingToBasket:
84
+ return activeBasket !== undefined && mat.action.loadToBasketId === activeBasket.basketId;
85
+ default:
86
+ return false;
87
+ }
81
88
  }
82
89
  function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading) {
83
90
  // search for pallet
@@ -148,8 +155,7 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
148
155
  }
149
156
  if (m.location.type === api.LocType.InQueue &&
150
157
  m.location.currentQueue &&
151
- m.action.type === api.ActionType.Loading &&
152
- m.action.loadFromBasketId === activeBasket.basketId) {
158
+ isLoadingToDisplayedTarget(m, undefined, activeBasket)) {
153
159
  queuesToShow.add(m.location.currentQueue);
154
160
  }
155
161
  }
@@ -205,6 +211,11 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
205
211
  if (isLoadingToDisplayedTarget(m, pal, activeBasket) && m.action.elapsedLoadUnloadTime) {
206
212
  elapsedLoadingTime = m.action.elapsedLoadUnloadTime;
207
213
  }
214
+ if (m.action.type === api.ActionType.LoadingToBasket &&
215
+ m.action.loadToBasketId === activeBasket.basketId &&
216
+ m.location.type === api.LocType.Free) {
217
+ freeLoading.push(m);
218
+ }
208
219
  if (m.location.type === api.LocType.InBasket &&
209
220
  m.location.basketId === activeBasket.basketId) {
210
221
  if ((m.action.type === api.ActionType.UnloadToCompletedMaterial ||
@@ -274,6 +285,7 @@ function selectLoadStationAndQueueProps(loadNum, queues, curSt, hideNonLoading)
274
285
  return {
275
286
  pallet: pal,
276
287
  activeBasket,
288
+ allMaterial: curSt.material,
277
289
  face: palFaces,
278
290
  freeLoadingMaterial: freeLoading,
279
291
  queues: queueMat,
@@ -346,7 +358,7 @@ function ElapsedLoadTime({ elapsedLoadTime }) {
346
358
  return null;
347
359
  }
348
360
  }
349
- function PalletFace({ data, faceNum }) {
361
+ function PalletFace({ data, faceNum, loadNum, submitBasketLoadStationCommand, }) {
350
362
  const face = data.face.get(faceNum);
351
363
  const fmsInfo = useAtomValue(fmsInformation);
352
364
  const basketName = basketDisplayName(fmsInfo.basketName);
@@ -358,17 +370,10 @@ function PalletFace({ data, faceNum }) {
358
370
  gridTemplateColumns: "1fr auto",
359
371
  }, children: [_jsxs(Typography, { variant: "h4", children: [basketName, " ", data.activeBasket.basketId] }), _jsx(Box, { sx: {
360
372
  justifySelf: "flex-end",
361
- }, children: _jsx(ElapsedLoadTime, { elapsedLoadTime: data.elapsedLoadingTime }) })] })) : null, _jsx(Box, { sx: {
362
- ml: "4em",
363
- mr: "4em",
364
- }, children: _jsx(MoveMaterialArrowNode, { kind: {
373
+ }, children: _jsx(ElapsedLoadTime, { elapsedLoadTime: data.elapsedLoadingTime }) })] })) : null, _jsx(Box, { sx: { ml: "4em", mr: "4em" }, children: _jsx(MoveMaterialArrowNode, { kind: {
365
374
  type: MoveMaterialNodeKindType.BasketZone,
366
375
  basketId: data.activeBasket.basketId,
367
- }, children: _jsx(Box, { sx: {
368
- display: "flex",
369
- flexWrap: "wrap",
370
- justifyContent: "space-around",
371
- }, children: face.map((m, idx) => (_jsx(MoveMaterialArrowNode, { kind: { type: MoveMaterialNodeKindType.Material, material: m }, children: _jsx(InProcMaterial, { mat: m, fsize: data.fsize }) }, idx))) }) }) })] }));
376
+ }, children: _jsx(BasketLoadStationWorkflow, { stationNumber: loadNum, basket: data.activeBasket, material: data.allMaterial, fsize: data.fsize, submitCommand: submitBasketLoadStationCommand }) }) })] }));
372
377
  }
373
378
  if (!data.pallet)
374
379
  return null;
@@ -395,6 +400,9 @@ function MaterialRegion({ data, mat, }) {
395
400
  return (_jsxs("div", { children: [mat.label !== "" ? _jsx(Typography, { variant: "h4", children: mat.label }) : null, mat.material.mats.map((m, matIdx) => (_jsx(MoveMaterialArrowNode, { kind: {
396
401
  type: MoveMaterialNodeKindType.Material,
397
402
  material: m,
403
+ identifier: m.materialID < 0
404
+ ? `raw-${mat.isFree ? "free" : "queue"}-${mat.label}-${matIdx}`
405
+ : undefined,
398
406
  }, children: mat.isFree || mat.sortable === false ? (_jsx(InProcMaterial, { mat: m, displayActionForSinglePallet: data.pallet ? data.pallet.palletNum : 0, fsize: data.fsize, showRawMaterial: true, showJobComment: true })) : (_jsx(SortableInProcMaterial, { mat: m, displayActionForSinglePallet: data.pallet ? data.pallet.palletNum : 0, shake: m.action.type === api.ActionType.Loading &&
399
407
  m.action.loadOntoPalletNum === data.pallet?.palletNum, fsize: m.action.type === api.ActionType.Loading &&
400
408
  m.action.loadOntoPalletNum === data.pallet?.palletNum
@@ -534,7 +542,8 @@ function AddMatButton({ queues, onClose, toQueue, showAddToQueue, setShowAddToQu
534
542
  const [addExistingMat, addingExistingMat] = matDetails.useAddExistingMaterialToQueue();
535
543
  if (!existingMat ||
536
544
  inProcMat?.location.type === api.LocType.OnPallet ||
537
- inProcMat?.action.type === api.ActionType.Loading) {
545
+ inProcMat?.action.type === api.ActionType.Loading ||
546
+ inProcMat?.action.type === api.ActionType.LoadingToBasket) {
538
547
  return null;
539
548
  }
540
549
  if (queues.length === 0)
@@ -695,7 +704,7 @@ export function LoadStation(props) {
695
704
  gridArea: `palface${idx}`,
696
705
  padding: "8px",
697
706
  borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
698
- }, children: _jsx(PalletFace, { data: data, faceNum: faceNum }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
707
+ }, children: _jsx(PalletFace, { data: data, faceNum: faceNum, loadNum: props.loadNum, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
699
708
  borderLeft: fillViewPort ? "1px solid black" : undefined,
700
709
  borderTop: !fillViewPort ? "1px solid black" : undefined,
701
710
  gridArea: "completed",
@@ -89,11 +89,12 @@ function shakeAnimationIteration(event) {
89
89
  }
90
90
  const MatCardHeader = styled("div", { shouldForwardProp: (prop) => prop !== "fsize" })(({ fsize, theme }) => {
91
91
  if (!fsize)
92
- return { fontSize: "1.25rem" };
92
+ return { fontSize: "1.25rem", fontWeight: "500" };
93
93
  switch (fsize) {
94
94
  case "normal":
95
95
  return {
96
96
  fontSize: "1rem",
97
+ fontWeight: "500",
97
98
  [theme.breakpoints.up("md")]: {
98
99
  fontSize: "1.25rem",
99
100
  },
@@ -104,6 +105,7 @@ const MatCardHeader = styled("div", { shouldForwardProp: (prop) => prop !== "fsi
104
105
  case "large":
105
106
  return {
106
107
  fontSize: "1.5rem",
108
+ fontWeight: "500",
107
109
  [theme.breakpoints.up("lg")]: {
108
110
  fontSize: "1.75rem",
109
111
  },
@@ -114,6 +116,7 @@ const MatCardHeader = styled("div", { shouldForwardProp: (prop) => prop !== "fsi
114
116
  case "x-large":
115
117
  return {
116
118
  fontSize: "1.5rem",
119
+ fontWeight: "500",
117
120
  [theme.breakpoints.up("md")]: {
118
121
  fontSize: "1.75rem",
119
122
  },
@@ -128,11 +131,12 @@ const MatCardHeader = styled("div", { shouldForwardProp: (prop) => prop !== "fsi
128
131
  });
129
132
  const MatCardDetail = styled("div", { shouldForwardProp: (prop) => prop !== "fsize" })(({ fsize, theme }) => {
130
133
  if (!fsize)
131
- return { fontSize: "0.75rem" };
134
+ return { fontSize: "0.75rem", fontWeight: "500" };
132
135
  switch (fsize) {
133
136
  case "normal":
134
137
  return {
135
138
  fontSize: "0.75rem",
139
+ fontWeight: "500",
136
140
  [theme.breakpoints.up("md")]: {
137
141
  fontSize: "1rem",
138
142
  },
@@ -140,6 +144,7 @@ const MatCardDetail = styled("div", { shouldForwardProp: (prop) => prop !== "fsi
140
144
  case "large":
141
145
  return {
142
146
  fontSize: "1rem",
147
+ fontWeight: "500",
143
148
  [theme.breakpoints.up("lg")]: {
144
149
  fontSize: "1.5rem",
145
150
  },
@@ -150,6 +155,7 @@ const MatCardDetail = styled("div", { shouldForwardProp: (prop) => prop !== "fsi
150
155
  case "x-large":
151
156
  return {
152
157
  fontSize: "1rem",
158
+ fontWeight: "500",
153
159
  [theme.breakpoints.up("md")]: {
154
160
  fontSize: "1.5rem",
155
161
  },
@@ -228,6 +234,10 @@ export function MaterialAction({ mat, displayActionForSinglePallet, fsize, }) {
228
234
  }
229
235
  }
230
236
  }
237
+ case api.ActionType.LoadingToBasket:
238
+ return (_jsxs(MatCardDetail, { fsize: fsize, children: ["Load into ", basketName, " ", mat.action.loadToBasketId ?? "", mat.action.loadToBasketSlot !== undefined
239
+ ? ` slot ${mat.action.loadToBasketSlot + 1}`
240
+ : ""] }));
231
241
  case api.ActionType.UnloadToInProcess:
232
242
  case api.ActionType.UnloadToCompletedMaterial:
233
243
  if (mat.action.unloadToBasketId) {
@@ -238,6 +248,13 @@ export function MaterialAction({ mat, displayActionForSinglePallet, fsize, }) {
238
248
  else if (mat.action.unloadIntoQueue) {
239
249
  return (_jsxs(MatCardDetail, { fsize: fsize, children: ["Unload into queue ", mat.action.unloadIntoQueue] }));
240
250
  }
251
+ else if (mat.action.type === api.ActionType.UnloadToCompletedMaterial &&
252
+ mat.location.type === api.LocType.InBasket) {
253
+ return (_jsxs(MatCardDetail, { fsize: fsize, children: ["Unload from ", basketName, " ", mat.location.basketId, " to completed material"] }));
254
+ }
255
+ else if (mat.location.type === api.LocType.InBasket) {
256
+ return (_jsxs(MatCardDetail, { fsize: fsize, children: ["Unload from ", basketName, " ", mat.location.basketId] }));
257
+ }
241
258
  else {
242
259
  return _jsx(MatCardDetail, { fsize: fsize, children: "Unload from pallet" });
243
260
  }
@@ -282,7 +299,9 @@ function Warning({ mat }) {
282
299
  function JobRawMaterial({ fsize, mat, }) {
283
300
  const job = useAtomValue(currentStatus).jobs[mat.jobUnique];
284
301
  let path = mat.path;
285
- if (mat.action.type === api.ActionType.Loading && mat.action.pathAfterLoad) {
302
+ if ((mat.action.type === api.ActionType.Loading ||
303
+ mat.action.type === api.ActionType.LoadingToBasket) &&
304
+ mat.action.pathAfterLoad) {
286
305
  path = mat.action.pathAfterLoad;
287
306
  }
288
307
  if (!job) {