@seedtactics/insight-client 16.5.2 → 16.7.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 (110) hide show
  1. package/dist/assets/ProgramHighlight-DPTeZ8Si.js +3 -0
  2. package/dist/assets/index-bPAFn3jp.js +364 -0
  3. package/dist/cell-status/basket-cycles.d.ts +21 -0
  4. package/dist/cell-status/basket-cycles.js +80 -0
  5. package/dist/cell-status/current-status.js +11 -3
  6. package/dist/cell-status/estimated-cycle-times.js +8 -4
  7. package/dist/cell-status/inspections.js +2 -2
  8. package/dist/cell-status/loading.js +4 -0
  9. package/dist/cell-status/material-details.d.ts +12 -4
  10. package/dist/cell-status/material-details.js +24 -13
  11. package/dist/cell-status/rebookings.js +15 -17
  12. package/dist/cell-status/scheduled-jobs.d.ts +1 -1
  13. package/dist/cell-status/scheduled-jobs.js +10 -3
  14. package/dist/cell-status/sim-production.js +3 -3
  15. package/dist/cell-status/sim-station-use.d.ts +1 -0
  16. package/dist/cell-status/sim-station-use.js +14 -8
  17. package/dist/cell-status/station-cycles.d.ts +29 -2
  18. package/dist/cell-status/station-cycles.js +81 -11
  19. package/dist/cell-status/tool-replacements.js +1 -1
  20. package/dist/cell-status/tool-usage.js +1 -1
  21. package/dist/components/App.js +101 -66
  22. package/dist/components/BarcodeScanning.js +12 -2
  23. package/dist/components/ErrorsAndLoading.js +10 -1
  24. package/dist/components/LogEntry.d.ts +0 -1
  25. package/dist/components/LogEntry.js +50 -26
  26. package/dist/components/Navigation.js +30 -4
  27. package/dist/components/analysis/AnalysisSelectToolbar.js +5 -1
  28. package/dist/components/analysis/BasketCycleCards.d.ts +1 -0
  29. package/dist/components/analysis/BasketCycleCards.js +145 -0
  30. package/dist/components/analysis/BufferChart.js +10 -4
  31. package/dist/components/analysis/CostPerPiece.js +28 -8
  32. package/dist/components/analysis/CycleChart.js +1 -1
  33. package/dist/components/analysis/DataTable.js +6 -4
  34. package/dist/components/analysis/HeatChart.js +27 -14
  35. package/dist/components/analysis/InspectionSankey.js +17 -6
  36. package/dist/components/analysis/PalletCycleCards.js +15 -4
  37. package/dist/components/analysis/PartCycleCards.js +62 -18
  38. package/dist/components/analysis/StationDataTable.js +14 -11
  39. package/dist/components/analysis/ToolReplacements.js +16 -10
  40. package/dist/components/log-entry-queue-filter.d.ts +2 -0
  41. package/dist/components/log-entry-queue-filter.js +21 -0
  42. package/dist/components/operations/AllMaterial.js +26 -10
  43. package/dist/components/operations/ChartRangeEdit.js +82 -4
  44. package/dist/components/operations/CloseoutReport.js +13 -4
  45. package/dist/components/operations/CompletedParts.js +23 -11
  46. package/dist/components/operations/CurrentWorkorders.js +31 -9
  47. package/dist/components/operations/OEEChart.js +8 -2
  48. package/dist/components/operations/Outliers.js +18 -7
  49. package/dist/components/operations/ProgramHighlight.js +4 -6
  50. package/dist/components/operations/Programs.js +9 -3
  51. package/dist/components/operations/Rebookings.js +8 -4
  52. package/dist/components/operations/RecentCycleChart.js +5 -3
  53. package/dist/components/operations/RecentProduction.js +30 -13
  54. package/dist/components/operations/RecentSchedules.js +6 -2
  55. package/dist/components/operations/RecentStationCycles.js +38 -11
  56. package/dist/components/operations/ShiftSettings.js +3 -3
  57. package/dist/components/operations/SimDayUsage.js +27 -4
  58. package/dist/components/operations/ToolReport.js +5 -1
  59. package/dist/components/operations/WorkorderGantt.js +22 -2
  60. package/dist/components/quality/QualityMaterial.js +11 -8
  61. package/dist/components/quality/RecentFailedInspections.js +3 -1
  62. package/dist/components/routes.d.ts +3 -0
  63. package/dist/components/routes.js +2 -0
  64. package/dist/components/station-monitor/BulkRawMaterial.js +11 -7
  65. package/dist/components/station-monitor/Closeout.js +14 -2
  66. package/dist/components/station-monitor/CustomStationMonitorDialog.js +1 -1
  67. package/dist/components/station-monitor/Inspection.js +23 -11
  68. package/dist/components/station-monitor/InvalidateCycle.js +3 -3
  69. package/dist/components/station-monitor/JobDetails.js +15 -2
  70. package/dist/components/station-monitor/LoadStation.js +274 -31
  71. package/dist/components/station-monitor/Material.js +71 -11
  72. package/dist/components/station-monitor/MoveMaterialArrows.js +4 -4
  73. package/dist/components/station-monitor/QuarantineButton.js +11 -0
  74. package/dist/components/station-monitor/Queues.js +28 -9
  75. package/dist/components/station-monitor/QueuesAddMaterial.js +8 -6
  76. package/dist/components/station-monitor/SelectInspType.js +1 -1
  77. package/dist/components/station-monitor/SelectWorkorder.js +1 -1
  78. package/dist/components/station-monitor/StationToolbar.js +17 -5
  79. package/dist/components/station-monitor/SystemOverview.d.ts +19 -1
  80. package/dist/components/station-monitor/SystemOverview.js +439 -20
  81. package/dist/components/station-monitor/Whiteboard.js +15 -7
  82. package/dist/data/all-material-bins.d.ts +7 -0
  83. package/dist/data/all-material-bins.js +47 -1
  84. package/dist/data/cost-per-piece.js +11 -8
  85. package/dist/data/current-cycles.d.ts +1 -1
  86. package/dist/data/current-cycles.js +62 -17
  87. package/dist/data/move-arrows.d.ts +5 -1
  88. package/dist/data/move-arrows.js +54 -4
  89. package/dist/data/part-summary.js +13 -13
  90. package/dist/data/path-lookup.js +6 -23
  91. package/dist/data/queue-material.d.ts +1 -1
  92. package/dist/data/queue-material.js +18 -17
  93. package/dist/data/results.completed-parts.js +4 -3
  94. package/dist/data/results.cycles.d.ts +15 -6
  95. package/dist/data/results.cycles.js +51 -30
  96. package/dist/data/results.inspection.js +11 -6
  97. package/dist/data/results.oee.js +8 -8
  98. package/dist/data/results.schedules.js +2 -11
  99. package/dist/data/tools-programs.js +5 -6
  100. package/dist/index.html +1 -1
  101. package/dist/network/api.d.ts +22 -4
  102. package/dist/network/api.js +40 -5
  103. package/dist/network/backend-mock.js +15 -8
  104. package/dist/network/backend.js +3 -3
  105. package/dist/network/websocket.js +15 -15
  106. package/dist/util/chart-helpers.d.ts +1 -1
  107. package/dist/util/chart-helpers.js +14 -8
  108. package/package.json +29 -31
  109. package/dist/assets/ProgramHighlight-LvRM40Qr.js +0 -3
  110. package/dist/assets/index-gAFi3Oss.js +0 -364
@@ -0,0 +1,21 @@
1
+ import { ILogEntry } from "../network/api.js";
2
+ import { HashMap } from "@seedtactics/immutable-collections";
3
+ import type { ServerEventAndTime } from "./loading.js";
4
+ import { Atom } from "jotai";
5
+ import type { PalletCycleData } from "./pallet-cycles.js";
6
+ export type BasketCycleData = PalletCycleData;
7
+ export type BasketCyclesByCntr = HashMap<number, BasketCycleData>;
8
+ export type BasketCyclesByBasket = HashMap<number, BasketCyclesByCntr>;
9
+ export declare const last30BasketCycles: Atom<BasketCyclesByBasket>;
10
+ export declare const last30HasBasketCycleData: Atom<boolean>;
11
+ export declare const specificMonthBasketCycles: Atom<BasketCyclesByBasket>;
12
+ export declare const specificMonthHasBasketCycleData: Atom<boolean>;
13
+ export declare const setLast30BasketCycles: import("jotai").WritableAtom<null, [log: readonly Readonly<ILogEntry>[]], void> & {
14
+ init: null;
15
+ };
16
+ export declare const updateLast30BasketCycles: import("jotai").WritableAtom<null, [ServerEventAndTime], void> & {
17
+ init: null;
18
+ };
19
+ export declare const setSpecificMonthBasketCycles: import("jotai").WritableAtom<null, [log: readonly Readonly<ILogEntry>[]], void> & {
20
+ init: null;
21
+ };
@@ -0,0 +1,80 @@
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 { addDays } from "date-fns";
34
+ import { LogType } from "../network/api.js";
35
+ import { HashMap, LazySeq } from "@seedtactics/immutable-collections";
36
+ import { durationToMinutes } from "../util/parseISODuration.js";
37
+ import { atom } from "jotai";
38
+ const last30BasketCyclesRW = atom(HashMap.empty());
39
+ export const last30BasketCycles = last30BasketCyclesRW;
40
+ export const last30HasBasketCycleData = atom((get) => get(last30BasketCyclesRW).size > 0);
41
+ const specificMonthBasketCyclesRW = atom(HashMap.empty());
42
+ export const specificMonthBasketCycles = specificMonthBasketCyclesRW;
43
+ export const specificMonthHasBasketCycleData = atom((get) => get(specificMonthBasketCyclesRW).size > 0);
44
+ function logToBasketCycle(c) {
45
+ return {
46
+ cntr: c.counter,
47
+ x: c.endUTC,
48
+ y: durationToMinutes(c.elapsed),
49
+ active: durationToMinutes(c.active),
50
+ mats: c.material,
51
+ };
52
+ }
53
+ export const setLast30BasketCycles = atom(null, (_, set, log) => {
54
+ set(last30BasketCyclesRW, (oldCycles) => oldCycles.union(LazySeq.of(log)
55
+ .filter((c) => !c.startofcycle && c.type === LogType.BasketCycle && c.pal !== 0)
56
+ .toLookupMap((c) => c.pal, (c) => c.counter, logToBasketCycle), (e1, e2) => e1.union(e2)));
57
+ });
58
+ export const updateLast30BasketCycles = atom(null, (_, set, { evt, now, expire }) => {
59
+ if (evt.logEntry &&
60
+ !evt.logEntry.startofcycle &&
61
+ evt.logEntry.type === LogType.BasketCycle &&
62
+ evt.logEntry.pal !== 0) {
63
+ const log = evt.logEntry;
64
+ set(last30BasketCyclesRW, (oldCycles) => {
65
+ if (expire) {
66
+ const thirtyDaysAgo = addDays(now, -30);
67
+ oldCycles = oldCycles.collectValues((es) => {
68
+ const newEs = es.filter((e) => e.x >= thirtyDaysAgo);
69
+ return newEs.size > 0 ? newEs : null;
70
+ });
71
+ }
72
+ return oldCycles.modify(log.pal, (old) => (old ?? HashMap.empty()).set(log.counter, logToBasketCycle(log)));
73
+ });
74
+ }
75
+ });
76
+ export const setSpecificMonthBasketCycles = atom(null, (_, set, log) => {
77
+ set(specificMonthBasketCyclesRW, LazySeq.of(log)
78
+ .filter((c) => !c.startofcycle && c.type === LogType.BasketCycle && c.pal !== 0)
79
+ .toLookupMap((c) => c.pal, (c) => c.counter, logToBasketCycle));
80
+ });
@@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
31
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  */
33
33
  import { JobsBackend, LogBackend } from "../network/backend.js";
34
- import { InProcessMaterial, LogType, LocType, ActiveJob, WorkorderComment, ActiveWorkorder, } from "../network/api.js";
34
+ import { InProcessMaterial, LogType, LocType, ActiveJob, WorkorderComment, ActiveWorkorder, InProcessMaterialLocation, } from "../network/api.js";
35
35
  import { last30JobComment } from "./scheduled-jobs.js";
36
36
  import { atom } from "jotai";
37
37
  import { currentOperator } from "../data/operators.js";
@@ -173,7 +173,12 @@ export const reorderQueuedMatInCurrentStatus = atom(null, (_, set, { queue, matI
173
173
  if (m.materialID === matId) {
174
174
  return new InProcessMaterial({
175
175
  ...m,
176
- location: { type: LocType.InQueue, currentQueue: queue, queuePosition: newIdx },
176
+ location: new InProcessMaterialLocation({
177
+ ...m.location,
178
+ type: LocType.InQueue,
179
+ currentQueue: queue,
180
+ queuePosition: newIdx,
181
+ }),
177
182
  });
178
183
  }
179
184
  let idx = m.location.queuePosition;
@@ -188,7 +193,10 @@ export const reorderQueuedMatInCurrentStatus = atom(null, (_, set, { queue, matI
188
193
  if (idx !== m.location.queuePosition) {
189
194
  return new InProcessMaterial({
190
195
  ...m,
191
- location: { ...m.location, queuePosition: idx },
196
+ location: new InProcessMaterialLocation({
197
+ ...m.location,
198
+ queuePosition: idx,
199
+ }),
192
200
  });
193
201
  }
194
202
  else {
@@ -42,12 +42,14 @@ export class PartAndStationOperation {
42
42
  this.operation = operation;
43
43
  }
44
44
  static ofLogCycle(c) {
45
- return new PartAndStationOperation(c.material[0].part, c.loc, c.type === LogType.LoadUnloadCycle && c.material.length > 0
45
+ return new PartAndStationOperation(c.material[0].part, c.loc, (c.type === LogType.LoadUnloadCycle || c.type === LogType.BasketLoadUnload) && c.material.length > 0
46
46
  ? c.result + "-" + c.material[0].proc.toString()
47
47
  : c.program);
48
48
  }
49
49
  static ofPartCycle(c) {
50
- return new PartAndStationOperation(c.part, c.stationGroup, c.isLabor && c.material.length > 0 ? c.operation + "-" + c.material[0].proc.toString() : c.operation);
50
+ return new PartAndStationOperation(c.part, c.stationGroup, c.carrier.kind !== "machining" && c.material.length > 0
51
+ ? c.operation + "-" + c.material[0].proc.toString()
52
+ : c.operation);
51
53
  }
52
54
  compare(other) {
53
55
  let cmp = this.part.localeCompare(other.part);
@@ -99,7 +101,7 @@ export function isOutlierAbove(s, mins) {
99
101
  }
100
102
  }
101
103
  function median(vals) {
102
- const sorted = vals.toMutableArray().sort((a, b) => a - b);
104
+ const sorted = vals.toSortedArray((x) => x);
103
105
  const cnt = sorted.length;
104
106
  if (cnt === 0) {
105
107
  return 0;
@@ -222,7 +224,9 @@ export function calcElapsedForCycles(eventLog) {
222
224
  }
223
225
  }
224
226
  }
225
- return chunkCyclesWithSimilarEndTime(LazySeq.of(eventLog).filter((e) => (e.type === LogType.LoadUnloadCycle || e.type === LogType.MachineCycle) &&
227
+ return chunkCyclesWithSimilarEndTime(LazySeq.of(eventLog).filter((e) => (e.type === LogType.LoadUnloadCycle ||
228
+ e.type === LogType.BasketLoadUnload ||
229
+ e.type === LogType.MachineCycle) &&
226
230
  !e.startofcycle &&
227
231
  e.loc !== "" &&
228
232
  e.material.length > 0), (c) => c.loc + " #" + c.locnum.toString(), (c) => c.endUTC)
@@ -74,8 +74,8 @@ export function convertLogToInspections(c) {
74
74
  }
75
75
  return c.material.map((m) => {
76
76
  if (c.type === LogType.Inspection) {
77
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
78
- const pathsJson = JSON.parse((c.details || {}).ActualPath || "[]");
77
+ const parsedPaths = JSON.parse((c.details || {}).ActualPath || "[]");
78
+ const pathsJson = Array.isArray(parsedPaths) ? parsedPaths : [];
79
79
  const paths = [];
80
80
  for (const pathJson of pathsJson) {
81
81
  paths.push(MaterialProcessActualPath.fromJS(pathJson));
@@ -42,6 +42,7 @@ import * as names from "./names.js";
42
42
  import * as estimated from "./estimated-cycle-times.js";
43
43
  import * as tool from "./tool-usage.js";
44
44
  import * as palCycles from "./pallet-cycles.js";
45
+ import * as basketCycles from "./basket-cycles.js";
45
46
  import * as statCycles from "./station-cycles.js";
46
47
  import * as toolReplace from "./tool-replacements.js";
47
48
  import * as simDayUsage from "./sim-day-usage.js";
@@ -60,6 +61,7 @@ export const onServerEvent = atom(null, (_, set, evt) => {
60
61
  set(names.updateNames, evt);
61
62
  set(tool.updateLast30ToolUse, evt);
62
63
  set(palCycles.updateLast30PalletCycles, evt);
64
+ set(basketCycles.updateLast30BasketCycles, evt);
63
65
  set(toolReplace.updateLastToolReplacements, evt);
64
66
  set(statCycles.updateLast30StationCycles, evt);
65
67
  set(rebookings.updateLast30Rebookings, evt);
@@ -86,6 +88,7 @@ export const onLoadLast30Log = atom(null, (_, set, log) => {
86
88
  set(names.setNamesFromLast30Evts, log);
87
89
  set(tool.setLast30ToolUse, log);
88
90
  set(palCycles.setLast30PalletCycles, log);
91
+ set(basketCycles.setLast30BasketCycles, log);
89
92
  set(toolReplace.setLast30ToolReplacements, log);
90
93
  set(statCycles.setLast30StationCycles, log);
91
94
  set(rebookings.setLast30Rebookings, log);
@@ -107,6 +110,7 @@ export const onLoadSpecificMonthLog = atom(null, (_, set, log) => {
107
110
  set(insp.setSpecificMonthInspections, log);
108
111
  set(mats.setSpecificMonthMatSummary, log);
109
112
  set(palCycles.setSpecificMonthPalletCycles, log);
113
+ set(basketCycles.setSpecificMonthBasketCycles, log);
110
114
  set(toolReplace.setSpecificMonthToolReplacements, log);
111
115
  set(statCycles.setSpecificMonthStationCycles, log);
112
116
  });
@@ -32,8 +32,10 @@ export type MaterialToShow = {
32
32
  export declare const materialDialogOpen: import("jotai").WritableAtom<MaterialToShow | null, [mat: MaterialToShow | null], void>;
33
33
  export declare const barcodePotentialNewMaterial: import("jotai").Atom<Promise<Readonly<IScannedPotentialNewMaterial> | null>>;
34
34
  export declare const materialInDialogInfo: import("jotai").Atom<Promise<MaterialToShowInfo | null>>;
35
+ export declare const materialInDialogInfoUnwrapped: import("jotai").Atom<MaterialToShowInfo | null>;
35
36
  export declare const inProcessMaterialInDialog: import("jotai").Atom<Promise<IInProcessMaterial | null>>;
36
37
  export declare const serialInMaterialDialog: import("jotai").Atom<Promise<string | null>>;
38
+ export declare const serialInMaterialDialogUnwrapped: import("jotai").Atom<string | null>;
37
39
  export declare const workorderInMaterialDialog: import("jotai").Atom<Promise<string | null>>;
38
40
  export declare const materialInDialogEvents: import("jotai").Atom<readonly Readonly<ILogEntry>[]>;
39
41
  export type LargestUsedProces = {
@@ -66,7 +68,10 @@ export interface CompleteCloseoutData {
66
68
  readonly failed: boolean;
67
69
  }
68
70
  export declare function useCompleteCloseout(): [(d: CompleteCloseoutData) => void, boolean];
69
- export declare function useAssignWorkorder(): [(mat: MaterialToShowInfo, workorder: string) => void, boolean];
71
+ export declare function useAssignWorkorder(): [
72
+ (mat: MaterialToShowInfo, workorder: string) => void,
73
+ boolean
74
+ ];
70
75
  export interface AddNoteData {
71
76
  readonly matId: number;
72
77
  readonly process: number;
@@ -90,7 +95,10 @@ export interface AddExistingMaterialToQueueData {
90
95
  readonly queuePosition: number;
91
96
  readonly operator: string | null;
92
97
  }
93
- export declare function useAddExistingMaterialToQueue(): [(d: AddExistingMaterialToQueueData) => void, boolean];
98
+ export declare function useAddExistingMaterialToQueue(): [
99
+ (d: AddExistingMaterialToQueueData) => void,
100
+ boolean
101
+ ];
94
102
  export interface AddNewMaterialToQueueData {
95
103
  readonly jobUnique: string;
96
104
  readonly lastCompletedProcess: number;
@@ -100,7 +108,7 @@ export interface AddNewMaterialToQueueData {
100
108
  readonly operator: string | null;
101
109
  readonly workorder: string | null;
102
110
  readonly onNewMaterial?: (mat: Readonly<IInProcessMaterial>) => void;
103
- readonly onError?: (reason: any) => void;
111
+ readonly onError?: (reason: unknown) => void;
104
112
  }
105
113
  export declare function useAddNewMaterialToQueue(): [(d: AddNewMaterialToQueueData) => void, boolean];
106
114
  export interface AddNewCastingToQueueData {
@@ -111,6 +119,6 @@ export interface AddNewCastingToQueueData {
111
119
  readonly workorder: string | null;
112
120
  readonly operator: string | null;
113
121
  readonly onNewMaterial?: (mats: ReadonlyArray<Readonly<IInProcessMaterial>>) => void;
114
- readonly onError?: (reason: any) => void;
122
+ readonly onError?: (reason: unknown) => void;
115
123
  }
116
124
  export declare function useAddNewCastingToQueue(): [(d: AddNewCastingToQueueData) => void, boolean];
@@ -36,7 +36,7 @@ import { LogType, NewInspectionCompleted, NewCloseout, QueuePosition, } from "..
36
36
  import { useCallback, useState } from "react";
37
37
  import { currentStatus } from "./current-status.js";
38
38
  import { atom, useSetAtom } from "jotai";
39
- import { loadable } from "jotai/utils";
39
+ import { unwrap } from "jotai/utils";
40
40
  import { isLogEntryInvalidated } from "../components/LogEntry.js";
41
41
  import { currentRoute, RouteLocation } from "../components/routes.js";
42
42
  const matToShow = atom(null);
@@ -55,6 +55,9 @@ export const materialDialogOpen = atom((get) => get(matToShow), (_, set, mat) =>
55
55
  const barcodeMaterialDetail = atom(async (get) => {
56
56
  const toShow = get(matToShow);
57
57
  if (toShow && toShow.type === "Barcode") {
58
+ if (typeof FmsServerBackend === "undefined") {
59
+ return null;
60
+ }
58
61
  const route = get(currentRoute);
59
62
  const queues = toShow.toQueue
60
63
  ? [toShow.toQueue]
@@ -101,10 +104,14 @@ export const materialInDialogInfo = atom(async (get) => {
101
104
  }
102
105
  case "ManuallyEnteredSerial":
103
106
  case "AddMatWithEnteredSerial": {
107
+ if (typeof LogBackend === "undefined") {
108
+ return null;
109
+ }
104
110
  return (await LogBackend.materialForSerial(curMat.serial))?.[0] ?? null;
105
111
  }
106
112
  }
107
113
  });
114
+ export const materialInDialogInfoUnwrapped = unwrap(materialInDialogInfo, (prev) => prev ?? null);
108
115
  export const inProcessMaterialInDialog = atom(async (get) => {
109
116
  const status = get(currentStatus);
110
117
  const toShow = get(matToShow);
@@ -113,7 +120,9 @@ export const inProcessMaterialInDialog = atom(async (get) => {
113
120
  if (toShow.type === "InProcMat")
114
121
  return toShow.inproc;
115
122
  const matId = (await get(materialInDialogInfo))?.materialID ?? null;
116
- return matId !== null && matId >= 0 ? (status.material.find((m) => m.materialID === matId) ?? null) : null;
123
+ return matId !== null && matId >= 0
124
+ ? (status.material.find((m) => m.materialID === matId) ?? null)
125
+ : null;
117
126
  });
118
127
  export const serialInMaterialDialog = atom(async (get) => {
119
128
  const toShow = get(matToShow);
@@ -130,13 +139,14 @@ export const serialInMaterialDialog = atom(async (get) => {
130
139
  return toShow.logMat.serial ?? null;
131
140
  case "Barcode": {
132
141
  const barcodeMat = await get(barcodeMaterialDetail);
133
- return barcodeMat?.existingMaterial?.serial ?? barcodeMat?.potentialNewMaterial?.serial ?? null;
142
+ return (barcodeMat?.existingMaterial?.serial ?? barcodeMat?.potentialNewMaterial?.serial ?? null);
134
143
  }
135
144
  case "ManuallyEnteredSerial":
136
145
  case "AddMatWithEnteredSerial":
137
146
  return toShow.serial;
138
147
  }
139
148
  });
149
+ export const serialInMaterialDialogUnwrapped = unwrap(serialInMaterialDialog, (prev) => prev ?? null);
140
150
  export const workorderInMaterialDialog = atom(async (get) => {
141
151
  const toShow = get(matToShow);
142
152
  if (toShow === null)
@@ -152,7 +162,9 @@ export const workorderInMaterialDialog = atom(async (get) => {
152
162
  return toShow.logMat.workorder ?? null;
153
163
  case "Barcode": {
154
164
  const barcodeMat = await get(barcodeMaterialDetail);
155
- return barcodeMat?.existingMaterial?.workorder ?? barcodeMat?.potentialNewMaterial?.workorder ?? null;
165
+ return (barcodeMat?.existingMaterial?.workorder ??
166
+ barcodeMat?.potentialNewMaterial?.workorder ??
167
+ null);
156
168
  }
157
169
  case "ManuallyEnteredSerial":
158
170
  case "AddMatWithEnteredSerial":
@@ -168,6 +180,9 @@ const localMatEvents = atom(async (get, { signal }) => {
168
180
  if (mat === null) {
169
181
  return [];
170
182
  }
183
+ else if (typeof LogBackend === "undefined") {
184
+ return [];
185
+ }
171
186
  else if (mat.materialID >= 0) {
172
187
  return await LogBackend.logForMaterial(mat.materialID, signal);
173
188
  }
@@ -178,7 +193,7 @@ const localMatEvents = atom(async (get, { signal }) => {
178
193
  return [];
179
194
  }
180
195
  });
181
- const localMatEventsLoadable = loadable(localMatEvents);
196
+ const localMatEventsUnwrapped = unwrap(localMatEvents, (prev) => prev ?? []);
182
197
  const otherMatEvents = atom(async (get, { signal }) => {
183
198
  const serial = await get(serialInMaterialDialog);
184
199
  if (serial === null || serial === "")
@@ -189,13 +204,11 @@ const otherMatEvents = atom(async (get, { signal }) => {
189
204
  }
190
205
  return evts;
191
206
  });
192
- const otherMatEventsLoadable = loadable(otherMatEvents);
207
+ const otherMatEventsUnwrapped = unwrap(otherMatEvents, (prev) => prev ?? []);
193
208
  export const materialInDialogEvents = atom((get) => {
194
- const localEvts = get(localMatEventsLoadable);
195
- const otherEvts = get(otherMatEventsLoadable);
196
209
  const evtsFromUpdate = get(extraLogEventsFromUpdates);
197
- return LazySeq.of(localEvts.state === "hasData" ? localEvts.data : [])
198
- .concat(otherEvts.state === "hasData" ? otherEvts.data : [])
210
+ return LazySeq.of(get(localMatEventsUnwrapped))
211
+ .concat(get(otherMatEventsUnwrapped))
199
212
  .sortBy((e) => e.endUTC.getTime(), (e) => e.counter)
200
213
  .concat(evtsFromUpdate)
201
214
  .toRArray();
@@ -408,9 +421,7 @@ export function useAddNewCastingToQueue() {
408
421
  const [updating, setUpdating] = useState(false);
409
422
  const callback = useCallback((d) => {
410
423
  setUpdating(true);
411
- JobsBackend.addUnallocatedCastingToQueue(d.casting, d.queue, d.quantity, d.operator, d.workorder, [
412
- ...(d.serials || []),
413
- ])
424
+ JobsBackend.addUnallocatedCastingToQueue(d.casting, d.queue, d.quantity, d.operator, d.workorder, [...(d.serials || [])])
414
425
  .then((ms) => {
415
426
  if (d.onNewMaterial)
416
427
  d.onNewMaterial(ms);
@@ -34,26 +34,24 @@ import { atom } from "jotai";
34
34
  import { LogType } from "../network/api";
35
35
  import { LazySeq, OrderedMap } from "@seedtactics/immutable-collections";
36
36
  import { JobsBackend, LogBackend } from "../network/backend";
37
- import { loadable } from "jotai/utils";
37
+ import { unwrap } from "jotai/utils";
38
38
  import { addDays } from "date-fns";
39
39
  import { useCallback, useState } from "react";
40
40
  const rebookingEvts = atom(OrderedMap.empty());
41
41
  // unscheduled rebookings should be loaded from the last 30 events, but it is
42
- // possible for some to be older than 30 days, so load them here. But don't
43
- // wait for them to be loaded before rendering, so everything is wrapped in loadable.
44
- const unschRebookings = loadable(atom(async (_, { signal }) => {
45
- const b = await JobsBackend.unscheduledRebookings(signal);
46
- return OrderedMap.build(b, (b) => b.bookingId);
47
- }));
42
+ // possible for some to be older than 30 days, so load them here without
43
+ // blocking rendering while the request is still pending.
44
+ const unschRebookings = unwrap(atom(async (_, { signal }) => {
45
+ if (typeof JobsBackend === "undefined") {
46
+ return OrderedMap.empty();
47
+ }
48
+ const bookings = await JobsBackend.unscheduledRebookings(signal);
49
+ return OrderedMap.build(bookings, (booking) => booking.bookingId);
50
+ }), (prev) => prev ?? OrderedMap.empty());
48
51
  export const last30Rebookings = atom((get) => {
49
52
  const evts = get(rebookingEvts);
50
53
  const unsch = get(unschRebookings);
51
- if (unsch.state === "hasData") {
52
- return evts.union(unsch.data, (fromEvt, fromUnsch) => fromEvt ?? fromUnsch);
53
- }
54
- else {
55
- return evts;
56
- }
54
+ return evts.union(unsch, (fromEvt, fromUnsch) => fromEvt ?? fromUnsch);
57
55
  });
58
56
  const canceledRebookingsRW = atom(OrderedMap.empty());
59
57
  export const canceledRebookings = canceledRebookingsRW;
@@ -85,11 +83,11 @@ export const setLast30Rebookings = atom(null, (get, set, log) => {
85
83
  function updateJobs(set, jobs, expire) {
86
84
  set(scheduledRW, (old) => {
87
85
  if (expire) {
88
- old = old.filter((b) => b.scheduledTime >= expire);
86
+ old = old.filter((booking) => booking.scheduledTime >= expire);
89
87
  }
90
88
  return old.union(jobs
91
- .flatMap((j) => (j.bookings ?? []).map((b) => [
92
- b,
89
+ .flatMap((j) => (j.bookings ?? []).map((booking) => [
90
+ booking,
93
91
  {
94
92
  scheduledTime: j.routeStartUTC,
95
93
  jobUnique: j.unique,
@@ -99,7 +97,7 @@ function updateJobs(set, jobs, expire) {
99
97
  });
100
98
  }
101
99
  export const setLast30RebookingJobs = atom(null, (_, set, historic) => {
102
- updateJobs(set, LazySeq.ofObject(historic.jobs).map(([_, j]) => j));
100
+ updateJobs(set, LazySeq.ofObject(historic.jobs).map(([, j]) => j));
103
101
  });
104
102
  export const updateLast30Rebookings = atom(null, (get, set, { evt, now, expire }) => {
105
103
  if (evt.newJobs) {
@@ -1,5 +1,5 @@
1
1
  import type { ServerEventAndTime } from "./loading.js";
2
- import { IHistoricData, IHistoricJob, IRecentHistoricData } from "../network/api.js";
2
+ import { IHistoricData, IRecentHistoricData, IHistoricJob } from "../network/api.js";
3
3
  import { HashMap, HashSet } from "@seedtactics/immutable-collections";
4
4
  import { Atom } from "jotai";
5
5
  export declare const last30Jobs: Atom<HashMap<string, Readonly<IHistoricJob>>>;
@@ -31,6 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
31
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  */
33
33
  import { addDays } from "date-fns";
34
+ import { HistoricJob } from "../network/api.js";
34
35
  import { HashMap, HashSet, LazySeq } from "@seedtactics/immutable-collections";
35
36
  import { atom } from "jotai";
36
37
  import { atomFamily } from "jotai-family";
@@ -67,10 +68,16 @@ export const updateLast30Jobs = atom(null, (_, set, { evt, now, expire }) => {
67
68
  const newJobs = LazySeq.of(evt.newJobs.jobs);
68
69
  set(last30JobsRW, (oldJobs) => {
69
70
  if (expire) {
70
- const expire = addDays(now, -30);
71
- oldJobs = oldJobs.filter((j) => j.routeStartUTC >= expire);
71
+ const expireDate = addDays(now, -30);
72
+ oldJobs = oldJobs.filter((j) => j.routeStartUTC >= expireDate);
72
73
  }
73
- return oldJobs.union(newJobs.toHashMap((j) => [j.unique, { ...j, copiedToSystem: true }]));
74
+ return oldJobs.union(newJobs.toHashMap((j) => {
75
+ const historicJob = new HistoricJob({
76
+ ...j,
77
+ copiedToSystem: true,
78
+ });
79
+ return [j.unique, historicJob];
80
+ }));
74
81
  });
75
82
  if (schId) {
76
83
  set(last30SchIdsRW, (oldIds) => oldIds.add(schId));
@@ -74,13 +74,13 @@ export const updateLast30JobProduction = atom(null, (_, set, { evt, now, expire
74
74
  const apiNewJobs = evt.newJobs.jobs;
75
75
  set(last30SimProductionRW, (simProd) => {
76
76
  if (expire) {
77
- const expire = addDays(now, -30);
77
+ const expireDate = addDays(now, -30);
78
78
  // check if nothing to expire and no new data
79
79
  const minProd = LazySeq.of(simProd).minBy((e) => e.completeTime.getTime());
80
- if ((minProd === undefined || minProd.completeTime >= expire) && apiNewJobs.length === 0) {
80
+ if ((minProd === undefined || minProd.completeTime >= expireDate) && apiNewJobs.length === 0) {
81
81
  return simProd;
82
82
  }
83
- simProd = simProd.filter((e) => e.completeTime >= expire);
83
+ simProd = simProd.filter((e) => e.completeTime >= expireDate);
84
84
  }
85
85
  return [...simProd, ...jobToPartCompleted(apiNewJobs)];
86
86
  });
@@ -3,6 +3,7 @@ import { IHistoricData } from "../network/api.js";
3
3
  import { Atom } from "jotai";
4
4
  export interface SimStationUse {
5
5
  readonly station: string;
6
+ readonly stationLabel: string;
6
7
  readonly start: Date;
7
8
  readonly end: Date;
8
9
  readonly plannedDown: boolean;
@@ -33,25 +33,30 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  import { LazySeq } from "@seedtactics/immutable-collections";
34
34
  import { addDays } from "date-fns";
35
35
  import { atom } from "jotai";
36
+ import { fmsInformation } from "../network/server-settings.js";
37
+ import { displayStationName } from "./station-cycles.js";
36
38
  const last30SimStationUseRW = atom([]);
37
39
  export const last30SimStationUse = last30SimStationUseRW;
38
40
  const specificMonthSimStationUseRW = atom([]);
39
41
  export const specificMonthSimStationUse = specificMonthSimStationUseRW;
40
- function procSimUse(apiSimUse) {
42
+ function procSimUse(apiSimUse, loadStationNames) {
41
43
  return apiSimUse.map((simUse) => ({
42
44
  station: simUse.stationGroup + " #" + simUse.stationNum.toString(),
45
+ stationLabel: displayStationName(simUse.stationGroup, simUse.stationNum, loadStationNames),
43
46
  start: simUse.startUTC,
44
47
  end: simUse.endUTC,
45
48
  plannedDown: simUse.planDown ?? false,
46
- part: simUse.parts?.map((p) => ({ uniq: p.jobUnique, proc: p.process, path: p.path })),
49
+ parts: simUse.parts?.map((p) => ({ uniq: p.jobUnique, proc: p.process, path: p.path })),
47
50
  }));
48
51
  }
49
- export const setLast30SimStatUse = atom(null, (_, set, history) => {
50
- set(last30SimStationUseRW, (oldSimUse) => oldSimUse.concat(procSimUse(history.stationUse)));
52
+ export const setLast30SimStatUse = atom(null, (get, set, history) => {
53
+ const loadStationNames = get(fmsInformation)?.loadStationNames;
54
+ set(last30SimStationUseRW, (oldSimUse) => oldSimUse.concat(procSimUse(history.stationUse, loadStationNames)));
51
55
  });
52
- export const updateLast30SimStatUse = atom(null, (_, set, { evt, now, expire }) => {
56
+ export const updateLast30SimStatUse = atom(null, (get, set, { evt, now, expire }) => {
53
57
  if (evt.newJobs?.stationUse) {
54
58
  const apiSimUse = evt.newJobs?.stationUse;
59
+ const loadStationNames = get(fmsInformation)?.loadStationNames;
55
60
  set(last30SimStationUseRW, (simUse) => {
56
61
  if (expire) {
57
62
  const expireT = addDays(now, -30);
@@ -62,10 +67,11 @@ export const updateLast30SimStatUse = atom(null, (_, set, { evt, now, expire })
62
67
  }
63
68
  simUse = simUse.filter((e) => e.start >= expireT);
64
69
  }
65
- return simUse.concat(procSimUse(apiSimUse));
70
+ return simUse.concat(procSimUse(apiSimUse, loadStationNames));
66
71
  });
67
72
  }
68
73
  });
69
- export const setSpecificMonthSimStatUse = atom(null, (_, set, history) => {
70
- set(specificMonthSimStationUseRW, procSimUse(history.stationUse));
74
+ export const setSpecificMonthSimStatUse = atom(null, (get, set, history) => {
75
+ const loadStationNames = get(fmsInformation)?.loadStationNames;
76
+ set(specificMonthSimStationUseRW, procSimUse(history.stationUse, loadStationNames));
71
77
  });
@@ -2,6 +2,16 @@ import { ILogEntry, ILogMaterial } from "../network/api.js";
2
2
  import type { ServerEventAndTime } from "./loading.js";
3
3
  import { HashMap } from "@seedtactics/immutable-collections";
4
4
  import { Atom } from "jotai";
5
+ export type PartCycleCarrier = {
6
+ readonly kind: "machining";
7
+ readonly pallet: number;
8
+ } | {
9
+ readonly kind: "pallet-lul";
10
+ readonly pallet: number;
11
+ } | {
12
+ readonly kind: "basket-lul";
13
+ readonly basket: number;
14
+ };
5
15
  export interface PartCycleData {
6
16
  readonly endTime: Date;
7
17
  readonly elapsedMinsPerMaterial: number;
@@ -9,10 +19,10 @@ export interface PartCycleData {
9
19
  readonly part: string;
10
20
  readonly stationGroup: string;
11
21
  readonly stationNumber: number;
22
+ readonly stationLabel: string;
12
23
  readonly operation: string;
13
- readonly pallet: number;
24
+ readonly carrier: PartCycleCarrier;
14
25
  readonly activeMinutes: number;
15
- readonly isLabor: boolean;
16
26
  readonly material: ReadonlyArray<Readonly<ILogMaterial>>;
17
27
  readonly operator: string;
18
28
  readonly medianCycleMinutes: number;
@@ -20,9 +30,26 @@ export interface PartCycleData {
20
30
  readonly isOutlier: boolean;
21
31
  }
22
32
  export type StationCyclesByCntr = HashMap<number, PartCycleData>;
33
+ export declare function isLaborCycle(cycle: Readonly<PartCycleData>): boolean;
34
+ export declare function isMachineCycle(cycle: Readonly<PartCycleData>): boolean;
35
+ export declare function isPalletLoadCycle(cycle: Readonly<PartCycleData>): boolean;
36
+ export declare function isBasketLoadCycle(cycle: Readonly<PartCycleData>): boolean;
37
+ export declare function palletForCycle(cycle: Readonly<PartCycleData>): number | undefined;
38
+ export declare function basketForCycle(cycle: Readonly<PartCycleData>): number | undefined;
23
39
  export declare function stat_name_and_num(stationGroup: string, stationNumber: number): string;
40
+ export declare function loadStationDisplayName(stationNumber: number, loadStationNames: {
41
+ [key: string]: string;
42
+ } | undefined): string;
43
+ export declare function displayStationName(stationGroup: string, stationNumber: number, loadStationNames: {
44
+ [key: string]: string;
45
+ } | undefined): string;
46
+ export declare function basketDisplayName(basketName: string | null | undefined): string;
47
+ export declare function carrierLabel(cycle: Readonly<PartCycleData>, basketName?: string): string;
48
+ export declare function carrierSortKey(cycle: Readonly<PartCycleData>): number;
24
49
  export declare const last30StationCycles: Atom<StationCyclesByCntr>;
50
+ export declare const last30HasBasketCycles: Atom<boolean>;
25
51
  export declare const specificMonthStationCycles: Atom<StationCyclesByCntr>;
52
+ export declare const specificMonthHasBasketCycles: Atom<boolean>;
26
53
  export declare const setLast30StationCycles: import("jotai").WritableAtom<null, [log: readonly Readonly<ILogEntry>[]], void> & {
27
54
  init: null;
28
55
  };