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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +21 -0
  2. package/dist/assets/index-CQhVwNX7.js +388 -0
  3. package/dist/cell-status/material-details.d.ts +15 -5
  4. package/dist/cell-status/material-details.js +58 -37
  5. package/dist/components/LogEntry.js +2 -9
  6. package/dist/components/log-entry-container-name.d.ts +2 -0
  7. package/dist/components/log-entry-container-name.js +10 -0
  8. package/dist/components/operations/AllMaterial.js +4 -3
  9. package/dist/components/operations/Programs.js +1 -1
  10. package/dist/components/station-monitor/BasketLoadStationWork.js +44 -36
  11. package/dist/components/station-monitor/BulkRawMaterial.js +4 -4
  12. package/dist/components/station-monitor/CancelLoadButton.d.ts +4 -0
  13. package/dist/components/station-monitor/CancelLoadButton.js +59 -0
  14. package/dist/components/station-monitor/Closeout.js +2 -1
  15. package/dist/components/station-monitor/Inspection.js +2 -1
  16. package/dist/components/station-monitor/InvalidateCycle.d.ts +4 -1
  17. package/dist/components/station-monitor/InvalidateCycle.js +61 -10
  18. package/dist/components/station-monitor/LoadStation.js +23 -11
  19. package/dist/components/station-monitor/Material.js +2 -4
  20. package/dist/components/station-monitor/QuarantineButton.js +80 -60
  21. package/dist/components/station-monitor/Queues.js +2 -1
  22. package/dist/components/station-monitor/QueuesAddMaterial.js +59 -48
  23. package/dist/components/station-monitor/SystemOverview.js +3 -2
  24. package/dist/components/station-monitor/Whiteboard.js +2 -2
  25. package/dist/extension.d.ts +11 -0
  26. package/dist/extension.js +21 -0
  27. package/dist/index.html +1 -2
  28. package/dist/index.js +1 -1
  29. package/dist/network/api.d.ts +18 -4
  30. package/dist/network/api.js +69 -2
  31. package/dist/network/backend-mock.js +3 -0
  32. package/dist/network/backend.d.ts +1 -0
  33. package/dist/network/backend.js +2 -1
  34. package/package.json +12 -11
  35. package/dist/assets/index-Y5yNHUvc.js +0 -388
  36. package/dist/assets/manifest-BzJli0lx.webmanifest +0 -15
  37. package/dist/bootstrap.d.ts +0 -5
  38. package/dist/bootstrap.js +0 -56
  39. package/dist/components/station-monitor/Material.test.d.ts +0 -1
  40. package/dist/components/station-monitor/Material.test.js +0 -199
@@ -49,6 +49,7 @@ export declare const serialInMaterialDialog: Atom<Promise<string | null>>;
49
49
  export declare const serialInMaterialDialogUnwrapped: Atom<string | null>;
50
50
  export declare const serialInMaterialDialogState: Atom<AsyncAtomState<string | null>>;
51
51
  export declare const workorderInMaterialDialog: Atom<Promise<string | null>>;
52
+ export declare const materialInDialogLocalEvents: Atom<readonly Readonly<ILogEntry>[]>;
52
53
  export declare const materialInDialogEvents: Atom<readonly Readonly<ILogEntry>[]>;
53
54
  export type LargestUsedProces = {
54
55
  readonly process: number;
@@ -96,9 +97,12 @@ export interface PrintLabelData {
96
97
  readonly proc: number;
97
98
  }
98
99
  export declare function usePrintLabel(): [(data: PrintLabelData) => void, boolean, boolean];
99
- export declare function useRemoveFromQueue(): [(matId: number, operator: string | null) => void, boolean];
100
+ export declare function useRemoveFromQueue(): [
101
+ (matId: number, operator: string | null) => Promise<void>,
102
+ boolean
103
+ ];
100
104
  export declare function useSignalForQuarantine(): [
101
- (matId: number, operator: string | null, reason: string) => void,
105
+ (matId: number, operator: string | null, reason: string) => Promise<void>,
102
106
  boolean
103
107
  ];
104
108
  export interface AddExistingMaterialToQueueData {
@@ -108,7 +112,7 @@ export interface AddExistingMaterialToQueueData {
108
112
  readonly operator: string | null;
109
113
  }
110
114
  export declare function useAddExistingMaterialToQueue(): [
111
- (d: AddExistingMaterialToQueueData) => void,
115
+ (d: AddExistingMaterialToQueueData) => Promise<void>,
112
116
  boolean
113
117
  ];
114
118
  export interface AddNewMaterialToQueueData {
@@ -122,7 +126,10 @@ export interface AddNewMaterialToQueueData {
122
126
  readonly onNewMaterial?: (mat: Readonly<IInProcessMaterial>) => void;
123
127
  readonly onError?: (reason: unknown) => void;
124
128
  }
125
- export declare function useAddNewMaterialToQueue(): [(d: AddNewMaterialToQueueData) => void, boolean];
129
+ export declare function useAddNewMaterialToQueue(): [
130
+ (d: AddNewMaterialToQueueData) => Promise<Readonly<IInProcessMaterial>>,
131
+ boolean
132
+ ];
126
133
  export interface AddNewCastingToQueueData {
127
134
  readonly casting: string;
128
135
  readonly quantity: number;
@@ -133,4 +140,7 @@ export interface AddNewCastingToQueueData {
133
140
  readonly onNewMaterial?: (mats: ReadonlyArray<Readonly<IInProcessMaterial>>) => void;
134
141
  readonly onError?: (reason: unknown) => void;
135
142
  }
136
- export declare function useAddNewCastingToQueue(): [(d: AddNewCastingToQueueData) => void, boolean];
143
+ export declare function useAddNewCastingToQueue(): [
144
+ (d: AddNewCastingToQueueData) => Promise<ReadonlyArray<Readonly<IInProcessMaterial>>>,
145
+ boolean
146
+ ];
@@ -121,8 +121,9 @@ export const inProcessMaterialInDialog = atom(async (get) => {
121
121
  const toShow = get(matToShow);
122
122
  if (toShow === null)
123
123
  return null;
124
- if (toShow.type === "InProcMat")
125
- return toShow.inproc;
124
+ if (toShow.type === "InProcMat") {
125
+ return status.material.find((m) => m.materialID === toShow.inproc.materialID) ?? null;
126
+ }
126
127
  const matId = (await get(materialInDialogInfo))?.materialID ?? null;
127
128
  return matId !== null && matId >= 0
128
129
  ? (status.material.find((m) => m.materialID === matId) ?? null)
@@ -210,6 +211,9 @@ const otherMatEvents = atom(async (get, { signal }) => {
210
211
  return evts;
211
212
  });
212
213
  const otherMatEventsUnwrapped = unwrap(otherMatEvents, (prev) => prev ?? []);
214
+ export const materialInDialogLocalEvents = atom((get) => {
215
+ return LazySeq.of(get(localMatEventsUnwrapped)).concat(get(extraLogEventsFromUpdates)).toRArray();
216
+ });
213
217
  export const materialInDialogEvents = atom((get) => {
214
218
  const evtsFromUpdate = get(extraLogEventsFromUpdates);
215
219
  return LazySeq.of(get(localMatEventsUnwrapped))
@@ -373,66 +377,83 @@ export function usePrintLabel() {
373
377
  }
374
378
  export function useRemoveFromQueue() {
375
379
  const [updating, setUpdating] = useState(false);
376
- const callback = useCallback((matId, operator) => {
380
+ const callback = useCallback(async (matId, operator) => {
377
381
  setUpdating(true);
378
- JobsBackend.removeMaterialFromAllQueues(matId, operator ?? undefined)
379
- .catch(console.log)
380
- .finally(() => setUpdating(false));
382
+ try {
383
+ await JobsBackend.removeMaterialFromAllQueues(matId, operator ?? undefined);
384
+ }
385
+ finally {
386
+ setUpdating(false);
387
+ }
381
388
  }, []);
382
389
  return [callback, updating];
383
390
  }
384
391
  export function useSignalForQuarantine() {
385
392
  const [updating, setUpdating] = useState(false);
386
- const callback = useCallback((matId, operator, reason) => {
393
+ const callback = useCallback(async (matId, operator, reason) => {
387
394
  setUpdating(true);
388
- JobsBackend.signalMaterialForQuarantine(matId, operator, reason === "" ? undefined : reason)
389
- .catch(console.log)
390
- .finally(() => setUpdating(false));
395
+ try {
396
+ await JobsBackend.signalMaterialForQuarantine(matId, operator, reason === "" ? undefined : reason);
397
+ }
398
+ finally {
399
+ setUpdating(false);
400
+ }
391
401
  }, []);
392
402
  return [callback, updating];
393
403
  }
394
404
  export function useAddExistingMaterialToQueue() {
395
405
  const [updating, setUpdating] = useState(false);
396
- const callback = useCallback((d) => {
406
+ const callback = useCallback(async (d) => {
397
407
  setUpdating(true);
398
- JobsBackend.setMaterialInQueue(d.materialId, d.operator, new QueuePosition({
399
- queue: d.queue,
400
- position: d.queuePosition,
401
- }))
402
- .catch(console.log)
403
- .finally(() => setUpdating(false));
408
+ try {
409
+ await JobsBackend.setMaterialInQueue(d.materialId, d.operator, new QueuePosition({
410
+ queue: d.queue,
411
+ position: d.queuePosition,
412
+ }));
413
+ }
414
+ finally {
415
+ setUpdating(false);
416
+ }
404
417
  }, []);
405
418
  return [callback, updating];
406
419
  }
407
420
  export function useAddNewMaterialToQueue() {
408
421
  const [updating, setUpdating] = useState(false);
409
- const callback = useCallback((d) => {
422
+ const callback = useCallback(async (d) => {
410
423
  setUpdating(true);
411
- JobsBackend.addUnprocessedMaterialToQueue(d.jobUnique, d.lastCompletedProcess, d.queue, d.queuePosition, d.operator, d.workorder, d.serial || "")
412
- .then((m) => {
413
- if (d.onNewMaterial && m) {
414
- d.onNewMaterial(m);
415
- }
416
- else if (d.onError) {
417
- d.onError("No material returned");
418
- }
419
- }, d.onError)
420
- .catch(console.log)
421
- .finally(() => setUpdating(false));
424
+ try {
425
+ const material = await JobsBackend.addUnprocessedMaterialToQueue(d.jobUnique, d.lastCompletedProcess, d.queue, d.queuePosition, d.operator, d.workorder, d.serial || "");
426
+ if (material === undefined)
427
+ throw new Error("No material returned");
428
+ d.onNewMaterial?.(material);
429
+ return material;
430
+ }
431
+ catch (e) {
432
+ d.onError?.(e);
433
+ throw e;
434
+ }
435
+ finally {
436
+ setUpdating(false);
437
+ }
422
438
  }, []);
423
439
  return [callback, updating];
424
440
  }
425
441
  export function useAddNewCastingToQueue() {
426
442
  const [updating, setUpdating] = useState(false);
427
- const callback = useCallback((d) => {
443
+ const callback = useCallback(async (d) => {
428
444
  setUpdating(true);
429
- JobsBackend.addUnallocatedCastingToQueue(d.casting, d.queue, d.quantity, d.operator, d.workorder, [...(d.serials || [])])
430
- .then((ms) => {
431
- if (d.onNewMaterial)
432
- d.onNewMaterial(ms);
433
- }, d.onError)
434
- .catch(console.log)
435
- .finally(() => setUpdating(false));
445
+ try {
446
+ const material = await JobsBackend.addUnallocatedCastingToQueue(d.casting, d.queue, d.quantity, d.operator, d.workorder, [...(d.serials || [])]);
447
+ d.onNewMaterial?.(material);
448
+ return material;
449
+ }
450
+ catch (e) {
451
+ d.onError?.(e);
452
+ throw e;
453
+ }
454
+ finally {
455
+ setUpdating(false);
456
+ }
436
457
  }, []);
437
458
  return [callback, updating];
438
459
  }
@@ -48,7 +48,7 @@ import { useAtomValue } from "jotai";
48
48
  import { fmsInformation } from "../network/server-settings.js";
49
49
  import { basketDisplayName, loadStationDisplayName } from "../cell-status/station-cycles.js";
50
50
  import { filterRemoveAddQueue } from "./log-entry-queue-filter.js";
51
- import { basketContainerName } from "./log-entry-container-name.js";
51
+ import { basketContainerName, basketCycleDescription } from "./log-entry-container-name.js";
52
52
  const ColoredSpan = styled("span", { shouldForwardProp: (prop) => prop.toString()[0] !== "$" })(({ $type }) => {
53
53
  switch ($type) {
54
54
  case "machine":
@@ -191,14 +191,7 @@ function display(props, fmsInfo) {
191
191
  }
192
192
  case api.LogType.BasketCycle: {
193
193
  const basketName = basketDisplayName(fmsInfo.basketName);
194
- if (entry.startofcycle) {
195
- return (_jsxs("span", { children: [basketName, " ", entry.pal, " started cycle"] }));
196
- }
197
- else {
198
- return (_jsxs("span", { children: [basketName, " ", entry.pal, " completed cycle", entry.containerIds && entry.containerIds.length > 0
199
- ? ` from ${entry.containerIds.length} UUID fragment${entry.containerIds.length === 1 ? "" : "s"}`
200
- : ""] }));
201
- }
194
+ return _jsx("span", { children: basketCycleDescription(entry, basketName) });
202
195
  }
203
196
  case api.LogType.BasketInLocation: {
204
197
  const basketName = basketDisplayName(fmsInfo.basketName);
@@ -1,4 +1,6 @@
1
1
  import * as api from "../network/api.js";
2
2
  type ContainerNameEntry = Readonly<Pick<api.ILogEntry, "pal" | "containerId">>;
3
+ type BasketCycleEntry = ContainerNameEntry & Readonly<Pick<api.ILogEntry, "startofcycle" | "containerIds">>;
3
4
  export declare function basketContainerName(entry: ContainerNameEntry, basketName: string): string;
5
+ export declare function basketCycleDescription(entry: BasketCycleEntry, basketName: string): string;
4
6
  export {};
@@ -5,3 +5,13 @@ export function basketContainerName(entry, basketName) {
5
5
  return `${basketName} ${entry.pal}`;
6
6
  return basketName;
7
7
  }
8
+ export function basketCycleDescription(entry, basketName) {
9
+ const container = basketContainerName(entry, basketName);
10
+ if (entry.startofcycle)
11
+ return `${container} started cycle`;
12
+ const fragmentCount = entry.containerIds?.length ?? 0;
13
+ const fragments = fragmentCount > 0
14
+ ? ` from ${fragmentCount} UUID fragment${fragmentCount === 1 ? "" : "s"}`
15
+ : "";
16
+ return `${container} completed cycle${fragments}`;
17
+ }
@@ -43,6 +43,7 @@ import { InvalidateCycleDialogButton, InvalidateCycleDialogContent, SwapMaterial
43
43
  import { horizontalListSortingStrategy, SortableContext, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
44
44
  import { closestCenter, DndContext, DragOverlay, getFirstCollision, MeasuringStrategy, pointerWithin, rectIntersection, } from "@dnd-kit/core";
45
45
  import { QuarantineMatButton } from "../station-monitor/QuarantineButton.js";
46
+ import { CancelLoadButton } from "../station-monitor/CancelLoadButton.js";
46
47
  import { useSetTitle } from "../routes.js";
47
48
  import { useAtom, useAtomValue, useSetAtom } from "jotai";
48
49
  import { fmsInformation } from "../../network/server-settings.js";
@@ -152,7 +153,7 @@ const AllMatDialog = memo(function AllMatDialog() {
152
153
  setSwapSt(null);
153
154
  setInvalidateSt(null);
154
155
  }
155
- return (_jsx(MaterialDialog, { onClose: onClose, allowNote: true, highlightProcsGreaterOrEqualTo: invalidateSt?.process ?? undefined, extraDialogElements: _jsxs(_Fragment, { children: [_jsx(SwapMaterialDialogContent, { st: swapSt, setState: setSwapSt }), invalidateSt !== null ? (_jsx(InvalidateCycleDialogContent, { st: invalidateSt, setState: setInvalidateSt })) : null] }), buttons: _jsxs(_Fragment, { children: [_jsx(QuarantineMatButton, { onClose: onClose, ignoreOperator: true }), _jsx(SwapMaterialButtons, { st: swapSt, setState: setSwapSt, onClose: onClose, ignoreOperator: true }), _jsx(InvalidateCycleDialogButton, { st: invalidateSt, setState: setInvalidateSt, onClose: onClose, ignoreOperator: true })] }) }));
156
+ return (_jsx(MaterialDialog, { onClose: onClose, allowNote: true, highlightProcsGreaterOrEqualTo: invalidateSt?.process ?? undefined, extraDialogElements: _jsxs(_Fragment, { children: [_jsx(SwapMaterialDialogContent, { st: swapSt, setState: setSwapSt }), invalidateSt !== null ? (_jsx(InvalidateCycleDialogContent, { st: invalidateSt, setState: setInvalidateSt })) : null] }), buttons: _jsxs(_Fragment, { children: [_jsx(QuarantineMatButton, { onClose: onClose, ignoreOperator: true }), _jsx(CancelLoadButton, { onClose: onClose, ignoreOperator: true }), _jsx(SwapMaterialButtons, { st: swapSt, setState: setSwapSt, onClose: onClose, ignoreOperator: true }), _jsx(InvalidateCycleDialogButton, { st: invalidateSt, setState: setInvalidateSt, onClose: onClose, ignoreOperator: true })] }) }));
156
157
  });
157
158
  function useCollisionDetection(allBins) {
158
159
  return useCallback((args) => {
@@ -260,12 +261,12 @@ export function AllMaterial(props) {
260
261
  ? findQueueInQuarantineQueues(over.id, allBins)
261
262
  : findMaterialInQuarantineQueues(over.id, allBins);
262
263
  if (overCol) {
263
- addExistingMatToQueue({
264
+ void addExistingMatToQueue({
264
265
  materialId: activeDrag.mat.materialID,
265
266
  queue: overCol.bin.queueName,
266
267
  queuePosition: overCol.idx,
267
268
  operator: null,
268
- });
269
+ }).catch(console.error);
269
270
  reorderQueuedMat({
270
271
  queue: overCol.bin.queueName,
271
272
  matId: activeDrag.mat.materialID,
@@ -249,7 +249,7 @@ export function ProgramSummaryTable() {
249
249
  function ProgramContentCode() {
250
250
  const ct = useAtomValue(programContent);
251
251
  const [highlighted, setHighlighted] = useState(null);
252
- const worker = useMemo(() => new Worker(new URL("./ProgramHighlight.ts", import.meta.url), { type: "module" }), []);
252
+ const worker = useMemo(() => new Worker(new URL("./ProgramHighlight.js", import.meta.url), { type: "module" }), []);
253
253
  useEffect(() => {
254
254
  let set = (h) => setHighlighted(h);
255
255
  const handleMessage = (event) => set(event.data);
@@ -28,81 +28,86 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
28
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
29
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
30
  */
31
- import { useEffect, useMemo, useState } from "react";
31
+ import { useMemo, useState } from "react";
32
32
  import { Alert, Box, Button, Card, CardContent, Stack, Typography } from "@mui/material";
33
33
  import { LazySeq } from "@seedtactics/immutable-collections";
34
34
  import * as api from "../../network/api.js";
35
35
  import { InProcMaterial } from "./Material.js";
36
36
  import { MoveMaterialArrowNode } from "./MoveMaterialArrows.js";
37
37
  import { MoveMaterialNodeKindType } from "../../data/move-arrows.js";
38
- function isNonNegativeInteger(value) {
39
- return Number.isInteger(value) && (value ?? -1) >= 0;
38
+ function isPositiveInteger(value) {
39
+ return Number.isInteger(value) && (value ?? 0) > 0;
40
40
  }
41
41
  function isNonBlank(value) {
42
42
  return typeof value === "string" && value.trim() !== "";
43
43
  }
44
- function actionPhase(mat, basketId) {
44
+ function basketAction(mat, basketId) {
45
45
  if (mat.location.type === api.LocType.InBasket &&
46
46
  mat.location.basketId === basketId &&
47
47
  (mat.action.type === api.ActionType.UnloadToInProcess ||
48
48
  mat.action.type === api.ActionType.UnloadToCompletedMaterial)) {
49
- if (!isNonNegativeInteger(mat.location.basketSlot)) {
50
- return "invalid";
49
+ if (!isPositiveInteger(mat.location.basketSlot)) {
50
+ return { type: "invalid" };
51
51
  }
52
- return "unload";
52
+ return { type: "valid", workId: mat.action.workId };
53
53
  }
54
54
  if (mat.action.type === api.ActionType.LoadingToBasket) {
55
- if (!isNonNegativeInteger(mat.action.loadToBasketId))
56
- return "invalid";
55
+ if (!isPositiveInteger(mat.action.loadToBasketId))
56
+ return { type: "invalid" };
57
57
  if (mat.action.loadToBasketId !== basketId)
58
58
  return undefined;
59
- if (!isNonNegativeInteger(mat.action.loadToBasketSlot) ||
60
- (mat.location.type !== api.LocType.Free &&
59
+ const reloadsSameSlot = mat.location.type === api.LocType.InBasket &&
60
+ mat.location.basketId === basketId &&
61
+ mat.location.basketSlot === mat.action.loadToBasketSlot;
62
+ if (!isPositiveInteger(mat.action.loadToBasketSlot) ||
63
+ (!reloadsSameSlot &&
64
+ mat.location.type !== api.LocType.Free &&
61
65
  (mat.location.type !== api.LocType.InQueue || !isNonBlank(mat.location.currentQueue)))) {
62
- return "invalid";
66
+ return { type: "invalid" };
63
67
  }
64
- return "load";
68
+ return { type: "valid", workId: mat.action.workId };
65
69
  }
66
70
  return undefined;
67
71
  }
68
72
  function confirmableWork(material, basketId) {
69
73
  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
+ .collect((mat) => basketAction(mat, basketId))
74
75
  .toRArray();
75
- if (classifiedActions.some(({ phase }) => phase === "invalid")) {
76
+ if (classifiedActions.some((action) => action.type === "invalid")) {
76
77
  return { type: "inconsistent" };
77
78
  }
78
- const phaseActions = classifiedActions.filter((action) => action.phase !== "invalid");
79
- if (phaseActions.length === 0)
79
+ const validActions = classifiedActions.filter((action) => action.type === "valid");
80
+ if (validActions.length === 0)
80
81
  return { type: "none" };
81
- const tagged = phaseActions.filter((action) => isNonBlank(action.workId));
82
+ const tagged = validActions.filter((action) => isNonBlank(action.workId));
82
83
  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) {
84
+ if (tagged.length !== validActions.length || workIds.size !== 1) {
85
85
  return { type: "inconsistent" };
86
86
  }
87
87
  return {
88
88
  type: "confirmable",
89
- work: { workId: tagged[0].workId, phase: tagged[0].phase },
89
+ work: { workId: tagged[0].workId },
90
90
  };
91
91
  }
92
- function loadingSource(mat) {
92
+ function loadingInstruction(mat) {
93
+ if (mat.location.type === api.LocType.InBasket &&
94
+ mat.location.basketId === mat.action.loadToBasketId &&
95
+ mat.location.basketSlot === mat.action.loadToBasketSlot) {
96
+ return "Unload, re-plate, and reload this slot";
97
+ }
93
98
  if (mat.location.type === api.LocType.InQueue && mat.location.currentQueue) {
94
- return mat.location.currentQueue;
99
+ return `Load from ${mat.location.currentQueue}`;
95
100
  }
96
- return "raw material";
101
+ return "Load from raw material";
97
102
  }
98
103
  function SlotMaterial({ material, fsize, }) {
99
104
  return material.map((mat) => (_jsx(MoveMaterialArrowNode, { kind: { type: MoveMaterialNodeKindType.Material, material: mat }, children: _jsx(InProcMaterial, { mat: mat, fsize: fsize }) }, mat.materialID)));
100
105
  }
101
106
  export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }) {
102
- const [submission, setSubmission] = useState("idle");
107
+ const [submission, setSubmission] = useState();
103
108
  const workState = useMemo(() => confirmableWork(material, basket.basketId), [basket.basketId, material]);
104
109
  const work = workState.type === "confirmable" ? workState.work : undefined;
105
- useEffect(() => setSubmission("idle"), [work?.workId]);
110
+ const submissionState = submission !== undefined && submission.workId === work?.workId ? submission.state : "idle";
106
111
  const materialBySlot = useMemo(() => LazySeq.of(material)
107
112
  .filter((mat) => mat.location.type === api.LocType.InBasket &&
108
113
  mat.location.basketId === basket.basketId &&
@@ -124,16 +129,19 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
124
129
  async function submit() {
125
130
  if (work === undefined || submitCommand === undefined)
126
131
  return;
127
- setSubmission("submitting");
132
+ const submittedWorkId = work.workId;
133
+ setSubmission({ workId: submittedWorkId, state: "submitting" });
128
134
  try {
129
- setSubmission(await submitCommand(stationNumber, { workId: work.workId }));
135
+ const state = await submitCommand(stationNumber, { workId: submittedWorkId });
136
+ setSubmission((current) => current?.workId === submittedWorkId ? { workId: submittedWorkId, state } : current);
130
137
  }
131
138
  catch {
132
- setSubmission("error");
139
+ setSubmission((current) => current?.workId === submittedWorkId ? { workId: submittedWorkId, state: "error" } : current);
133
140
  }
134
141
  }
135
- const submissionDisabled = submission === "submitting" || submission === "accepted" || submission === "conflict";
136
- const buttonLabel = work?.phase === "unload" ? "Unload Complete" : "Load Complete";
142
+ const submissionDisabled = submissionState === "submitting" ||
143
+ submissionState === "accepted" ||
144
+ submissionState === "conflict";
137
145
  return (_jsxs(Stack, { spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { variant: "h6", children: "Basket slots" }), _jsx(Box, { sx: {
138
146
  display: "grid",
139
147
  gridTemplateColumns: "repeat(auto-fit, minmax(15em, 1fr))",
@@ -142,6 +150,6 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
142
150
  const slotMaterial = materialBySlot.get(slot) ?? [];
143
151
  const loads = loadsBySlot.get(slot) ?? [];
144
152
  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] }));
153
+ return (_jsx(Card, { "data-testid": `basket-load-station-slot-${slot}`, variant: "outlined", children: _jsxs(CardContent, { children: [_jsxs(Typography, { variant: "h6", children: ["Slot ", slot] }), slotMaterial.length > 0 ? (_jsx(SlotMaterial, { material: slotMaterial, fsize: fsize })) : (_jsx(Typography, { color: isUnknown ? "warning.main" : "text.secondary", children: isUnknown ? "Unknown" : "Empty" })), loads.map((mat, index) => (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { children: loadingInstruction(mat) }), _jsxs(Typography, { variant: "body2", color: "text.secondary", children: [mat.partName, " \u00B7 Process ", mat.action.processAfterLoad ?? mat.process] })] }, `${mat.jobUnique}-${mat.process}-${index}`)))] }) }, slot));
154
+ }) }), work && submitCommand ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: "Confirm" }) })) : work ? (_jsx(Alert, { severity: "warning", children: "Basket work confirmation is not configured." })) : workState.type === "inconsistent" ? (_jsx(Alert, { severity: "warning", children: "Basket work is inconsistent. Wait for refreshed material actions before confirming." })) : null, submissionState === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submissionState === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submissionState === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
147
155
  }
@@ -122,20 +122,20 @@ export const BulkAddCastingWithoutSerialDialog = memo(function BulkAddCastingWit
122
122
  enteredQty !== null &&
123
123
  !isNaN(enteredQty) &&
124
124
  enteredQty > 0) {
125
- addNewCasting({
125
+ void addNewCasting({
126
126
  casting: selectedCasting,
127
127
  quantity: enteredQty,
128
128
  queue: queue,
129
129
  workorder: null,
130
130
  operator: fmsInfo.requireOperatorNamePromptWhenAddingMaterial ? enteredOperator : operator,
131
- });
131
+ }).catch(console.log);
132
132
  }
133
133
  close();
134
134
  }
135
135
  function addAndPrint() {
136
136
  if (queue !== null && selectedCasting !== null && enteredQty !== null && !isNaN(enteredQty)) {
137
137
  setAdding(true);
138
- addNewCasting({
138
+ void addNewCasting({
139
139
  casting: selectedCasting,
140
140
  quantity: enteredQty,
141
141
  queue: queue,
@@ -154,7 +154,7 @@ export const BulkAddCastingWithoutSerialDialog = memo(function BulkAddCastingWit
154
154
  onError: () => {
155
155
  close();
156
156
  },
157
- });
157
+ }).catch(() => { });
158
158
  }
159
159
  }
160
160
  return (_jsxs(_Fragment, { children: [_jsxs(Dialog, { open: queue !== null, onClose: () => close(), maxWidth: "md", children: [_jsx(DialogTitle, { children: "Add Raw Material" }), _jsx(DialogContent, { children: _jsxs(Stack, { direction: "column", spacing: 2, sx: {
@@ -0,0 +1,4 @@
1
+ export declare function CancelLoadButton({ onClose, ignoreOperator, }: {
2
+ readonly onClose?: () => void;
3
+ readonly ignoreOperator?: boolean;
4
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,59 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Tooltip, } from "@mui/material";
3
+ import { useAtomValue, useSetAtom } from "jotai";
4
+ import { useState } from "react";
5
+ import { LazySeq } from "@seedtactics/immutable-collections";
6
+ import { currentStatus } from "../../cell-status/current-status.js";
7
+ import { inProcessMaterialInDialog, materialDialogOpen, } from "../../cell-status/material-details.js";
8
+ import { currentOperator } from "../../data/operators.js";
9
+ import { JobsBackend } from "../../network/backend.js";
10
+ import { ApiException, CancelLoadRequest } from "../../network/api.js";
11
+ export function CancelLoadButton({ onClose, ignoreOperator = false, }) {
12
+ const material = useAtomValue(inProcessMaterialInDialog);
13
+ const status = useAtomValue(currentStatus);
14
+ const operator = useAtomValue(currentOperator);
15
+ const setMaterialDialogOpen = useSetAtom(materialDialogOpen);
16
+ const [open, setOpen] = useState(false);
17
+ const [reason, setReason] = useState("");
18
+ const [updating, setUpdating] = useState(false);
19
+ const [error, setError] = useState(null);
20
+ const [snapshot, setSnapshot] = useState(null);
21
+ const cancellationId = material?.action.loadCancellationId ?? null;
22
+ const currentSnapshot = material === null || cancellationId === null
23
+ ? null
24
+ : {
25
+ material,
26
+ cancellationId,
27
+ group: LazySeq.of(status.material)
28
+ .filter((candidate) => candidate.action.loadCancellationId === cancellationId)
29
+ .toRArray(),
30
+ };
31
+ if (currentSnapshot === null && snapshot === null)
32
+ return null;
33
+ async function cancelLoad() {
34
+ if (snapshot === null)
35
+ return;
36
+ setUpdating(true);
37
+ setError(null);
38
+ try {
39
+ await JobsBackend.cancelLoad(snapshot.material.materialID, ignoreOperator ? null : operator, new CancelLoadRequest({
40
+ expectedLoadCancellationId: snapshot.cancellationId,
41
+ reason: reason || undefined,
42
+ }));
43
+ setMaterialDialogOpen(null);
44
+ onClose?.();
45
+ }
46
+ catch (e) {
47
+ setError(ApiException.isApiException(e) ? e.response : e instanceof Error ? e.message : String(e));
48
+ }
49
+ finally {
50
+ setUpdating(false);
51
+ }
52
+ }
53
+ return (_jsxs(_Fragment, { children: [currentSnapshot ? (_jsx(Tooltip, { title: "Cancel the displayed load instruction", children: _jsx(Button, { color: "primary", disabled: updating, onClick: () => {
54
+ setSnapshot(currentSnapshot);
55
+ setReason("");
56
+ setError(null);
57
+ setOpen(true);
58
+ }, children: "Cancel Load" }) })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: () => setOpen(false), children: [_jsx(DialogTitle, { children: "Cancel Load" }), _jsxs(DialogContent, { children: [_jsx("p", { children: "The displayed load instruction will be cancelled and recalculated for:" }), _jsx("ul", { children: snapshot?.group.map((candidate) => (_jsx("li", { children: candidate.serial || `Material ID ${candidate.materialID}` }, candidate.materialID))) }), _jsx(TextField, { label: "Reason (optional)", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: updating, onClick: cancelLoad, children: "Cancel Load" }), _jsx(Button, { color: "secondary", disabled: updating, onClick: () => setOpen(false), children: "Keep Load" })] })] })] }));
59
+ }
@@ -44,6 +44,7 @@ import { materialDialogOpen, materialInDialogInfo, materialInDialogLargestUsedPr
44
44
  import { last30MaterialSummary, } from "../../cell-status/material-summary.js";
45
45
  import { instructionUrl } from "../../network/backend.js";
46
46
  import { QuarantineMatButton } from "./QuarantineButton.js";
47
+ import { CancelLoadButton } from "./CancelLoadButton.js";
47
48
  import { useIsDemo, useSetTitle } from "../routes.js";
48
49
  import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
49
50
  function CompleteButton() {
@@ -108,7 +109,7 @@ function AssignWorkorderButton() {
108
109
  return (_jsx(Button, { color: "primary", onClick: () => setWorkorderDialogOpen(true), children: "Assign Workorder" }));
109
110
  }
110
111
  const CloseoutMaterialDialog = memo(function CloseoutDialog() {
111
- return (_jsx(MaterialDialog, { allowNote: true, buttons: _jsxs(_Fragment, { children: [_jsx(InstrButton, {}), _jsx(QuarantineMatButton, {}), _jsx(AssignWorkorderButton, {}), _jsx(CompleteButton, {})] }) }));
112
+ return (_jsx(MaterialDialog, { allowNote: true, buttons: _jsxs(_Fragment, { children: [_jsx(InstrButton, {}), _jsx(QuarantineMatButton, {}), _jsx(CancelLoadButton, {}), _jsx(AssignWorkorderButton, {}), _jsx(CompleteButton, {})] }) }));
112
113
  });
113
114
  const currentNearestMinutes = atom(new Date());
114
115
  currentNearestMinutes.onMount = (set) => {
@@ -43,6 +43,7 @@ import { last30MaterialSummary } from "../../cell-status/material-summary.js";
43
43
  import { LazySeq } from "@seedtactics/immutable-collections";
44
44
  import { instructionUrl } from "../../network/backend.js";
45
45
  import { QuarantineMatButton } from "./QuarantineButton.js";
46
+ import { CancelLoadButton } from "./CancelLoadButton.js";
46
47
  import { useIsDemo, useSetTitle } from "../routes.js";
47
48
  import { useAtomValue, useSetAtom } from "jotai";
48
49
  function InspButtons(props) {
@@ -67,7 +68,7 @@ function InspButtons(props) {
67
68
  setMatToShow(null);
68
69
  }
69
70
  const url = instructionUrl(material.partName, props.inspection_type, material.materialID, null, maxProc, operator);
70
- return (_jsxs(_Fragment, { children: [material.partName !== "" ? (demo ? (_jsx(Button, { color: "primary", children: "Instructions" })) : (_jsx(Button, { href: url, target: "bms-instructions", color: "primary", children: "Instructions" }))) : undefined, _jsx(QuarantineMatButton, {}), _jsxs(Button, { color: "primary", disabled: completeInspUpdating, onClick: () => markInspComplete(true), children: ["Mark ", props.inspection_type, " Success"] }), _jsxs(Button, { color: "primary", disabled: completeInspUpdating, onClick: () => markInspComplete(false), children: ["Mark ", props.inspection_type, " Failed"] })] }));
71
+ return (_jsxs(_Fragment, { children: [material.partName !== "" ? (demo ? (_jsx(Button, { color: "primary", children: "Instructions" })) : (_jsx(Button, { href: url, target: "bms-instructions", color: "primary", children: "Instructions" }))) : undefined, _jsx(QuarantineMatButton, {}), _jsx(CancelLoadButton, {}), _jsxs(Button, { color: "primary", disabled: completeInspUpdating, onClick: () => markInspComplete(true), children: ["Mark ", props.inspection_type, " Success"] }), _jsxs(Button, { color: "primary", disabled: completeInspUpdating, onClick: () => markInspComplete(false), children: ["Mark ", props.inspection_type, " Failed"] })] }));
71
72
  }
72
73
  function DialogBodyInspButtons({ focusInspectionType }) {
73
74
  const material = useAtomValue(matDetails.materialInDialogInspections);
@@ -1,15 +1,18 @@
1
- import { IInProcessMaterial } from "../../network/api.js";
1
+ import { IInProcessMaterial, ILogEntry } from "../../network/api.js";
2
+ import { HashMap } from "@seedtactics/immutable-collections";
2
3
  import { ReactNode } from "react";
3
4
  export type InvalidateCycleState = {
4
5
  readonly process: number | null;
5
6
  readonly changeRawMat: string | null;
6
7
  readonly changeJobUnique: string | null;
7
8
  readonly updating: boolean;
9
+ readonly error: string | null;
8
10
  };
9
11
  export type InvalidateCycleProps = {
10
12
  readonly st: InvalidateCycleState | null;
11
13
  readonly setState: (s: InvalidateCycleState | null) => void;
12
14
  };
15
+ export declare function affectedMaterialForInvalidation(events: ReadonlyArray<Readonly<ILogEntry>>, materialId: number, process: number | null): HashMap<number, string | null>;
13
16
  export declare function InvalidateCycleDialogContent(props: InvalidateCycleProps): import("react").JSX.Element;
14
17
  export declare function InvalidateCycleDialogButton(props: InvalidateCycleProps & {
15
18
  readonly onClose: () => void;