@seedtactics/insight-client 16.8.4-beta.8 → 17.0.0-beta.0

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 (41) hide show
  1. package/README.md +21 -0
  2. package/dist/assets/index-BkKYqUM0.js +388 -0
  3. package/dist/cell-status/material-details.d.ts +19 -5
  4. package/dist/cell-status/material-details.js +71 -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/station-monitor/BasketLoadStationWork.js +12 -8
  10. package/dist/components/station-monitor/BulkRawMaterial.js +4 -4
  11. package/dist/components/station-monitor/CancelLoadButton.d.ts +4 -0
  12. package/dist/components/station-monitor/CancelLoadButton.js +60 -0
  13. package/dist/components/station-monitor/Closeout.js +2 -1
  14. package/dist/components/station-monitor/Inspection.js +2 -1
  15. package/dist/components/station-monitor/InvalidateCycle.d.ts +4 -1
  16. package/dist/components/station-monitor/InvalidateCycle.js +104 -38
  17. package/dist/components/station-monitor/LoadStation.js +25 -15
  18. package/dist/components/station-monitor/Material.js +14 -1
  19. package/dist/components/station-monitor/QuarantineButton.js +160 -90
  20. package/dist/components/station-monitor/Queues.js +2 -1
  21. package/dist/components/station-monitor/QueuesAddMaterial.js +65 -57
  22. package/dist/components/station-monitor/SystemOverview.js +2 -1
  23. package/dist/components/station-monitor/Whiteboard.js +2 -2
  24. package/dist/data/material-operation-policy.d.ts +21 -0
  25. package/dist/data/material-operation-policy.js +89 -0
  26. package/dist/extension.d.ts +11 -0
  27. package/dist/extension.js +21 -0
  28. package/dist/index.html +1 -2
  29. package/dist/index.js +1 -1
  30. package/dist/network/api.d.ts +20 -4
  31. package/dist/network/api.js +163 -2
  32. package/dist/network/backend-mock.js +6 -0
  33. package/dist/network/backend.d.ts +2 -0
  34. package/dist/network/backend.js +2 -1
  35. package/package.json +12 -11
  36. package/dist/assets/index-DHusyIyb.js +0 -388
  37. package/dist/assets/manifest-BzJli0lx.webmanifest +0 -15
  38. package/dist/bootstrap.d.ts +0 -5
  39. package/dist/bootstrap.js +0 -56
  40. package/dist/components/station-monitor/Material.test.d.ts +0 -1
  41. 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,16 @@ 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>,
106
+ boolean
107
+ ];
108
+ export declare function useQuarantineQueuedMaterial(): [
109
+ (matId: number, operator: string | null, reason: string) => Promise<void>,
102
110
  boolean
103
111
  ];
104
112
  export interface AddExistingMaterialToQueueData {
@@ -108,7 +116,7 @@ export interface AddExistingMaterialToQueueData {
108
116
  readonly operator: string | null;
109
117
  }
110
118
  export declare function useAddExistingMaterialToQueue(): [
111
- (d: AddExistingMaterialToQueueData) => void,
119
+ (d: AddExistingMaterialToQueueData) => Promise<void>,
112
120
  boolean
113
121
  ];
114
122
  export interface AddNewMaterialToQueueData {
@@ -122,7 +130,10 @@ export interface AddNewMaterialToQueueData {
122
130
  readonly onNewMaterial?: (mat: Readonly<IInProcessMaterial>) => void;
123
131
  readonly onError?: (reason: unknown) => void;
124
132
  }
125
- export declare function useAddNewMaterialToQueue(): [(d: AddNewMaterialToQueueData) => void, boolean];
133
+ export declare function useAddNewMaterialToQueue(): [
134
+ (d: AddNewMaterialToQueueData) => Promise<Readonly<IInProcessMaterial>>,
135
+ boolean
136
+ ];
126
137
  export interface AddNewCastingToQueueData {
127
138
  readonly casting: string;
128
139
  readonly quantity: number;
@@ -133,4 +144,7 @@ export interface AddNewCastingToQueueData {
133
144
  readonly onNewMaterial?: (mats: ReadonlyArray<Readonly<IInProcessMaterial>>) => void;
134
145
  readonly onError?: (reason: unknown) => void;
135
146
  }
136
- export declare function useAddNewCastingToQueue(): [(d: AddNewCastingToQueueData) => void, boolean];
147
+ export declare function useAddNewCastingToQueue(): [
148
+ (d: AddNewCastingToQueueData) => Promise<ReadonlyArray<Readonly<IInProcessMaterial>>>,
149
+ boolean
150
+ ];
@@ -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,96 @@ 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
+ }
401
+ }, []);
402
+ return [callback, updating];
403
+ }
404
+ export function useQuarantineQueuedMaterial() {
405
+ const [updating, setUpdating] = useState(false);
406
+ const callback = useCallback(async (matId, operator, reason) => {
407
+ setUpdating(true);
408
+ try {
409
+ await JobsBackend.quarantineQueuedMaterial(matId, operator, reason === "" ? undefined : reason);
410
+ }
411
+ finally {
412
+ setUpdating(false);
413
+ }
391
414
  }, []);
392
415
  return [callback, updating];
393
416
  }
394
417
  export function useAddExistingMaterialToQueue() {
395
418
  const [updating, setUpdating] = useState(false);
396
- const callback = useCallback((d) => {
419
+ const callback = useCallback(async (d) => {
397
420
  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));
421
+ try {
422
+ await JobsBackend.setMaterialInQueue(d.materialId, d.operator, new QueuePosition({
423
+ queue: d.queue,
424
+ position: d.queuePosition,
425
+ }));
426
+ }
427
+ finally {
428
+ setUpdating(false);
429
+ }
404
430
  }, []);
405
431
  return [callback, updating];
406
432
  }
407
433
  export function useAddNewMaterialToQueue() {
408
434
  const [updating, setUpdating] = useState(false);
409
- const callback = useCallback((d) => {
435
+ const callback = useCallback(async (d) => {
410
436
  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));
437
+ try {
438
+ const material = await JobsBackend.addUnprocessedMaterialToQueue(d.jobUnique, d.lastCompletedProcess, d.queue, d.queuePosition, d.operator, d.workorder, d.serial || "");
439
+ if (material === undefined)
440
+ throw new Error("No material returned");
441
+ d.onNewMaterial?.(material);
442
+ return material;
443
+ }
444
+ catch (e) {
445
+ d.onError?.(e);
446
+ throw e;
447
+ }
448
+ finally {
449
+ setUpdating(false);
450
+ }
422
451
  }, []);
423
452
  return [callback, updating];
424
453
  }
425
454
  export function useAddNewCastingToQueue() {
426
455
  const [updating, setUpdating] = useState(false);
427
- const callback = useCallback((d) => {
456
+ const callback = useCallback(async (d) => {
428
457
  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));
458
+ try {
459
+ const material = await JobsBackend.addUnallocatedCastingToQueue(d.casting, d.queue, d.quantity, d.operator, d.workorder, [...(d.serials || [])]);
460
+ d.onNewMaterial?.(material);
461
+ return material;
462
+ }
463
+ catch (e) {
464
+ d.onError?.(e);
465
+ throw e;
466
+ }
467
+ finally {
468
+ setUpdating(false);
469
+ }
436
470
  }, []);
437
471
  return [callback, updating];
438
472
  }
@@ -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,
@@ -28,7 +28,7 @@ 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";
@@ -104,10 +104,10 @@ function SlotMaterial({ material, fsize, }) {
104
104
  return material.map((mat) => (_jsx(MoveMaterialArrowNode, { kind: { type: MoveMaterialNodeKindType.Material, material: mat }, children: _jsx(InProcMaterial, { mat: mat, fsize: fsize }) }, mat.materialID)));
105
105
  }
106
106
  export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }) {
107
- const [submission, setSubmission] = useState("idle");
107
+ const [submission, setSubmission] = useState();
108
108
  const workState = useMemo(() => confirmableWork(material, basket.basketId), [basket.basketId, material]);
109
109
  const work = workState.type === "confirmable" ? workState.work : undefined;
110
- useEffect(() => setSubmission("idle"), [work?.workId]);
110
+ const submissionState = submission !== undefined && submission.workId === work?.workId ? submission.state : "idle";
111
111
  const materialBySlot = useMemo(() => LazySeq.of(material)
112
112
  .filter((mat) => mat.location.type === api.LocType.InBasket &&
113
113
  mat.location.basketId === basket.basketId &&
@@ -129,15 +129,19 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
129
129
  async function submit() {
130
130
  if (work === undefined || submitCommand === undefined)
131
131
  return;
132
- setSubmission("submitting");
132
+ const submittedWorkId = work.workId;
133
+ setSubmission({ workId: submittedWorkId, state: "submitting" });
133
134
  try {
134
- 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);
135
137
  }
136
138
  catch {
137
- setSubmission("error");
139
+ setSubmission((current) => current?.workId === submittedWorkId ? { workId: submittedWorkId, state: "error" } : current);
138
140
  }
139
141
  }
140
- const submissionDisabled = submission === "submitting" || submission === "accepted" || submission === "conflict";
142
+ const submissionDisabled = submissionState === "submitting" ||
143
+ submissionState === "accepted" ||
144
+ submissionState === "conflict";
141
145
  return (_jsxs(Stack, { spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { variant: "h6", children: "Basket slots" }), _jsx(Box, { sx: {
142
146
  display: "grid",
143
147
  gridTemplateColumns: "repeat(auto-fit, minmax(15em, 1fr))",
@@ -147,5 +151,5 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
147
151
  const loads = loadsBySlot.get(slot) ?? [];
148
152
  const isUnknown = basket.unknownSlots?.includes(slot) === true;
149
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));
150
- }) }), 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, 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] }));
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] }));
151
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,60 @@
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
+ import { canCancelLoad } from "../../data/material-operation-policy.js";
12
+ export function CancelLoadButton({ onClose, ignoreOperator = false, }) {
13
+ const material = useAtomValue(inProcessMaterialInDialog);
14
+ const status = useAtomValue(currentStatus);
15
+ const operator = useAtomValue(currentOperator);
16
+ const setMaterialDialogOpen = useSetAtom(materialDialogOpen);
17
+ const [open, setOpen] = useState(false);
18
+ const [reason, setReason] = useState("");
19
+ const [updating, setUpdating] = useState(false);
20
+ const [error, setError] = useState(null);
21
+ const [snapshot, setSnapshot] = useState(null);
22
+ const cancellationId = material?.action.loadCancellationId ?? null;
23
+ const currentSnapshot = material === null || cancellationId === null || !canCancelLoad(material)
24
+ ? null
25
+ : {
26
+ material,
27
+ cancellationId,
28
+ group: LazySeq.of(status.material)
29
+ .filter((candidate) => candidate.action.loadCancellationId === cancellationId)
30
+ .toRArray(),
31
+ };
32
+ if (currentSnapshot === null && snapshot === null)
33
+ return null;
34
+ async function cancelLoad() {
35
+ if (snapshot === null)
36
+ return;
37
+ setUpdating(true);
38
+ setError(null);
39
+ try {
40
+ await JobsBackend.cancelLoad(snapshot.material.materialID, ignoreOperator ? null : operator, new CancelLoadRequest({
41
+ expectedLoadCancellationId: snapshot.cancellationId,
42
+ reason: reason || undefined,
43
+ }));
44
+ setMaterialDialogOpen(null);
45
+ onClose?.();
46
+ }
47
+ catch (e) {
48
+ setError(ApiException.isApiException(e) ? e.response : e instanceof Error ? e.message : String(e));
49
+ }
50
+ finally {
51
+ setUpdating(false);
52
+ }
53
+ }
54
+ return (_jsxs(_Fragment, { children: [currentSnapshot ? (_jsx(Tooltip, { title: "Cancel the displayed load instruction", children: _jsx(Button, { color: "primary", disabled: updating, onClick: () => {
55
+ setSnapshot(currentSnapshot);
56
+ setReason("");
57
+ setError(null);
58
+ setOpen(true);
59
+ }, children: "Cancel Load" }) })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: () => setOpen(false), children: [_jsx(DialogTitle, { children: "Cancel Load" }), _jsxs(DialogContent, { children: [_jsx("p", { children: "The displayed load instruction will be cancelled and recalculated for:" }), _jsx("ul", { children: snapshot?.group.map((candidate) => (_jsx("li", { children: candidate.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" })] })] })] }));
60
+ }
@@ -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;