@seedtactics/insight-client 16.6.0 → 16.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/assets/ProgramHighlight-DPTeZ8Si.js +3 -0
  2. package/dist/assets/index-DL7cXxkt.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 +30 -32
  109. package/dist/assets/ProgramHighlight-LvRM40Qr.js +0 -3
  110. package/dist/assets/index-gAFi3Oss.js +0 -364
@@ -32,7 +32,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  */
34
34
  import { useTransition } from "react";
35
- import { Notifications, HelpOutline, ExitToApp } from "@mui/icons-material";
35
+ import { Notifications, HelpOutlined, ExitToApp } from "@mui/icons-material";
36
36
  import { Typography, Tooltip, Badge, IconButton, AppBar, Toolbar, Box, List, ListItem, ListItemIcon, ListItemButton, ListItemText, ListSubheader, Paper, Select, MenuItem, FormControl, } from "@mui/material";
37
37
  import { startOfToday, differenceInDays } from "date-fns";
38
38
  import { currentStatus } from "../cell-status/current-status";
@@ -77,7 +77,7 @@ function Alarms() {
77
77
  }
78
78
  function HelpButton() {
79
79
  const route = useAtomValue(currentRoute);
80
- return (_jsx(Tooltip, { title: "Help", children: _jsx(IconButton, { "aria-label": "Help", href: helpUrl(route), target: "_help", size: "large", children: _jsx(HelpOutline, {}) }) }));
80
+ return (_jsx(Tooltip, { title: "Help", children: _jsx(IconButton, { "aria-label": "Help", href: helpUrl(route), target: "_help", size: "large", children: _jsx(HelpOutlined, {}) }) }));
81
81
  }
82
82
  function LogoutButton() {
83
83
  return (_jsx(Tooltip, { title: "Logout", children: _jsx(IconButton, { "aria-label": "Logout", onClick: logout, size: "large", children: _jsx(ExitToApp, {}) }) }));
@@ -98,11 +98,37 @@ function MenuNavSelect({ menuNavs }) {
98
98
  const item = menuNavs.find((i) => ("separator" in i ? false : i.route.route === curRoute.route));
99
99
  if (!item || "separator" in item)
100
100
  return null;
101
- return (_jsxs(Box, { display: "flex", alignItems: "center", children: [item.icon, _jsxs(Typography, { variant: "h6", style: { marginLeft: "1em" }, children: [typeof item.name === "string" ? item.name : item.name(fmsInfo), isPending ? "..." : ""] })] }));
101
+ return (_jsxs(Box, { sx: {
102
+ display: "flex",
103
+ alignItems: "center",
104
+ }, children: [item.icon, _jsxs(Typography, { variant: "h6", style: { marginLeft: "1em" }, children: [typeof item.name === "string" ? item.name : item.name(fmsInfo), isPending ? "..." : ""] })] }));
102
105
  }, children: menuNavs.map((item, idx) => "separator" in item ? (_jsx(ListSubheader, { children: item.separator }, item.separator)) : item.hidden?.(fmsInfo) ? undefined : (_jsxs(MenuItem, { value: item.route.route, children: [_jsx(ListItemIcon, { children: item.icon }), _jsx(ListItemText, { primary: typeof item.name === "string" ? item.name : item.name(fmsInfo) })] }, idx))) }) }));
103
106
  }
104
107
  export function Header({ showAlarms, showLogout, showSearch, showOperator, Nav1, Nav2, menuNavs, }) {
105
- return (_jsxs(_Fragment, { children: [_jsx(AppBar, { position: "static", sx: { display: { xs: "none", md: "block" } }, children: _jsx(Toolbar, { children: _jsxs(Box, { display: "grid", gridTemplateColumns: Nav2 ? "1fr auto 1fr" : "minmax(200px, 1fr) auto", width: "100vw", gridTemplateAreas: Nav2 ? '"nav navCenter tools"' : '"nav tools"', children: [_jsxs(Box, { gridArea: "nav", display: "flex", alignItems: "center", children: [_jsx(Brand, {}), Nav1 ? _jsx(Nav1, {}) : undefined] }), Nav2 ? (_jsx(Box, { gridArea: "navCenter", children: _jsx(Nav2, {}) })) : undefined, _jsxs(Box, { gridArea: "tools", display: "flex", alignItems: "center", justifyContent: "flex-end", children: [showOperator ? _jsx(OperatorSelect, {}) : undefined, _jsx(ToolButtons, { showAlarms, showLogout, showSearch })] })] }) }) }), _jsxs(AppBar, { position: "static", sx: { display: { xs: "block", md: "none" } }, children: [_jsxs(Toolbar, { children: [_jsx(Brand, {}), _jsx("div", { style: { flexGrow: 1 } }), _jsx(ToolButtons, { showAlarms, showLogout, showSearch })] }), _jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(200px, 1fr) auto", children: [_jsx(Box, { gridColumn: "1", children: Nav1 ? _jsx(Nav1, {}) : undefined }), _jsx(Box, { gridColumn: "2", children: Nav2 ? _jsx(Nav2, {}) : undefined })] })] }), menuNavs && menuNavs.length > 0 ? (_jsx(Paper, { elevation: 4, square: true, component: "nav", sx: { display: { xs: "block", xl: "none" }, padding: "2px" }, children: _jsx(MenuNavSelect, { menuNavs: menuNavs }) })) : undefined] }));
108
+ return (_jsxs(_Fragment, { children: [_jsx(AppBar, { position: "static", sx: { display: { xs: "none", md: "block" } }, children: _jsx(Toolbar, { children: _jsxs(Box, { sx: {
109
+ display: "grid",
110
+ gridTemplateColumns: Nav2 ? "1fr auto 1fr" : "minmax(200px, 1fr) auto",
111
+ width: "100vw",
112
+ gridTemplateAreas: Nav2 ? '"nav navCenter tools"' : '"nav tools"',
113
+ }, children: [_jsxs(Box, { sx: {
114
+ gridArea: "nav",
115
+ display: "flex",
116
+ alignItems: "center",
117
+ }, children: [_jsx(Brand, {}), Nav1 ? _jsx(Nav1, {}) : undefined] }), Nav2 ? (_jsx(Box, { sx: {
118
+ gridArea: "navCenter",
119
+ }, children: _jsx(Nav2, {}) })) : undefined, _jsxs(Box, { sx: {
120
+ gridArea: "tools",
121
+ display: "flex",
122
+ alignItems: "center",
123
+ justifyContent: "flex-end",
124
+ }, children: [showOperator ? _jsx(OperatorSelect, {}) : undefined, _jsx(ToolButtons, { showAlarms, showLogout, showSearch })] })] }) }) }), _jsxs(AppBar, { position: "static", sx: { display: { xs: "block", md: "none" } }, children: [_jsxs(Toolbar, { children: [_jsx(Brand, {}), _jsx("div", { style: { flexGrow: 1 } }), _jsx(ToolButtons, { showAlarms, showLogout, showSearch })] }), _jsxs(Box, { sx: {
125
+ display: "grid",
126
+ gridTemplateColumns: "minmax(200px, 1fr) auto",
127
+ }, children: [_jsx(Box, { sx: {
128
+ gridColumn: "1",
129
+ }, children: Nav1 ? _jsx(Nav1, {}) : undefined }), _jsx(Box, { sx: {
130
+ gridColumn: "2",
131
+ }, children: Nav2 ? _jsx(Nav2, {}) : undefined })] })] }), menuNavs && menuNavs.length > 0 ? (_jsx(Paper, { elevation: 4, square: true, component: "nav", sx: { display: { xs: "block", xl: "none" }, padding: "2px" }, children: _jsx(MenuNavSelect, { menuNavs: menuNavs }) })) : undefined] }));
106
132
  }
107
133
  export function SideMenu({ menuItems }) {
108
134
  const fmsInfo = useAtomValue(fmsInformation);
@@ -42,7 +42,11 @@ export const AnalysisSelectToolbar = memo(function AnalysisSelectToolbar() {
42
42
  const [selMonth, analyzeMonth] = useAtom(selectedMonth);
43
43
  const setMonthWithoutLoading = useSetSpecificMonthWithoutLoading();
44
44
  const setLast30 = useSetLast30();
45
- return (_jsxs(Stack, { component: "nav", direction: "row", spacing: 3, paddingLeft: "24px", paddingRight: "24px", alignItems: "center", children: [_jsx(FormControlLabel, { control: _jsx(Radio, { checked: period.type === "Last30", color: "secondary", onChange: (e, checked) => (checked ? setLast30() : null) }), label: "Last 30 days" }), _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx(FormControlLabel, { control: _jsx(Radio, { checked: period.type === "SpecificMonth", color: "secondary", onChange: (e, checked) => (checked ? analyzeMonth(selMonth) : null) }), label: "Select Month" }), _jsx(MonthSelect, { curMonth: selMonth, onSelectMonth: (m) => {
45
+ return (_jsxs(Stack, { component: "nav", direction: "row", spacing: 3, sx: {
46
+ paddingLeft: "24px",
47
+ paddingRight: "24px",
48
+ alignItems: "center",
49
+ }, children: [_jsx(FormControlLabel, { control: _jsx(Radio, { checked: period.type === "Last30", color: "secondary", onChange: (e, checked) => (checked ? setLast30() : null) }), label: "Last 30 days" }), _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx(FormControlLabel, { control: _jsx(Radio, { checked: period.type === "SpecificMonth", color: "secondary", onChange: (e, checked) => (checked ? analyzeMonth(selMonth) : null) }), label: "Select Month" }), _jsx(MonthSelect, { curMonth: selMonth, onSelectMonth: (m) => {
46
50
  if (period.type === "SpecificMonth") {
47
51
  // if month type is selected, reload data
48
52
  analyzeMonth(m);
@@ -0,0 +1 @@
1
+ export declare function BasketCycleChart(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,145 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /* Copyright (c) 2026, John Lenz
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above
13
+ copyright notice, this list of conditions and the following
14
+ disclaimer in the documentation and/or other materials provided
15
+ with the distribution.
16
+
17
+ * Neither the name of John Lenz, Black Maple Software, SeedTactics,
18
+ nor the names of other contributors may be used to endorse or
19
+ promote products derived from this software without specific
20
+ prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
+ */
34
+ import { addMonths, addDays, startOfToday } from "date-fns";
35
+ import { Box, FormControl, Typography, Tooltip, IconButton, MenuItem, Select } from "@mui/material";
36
+ import { ImportExport } from "@mui/icons-material";
37
+ import { useCallback } from "react";
38
+ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
39
+ import { LazySeq } from "@seedtactics/immutable-collections";
40
+ import { selectedAnalysisPeriod } from "../../network/load-specific-month.js";
41
+ import { CycleChart } from "./CycleChart.js";
42
+ import { copyPalletCyclesToClipboard } from "../../data/results.cycles.js";
43
+ import { useSetTitle } from "../routes.js";
44
+ import { last30BasketCycles, specificMonthBasketCycles, } from "../../cell-status/basket-cycles.js";
45
+ import { PartIdenticon } from "../station-monitor/Material.js";
46
+ import { materialDialogOpen } from "../../cell-status/material-details.js";
47
+ import { fmsInformation } from "../../network/server-settings.js";
48
+ import { basketDisplayName } from "../../cell-status/station-cycles.js";
49
+ const selectedBasketAtom = atom(undefined);
50
+ const selectedPartAtom = atom(undefined);
51
+ const zoomDateRangeAtom = atom(undefined);
52
+ function isBasketCycleData(point) {
53
+ return "mats" in point && Array.isArray(point.mats);
54
+ }
55
+ const yZoomAtom = atom(null);
56
+ const filteredPoints = atom((get) => {
57
+ const selBasket = get(selectedBasketAtom);
58
+ const selPart = get(selectedPartAtom);
59
+ if (!selBasket && !selPart)
60
+ return new Map();
61
+ const period = get(selectedAnalysisPeriod);
62
+ const basketCycles = get(period.type === "Last30" ? last30BasketCycles : specificMonthBasketCycles);
63
+ if (selBasket) {
64
+ let cyclesForBasket = basketCycles.get(selBasket)?.valuesToLazySeq() ?? LazySeq.of([]);
65
+ if (selPart) {
66
+ cyclesForBasket = cyclesForBasket.filter((c) => c.mats.some((m) => m.part === selPart.part && m.proc === selPart.proc));
67
+ }
68
+ return new Map([
69
+ [selBasket.toString(), Array.from(cyclesForBasket)],
70
+ ]);
71
+ }
72
+ else if (selPart) {
73
+ return LazySeq.of(basketCycles)
74
+ .collect(([basket, cycles]) => {
75
+ const cyclesForPart = cycles
76
+ .valuesToLazySeq()
77
+ .filter((c) => c.mats.some((m) => m.part === selPart.part && m.proc === selPart.proc))
78
+ .toRArray();
79
+ if (cyclesForPart.length > 0) {
80
+ return [basket, cyclesForPart];
81
+ }
82
+ else {
83
+ return null;
84
+ }
85
+ })
86
+ .toRMap(([basket, cycles]) => [basket.toString(), cycles]);
87
+ }
88
+ else {
89
+ return new Map();
90
+ }
91
+ });
92
+ const allPartNames = atom((get) => {
93
+ const period = get(selectedAnalysisPeriod);
94
+ const basketCycles = get(period.type === "Last30" ? last30BasketCycles : specificMonthBasketCycles);
95
+ return LazySeq.of(basketCycles)
96
+ .flatMap(([, cycles]) => cycles)
97
+ .flatMap(([, c]) => c.mats)
98
+ .distinctAndSortBy((m) => m.part, (m) => m.proc)
99
+ .map((m) => ({ part: m.part, proc: m.proc }))
100
+ .toRArray();
101
+ });
102
+ export function BasketCycleChart() {
103
+ const fmsInfo = useAtomValue(fmsInformation);
104
+ const basketName = basketDisplayName(fmsInfo.basketName);
105
+ useSetTitle(`${basketName} Cycles`);
106
+ const [selectedBasket, setSelectedBasket] = useAtom(selectedBasketAtom);
107
+ const [selectedPart, setSelectedPart] = useAtom(selectedPartAtom);
108
+ const [zoomDateRange, setZoomRange] = useAtom(zoomDateRangeAtom);
109
+ const [yZoom, setYZoom] = useAtom(yZoomAtom);
110
+ const period = useAtomValue(selectedAnalysisPeriod);
111
+ const defaultDateRange = period.type === "Last30"
112
+ ? [addDays(startOfToday(), -29), addDays(startOfToday(), 1)]
113
+ : [period.month, addMonths(period.month, 1)];
114
+ const basketCycles = useAtomValue(period.type === "Last30" ? last30BasketCycles : specificMonthBasketCycles);
115
+ const points = useAtomValue(filteredPoints);
116
+ const partNames = useAtomValue(allPartNames);
117
+ const setMatToShow = useSetAtom(materialDialogOpen);
118
+ const extraBasketTooltip = useCallback(function extraBasketTooltip(point) {
119
+ if (!isBasketCycleData(point)) {
120
+ return [];
121
+ }
122
+ return point.mats.map((mat) => ({
123
+ title: mat.part + "-" + mat.proc,
124
+ value: mat.serial ?? "",
125
+ link: mat.serial ? () => setMatToShow({ type: "LogMat", logMat: mat }) : undefined,
126
+ }));
127
+ }, [setMatToShow]);
128
+ return (_jsxs(Box, { sx: {
129
+ paddingLeft: "24px",
130
+ paddingRight: "24px",
131
+ paddingTop: "10px",
132
+ }, children: [_jsxs(Box, { component: "nav", sx: {
133
+ display: "flex",
134
+ minHeight: "2.5em",
135
+ alignItems: "center",
136
+ maxWidth: "calc(100vw - 24px - 24px)",
137
+ }, children: [_jsxs(Typography, { variant: "subtitle1", children: [basketName, " Cycles"] }), _jsx(Box, { sx: { flexGrow: 1 } }), _jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, displayEmpty: true, value: selectedBasket ?? -1, onChange: (e) => setSelectedBasket(e.target.value === -1 ? undefined : (e.target.value)), children: [_jsx(MenuItem, { value: -1, children: _jsxs("em", { children: ["Any ", basketName] }) }, 0), basketCycles
138
+ .keysToLazySeq()
139
+ .sortBy((x) => x)
140
+ .map((n) => (_jsx(MenuItem, { value: n, children: _jsx("div", { style: { display: "flex", alignItems: "center" }, children: _jsx("span", { style: { marginRight: "1em" }, children: n }) }) }, n)))] }) }), partNames.length > 0 ? (_jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, displayEmpty: true, value: selectedPart
141
+ ? partNames.findIndex((o) => selectedPart.part === o.part && selectedPart.proc === o.proc)
142
+ : -1, style: { marginLeft: "1em" }, onChange: (e) => {
143
+ setSelectedPart(e.target.value === -1 ? undefined : partNames[e.target.value]);
144
+ }, children: [_jsx(MenuItem, { value: -1, children: _jsx("em", { children: "Any Part" }) }, 0), partNames.map((n, idx) => (_jsx(MenuItem, { value: idx, children: _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx(PartIdenticon, { part: n.part, size: 20 }), _jsxs("span", { style: { marginRight: "1em" }, children: [n.part, "-", n.proc] })] }) }, idx)))] }) })) : undefined, _jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { onClick: () => copyPalletCyclesToClipboard(basketCycles, basketName), style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, size: "large", children: _jsx(ImportExport, {}) }) })] }), _jsx("main", { children: _jsx(CycleChart, { points: points, series_label: basketName, default_date_range: defaultDateRange, current_date_zoom: zoomDateRange, set_date_zoom_range: (z) => setZoomRange(z.zoom), yZoom: yZoom, setYZoom: setYZoom, extra_tooltip: extraBasketTooltip }) })] }));
145
+ }
@@ -55,7 +55,7 @@ const AnimatedPath = memo(function AnimatedPath({ series, xScale, yScale, color,
55
55
  .y((p) => yScale(p.y))(series.points) ?? "";
56
56
  // don't update in quick succession
57
57
  const previous = useRef(d);
58
- // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ // oxlint-disable-next-line react/exhaustive-deps
59
59
  const setPrevious = useCallback(debounce((val) => {
60
60
  previous.current = val;
61
61
  }, 50), []);
@@ -125,15 +125,21 @@ const BufferChart = memo(function BufferChart(props) {
125
125
  }, children: width && height && width > 0 && height > 0 && (_jsx(BufferChartSVG, { width: width, height: height, series: series, disabled: disabledBuffers })) }), _jsx("div", { style: { marginTop: "1em", display: "flex", flexWrap: "wrap", justifyContent: "space-around" }, children: series.map((s, idx) => (_jsx(ToggleButton, { selected: !disabledBuffers.has(s.label), value: s.label, onChange: () => setDisabledBuffers((db) => (db.has(s.label) ? db.delete(s.label) : db.add(s.label))), children: _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx("div", { style: { width: "14px", height: "14px", backgroundColor: seriesColor(idx, series.length) } }), _jsx("div", { style: { marginLeft: "1em" }, children: s.label })] }) }, s.label))) })] }));
126
126
  });
127
127
  // https://github.com/mui-org/material-ui/issues/20191
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ // oxlint-disable-next-line typescript/no-explicit-any
129
129
  const SliderAny = Slider;
130
130
  export function BufferOccupancyChart() {
131
131
  useSetTitle("Buffer Occupancy");
132
132
  const [movingAverageHours, setMovingAverage] = useState(12);
133
- return (_jsxs(Box, { paddingLeft: "24px", paddingRight: "24px", paddingTop: "10px", children: [_jsxs(Box, { component: "nav", sx: {
133
+ return (_jsxs(Box, { sx: {
134
+ paddingLeft: "24px",
135
+ paddingRight: "24px",
136
+ paddingTop: "10px",
137
+ }, children: [_jsxs(Box, { component: "nav", sx: {
134
138
  display: "flex",
135
139
  minHeight: "2.5em",
136
140
  alignItems: "center",
137
141
  maxWidth: "calc(100vw - 24px - 24px)",
138
- }, children: [_jsx(Typography, { variant: "subtitle1", children: "Average material occupancy of rotary tables and stocker positions over time" }), _jsx(Box, { flexGrow: 1 }), _jsx("span", { style: { fontSize: "small", marginRight: "1em" }, children: "Moving Average Window: " }), _jsx(SliderAny, { style: { width: "10em" }, min: 1, max: 36, steps: 0.2, valueLabelDisplay: "off", value: movingAverageHours, onChange: (e, v) => setMovingAverage(v) })] }), _jsx("main", { children: _jsx(BufferChart, { movingAverageDistanceInHours: movingAverageHours }) })] }));
142
+ }, children: [_jsx(Typography, { variant: "subtitle1", children: "Average material occupancy of rotary tables and stocker positions over time" }), _jsx(Box, { sx: {
143
+ flexGrow: 1,
144
+ } }), _jsx("span", { style: { fontSize: "small", marginRight: "1em" }, children: "Moving Average Window: " }), _jsx(SliderAny, { style: { width: "10em" }, min: 1, max: 36, steps: 0.2, valueLabelDisplay: "off", value: movingAverageHours, onChange: (e, v) => setMovingAverage(v) })] }), _jsx("main", { children: _jsx(BufferChart, { movingAverageDistanceInHours: movingAverageHours }) })] }));
139
145
  }
@@ -91,12 +91,14 @@ const computedCosts = atom((get) => {
91
91
  function AutomationCostInput() {
92
92
  const [cost, setCost] = useState(null);
93
93
  const [automationCost, saveAutomationCostPerYear] = useAtom(automationCostPerYear);
94
- return (_jsx(TextField, { id: "auotmation-cost-year", type: "number", label: "Cost for automated handling system per year", style: { marginTop: "1.5em" }, inputProps: { min: 0 }, variant: "outlined", value: cost === null ? (automationCost ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
94
+ return (_jsx(TextField, { id: "auotmation-cost-year", type: "number", label: "Cost for automated handling system per year", style: { marginTop: "1.5em" }, variant: "outlined", value: cost === null ? (automationCost ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
95
95
  if (cost != null) {
96
96
  const newCost = isNaN(cost) ? null : cost;
97
97
  saveAutomationCostPerYear(newCost);
98
98
  }
99
99
  setCost(null);
100
+ }, slotProps: {
101
+ htmlInput: { min: 0 },
100
102
  } }));
101
103
  }
102
104
  function LaborCost() {
@@ -107,18 +109,20 @@ function LaborCost() {
107
109
  return (_jsx(TextField, { type: "number", label: "Total labor cost for " +
108
110
  (month === null
109
111
  ? "last 30 days"
110
- : month.toLocaleDateString(undefined, { month: "long", year: "numeric" })), inputProps: { min: 0 }, variant: "outlined", value: cost === null ? (laborCost ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
112
+ : month.toLocaleDateString(undefined, { month: "long", year: "numeric" })), variant: "outlined", value: cost === null ? (laborCost ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
111
113
  if (cost != null) {
112
114
  const newCost = isNaN(cost) ? null : cost;
113
115
  saveCost(newCost);
114
116
  }
115
117
  setCost(null);
118
+ }, slotProps: {
119
+ htmlInput: { min: 0 },
116
120
  } }));
117
121
  }
118
122
  function SingleStationCostInput(props) {
119
123
  const [cost, setCost] = useState(null);
120
124
  const [machineCost, saveCost] = useAtom(machineCostPerYear);
121
- return (_jsx(TextField, { type: "number", inputProps: { min: 0 }, variant: "outlined", label: "Cost for " + props.machineGroup + " per station per year", style: { marginTop: "1.5em" }, value: cost === null ? (machineCost[props.machineGroup] ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
125
+ return (_jsx(TextField, { type: "number", variant: "outlined", label: "Cost for " + props.machineGroup + " per station per year", style: { marginTop: "1.5em" }, value: cost === null ? (machineCost[props.machineGroup] ?? "") : isNaN(cost) ? "" : cost, onChange: (e) => setCost(parseFloat(e.target.value)), onBlur: () => {
122
126
  if (cost != null) {
123
127
  const newCost = { ...machineCost };
124
128
  if (isNaN(cost)) {
@@ -130,6 +134,8 @@ function SingleStationCostInput(props) {
130
134
  saveCost(newCost);
131
135
  }
132
136
  setCost(null);
137
+ }, slotProps: {
138
+ htmlInput: { min: 0 },
133
139
  } }));
134
140
  }
135
141
  function StationCostInputs() {
@@ -148,17 +154,25 @@ const pctFormat = new Intl.NumberFormat(undefined, {
148
154
  export function CostBreakdownPage() {
149
155
  useSetTitle("Cost Percentages");
150
156
  const costs = useAtomValue(costPercentages);
151
- return (_jsxs(Box, { paddingLeft: "24px", paddingRight: "24px", paddingTop: "10px", children: [_jsxs(Box, { component: "nav", sx: {
157
+ return (_jsxs(Box, { sx: {
158
+ paddingLeft: "24px",
159
+ paddingRight: "24px",
160
+ paddingTop: "10px",
161
+ }, children: [_jsxs(Box, { component: "nav", sx: {
152
162
  display: "flex",
153
163
  minHeight: "2.5em",
154
164
  alignItems: "center",
155
165
  maxWidth: "calc(100vw - 24px - 24px)",
156
- }, children: [_jsx(Typography, { variant: "subtitle1", children: "Part Cost Percentage Breakdown" }), _jsx(Box, { flexGrow: 1 }), _jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, onClick: () => copyCostBreakdownToClipboard(costs), size: "large", children: _jsx(ImportExport, {}) }) })] }), _jsx("main", { children: _jsxs(Table, { stickyHeader: true, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Part" }), _jsx(TableCell, { align: "right", children: "Completed Quantity" }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsxs(TableCell, { align: "right", children: [m, " Cost %"] }, m))), _jsx(TableCell, { align: "right", children: "Labor Cost %" }), _jsx(TableCell, { align: "right", children: "Automation Cost %" })] }) }), _jsx(TableBody, { children: LazySeq.of(costs.parts)
166
+ }, children: [_jsx(Typography, { variant: "subtitle1", children: "Part Cost Percentage Breakdown" }), _jsx(Box, { sx: {
167
+ flexGrow: 1,
168
+ } }), _jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, onClick: () => copyCostBreakdownToClipboard(costs), size: "large", children: _jsx(ImportExport, {}) }) })] }), _jsx("main", { children: _jsxs(Table, { stickyHeader: true, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Part" }), _jsx(TableCell, { align: "right", children: "Completed Quantity" }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsxs(TableCell, { align: "right", children: [m, " Cost %"] }, m))), _jsx(TableCell, { align: "right", children: "Labor Cost %" }), _jsx(TableCell, { align: "right", children: "Automation Cost %" })] }) }), _jsx(TableBody, { children: LazySeq.of(costs.parts)
157
169
  .sortBy((c) => c.part)
158
170
  .map((c, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs(Box, { sx: {
159
171
  display: "flex",
160
172
  alignItems: "center",
161
- }, children: [_jsx(Box, { sx: { mr: "0.2em" }, children: _jsx(PartIdenticon, { part: c.part, size: 25 }) }), _jsx("div", { children: _jsx(Typography, { variant: "body2", component: "span", display: "block", children: c.part }) })] }) }), _jsx(TableCell, { align: "right", children: c.parts_completed }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsx(TableCell, { align: "right", children: pctFormat.format(c.machine.get(m) ?? 0) }, m))), _jsx(TableCell, { align: "right", children: pctFormat.format(c.labor) }), _jsx(TableCell, { align: "right", children: pctFormat.format(c.automation) })] }, idx))) })] }) })] }));
173
+ }, children: [_jsx(Box, { sx: { mr: "0.2em" }, children: _jsx(PartIdenticon, { part: c.part, size: 25 }) }), _jsx("div", { children: _jsx(Typography, { variant: "body2", component: "span", sx: {
174
+ display: "block",
175
+ }, children: c.part }) })] }) }), _jsx(TableCell, { align: "right", children: c.parts_completed }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsx(TableCell, { align: "right", children: pctFormat.format(c.machine.get(m) ?? 0) }, m))), _jsx(TableCell, { align: "right", children: pctFormat.format(c.labor) }), _jsx(TableCell, { align: "right", children: pctFormat.format(c.automation) })] }, idx))) })] }) })] }));
162
176
  }
163
177
  function CostOutputCard() {
164
178
  const costs = useAtomValue(computedCosts);
@@ -167,9 +181,15 @@ function CostOutputCard() {
167
181
  .map((c, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs(Box, { sx: {
168
182
  display: "flex",
169
183
  alignItems: "center",
170
- }, children: [_jsx(Box, { sx: { mr: "0.2em" }, children: _jsx(PartIdenticon, { part: c.part, size: 25 }) }), _jsx("div", { children: _jsx(Typography, { variant: "body2", component: "span", display: "block", children: c.part }) })] }) }), _jsx(TableCell, { align: "right", children: c.parts_completed }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsx(TableCell, { align: "right", children: decimalFormat.format(c.machine.get(m) ?? 0) }, m))), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.labor) }), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.automation) }), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.machine.valuesToAscLazySeq().sumBy((x) => x) + c.labor + c.automation) })] }, idx))) })] }));
184
+ }, children: [_jsx(Box, { sx: { mr: "0.2em" }, children: _jsx(PartIdenticon, { part: c.part, size: 25 }) }), _jsx("div", { children: _jsx(Typography, { variant: "body2", component: "span", sx: {
185
+ display: "block",
186
+ }, children: c.part }) })] }) }), _jsx(TableCell, { align: "right", children: c.parts_completed }), costs.machineQuantities.keysToAscLazySeq().map((m) => (_jsx(TableCell, { align: "right", children: decimalFormat.format(c.machine.get(m) ?? 0) }, m))), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.labor) }), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.automation) }), _jsx(TableCell, { align: "right", children: decimalFormat.format(c.machine.valuesToAscLazySeq().sumBy((x) => x) + c.labor + c.automation) })] }, idx))) })] }));
171
187
  }
172
188
  export const CostPerPiecePage = memo(function CostPerPiecePage() {
173
189
  useSetTitle("Cost Per Piece");
174
- return (_jsx(Box, { component: "main", paddingLeft: "24px", paddingRight: "24px", paddingTop: "20px", children: _jsxs(Stack, { direction: "column", spacing: 4, children: [_jsxs(Stack, { direction: "column", spacing: 2, children: [_jsx(LaborCost, {}), _jsx(AutomationCostInput, {}), _jsx(StationCostInputs, {})] }), _jsx(CostOutputCard, {})] }) }));
190
+ return (_jsx(Box, { component: "main", sx: {
191
+ paddingLeft: "24px",
192
+ paddingRight: "24px",
193
+ paddingTop: "20px",
194
+ }, children: _jsxs(Stack, { direction: "column", spacing: 4, children: [_jsxs(Stack, { direction: "column", spacing: 2, children: [_jsx(LaborCost, {}), _jsx(AutomationCostInput, {}), _jsx(StationCostInputs, {})] }), _jsx(CostOutputCard, {})] }) }));
175
195
  });
@@ -150,7 +150,7 @@ const SingleSeries = memo(function SingleSeries({ seriesName, points, color, xSc
150
150
  const p = localPoint(e);
151
151
  if (p === null)
152
152
  return;
153
- const idxS = e.target.dataset.idx;
153
+ const idxS = e.currentTarget.dataset.idx;
154
154
  if (idxS === undefined)
155
155
  return;
156
156
  showTooltip({
@@ -94,9 +94,11 @@ function SelectDateRange(props) {
94
94
  });
95
95
  setOpen(false);
96
96
  }
97
- // @types/react-calendar has the wrong type for onChange
98
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
99
- const matchingOnChange = onChange;
97
+ const matchingOnChange = (value) => {
98
+ if (Array.isArray(value) && value[0] instanceof Date && value[1] instanceof Date) {
99
+ onChange([value[0], value[1]]);
100
+ }
101
+ };
100
102
  return (_jsxs(_Fragment, { children: [_jsxs("span", { children: [dateFormat.format(props.zoom.current_date_zoom?.start ?? props.zoom.default_date_range[0]), " -", " ", dateFormat.format(props.zoom.current_date_zoom?.end ?? props.zoom.default_date_range[1])] }), _jsx(Tooltip, { title: "Zoom To Date Range", children: _jsx(IconButton, { onClick: () => setOpen(true), size: "large", children: _jsx(ZoomInIcon, {}) }) }), _jsx(Tooltip, { title: "Reset Date Range", children: _jsx(IconButton, { onClick: () => props.zoom.set_date_zoom_range(undefined), size: "large", children: _jsx(ZoomOutIcon, {}) }) }), _jsxs(Dialog, { open: open, onClose: () => setOpen(false), children: [_jsxs(DialogTitle, { children: ["Select Date Range ", monthFormat.format(props.zoom.default_date_range[0])] }), _jsx(DialogContent, { children: _jsx(StyledCalendar, { minDate: props.zoom.default_date_range[0], maxDate: props.zoom.default_date_range[1], calendarType: "gregory", selectRange: true, showNavigation: false, showNeighboringMonth: false, value: [start, end], onChange: matchingOnChange }) }), _jsx(DialogActions, { children: _jsx(Button, { onClick: () => setOpen(false), children: "Close" }) })] })] }));
101
103
  }
102
104
  export const DataTableActions = memo(function DataTableActions({ zoom, tpage, count, }) {
@@ -211,5 +213,5 @@ export function buildClipboardTableAsString(columns, rows) {
211
213
  return table;
212
214
  }
213
215
  export function copyTableToClipboard(columns, rows) {
214
- copy(buildClipboardTableAsString(columns, rows));
216
+ void copy(buildClipboardTableAsString(columns, rows));
215
217
  }
@@ -42,6 +42,9 @@ import { AxisBottom, AxisLeft } from "../AxisAndGrid.js";
42
42
  import { measureSvgString } from "../../util/chart-helpers.js";
43
43
  import { atom, useSetAtom } from "jotai";
44
44
  import { ChartWithTooltip } from "../ChartTooltip.js";
45
+ function isSelectableOption(options, value) {
46
+ return options.some((option) => option === value);
47
+ }
45
48
  const marginBottom = 20;
46
49
  const marginTop = 10;
47
50
  const marginRight = 2;
@@ -73,29 +76,29 @@ function useScales({ yType, dateRange, containerWidth, points, }) {
73
76
  }
74
77
  return scaleBand().domain(xValues).range([0, xMax]).align(0).padding(0.05);
75
78
  }, [dateRangeStart, dateRangeEnd, xMax]);
76
- const { yScale, height, colorScale } = useMemo(() => {
79
+ const { yScale: bandScale, height: chartHeight, colorScale: fillScale, } = useMemo(() => {
77
80
  const yValues = new Set();
78
- for (const pt of points) {
79
- yValues.add(pt.y);
81
+ for (const point of points) {
82
+ yValues.add(point.y);
80
83
  }
81
84
  const yMax = 60 * yValues.size;
82
- const height = yMax + marginTop + marginBottom;
83
- const yScale = scaleBand()
84
- .domain(Array.from(yValues).sort((a, b) => a.localeCompare(b)))
85
+ const nextChartHeight = yMax + marginTop + marginBottom;
86
+ const nextBandScale = scaleBand()
87
+ .domain(LazySeq.of(yValues).toSortedArray((x) => x))
85
88
  .range([0, yMax])
86
89
  .align(0)
87
90
  .padding(0.05);
88
- let colorScale;
91
+ let nextFillScale;
89
92
  if (yType === "Station") {
90
- colorScale = scaleLinear().domain([0, 1]).range([color1, color2]);
93
+ nextFillScale = scaleLinear().domain([0, 1]).range([color1, color2]);
91
94
  }
92
95
  else {
93
96
  const maxCnt = LazySeq.of(points).maxBy((pt) => pt.color)?.color ?? 1;
94
- colorScale = scaleLinear().domain([0, maxCnt]).range([color1, color2]);
97
+ nextFillScale = scaleLinear().domain([0, maxCnt]).range([color1, color2]);
95
98
  }
96
- return { yScale, height, colorScale };
99
+ return { yScale: nextBandScale, height: nextChartHeight, colorScale: nextFillScale };
97
100
  }, [points, yType]);
98
- return { height, width, xScale, yScale, colorScale, marginLeft };
101
+ return { height: chartHeight, width, xScale, yScale: bandScale, colorScale: fillScale, marginLeft };
99
102
  }
100
103
  const HeatAxis = memo(function HeatAxis({ xScale, yScale }) {
101
104
  return (_jsxs(_Fragment, { children: [_jsx(AxisBottom, { scale: xScale, top: yScale.range()[1], tickFormat: (d) => d.toLocaleDateString(undefined, { month: "short", day: "numeric" }) }), _jsx(AxisLeft, { scale: yScale, left: xScale.range()[0] })] }));
@@ -110,7 +113,7 @@ const HeatSeries = memo(function HeatSeries({ points, xScale, yScale, colorScale
110
113
  clearTimeout(hideRef.current);
111
114
  hideRef.current = null;
112
115
  }
113
- const idxS = e.target.dataset.idx;
116
+ const idxS = e.currentTarget.dataset.idx;
114
117
  if (idxS === undefined)
115
118
  return;
116
119
  const heatPoint = points[parseInt(idxS)];
@@ -149,7 +152,13 @@ function ChartToolbar(props) {
149
152
  minHeight: "2.5em",
150
153
  alignItems: "center",
151
154
  maxWidth: "calc(100vw - 24px - 24px)",
152
- }, children: [_jsx(Typography, { variant: "subtitle1", children: props.label }), _jsx(Box, { flexGrow: 1 }), setSelected ? (_jsx(FormControl, { size: "small", children: _jsx(Select, { autoWidth: true, displayEmpty: true, value: props.cur_selected, onChange: (e) => setSelected(e.target.value), children: props.options.map((v, idx) => (_jsx(MenuItem, { value: v, children: v }, idx))) }) })) : undefined, _jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { onClick: props.onExport, style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, size: "large", children: _jsx(ImportExport, {}) }) })] }));
155
+ }, children: [_jsx(Typography, { variant: "subtitle1", children: props.label }), _jsx(Box, { sx: {
156
+ flexGrow: 1,
157
+ } }), setSelected ? (_jsx(FormControl, { size: "small", children: _jsx(Select, { autoWidth: true, displayEmpty: true, value: props.cur_selected, onChange: (e) => {
158
+ if (isSelectableOption(props.options, e.target.value)) {
159
+ setSelected(e.target.value);
160
+ }
161
+ }, children: props.options.map((v, idx) => (_jsx(MenuItem, { value: v, children: v }, idx))) }) })) : undefined, _jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { onClick: props.onExport, style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, size: "large", children: _jsx(ImportExport, {}) }) })] }));
153
162
  }
154
163
  export function SelectableHeatChart(props) {
155
164
  const tooltipAtom = useMemo(() => atom(null), []);
@@ -157,5 +166,9 @@ export function SelectableHeatChart(props) {
157
166
  const pointerLeave = useCallback(() => {
158
167
  setTooltip(null);
159
168
  }, [setTooltip]);
160
- return (_jsxs(Box, { paddingLeft: "24px", paddingRight: "24px", paddingTop: "10px", children: [_jsx(ChartToolbar, { label: props.label, setSelected: props.setSelected, cur_selected: props.cur_selected, onExport: props.onExport, options: props.options }), _jsx("main", { onPointerLeave: pointerLeave, children: _jsx(ChartWithTooltip, { sx: { width: "100%" }, tooltipAtom: tooltipAtom, autoHeight: true, chart: ({ width }) => (_jsx(HeatChart, { points: props.points, y_title: props.y_title, dateRange: props.dateRange, parentWidth: width, setTooltip: setTooltip })), TooltipContent: ({ tooltip }) => (_jsx(HeatTooltip, { yType: props.y_title, seriesLabel: props.label_title, tooltip: tooltip })) }) })] }));
169
+ return (_jsxs(Box, { sx: {
170
+ paddingLeft: "24px",
171
+ paddingRight: "24px",
172
+ paddingTop: "10px",
173
+ }, children: [_jsx(ChartToolbar, { label: props.label, setSelected: props.setSelected, cur_selected: props.cur_selected, onExport: props.onExport, options: props.options }), _jsx("main", { onPointerLeave: pointerLeave, children: _jsx(ChartWithTooltip, { sx: { width: "100%" }, tooltipAtom: tooltipAtom, autoHeight: true, chart: ({ width }) => (_jsx(HeatChart, { points: props.points, y_title: props.y_title, dateRange: props.dateRange, parentWidth: width, setTooltip: setTooltip })), TooltipContent: ({ tooltip }) => (_jsx(HeatTooltip, { yType: props.y_title, seriesLabel: props.label_title, tooltip: tooltip })) }) })] }));
161
174
  }
@@ -74,8 +74,8 @@ function NodeDisplay({ node }) {
74
74
  }
75
75
  const SankeyDisplay = memo(function InspectionSankeyDiagram({ data, setTooltip, parentHeight, parentWidth, }) {
76
76
  const { nodes, links } = useMemo(() => {
77
- const { nodes, links } = inspectionDataToSankey(data);
78
- if (nodes.length === 0) {
77
+ const sankeyData = inspectionDataToSankey(data);
78
+ if (sankeyData.nodes.length === 0) {
79
79
  return { nodes: [], links: [] };
80
80
  }
81
81
  const generator = sankey()
@@ -86,8 +86,13 @@ const SankeyDisplay = memo(function InspectionSankeyDiagram({ data, setTooltip,
86
86
  [marginLeft, marginTop],
87
87
  [parentWidth - marginRight, parentHeight - marginBottom - marginTop],
88
88
  ]);
89
- generator({ nodes, links });
90
- return { nodes: nodes, links: links };
89
+ const layout = generator({ nodes: sankeyData.nodes, links: sankeyData.links });
90
+ return {
91
+ nodes: layout.nodes,
92
+ links: layout.links.flatMap((link) => typeof link.source === "object" && typeof link.target === "object" && link.width !== undefined
93
+ ? [{ ...link, source: link.source, target: link.target, width: link.width }]
94
+ : []),
95
+ };
91
96
  }, [data, parentWidth, parentHeight]);
92
97
  const path = sankeyLinkHorizontal();
93
98
  return (_jsxs("svg", { width: parentWidth, height: parentHeight, children: [_jsx("g", { children: nodes.map((node, i) => (_jsx(NodeDisplay, { node: node }, i))) }), _jsx("g", { children: links.map((link, i) => (_jsx(LinkDisplay, { link: link, path: path(link), strokeWidth: Math.max(link.width ?? 1, 1), setTooltip: setTooltip }, i))) })] }));
@@ -129,12 +134,18 @@ export function InspectionSankey(props) {
129
134
  .map((e) => e.inspType)
130
135
  .distinct()
131
136
  .toSortedArray((x) => x);
132
- return (_jsxs(Box, { paddingLeft: "24px", paddingRight: "24px", paddingTop: "10px", children: [_jsxs(Box, { component: "nav", sx: {
137
+ return (_jsxs(Box, { sx: {
138
+ paddingLeft: "24px",
139
+ paddingRight: "24px",
140
+ paddingTop: "10px",
141
+ }, children: [_jsxs(Box, { component: "nav", sx: {
133
142
  display: "flex",
134
143
  minHeight: "2.5em",
135
144
  alignItems: "center",
136
145
  maxWidth: "calc(100vw - 24px - 24px)",
137
- }, children: [props.subtitle ? _jsx(Typography, { variant: "subtitle1", children: props.subtitle }) : undefined, _jsx(Box, { flexGrow: 1 }), props.onlyTable ? undefined : (_jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, value: showTable ? "table" : "sankey", onChange: (e) => setShowTable(e.target.value === "table"), children: [_jsx(MenuItem, { value: "sankey", children: "Sankey" }, "sankey"), _jsx(MenuItem, { value: "table", children: "Table" }, "table")] }) })), _jsx(FormControl, { size: "small", children: _jsxs(Select, { name: "inspection-sankey-select-type", autoWidth: true, displayEmpty: true, style: { marginRight: "1em", marginLeft: "1em" }, value: selectedInspectType || "", onChange: (e) => setSelectedInspectType(e.target.value), children: [selectedInspectType ? undefined : (_jsx(MenuItem, { value: "", children: _jsx("em", { children: "Select Inspection Type" }) }, 0)), inspTypes.map((n) => (_jsx(MenuItem, { value: n, children: n }, n)))] }) }), props.restrictToPart === undefined ? (_jsx(FormControl, { size: "small", children: _jsxs(Select, { name: "inspection-sankey-select-part", autoWidth: true, displayEmpty: true, value: selectedPart || "", onChange: (e) => setSelectedPart(e.target.value), children: [selectedPart ? undefined : (_jsx(MenuItem, { value: "", children: _jsx("em", { children: "Select Part" }) }, 0)), parts.map((n) => (_jsx(MenuItem, { value: n, children: _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx(PartIdenticon, { part: n, size: 30 }), _jsx("span", { style: { marginRight: "1em" }, children: n })] }) }, n)))] }) })) : undefined, curData ? (_jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { onClick: () => curData
146
+ }, children: [props.subtitle ? _jsx(Typography, { variant: "subtitle1", children: props.subtitle }) : undefined, _jsx(Box, { sx: {
147
+ flexGrow: 1,
148
+ } }), props.onlyTable ? undefined : (_jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, value: showTable ? "table" : "sankey", onChange: (e) => setShowTable(e.target.value === "table"), children: [_jsx(MenuItem, { value: "sankey", children: "Sankey" }, "sankey"), _jsx(MenuItem, { value: "table", children: "Table" }, "table")] }) })), _jsx(FormControl, { size: "small", children: _jsxs(Select, { name: "inspection-sankey-select-type", autoWidth: true, displayEmpty: true, style: { marginRight: "1em", marginLeft: "1em" }, value: selectedInspectType || "", onChange: (e) => setSelectedInspectType(e.target.value), children: [selectedInspectType ? undefined : (_jsx(MenuItem, { value: "", children: _jsx("em", { children: "Select Inspection Type" }) }, 0)), inspTypes.map((n) => (_jsx(MenuItem, { value: n, children: n }, n)))] }) }), props.restrictToPart === undefined ? (_jsx(FormControl, { size: "small", children: _jsxs(Select, { name: "inspection-sankey-select-part", autoWidth: true, displayEmpty: true, value: selectedPart || "", onChange: (e) => setSelectedPart(e.target.value), children: [selectedPart ? undefined : (_jsx(MenuItem, { value: "", children: _jsx("em", { children: "Select Part" }) }, 0)), parts.map((n) => (_jsx(MenuItem, { value: n, children: _jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx(PartIdenticon, { part: n, size: 30 }), _jsx("span", { style: { marginRight: "1em" }, children: n })] }) }, n)))] }) })) : undefined, curData ? (_jsx(Tooltip, { title: "Copy to Clipboard", children: _jsx(IconButton, { onClick: () => curData
138
149
  ? copyInspectionEntriesToClipboard(selectedPart || "", selectedInspectType || "", curData)
139
150
  : undefined, style: { height: "25px", paddingTop: 0, paddingBottom: 0 }, size: "large", children: _jsx(ImportExport, {}) }) })) : undefined] }), _jsx("main", { children: curData ? (showTable || props.onlyTable ? (_jsx(InspectionDataTable, { zoomType: props.zoomType, points: curData, default_date_range: props.default_date_range, extendDateRange: props.extendDateRange, hideOpenDetailColumn: props.hideOpenDetailColumn })) : (_jsx(InspectionDiagram, { data: curData }))) : undefined })] }));
140
151
  }
@@ -53,6 +53,9 @@ const selectedPalletAtom = atomWithDefault((get) => (get(isDemoAtom) ? 3 : undef
53
53
  const selectedPartAtom = atom(undefined);
54
54
  const zoomDateRangeAtom = atom(undefined);
55
55
  const yZoomAtom = atom(null);
56
+ function isPalletCycleData(point) {
57
+ return "mats" in point && Array.isArray(point.mats);
58
+ }
56
59
  const filteredPoints = atom((get) => {
57
60
  const selPal = get(selectedPalletAtom);
58
61
  const selPart = get(selectedPartAtom);
@@ -114,19 +117,27 @@ export function PalletCycleChart() {
114
117
  const partNames = useAtomValue(allPartNames);
115
118
  const setMatToShow = useSetAtom(materialDialogOpen);
116
119
  const extraPalletTooltip = useCallback(function extraPalletTooltip(point) {
117
- const palC = point;
118
- return palC.mats.map((mat) => ({
120
+ if (!isPalletCycleData(point)) {
121
+ return [];
122
+ }
123
+ return point.mats.map((mat) => ({
119
124
  title: mat.part + "-" + mat.proc,
120
125
  value: mat.serial ?? "",
121
126
  link: mat.serial ? () => setMatToShow({ type: "LogMat", logMat: mat }) : undefined,
122
127
  }));
123
128
  }, [setMatToShow]);
124
- return (_jsxs(Box, { paddingLeft: "24px", paddingRight: "24px", paddingTop: "10px", children: [_jsxs(Box, { component: "nav", sx: {
129
+ return (_jsxs(Box, { sx: {
130
+ paddingLeft: "24px",
131
+ paddingRight: "24px",
132
+ paddingTop: "10px",
133
+ }, children: [_jsxs(Box, { component: "nav", sx: {
125
134
  display: "flex",
126
135
  minHeight: "2.5em",
127
136
  alignItems: "center",
128
137
  maxWidth: "calc(100vw - 24px - 24px)",
129
- }, children: [_jsx(Typography, { variant: "subtitle1", children: "Pallet Cycles" }), _jsx(Box, { flexGrow: 1 }), _jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, displayEmpty: true, value: selectedPallet ?? -1, onChange: (e) => setSelectedPallet(e.target.value === -1 ? undefined : e.target.value), children: [_jsx(MenuItem, { value: -1, children: _jsx("em", { children: "Any Pallet" }) }, 0), palletCycles
138
+ }, children: [_jsx(Typography, { variant: "subtitle1", children: "Pallet Cycles" }), _jsx(Box, { sx: {
139
+ flexGrow: 1,
140
+ } }), _jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, displayEmpty: true, value: selectedPallet ?? -1, onChange: (e) => setSelectedPallet(e.target.value === -1 ? undefined : (e.target.value)), children: [_jsx(MenuItem, { value: -1, children: _jsx("em", { children: "Any Pallet" }) }, 0), palletCycles
130
141
  .keysToLazySeq()
131
142
  .sortBy((x) => x)
132
143
  .map((n) => (_jsx(MenuItem, { value: n, children: _jsx("div", { style: { display: "flex", alignItems: "center" }, children: _jsx("span", { style: { marginRight: "1em" }, children: n }) }) }, n)))] }) }), partNames.length > 0 ? (_jsx(FormControl, { size: "small", children: _jsxs(Select, { autoWidth: true, displayEmpty: true, value: selectedPart