@seedtactics/insight-client 17.0.1-beta.12 → 17.0.1-beta.13
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.
- package/README.md +11 -0
- package/dist/assets/ProgramHighlight-CnZe9PDO.js +3 -0
- package/dist/assets/index-Dxxgr-QV.js +367 -0
- package/dist/cell-status/current-status.d.ts +1 -0
- package/dist/cell-status/current-status.js +2 -0
- package/dist/cell-status/loading.d.ts +6 -0
- package/dist/cell-status/loading.js +11 -3
- package/dist/cell-status/material-details.d.ts +2 -2
- package/dist/cell-status/material-details.js +26 -13
- package/dist/components/analysis/BufferChart.js +3 -2
- package/dist/components/operations/RecentCycleChart.js +16 -21
- package/dist/components/station-monitor/CancelLoadButton.js +2 -0
- package/dist/components/station-monitor/InvalidateCycle.js +3 -2
- package/dist/components/station-monitor/LoadStation.js +4 -3
- package/dist/components/station-monitor/PrintedLabel.js +15 -19
- package/dist/components/station-monitor/SystemOverview.js +3 -2
- package/dist/extension.d.ts +1 -0
- package/dist/extension.js +1 -0
- package/dist/index.html +2 -39
- package/dist/network/websocket.d.ts +1 -0
- package/dist/network/websocket.js +122 -36
- package/package.json +17 -17
- package/dist/assets/ProgramHighlight-C6zSEXCX.js +0 -3
- package/dist/assets/index-kk-GRa5i.js +0 -388
|
@@ -5,6 +5,7 @@ export declare const currentStatus: Atom<ICurrentStatus>;
|
|
|
5
5
|
export declare const secondsSinceEpochAtom: import("jotai").PrimitiveAtom<number> & {
|
|
6
6
|
init: number;
|
|
7
7
|
};
|
|
8
|
+
export declare const minutesSinceEpochAtom: Atom<number>;
|
|
8
9
|
export declare const setJobComment: import("jotai").WritableAtom<null, [uniq: string, newVal: string | null], void> & {
|
|
9
10
|
init: null;
|
|
10
11
|
};
|
|
@@ -51,6 +51,8 @@ secondsSinceEpochAtom.onMount = (setSelf) => {
|
|
|
51
51
|
}, 1000);
|
|
52
52
|
return () => clearInterval(interval);
|
|
53
53
|
};
|
|
54
|
+
// Minute precision keeps report time windows current without redrawing charts every second.
|
|
55
|
+
export const minutesSinceEpochAtom = atom((get) => Math.floor(get(secondsSinceEpochAtom) / 60));
|
|
54
56
|
export const setJobComment = atom(null, (_, set, uniq, newVal) => {
|
|
55
57
|
const newComment = newVal === null ? "" : newVal;
|
|
56
58
|
set(currentStatusRW, (st) => {
|
|
@@ -6,6 +6,12 @@ export interface ServerEventAndTime {
|
|
|
6
6
|
readonly expire: boolean;
|
|
7
7
|
}
|
|
8
8
|
export declare const lastEventCounter: Atom<number | null>;
|
|
9
|
+
export declare const onLiveServerEvent: import("jotai").WritableAtom<null, [evt: ServerEventAndTime], void> & {
|
|
10
|
+
init: null;
|
|
11
|
+
};
|
|
12
|
+
export declare const onHistoricalServerEvent: import("jotai").WritableAtom<null, [evt: ServerEventAndTime], void> & {
|
|
13
|
+
init: null;
|
|
14
|
+
};
|
|
9
15
|
export declare const onServerEvent: import("jotai").WritableAtom<null, [evt: ServerEventAndTime], void> & {
|
|
10
16
|
init: null;
|
|
11
17
|
};
|
|
@@ -50,16 +50,20 @@ import * as simDayUsage from "./sim-day-usage.js";
|
|
|
50
50
|
import * as rebookings from "./rebookings.js";
|
|
51
51
|
import { atom } from "jotai";
|
|
52
52
|
const lastEventCounterRW = atom(null);
|
|
53
|
+
// Highest counter applied to historical projections. Buffered websocket events do not advance it.
|
|
54
|
+
// This is a reconnect cursor, not proof that lower counters cannot arrive later on the socket.
|
|
53
55
|
export const lastEventCounter = lastEventCounterRW;
|
|
54
|
-
export const
|
|
56
|
+
export const onLiveServerEvent = atom(null, (_, set, evt) => {
|
|
55
57
|
if (evt.evt.newCurrentStatus) {
|
|
56
58
|
set(customSt.replaceCustomState, evt.evt.newCurrentStatus.customState ?? null);
|
|
57
59
|
}
|
|
60
|
+
set(currentSt.updateCurrentStatus, evt);
|
|
61
|
+
});
|
|
62
|
+
export const onHistoricalServerEvent = atom(null, (_, set, evt) => {
|
|
58
63
|
set(simProd.updateLast30JobProduction, evt);
|
|
59
64
|
set(simUse.updateLast30SimStatUse, evt);
|
|
60
65
|
set(schJobs.updateLast30Jobs, evt);
|
|
61
66
|
set(buffers.updateLast30Buffer, evt);
|
|
62
|
-
set(currentSt.updateCurrentStatus, evt);
|
|
63
67
|
set(mats.updateLast30MatSummary, evt);
|
|
64
68
|
set(insp.updateLast30Inspections, evt);
|
|
65
69
|
set(names.updateNames, evt);
|
|
@@ -72,9 +76,13 @@ export const onServerEvent = atom(null, (_, set, evt) => {
|
|
|
72
76
|
set(simDayUsage.updateLatestSimDayUsage, evt);
|
|
73
77
|
if (evt.evt.logEntry) {
|
|
74
78
|
const newCntr = evt.evt.logEntry.counter;
|
|
75
|
-
set(lastEventCounterRW, (old) =>
|
|
79
|
+
set(lastEventCounterRW, (old) => Math.max(old ?? newCntr, newCntr));
|
|
76
80
|
}
|
|
77
81
|
});
|
|
82
|
+
export const onServerEvent = atom(null, (_, set, evt) => {
|
|
83
|
+
set(onLiveServerEvent, evt);
|
|
84
|
+
set(onHistoricalServerEvent, evt);
|
|
85
|
+
});
|
|
78
86
|
export const onLoadLast30Jobs = atom(null, (get, set, historicData) => {
|
|
79
87
|
const filtered = schJobs.filterExistingJobs(get(schJobs.last30SchIds), historicData);
|
|
80
88
|
set(simUse.setLast30SimStatUse, filtered);
|
|
@@ -44,7 +44,7 @@ export declare const barcodePotentialNewMaterial: Atom<Promise<Readonly<IScanned
|
|
|
44
44
|
export declare const materialInDialogInfo: Atom<Promise<MaterialToShowInfo | null>>;
|
|
45
45
|
export declare const materialInDialogInfoUnwrapped: Atom<MaterialToShowInfo | null>;
|
|
46
46
|
export declare const materialInDialogInfoState: Atom<AsyncAtomState<MaterialToShowInfo | null>>;
|
|
47
|
-
export declare const inProcessMaterialInDialog: Atom<Promise<IInProcessMaterial | null
|
|
47
|
+
export declare const inProcessMaterialInDialog: Atom<IInProcessMaterial | Promise<IInProcessMaterial | null> | null>;
|
|
48
48
|
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>>;
|
|
@@ -61,7 +61,7 @@ export interface MaterialToShowInspections {
|
|
|
61
61
|
readonly completedInspections: ReadonlyArray<string>;
|
|
62
62
|
}
|
|
63
63
|
export declare const materialInDialogInspections: Atom<MaterialToShowInspections>;
|
|
64
|
-
export declare const possibleWorkordersForMaterialInDialog: Atom<Promise<readonly IActiveWorkorder[]
|
|
64
|
+
export declare const possibleWorkordersForMaterialInDialog: Atom<Promise<readonly IActiveWorkorder[]> | readonly IActiveWorkorder[]>;
|
|
65
65
|
export interface ForceInspectionData {
|
|
66
66
|
readonly mat: MaterialToShowInfo;
|
|
67
67
|
readonly inspType: string;
|
|
@@ -116,11 +116,26 @@ function asyncAtomState(source) {
|
|
|
116
116
|
return unwrap(atom((get) => get(source).then((data) => ({ state: "hasData", data }), (error) => ({ state: "hasError", error }))), () => ({ state: "loading" }));
|
|
117
117
|
}
|
|
118
118
|
export const materialInDialogInfoState = asyncAtomState(materialInDialogInfo);
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
// Some cells publish current status every second. Dialog atoms that combine current status with the
|
|
120
|
+
// dialog material must not suspend on each status update, since that hides the dialog content behind
|
|
121
|
+
// its loading fallback. They compute synchronously once the dialog material is known and suspend
|
|
122
|
+
// only while it is still loading (e.g., a barcode or serial lookup).
|
|
123
|
+
function withDialogMaterial(get, compute) {
|
|
124
|
+
const info = get(materialInDialogInfoState);
|
|
125
|
+
switch (info.state) {
|
|
126
|
+
case "hasData":
|
|
127
|
+
return compute(info.data);
|
|
128
|
+
case "hasError":
|
|
129
|
+
throw info.error;
|
|
130
|
+
case "loading":
|
|
131
|
+
return get(materialInDialogInfo).then(compute);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export const inProcessMaterialInDialog = atom((get) => {
|
|
121
135
|
const toShow = get(matToShow);
|
|
122
136
|
if (toShow === null)
|
|
123
137
|
return null;
|
|
138
|
+
const status = get(currentStatus);
|
|
124
139
|
if (toShow.type === "InProcMat") {
|
|
125
140
|
return (status.material.find((m) => m.materialID === toShow.inproc.materialID &&
|
|
126
141
|
(m.materialID >= 0 ||
|
|
@@ -130,10 +145,9 @@ export const inProcessMaterialInDialog = atom(async (get) => {
|
|
|
130
145
|
m.action.loadToBasketId === toShow.inproc.action.loadToBasketId &&
|
|
131
146
|
m.jobUnique === toShow.inproc.jobUnique))) ?? null);
|
|
132
147
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
: null;
|
|
148
|
+
return withDialogMaterial(get, (mat) => mat !== null && mat.materialID >= 0
|
|
149
|
+
? (status.material.find((m) => m.materialID === mat.materialID) ?? null)
|
|
150
|
+
: null);
|
|
137
151
|
});
|
|
138
152
|
export const serialInMaterialDialog = atom(async (get) => {
|
|
139
153
|
const toShow = get(matToShow);
|
|
@@ -284,14 +298,13 @@ export const materialInDialogInspections = atom((get) => {
|
|
|
284
298
|
//--------------------------------------------------------------------------------
|
|
285
299
|
// Workorders
|
|
286
300
|
//--------------------------------------------------------------------------------
|
|
287
|
-
export const possibleWorkordersForMaterialInDialog = atom(
|
|
288
|
-
const mat = await get(materialInDialogInfo);
|
|
289
|
-
if (mat === null || mat.partName === "")
|
|
290
|
-
return [];
|
|
301
|
+
export const possibleWorkordersForMaterialInDialog = atom((get) => {
|
|
291
302
|
const works = get(currentStatus)?.workorders ?? [];
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
|
|
303
|
+
return withDialogMaterial(get, (mat) => mat === null || mat.partName === ""
|
|
304
|
+
? []
|
|
305
|
+
: LazySeq.of(works)
|
|
306
|
+
.filter((w) => w.part === mat.partName)
|
|
307
|
+
.toSortedArray((w) => w.dueDate.getTime(), (w) => -w.priority));
|
|
295
308
|
});
|
|
296
309
|
export function useForceInspection() {
|
|
297
310
|
const [updating, setUpdating] = useState(false);
|
|
@@ -74,6 +74,7 @@ const marginBottom = 50;
|
|
|
74
74
|
const marginTop = 20;
|
|
75
75
|
const marginRight = 20;
|
|
76
76
|
function BufferChartSVG({ width, height, series, disabled, }) {
|
|
77
|
+
const [emptyDate] = useState(() => new Date());
|
|
77
78
|
const [dateMin, dateMax] = useMemo(() => {
|
|
78
79
|
let min = null;
|
|
79
80
|
let max = null;
|
|
@@ -87,8 +88,8 @@ function BufferChartSVG({ width, height, series, disabled, }) {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
return [min ??
|
|
91
|
-
}, [series]);
|
|
91
|
+
return [min ?? emptyDate, max ?? emptyDate];
|
|
92
|
+
}, [series, emptyDate]);
|
|
92
93
|
const xScale = useMemo(() => scaleTime()
|
|
93
94
|
.range([0, width - marginLeft - marginRight])
|
|
94
95
|
.domain([dateMin, dateMax]), [width, dateMin, dateMax]);
|
|
@@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
31
31
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
32
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
33
|
*/
|
|
34
|
-
import { useMemo, useCallback,
|
|
34
|
+
import { useMemo, useCallback, useRef } from "react";
|
|
35
35
|
import { last30StationCycles } from "../../cell-status/station-cycles.js";
|
|
36
36
|
import { last30EstimatedCycleTimes } from "../../cell-status/estimated-cycle-times.js";
|
|
37
37
|
import { recentCycles } from "../../data/results.cycles.js";
|
|
@@ -43,7 +43,7 @@ import { localPoint } from "../../util/chart-helpers.js";
|
|
|
43
43
|
import { Stack } from "@mui/material";
|
|
44
44
|
import { Tooltip } from "../ChartTooltip.js";
|
|
45
45
|
import { currentCycles } from "../../data/current-cycles.js";
|
|
46
|
-
import { currentStatus } from "../../cell-status/current-status.js";
|
|
46
|
+
import { currentStatus, minutesSinceEpochAtom } from "../../cell-status/current-status.js";
|
|
47
47
|
import { last30Jobs } from "../../cell-status/scheduled-jobs.js";
|
|
48
48
|
import { atom, useAtomValue, useSetAtom } from "jotai";
|
|
49
49
|
import { fmsInformation } from "../../network/server-settings.js";
|
|
@@ -56,11 +56,12 @@ const occupiedNonOutlierColor = green[900];
|
|
|
56
56
|
const occupiedOutlierColor = red[700];
|
|
57
57
|
const simColor = grey[400];
|
|
58
58
|
const downtimeColor = grey[100];
|
|
59
|
-
|
|
59
|
+
const chartTime = atom((get) => Math.floor(get(minutesSinceEpochAtom) / 5) * 5 * 60000);
|
|
60
|
+
function useSimCycles(now) {
|
|
60
61
|
const jobs = useAtomValue(last30Jobs);
|
|
61
62
|
const statUse = useAtomValue(last30SimStationUse);
|
|
62
63
|
return useMemo(() => {
|
|
63
|
-
const cutoff = addHours(
|
|
64
|
+
const cutoff = addHours(now, -12);
|
|
64
65
|
return (LazySeq.of(statUse)
|
|
65
66
|
.filter((s) => s.end >= cutoff)
|
|
66
67
|
// make sure all planned downtimes come last so they are drawn over the top of any cycles
|
|
@@ -81,7 +82,7 @@ function useSimCycles() {
|
|
|
81
82
|
.toRArray(),
|
|
82
83
|
}))
|
|
83
84
|
.toRArray());
|
|
84
|
-
}, [jobs, statUse]);
|
|
85
|
+
}, [jobs, statUse, now]);
|
|
85
86
|
}
|
|
86
87
|
const tooltipData = atom(null);
|
|
87
88
|
const stationFontSize = 14;
|
|
@@ -213,30 +214,24 @@ function NowLine({ now, xScale, yScale, }) {
|
|
|
213
214
|
export function RecentCycleChart({ height, width }) {
|
|
214
215
|
const last30Cycles = useAtomValue(last30StationCycles);
|
|
215
216
|
const estimated = useAtomValue(last30EstimatedCycleTimes);
|
|
216
|
-
const sim = useSimCycles();
|
|
217
217
|
const currentSt = useAtomValue(currentStatus);
|
|
218
|
+
const time = useAtomValue(chartTime);
|
|
219
|
+
// Keep history filtering independent of frequent status updates.
|
|
220
|
+
const historyNow = useMemo(() => new Date(time), [time]);
|
|
221
|
+
// Align the axis and now marker with cycles started after the chart's last clock tick.
|
|
222
|
+
const displayNow = useMemo(() => new Date(Math.max(time, currentSt.timeOfCurrentStatusUTC.getTime())), [time, currentSt.timeOfCurrentStatusUTC]);
|
|
223
|
+
const sim = useSimCycles(historyNow);
|
|
218
224
|
const fmsInfo = useAtomValue(fmsInformation);
|
|
219
225
|
const cycles = useMemo(() => {
|
|
220
|
-
const cutoff = addHours(
|
|
226
|
+
const cutoff = addHours(historyNow, -12);
|
|
221
227
|
return recentCycles(last30Cycles.valuesToLazySeq().filter((e) => e.endTime >= cutoff));
|
|
222
|
-
}, [last30Cycles]);
|
|
228
|
+
}, [last30Cycles, historyNow]);
|
|
223
229
|
const current = useMemo(() => {
|
|
224
230
|
return currentCycles(currentSt, estimated, fmsInfo.loadStationNames);
|
|
225
231
|
}, [currentSt, estimated, fmsInfo.loadStationNames]);
|
|
226
|
-
|
|
227
|
-
const now = new Date();
|
|
228
|
-
const [, forceRerender] = useState(0);
|
|
229
|
-
const refreshRef = useRef(null);
|
|
230
|
-
useEffect(() => {
|
|
231
|
-
if (refreshRef.current !== null)
|
|
232
|
-
clearTimeout(refreshRef.current);
|
|
233
|
-
refreshRef.current = setTimeout(() => {
|
|
234
|
-
forceRerender((x) => x + 1);
|
|
235
|
-
}, 5 * 60 * 1000);
|
|
236
|
-
});
|
|
237
|
-
const { xScale, yScale, actualPlannedScale, marginLeft } = useScales(cycles, current, now, width, height);
|
|
232
|
+
const { xScale, yScale, actualPlannedScale, marginLeft } = useScales(cycles, current, displayNow, width, height);
|
|
238
233
|
const hideTooltipRef = useRef(null);
|
|
239
234
|
if (height <= 0 || width <= 0)
|
|
240
235
|
return null;
|
|
241
|
-
return (_jsxs("div", { style: { position: "relative", overflow: "hidden" }, children: [_jsx("svg", { height: height, width: width, children: _jsxs("g", { transform: `translate(${marginLeft}, ${marginTop})`, children: [_jsx("clipPath", { id: "recent-cycle-clip-body", children: _jsx("rect", { x: 0, y: 0, width: width - marginRight - marginLeft, height: height - marginBottom - marginTop }) }), _jsx(AxisAndGrid, { xScale: xScale, yScale: yScale }), _jsxs("g", { clipPath: "url(#recent-cycle-clip-body)", children: [_jsx(RecentSeries, { cycles: cycles, xScale: xScale, yScale: yScale, hideTooltipRef: hideTooltipRef, actualPlannedScale: actualPlannedScale }), _jsx(CurrentSeries, { now:
|
|
236
|
+
return (_jsxs("div", { style: { position: "relative", overflow: "hidden" }, children: [_jsx("svg", { height: height, width: width, children: _jsxs("g", { transform: `translate(${marginLeft}, ${marginTop})`, children: [_jsx("clipPath", { id: "recent-cycle-clip-body", children: _jsx("rect", { x: 0, y: 0, width: width - marginRight - marginLeft, height: height - marginBottom - marginTop }) }), _jsx(AxisAndGrid, { xScale: xScale, yScale: yScale }), _jsxs("g", { clipPath: "url(#recent-cycle-clip-body)", children: [_jsx(RecentSeries, { cycles: cycles, xScale: xScale, yScale: yScale, hideTooltipRef: hideTooltipRef, actualPlannedScale: actualPlannedScale }), _jsx(CurrentSeries, { now: displayNow, cycles: current, xScale: xScale, yScale: yScale, hideTooltipRef: hideTooltipRef, actualPlannedScale: actualPlannedScale }), _jsx(SimSeries, { sim: sim, xScale: xScale, yScale: yScale, actualPlannedScale: actualPlannedScale, hideTooltipRef: hideTooltipRef })] }), _jsx(NowLine, { now: displayNow, xScale: xScale, yScale: yScale })] }) }), _jsx(Tooltip, { chartHeight: height, chartWidth: width, atom: tooltipData, TooltipContent: RecentTooltip })] }));
|
|
242
237
|
}
|
|
@@ -41,6 +41,8 @@ export function CancelLoadButton({ onClose, ignoreOperator = false, }) {
|
|
|
41
41
|
expectedLoadCancellationId: snapshot.cancellationId,
|
|
42
42
|
reason: reason || undefined,
|
|
43
43
|
}));
|
|
44
|
+
setOpen(false);
|
|
45
|
+
setSnapshot(null);
|
|
44
46
|
setMaterialDialogOpen(null);
|
|
45
47
|
onClose?.();
|
|
46
48
|
}
|
|
@@ -41,7 +41,7 @@ import { currentStatus } from "../../cell-status/current-status.js";
|
|
|
41
41
|
import { barcodePotentialNewMaterial, inProcessMaterialInDialog, materialDialogOpen, materialInDialogInfo, materialInDialogLargestUsedProcess, materialInDialogLocalEvents, } from "../../cell-status/material-details.js";
|
|
42
42
|
import { currentOperator } from "../../data/operators.js";
|
|
43
43
|
import { fmsInformation } from "../../network/server-settings.js";
|
|
44
|
-
import { useAtomValue, useSetAtom } from "jotai";
|
|
44
|
+
import { useAtomValue, useAtomValueRawSync, useSetAtom } from "jotai";
|
|
45
45
|
import { useEffect, useRef } from "react";
|
|
46
46
|
import { last30Jobs } from "../../cell-status/scheduled-jobs.js";
|
|
47
47
|
import { PartIdenticon } from "./Material.js";
|
|
@@ -139,7 +139,8 @@ function InvalidateSelect(props) {
|
|
|
139
139
|
}
|
|
140
140
|
export function InvalidateCycleDialogContent(props) {
|
|
141
141
|
const curMat = useAtomValue(materialInDialogInfo);
|
|
142
|
-
|
|
142
|
+
// History can resolve while InvalidateSelect suspends, before this component subscribes.
|
|
143
|
+
const events = useAtomValueRawSync(materialInDialogLocalEvents);
|
|
143
144
|
const status = useAtomValue(currentStatus);
|
|
144
145
|
const show = props.st !== null && curMat !== null;
|
|
145
146
|
const boxRef = useRef(null);
|
|
@@ -48,7 +48,7 @@ import { instructionUrl } from "../../network/backend.js";
|
|
|
48
48
|
import { Tooltip } from "@mui/material";
|
|
49
49
|
import { Fab } from "@mui/material";
|
|
50
50
|
import { fmsInformation } from "../../network/server-settings.js";
|
|
51
|
-
import { currentStatus, secondsSinceEpochAtom } from "../../cell-status/current-status.js";
|
|
51
|
+
import { currentStatus, secondsSinceEpochAtom, minutesSinceEpochAtom, } from "../../cell-status/current-status.js";
|
|
52
52
|
import { useIsDemo, useSetTitle } from "../routes.js";
|
|
53
53
|
import { QuarantineMatButton } from "./QuarantineButton.js";
|
|
54
54
|
import { CancelLoadButton } from "./CancelLoadButton.js";
|
|
@@ -487,16 +487,17 @@ function MaterialColumn({ data, region, }) {
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
function RecentCompletedMaterial() {
|
|
490
|
+
const minutes = useAtomValue(minutesSinceEpochAtom);
|
|
490
491
|
const matSummary = useAtomValue(last30MaterialSummary);
|
|
491
492
|
const recentCompleted = useMemo(() => {
|
|
492
|
-
const cutoff = addHours(new Date(), -5);
|
|
493
|
+
const cutoff = addHours(new Date(minutes * 60000), -5);
|
|
493
494
|
return matSummary.matsById
|
|
494
495
|
.valuesToLazySeq()
|
|
495
496
|
.filter((e) => e.completed_last_proc_machining === true &&
|
|
496
497
|
e.last_unload_time !== undefined &&
|
|
497
498
|
e.last_unload_time >= cutoff)
|
|
498
499
|
.toSortedArray({ desc: (e) => e.last_unload_time?.getTime() ?? 0 });
|
|
499
|
-
}, [matSummary]);
|
|
500
|
+
}, [matSummary, minutes]);
|
|
500
501
|
return (_jsx("div", { children: recentCompleted.map((m, idx) => (_jsx(MatSummary, { mat: m }, idx))) }));
|
|
501
502
|
}
|
|
502
503
|
const CompletedCol = memo(function CompletedCol({ fillViewPort, showMaterial, }) {
|
|
@@ -34,7 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
34
34
|
import { useRef, useCallback, useEffect, useMemo, Fragment } from "react";
|
|
35
35
|
import JsBarcode from "jsbarcode";
|
|
36
36
|
import { LazySeq } from "@seedtactics/immutable-collections";
|
|
37
|
-
import { currentStatus } from "../../cell-status/current-status.js";
|
|
37
|
+
import { currentStatus, secondsSinceEpochAtom } from "../../cell-status/current-status.js";
|
|
38
38
|
import { useAtomValue } from "jotai";
|
|
39
39
|
import { useReactToPrint } from "react-to-print";
|
|
40
40
|
import { Button } from "@mui/material";
|
|
@@ -60,16 +60,20 @@ function Barcode(props) {
|
|
|
60
60
|
}, [props.text]);
|
|
61
61
|
return _jsx("svg", { ref: setRef });
|
|
62
62
|
}
|
|
63
|
+
function LabelDate() {
|
|
64
|
+
const seconds = useAtomValue(secondsSinceEpochAtom);
|
|
65
|
+
return new Date(seconds * 1000).toLocaleString(undefined, {
|
|
66
|
+
weekday: "long",
|
|
67
|
+
month: "long",
|
|
68
|
+
day: "numeric",
|
|
69
|
+
year: "numeric",
|
|
70
|
+
hour: "numeric",
|
|
71
|
+
minute: "2-digit",
|
|
72
|
+
second: "2-digit",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
63
75
|
function SinglePage(props) {
|
|
64
|
-
return (_jsxs("div", { children: [_jsx("div", { style: { marginTop: "4em", display: "flex", justifyContent: "center" }, children: _jsx("h2", { children: "SeedTactic: FMS Insight" }) }), _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: _jsx("p", { style: { fontSize: "x-large" }, children:
|
|
65
|
-
weekday: "long",
|
|
66
|
-
month: "long",
|
|
67
|
-
day: "numeric",
|
|
68
|
-
year: "numeric",
|
|
69
|
-
hour: "numeric",
|
|
70
|
-
minute: "2-digit",
|
|
71
|
-
second: "2-digit",
|
|
72
|
-
}) }) }), _jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.materialName ?? props.partName }), props.count >= 2 ? _jsx(Barcode, { text: props.count.toString() }) : undefined] }), props.serial1 && props.serial1 !== "" ? (_jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.serial1 }), props.serial2 && props.serial2 !== "" ? (_jsx(Barcode, { text: props.serial2 || "" })) : undefined] })) : undefined, _jsx("div", { style: { marginTop: "2em", marginLeft: "4em", marginRight: "4em", marginBottom: "9em" }, children: props.uniq === null || props.uniq === "" ? (_jsx("p", { style: { fontSize: "x-large" }, children: "Not currently assigned to any jobs" })) : (_jsxs(_Fragment, { children: [_jsx("p", { style: { fontSize: "x-large" }, children: "Assigned To" }), _jsxs("div", { style: { marginTop: "1em", marginLeft: "2em", display: "flex", alignItems: "center" }, children: [_jsx(Barcode, { text: props.partName }), _jsxs("h3", { style: { marginLeft: "4em" }, children: ["assigned to ", props.uniq, props.jobCycles && props.jobCycles > 0
|
|
76
|
+
return (_jsxs("div", { children: [_jsx("div", { style: { marginTop: "4em", display: "flex", justifyContent: "center" }, children: _jsx("h2", { children: "SeedTactic: FMS Insight" }) }), _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: _jsx("p", { style: { fontSize: "x-large" }, children: _jsx(LabelDate, {}) }) }), _jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.materialName ?? props.partName }), props.count >= 2 ? _jsx(Barcode, { text: props.count.toString() }) : undefined] }), props.serial1 && props.serial1 !== "" ? (_jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.serial1 }), props.serial2 && props.serial2 !== "" ? (_jsx(Barcode, { text: props.serial2 || "" })) : undefined] })) : undefined, _jsx("div", { style: { marginTop: "2em", marginLeft: "4em", marginRight: "4em", marginBottom: "9em" }, children: props.uniq === null || props.uniq === "" ? (_jsx("p", { style: { fontSize: "x-large" }, children: "Not currently assigned to any jobs" })) : (_jsxs(_Fragment, { children: [_jsx("p", { style: { fontSize: "x-large" }, children: "Assigned To" }), _jsxs("div", { style: { marginTop: "1em", marginLeft: "2em", display: "flex", alignItems: "center" }, children: [_jsx(Barcode, { text: props.partName }), _jsxs("h3", { style: { marginLeft: "4em" }, children: ["assigned to ", props.uniq, props.jobCycles && props.jobCycles > 0
|
|
73
77
|
? " (" + props.jobCycles.toString() + " scheduled)"
|
|
74
78
|
: ""] })] })] })) }), props.note ? (_jsx("div", { style: { marginBottom: "2em" }, children: _jsx("p", { style: { fontSize: "xxx-large", textAlign: "center" }, children: props.note }) })) : undefined, _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: props.operator ? _jsx("p", { style: { fontSize: "x-large" }, children: props.operator }) : undefined })] }));
|
|
75
79
|
}
|
|
@@ -114,15 +118,7 @@ function CombinedToOnePage(props) {
|
|
|
114
118
|
if (!props.material || props.material.length === 0) {
|
|
115
119
|
return _jsx("div", {});
|
|
116
120
|
}
|
|
117
|
-
return (_jsxs("div", { children: [_jsx("div", { style: { marginTop: "4em", display: "flex", justifyContent: "center" }, children: _jsx("h2", { children: "SeedTactic: FMS Insight" }) }), _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: _jsx("p", { style: { fontSize: "x-large" }, children:
|
|
118
|
-
weekday: "long",
|
|
119
|
-
month: "long",
|
|
120
|
-
day: "numeric",
|
|
121
|
-
year: "numeric",
|
|
122
|
-
hour: "numeric",
|
|
123
|
-
minute: "2-digit",
|
|
124
|
-
second: "2-digit",
|
|
125
|
-
}) }) }), _jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.materialName ?? props.material[0].partName }), props.material.length >= 2 ? (_jsx(Barcode, { text: props.material.length.toString() })) : undefined] }), props.material[0].serial && props.material[0].serial !== "" ? (_jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.material[0].serial }), props.material.length >= 2 &&
|
|
121
|
+
return (_jsxs("div", { children: [_jsx("div", { style: { marginTop: "4em", display: "flex", justifyContent: "center" }, children: _jsx("h2", { children: "SeedTactic: FMS Insight" }) }), _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: _jsx("p", { style: { fontSize: "x-large" }, children: _jsx(LabelDate, {}) }) }), _jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.materialName ?? props.material[0].partName }), props.material.length >= 2 ? (_jsx(Barcode, { text: props.material.length.toString() })) : undefined] }), props.material[0].serial && props.material[0].serial !== "" ? (_jsxs("div", { style: { marginTop: "2em", display: "flex", justifyContent: "space-around" }, children: [_jsx(Barcode, { text: props.material[0].serial }), props.material.length >= 2 &&
|
|
126
122
|
props.material[props.material.length - 1].serial &&
|
|
127
123
|
props.material[props.material.length - 1].serial !== "" ? (_jsx(Barcode, { text: props.material[props.material.length - 1].serial || "" })) : undefined] })) : undefined, _jsx("div", { style: { marginTop: "2em", marginLeft: "4em", marginRight: "4em", marginBottom: "9em" }, children: assignments.length === 0 ? (_jsx("p", { style: { fontSize: "x-large" }, children: "Not currently assigned to any jobs" })) : props.materialName ? (_jsxs(_Fragment, { children: [_jsx("p", { style: { fontSize: "x-large" }, children: "Assigned To" }), assignments.map(([uniq, part], idx) => (_jsxs("div", { style: {
|
|
128
124
|
marginTop: "1em",
|
|
@@ -36,7 +36,7 @@ import { Paper, ButtonBase, Box, Typography, Collapse, Stack, Menu, MenuItem, Li
|
|
|
36
36
|
import { Close as CloseIcon } from "@mui/icons-material";
|
|
37
37
|
import { InProcMaterial, MaterialAction, MaterialDialog, PartIdenticon } from "./Material.js";
|
|
38
38
|
import { ActionType, BasketLocationEnum, LocType, PalletLocationEnum, } from "../../network/api.js";
|
|
39
|
-
import { currentStatus, secondsSinceEpochAtom } from "../../cell-status/current-status.js";
|
|
39
|
+
import { currentStatus, secondsSinceEpochAtom, minutesSinceEpochAtom, } from "../../cell-status/current-status.js";
|
|
40
40
|
import { LazySeq, OrderedMap } from "@seedtactics/immutable-collections";
|
|
41
41
|
import { materialDialogOpen } from "../../cell-status/material-details.js";
|
|
42
42
|
import { last30Jobs } from "../../cell-status/scheduled-jobs.js";
|
|
@@ -53,6 +53,7 @@ import { basketDisplayName, loadStationDisplayName } from "../../cell-status/sta
|
|
|
53
53
|
const CollapsedIconSize = 45;
|
|
54
54
|
const rowSize = CollapsedIconSize + 10; // each material row has 5px above and 5px below for padding
|
|
55
55
|
function useCellOverview() {
|
|
56
|
+
const minutes = useAtomValue(minutesSinceEpochAtom);
|
|
56
57
|
const currentSt = useAtomValue(currentStatus);
|
|
57
58
|
const jobs = useAtomValue(last30Jobs);
|
|
58
59
|
const fmsInfo = useAtomValue(fmsInformation);
|
|
@@ -128,7 +129,7 @@ function useCellOverview() {
|
|
|
128
129
|
.toOrderedMap((p) => [p.pallet.palletNum, p]);
|
|
129
130
|
let maxLoadNum = 1;
|
|
130
131
|
let maxNumFacesOnPallet = 1;
|
|
131
|
-
const cutoff = addDays(new Date(), -7);
|
|
132
|
+
const cutoff = addDays(new Date(minutes * 60000), -7);
|
|
132
133
|
// now add empty locations
|
|
133
134
|
// Materialize ProcessInfo so we can iterate it at two levels (proc for basket stations, paths for the rest)
|
|
134
135
|
const allProcs = jobs
|
package/dist/extension.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AppProps } from "./components/App.js";
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function bootstrapInsight(appProps?: AppProps, rootElement?: HTMLElement): Promise<void>;
|
|
6
6
|
export { customState } from "./cell-status/custom-state.js";
|
|
7
|
+
export { currentStatusIsLive } from "./network/websocket.js";
|
|
7
8
|
export type { AppProps };
|
|
8
9
|
export { defaultChooseModes } from "./components/ChooseMode.js";
|
|
9
10
|
export type { ChooseModeItem } from "./components/ChooseMode.js";
|
package/dist/extension.js
CHANGED
|
@@ -16,6 +16,7 @@ export async function bootstrapInsight(appProps = {}, rootElement) {
|
|
|
16
16
|
render(appProps, root, store);
|
|
17
17
|
}
|
|
18
18
|
export { customState } from "./cell-status/custom-state.js";
|
|
19
|
+
export { currentStatusIsLive } from "./network/websocket.js";
|
|
19
20
|
export { defaultChooseModes } from "./components/ChooseMode.js";
|
|
20
21
|
export { RouteLocation } from "./components/routes.js";
|
|
21
22
|
export { authenticatedFetch } from "./network/backend.js";
|
package/dist/index.html
CHANGED
|
@@ -5,45 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
6
6
|
<link rel="shortcut icon" href="/assets/favicon-gWFxiJK2.ico" />
|
|
7
7
|
<title>SeedTactic FMS Insight</title>
|
|
8
|
-
<style type="text/css">
|
|
9
|
-
|
|
10
|
-
width: 100px;
|
|
11
|
-
height: 100px;
|
|
12
|
-
display: inline-block;
|
|
13
|
-
}
|
|
14
|
-
.download-circular {
|
|
15
|
-
width: 100px;
|
|
16
|
-
height: 100px;
|
|
17
|
-
animation: rotate 2s linear infinite;
|
|
18
|
-
}
|
|
19
|
-
.download-path {
|
|
20
|
-
stroke-dasharray: 1, 200;
|
|
21
|
-
stroke-dashoffset: 0;
|
|
22
|
-
animation: dash 1.5s ease-out infinite;
|
|
23
|
-
stroke-linecap: round;
|
|
24
|
-
stroke: #8d6e63;
|
|
25
|
-
}
|
|
26
|
-
@keyframes rotate {
|
|
27
|
-
100% {
|
|
28
|
-
transform: rotate(360deg);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
@keyframes dash {
|
|
32
|
-
0% {
|
|
33
|
-
stroke-dasharray: 1, 200;
|
|
34
|
-
stroke-dashoffset: 0;
|
|
35
|
-
}
|
|
36
|
-
50% {
|
|
37
|
-
stroke-dasharray: 89, 200;
|
|
38
|
-
stroke-dashoffset: -35px;
|
|
39
|
-
}
|
|
40
|
-
100% {
|
|
41
|
-
stroke-dasharray: 89, 200;
|
|
42
|
-
stroke-dashoffset: -124px;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
</style>
|
|
46
|
-
<script type="module" crossorigin src="/assets/index-kk-GRa5i.js"></script>
|
|
8
|
+
<style type="text/css">.download-loader{width:100px;height:100px;display:inline-block}.download-circular{width:100px;height:100px;animation:2s linear infinite rotate}.download-path{stroke-dasharray:1 200;stroke-dashoffset:0;stroke-linecap:round;stroke:#8d6e63;animation:1.5s ease-out infinite dash}@keyframes rotate{to{transform:rotate(360deg)}}@keyframes dash{0%{stroke-dasharray:1 200;stroke-dashoffset:0}50%{stroke-dasharray:89 200;stroke-dashoffset:-35px}to{stroke-dasharray:89 200;stroke-dashoffset:-124px}}</style>
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-Dxxgr-QV.js"></script>
|
|
47
10
|
<link rel="stylesheet" crossorigin href="/assets/index-DXfAwuj-.css">
|
|
48
11
|
</head>
|
|
49
12
|
<body>
|