@seedtactics/insight-client 17.0.1-beta.12 → 17.0.1-beta.14

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 (45) hide show
  1. package/README.md +16 -4
  2. package/dist/assets/ProgramHighlight-CnZe9PDO.js +3 -0
  3. package/dist/assets/index-DWZg8nkq.js +367 -0
  4. package/dist/cell-status/current-status.d.ts +1 -0
  5. package/dist/cell-status/current-status.js +2 -0
  6. package/dist/cell-status/loading.d.ts +13 -0
  7. package/dist/cell-status/loading.js +36 -3
  8. package/dist/cell-status/material-details.d.ts +2 -2
  9. package/dist/cell-status/material-details.js +26 -13
  10. package/dist/components/App.d.ts +0 -5
  11. package/dist/components/App.js +1 -1
  12. package/dist/components/analysis/BufferChart.js +3 -2
  13. package/dist/components/operations/AllMaterial.js +9 -12
  14. package/dist/components/operations/RecentCycleChart.js +16 -21
  15. package/dist/components/station-monitor/BasketLoadStationWork.d.ts +1 -7
  16. package/dist/components/station-monitor/BasketLoadStationWork.js +9 -6
  17. package/dist/components/station-monitor/CancelLoadButton.js +2 -0
  18. package/dist/components/station-monitor/InvalidateCycle.js +24 -19
  19. package/dist/components/station-monitor/LoadStation.d.ts +0 -5
  20. package/dist/components/station-monitor/LoadStation.js +33 -72
  21. package/dist/components/station-monitor/Material.js +4 -2
  22. package/dist/components/station-monitor/PrintedLabel.js +15 -19
  23. package/dist/components/station-monitor/QuarantineButton.js +17 -26
  24. package/dist/components/station-monitor/SystemOverview.js +3 -2
  25. package/dist/components/station-monitor/Whiteboard.js +14 -15
  26. package/dist/data/material-operation-policy.d.ts +6 -0
  27. package/dist/data/material-operation-policy.js +26 -2
  28. package/dist/extension.d.ts +1 -1
  29. package/dist/extension.js +1 -0
  30. package/dist/index.html +2 -39
  31. package/dist/index.js +1 -6
  32. package/dist/network/api.d.ts +14 -34
  33. package/dist/network/api.js +62 -64
  34. package/dist/network/backend-mock.js +3 -0
  35. package/dist/network/backend.d.ts +1 -0
  36. package/dist/network/websocket.d.ts +1 -0
  37. package/dist/network/websocket.js +122 -36
  38. package/docs/material-quarantine.md +5 -4
  39. package/docs/mazak.md +9 -12
  40. package/docs/operator-procedures.md +14 -0
  41. package/package.json +22 -17
  42. package/dist/assets/ProgramHighlight-C6zSEXCX.js +0 -3
  43. package/dist/assets/index-kk-GRa5i.js +0 -388
  44. package/dist/components/station-monitor/BasketMovementArrival.d.ts +0 -40
  45. package/dist/components/station-monitor/BasketMovementArrival.js +0 -165
@@ -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, memo, useState, useCallback, useEffect } from "react";
34
+ import { useMemo, memo, useState, useCallback } from "react";
35
35
  import { Box, useMediaQuery, Button, Typography } from "@mui/material";
36
36
  import { LazySeq, OrderedMap } from "@seedtactics/immutable-collections";
37
37
  import { FolderOpen as FolderOpenIcon } from "@mui/icons-material";
@@ -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";
@@ -63,8 +63,7 @@ import { PrintLabelButton } from "./PrintedLabel.js";
63
63
  import { hideNonLoadingMaterialOnLoadStation } from "../../data/queue-material.js";
64
64
  import { basketDisplayName, loadStationDisplayName } from "../../cell-status/station-cycles.js";
65
65
  import { canAddOrMoveMaterialToQueue } from "../../data/material-operation-policy.js";
66
- import { BasketLoadStationWorkflow, } from "./BasketLoadStationWork.js";
67
- import { BasketMovementArrival, loadStationArrivalInstruction, } from "./BasketMovementArrival.js";
66
+ import { BasketLoadStationWorkflow } from "./BasketLoadStationWork.js";
68
67
  function encodeLoadStationRegionIdPart(value) {
69
68
  return encodeURIComponent(value.toString());
70
69
  }
@@ -393,7 +392,7 @@ function ElapsedLoadTime({ elapsedLoadTime }) {
393
392
  return null;
394
393
  }
395
394
  }
396
- function PalletFace({ data, faceNum, loadNum, submitBasketLoadStationCommand, }) {
395
+ function PalletFace({ data, faceNum }) {
397
396
  const face = data.face.get(faceNum);
398
397
  const fmsInfo = useAtomValue(fmsInformation);
399
398
  const basketName = basketDisplayName(fmsInfo.basketName);
@@ -408,7 +407,7 @@ function PalletFace({ data, faceNum, loadNum, submitBasketLoadStationCommand, })
408
407
  }, children: _jsx(ElapsedLoadTime, { elapsedLoadTime: data.elapsedLoadingTime }) })] })) : null, _jsx(Box, { sx: { ml: "4em", mr: "4em" }, children: _jsx(MoveMaterialArrowNode, { kind: {
409
408
  type: MoveMaterialNodeKindType.BasketZone,
410
409
  basketId: data.activeBasket.basketId,
411
- }, children: _jsx(BasketLoadStationWorkflow, { stationNumber: loadNum, basket: data.activeBasket, material: data.allMaterial, fsize: data.fsize, submitCommand: submitBasketLoadStationCommand }) }) })] }));
410
+ }, children: _jsx(BasketLoadStationWorkflow, { basket: data.activeBasket, material: data.allMaterial, fsize: data.fsize }) }) })] }));
412
411
  }
413
412
  if (!data.pallet)
414
413
  return null;
@@ -487,16 +486,17 @@ function MaterialColumn({ data, region, }) {
487
486
  }
488
487
  }
489
488
  function RecentCompletedMaterial() {
489
+ const minutes = useAtomValue(minutesSinceEpochAtom);
490
490
  const matSummary = useAtomValue(last30MaterialSummary);
491
491
  const recentCompleted = useMemo(() => {
492
- const cutoff = addHours(new Date(), -5);
492
+ const cutoff = addHours(new Date(minutes * 60000), -5);
493
493
  return matSummary.matsById
494
494
  .valuesToLazySeq()
495
495
  .filter((e) => e.completed_last_proc_machining === true &&
496
496
  e.last_unload_time !== undefined &&
497
497
  e.last_unload_time >= cutoff)
498
498
  .toSortedArray({ desc: (e) => e.last_unload_time?.getTime() ?? 0 });
499
- }, [matSummary]);
499
+ }, [matSummary, minutes]);
500
500
  return (_jsx("div", { children: recentCompleted.map((m, idx) => (_jsx(MatSummary, { mat: m }, idx))) }));
501
501
  }
502
502
  const CompletedCol = memo(function CompletedCol({ fillViewPort, showMaterial, }) {
@@ -679,23 +679,6 @@ export function LoadStation(props) {
679
679
  const currentSt = useAtomValue(currentStatus);
680
680
  const hideNonLoading = useAtomValue(hideNonLoadingMaterialOnLoadStation);
681
681
  const data = useMemo(() => selectLoadStationAndQueueProps(props.loadNum, props.queues, currentSt, hideNonLoading), [currentSt, props.loadNum, props.queues, hideNonLoading]);
682
- const arrivalInstruction = loadStationArrivalInstruction(currentSt.basketMoveInstructions ?? [], props.loadNum);
683
- const [recentArrivalReceipt, setRecentArrivalReceipt] = useState();
684
- useEffect(() => {
685
- if (recentArrivalReceipt !== undefined &&
686
- (recentArrivalReceipt.stationNumber !== props.loadNum ||
687
- (arrivalInstruction !== undefined &&
688
- arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId))) {
689
- // This state is an ephemeral receipt; discard it permanently when a new instruction supersedes it.
690
- // oxlint-disable-next-line react/set-state-in-effect -- intentional invalidation of stale UI state.
691
- setRecentArrivalReceipt(undefined);
692
- }
693
- }, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
694
- const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
695
- const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
696
- displayedArrivalInstruction?.instructionId === recentArrivalReceipt?.instruction.instructionId
697
- ? recentArrivalReceipt
698
- : undefined;
699
682
  const queueCols = LazySeq.of(data.queues)
700
683
  .sortBy(([q]) => q)
701
684
  .map(([q, mats]) => ({
@@ -742,53 +725,31 @@ export function LoadStation(props) {
742
725
  horizontal: fillViewPort,
743
726
  showMatInCompleted: props.completed,
744
727
  });
745
- return (_jsxs(_Fragment, { children: [displayedArrivalInstruction === undefined ? null : (_jsx(BasketMovementArrival, { basketName: basketName, instruction: displayedArrivalInstruction, stationNumber: props.loadNum, submitCommand: props.submitBasketMovementCompletion, submitCorrection: props.submitBasketLocationCorrection, receipt: displayedArrivalReceipt, onAccepted: setRecentArrivalReceipt, onCorrected: (command) => setRecentArrivalReceipt((current) => {
746
- if (current === undefined ||
747
- current.stationNumber !== props.loadNum ||
748
- current.receipt.observationId !== command.targetObservationId)
749
- return current;
750
- return command.replacementBasketId === null ||
751
- command.replacementObservationId === null
752
- ? { ...current, status: "retracted" }
753
- : {
754
- ...current,
755
- receipt: {
756
- ...current.receipt,
757
- observationId: command.replacementObservationId,
758
- observedBasketId: command.replacementBasketId,
759
- },
760
- command: {
761
- ...current.command,
762
- commandId: command.replacementObservationId,
763
- observedBasketId: command.replacementBasketId,
764
- },
765
- status: "corrected",
766
- };
767
- }) })), _jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
768
- width: "100%",
769
- display: "grid",
770
- gridTemplate: grid,
771
- minHeight: {
772
- xs: "calc(100vh - 64px - 32px)",
773
- sm: "calc(100vh - 64px - 40px)",
774
- md: "calc(100vh - 64px)",
775
- },
776
- padding: fillViewPort ? undefined : "8px",
777
- }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
778
- gridArea: `mat${idx}`,
779
- padding: "8px",
780
- borderRight: fillViewPort ? "1px solid black" : undefined,
781
- borderBottom: !fillViewPort ? "1px solid black" : undefined,
782
- }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
783
- marginLeft: "15px",
784
- gridArea: `palface${idx}`,
785
- padding: "8px",
786
- borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
787
- }, children: _jsx(PalletFace, { data: data, faceNum: faceNum, loadNum: props.loadNum, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
788
- borderLeft: fillViewPort ? "1px solid black" : undefined,
789
- borderTop: !fillViewPort ? "1px solid black" : undefined,
790
- gridArea: "completed",
791
- }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) })] }));
728
+ return (_jsx(_Fragment, { children: _jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
729
+ width: "100%",
730
+ display: "grid",
731
+ gridTemplate: grid,
732
+ minHeight: {
733
+ xs: "calc(100vh - 64px - 32px)",
734
+ sm: "calc(100vh - 64px - 40px)",
735
+ md: "calc(100vh - 64px)",
736
+ },
737
+ padding: fillViewPort ? undefined : "8px",
738
+ }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
739
+ gridArea: `mat${idx}`,
740
+ padding: "8px",
741
+ borderRight: fillViewPort ? "1px solid black" : undefined,
742
+ borderBottom: !fillViewPort ? "1px solid black" : undefined,
743
+ }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
744
+ marginLeft: "15px",
745
+ gridArea: `palface${idx}`,
746
+ padding: "8px",
747
+ borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
748
+ }, children: _jsx(PalletFace, { data: data, faceNum: faceNum }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
749
+ borderLeft: fillViewPort ? "1px solid black" : undefined,
750
+ borderTop: !fillViewPort ? "1px solid black" : undefined,
751
+ gridArea: "completed",
752
+ }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) }) }));
792
753
  }
793
754
  function LoadStationCheckWidth(props) {
794
755
  const fmsInfo = useAtomValue(fmsInformation);
@@ -49,7 +49,7 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai";
49
49
  import { last30Rebookings } from "../../cell-status/rebookings.js";
50
50
  import { fmsInformation } from "../../network/server-settings.js";
51
51
  import { basketDisplayName, basketSlotLabel } from "../../cell-status/station-cycles.js";
52
- import { materialOperationState } from "../../data/material-operation-policy.js";
52
+ import { canAddOrMoveMaterialToQueue, canReorderQueuedMaterial, materialOperationState, } from "../../data/material-operation-policy.js";
53
53
  export class PartIdenticon extends PureComponent {
54
54
  render() {
55
55
  const iconSize = this.props.size || 50;
@@ -402,10 +402,12 @@ export const InProcMaterial = memo(function InProcMaterial(props) {
402
402
  return (_jsx(MatCard, { mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, fsize: props.fsize, hideAvatar: props.hideAvatar, displayJob: props.displayJob, showDragHandle: props.showHandle, hideEmptySerial: props.hideEmptySerial, showRawMaterial: props.showRawMaterial, showJobComment: props.showJobComment, shake: props.shake }));
403
403
  });
404
404
  export const SortableInProcMaterial = memo(function SortableInProcMaterial(props) {
405
+ const canDrag = canReorderQueuedMaterial(props.mat) || canAddOrMoveMaterialToQueue(props.mat);
405
406
  const d = { mat: props.mat };
406
407
  const { active, isDragging, attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, } = useSortable({
407
408
  id: props.mat.materialID,
408
409
  data: d,
410
+ disabled: { draggable: !canDrag },
409
411
  });
410
412
  const handleProps = {
411
413
  ...listeners,
@@ -421,7 +423,7 @@ export const SortableInProcMaterial = memo(function SortableInProcMaterial(props
421
423
  : undefined,
422
424
  transition: active !== null ? transition : undefined,
423
425
  };
424
- return (_jsx(MatCard, { ref: setNodeRef, dragRootProps: { style }, showDragHandle: true, dragHandleProps: handleProps, setDragHandleRef: setActivatorNodeRef, isActiveDrag: isDragging, mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, hideAvatar: props.hideAvatar, displayJob: props.displayJob, hideEmptySerial: props.hideEmptySerial, fsize: props.fsize, shake: active ? undefined : props.shake, showRawMaterial: props.showRawMaterial }));
426
+ return (_jsx(MatCard, { ref: setNodeRef, dragRootProps: { style }, showDragHandle: canDrag, dragHandleProps: handleProps, setDragHandleRef: setActivatorNodeRef, isActiveDrag: isDragging, mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, hideAvatar: props.hideAvatar, displayJob: props.displayJob, hideEmptySerial: props.hideEmptySerial, fsize: props.fsize, shake: active ? undefined : props.shake, showRawMaterial: props.showRawMaterial }));
425
427
  });
426
428
  export function DragOverlayInProcMaterial(props) {
427
429
  return (_jsx(MatCard, { mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, showDragHandle: true, hideAvatar: props.hideAvatar, displayJob: props.displayJob, fsize: props.fsize, hideEmptySerial: props.hideEmptySerial, isDragOverlay: true }));
@@ -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: new Date().toLocaleString(undefined, {
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: new Date().toLocaleString(undefined, {
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",
@@ -40,15 +40,16 @@ import { currentOperator } from "../../data/operators.js";
40
40
  import { ApiException, LocType, QueueRole, } from "../../network/api.js";
41
41
  import { fmsInformation } from "../../network/server-settings.js";
42
42
  import { useAtomValue, useSetAtom } from "jotai";
43
- import { canDirectlyQuarantine, canRemoveFromQueue, canSignalQuarantine, isActiveLoadStationOperation, } from "../../data/material-operation-policy.js";
43
+ import { canDirectlyQuarantine, canRemoveFromQueue, canSignalQuarantine, quarantineRoute, } from "../../data/material-operation-policy.js";
44
44
  function hasSupportedQuarantineExit(material, status) {
45
45
  const job = status.jobs[material.jobUnique];
46
46
  if (!job)
47
47
  return false;
48
- const path = job.procsAndPaths[material.process - 1]?.paths[material.path - 1];
48
+ const route = quarantineRoute(material);
49
+ const path = job.procsAndPaths[route.process - 1]?.paths[route.path - 1];
49
50
  if (!path)
50
51
  return false;
51
- return material.process === job.procsAndPaths.length || Boolean(path.outputQueue);
52
+ return route.process === job.procsAndPaths.length || Boolean(path.outputQueue);
52
53
  }
53
54
  function useQuarantineMaterial() {
54
55
  const fmsInfo = useAtomValue(fmsInformation);
@@ -56,8 +57,6 @@ function useQuarantineMaterial() {
56
57
  const curSt = useAtomValue(currentStatus);
57
58
  if (inProcMat === null || inProcMat.materialID < 0)
58
59
  return null;
59
- if (isActiveLoadStationOperation(inProcMat))
60
- return null;
61
60
  if (inProcMat.quarantineAfterUnload)
62
61
  return null;
63
62
  const quarantineQueue = fmsInfo.quarantineQueue?.length ? fmsInfo.quarantineQueue : null;
@@ -80,7 +79,8 @@ function useQuarantineMaterial() {
80
79
  .filter(([qname]) => !activeQueues.has(qname))
81
80
  .toRSet(([qname]) => qname);
82
81
  // If in a quarantine queue, allow removal from system
83
- if (inProcMat.location.type === LocType.InQueue &&
82
+ if (canRemoveFromQueue(inProcMat) &&
83
+ inProcMat.location.type === LocType.InQueue &&
84
84
  inProcMat.location.currentQueue &&
85
85
  quarantineQueues.has(inProcMat.location.currentQueue)) {
86
86
  return {
@@ -91,25 +91,16 @@ function useQuarantineMaterial() {
91
91
  };
92
92
  }
93
93
  let type = null;
94
- switch (inProcMat.location.type) {
95
- case LocType.OnPallet:
96
- case LocType.InBasket:
97
- if (!canSignalQuarantine(inProcMat) || !hasSupportedQuarantineExit(inProcMat, curSt)) {
98
- return null;
99
- }
100
- type = "SignalForScrap";
101
- break;
102
- case LocType.InQueue:
103
- if (!canDirectlyQuarantine(inProcMat))
104
- return null;
105
- if (inProcMat.location.currentQueue === undefined ||
106
- quarantineQueues.has(inProcMat.location.currentQueue)) {
107
- return null;
108
- }
109
- type = "Scrap";
110
- break;
111
- case LocType.Free:
94
+ if (canSignalQuarantine(inProcMat)) {
95
+ if (!hasSupportedQuarantineExit(inProcMat, curSt))
96
+ return null;
97
+ type = "SignalForScrap";
98
+ }
99
+ else if (canDirectlyQuarantine(inProcMat)) {
100
+ if (inProcMat.location.currentQueue === undefined ||
101
+ quarantineQueues.has(inProcMat.location.currentQueue))
112
102
  return null;
103
+ type = "Scrap";
113
104
  }
114
105
  if (type) {
115
106
  return {
@@ -214,7 +205,7 @@ export function QuarantineMatButton({ onClose, ignoreOperator, }) {
214
205
  title = displayedSnapshot.destination
215
206
  ? `The current automated operation will continue. When the material leaves automation control, move it to ${displayedSnapshot.destination}`
216
207
  : "The current automated operation will continue. When the material leaves automation control, remove it from normal production flow as scrap";
217
- btnTxt = displayedSnapshot.destination ? "Quarantine" : "Scrap";
208
+ btnTxt = displayedSnapshot.destination ? "Signal for quarantine" : "Scrap";
218
209
  break;
219
210
  }
220
211
  async function quarantine() {
@@ -266,5 +257,5 @@ export function QuarantineMatButton({ onClose, ignoreOperator, }) {
266
257
  ? quarantiningQueued
267
258
  : removingFromQueue);
268
259
  const removeFromQueues = q?.canRemoveFromQueues === true ? () => removeFromQueue(q.material.materialID, operator) : null;
269
- return (_jsxs(_Fragment, { children: [q ? (_jsx(Tooltip, { title: title, children: _jsx(Button, { color: "primary", disabled: removing, onClick: openQuarantine, children: btnTxt }) })) : null, removeFromQueues && q ? (_jsx(RemoveFromQueuesButton, { material: q.material, removeFromQueues: removeFromQueues, removing: removingFromQueue })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: closeQuarantine, children: [_jsx(DialogTitle, { children: "Quarantine Material" }), _jsxs(DialogContent, { children: [_jsx("p", { children: title }), _jsx(TextField, { label: "Reason", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, autoFocus: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: removing, onClick: quarantine, children: btnTxt }), _jsx(Button, { color: "secondary", disabled: removing, onClick: closeQuarantine, children: "Cancel" })] })] })] }));
260
+ return (_jsxs(_Fragment, { children: [q ? (_jsx(Tooltip, { title: title, describeChild: displayedSnapshot.operation === "DeferredQuarantine", children: _jsx(Button, { color: "primary", disabled: removing, onClick: openQuarantine, children: btnTxt }) })) : null, removeFromQueues && q ? (_jsx(RemoveFromQueuesButton, { material: q.material, removeFromQueues: removeFromQueues, removing: removingFromQueue })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: closeQuarantine, children: [_jsx(DialogTitle, { children: "Quarantine Material" }), _jsxs(DialogContent, { children: [_jsx("p", { children: title }), _jsx(TextField, { label: "Reason", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, autoFocus: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: removing, onClick: quarantine, children: btnTxt }), _jsx(Button, { color: "secondary", disabled: removing, onClick: closeQuarantine, children: "Cancel" })] })] })] }));
270
261
  }
@@ -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
@@ -34,9 +34,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
34
  import { memo, useMemo, useState } from "react";
35
35
  import { closestCenter, DndContext, DragOverlay, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core";
36
36
  import { rectSortingStrategy, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable";
37
- import { useAddExistingMaterialToQueue } from "../../cell-status/material-details.js";
38
- import { reorderQueuedMatInCurrentStatus } from "../../cell-status/current-status.js";
37
+ import { moveQueuedMatInCurrentStatus } from "../../cell-status/loading.js";
39
38
  import { currentOperator } from "../../data/operators.js";
39
+ import { canSetMaterialInQueue } from "../../data/material-operation-policy.js";
40
40
  import { useAtomValue, useSetAtom } from "jotai";
41
41
  function isSortableMatData(value) {
42
42
  return typeof value === "object" && value !== null && "mat" in value;
@@ -46,8 +46,7 @@ function numericId(id) {
46
46
  }
47
47
  export const SortableRegion = memo(function SortableRegion(props) {
48
48
  const [activeMat, setActiveMat] = useState(undefined);
49
- const [addExistingMatToQueue] = useAddExistingMaterialToQueue();
50
- const reorderQueuedMat = useSetAtom(reorderQueuedMatInCurrentStatus);
49
+ const moveQueuedMat = useSetAtom(moveQueuedMatInCurrentStatus);
51
50
  const operator = useAtomValue(currentOperator);
52
51
  const sortableItemIds = useMemo(() => props.matIds.slice(), [props.matIds]);
53
52
  const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
@@ -56,19 +55,19 @@ export const SortableRegion = memo(function SortableRegion(props) {
56
55
  return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragStart: ({ active }) => setActiveMat(isSortableMatData(active.data.current) ? active.data.current.mat : undefined), onDragCancel: () => setActiveMat(undefined), onDragEnd: ({ active, over }) => {
57
56
  const activeMatId = numericId(active.id);
58
57
  const overMatId = over ? numericId(over.id) : null;
59
- if (over && active.id !== over.id && activeMatId !== null && overMatId !== null) {
60
- const overIdx = props.matIds.indexOf(overMatId);
61
- void addExistingMatToQueue({
62
- materialId: activeMatId,
63
- queue: props.queueName,
64
- queuePosition: overIdx,
65
- operator: operator,
66
- }).catch(console.error);
67
- reorderQueuedMat({
58
+ const mat = isSortableMatData(active.data.current) ? active.data.current.mat : null;
59
+ if (over &&
60
+ active.id !== over.id &&
61
+ activeMatId !== null &&
62
+ overMatId !== null &&
63
+ mat !== null &&
64
+ canSetMaterialInQueue(mat, props.queueName)) {
65
+ moveQueuedMat({
68
66
  queue: props.queueName,
69
67
  matId: activeMatId,
70
- newIdx: overIdx,
71
- });
68
+ newIdx: props.matIds.indexOf(overMatId),
69
+ operator,
70
+ }).catch(console.error);
72
71
  }
73
72
  setActiveMat(undefined);
74
73
  }, children: _jsxs(SortableContext, { items: sortableItemIds, strategy: props.direction === "vertical" ? verticalListSortingStrategy : rectSortingStrategy, children: [props.children, _jsx(DragOverlay, { children: activeMat !== undefined ? props.renderDragOverlay(activeMat) : undefined })] }) }));
@@ -18,4 +18,10 @@ export declare function canDirectlyQuarantine(material: MaterialForPolicy): bool
18
18
  export declare function canRemoveFromQueue(material: MaterialForPolicy): boolean;
19
19
  export declare function canInvalidateMaterial(material: MaterialForPolicy): boolean;
20
20
  export declare function canAddOrMoveMaterialToQueue(material: MaterialForPolicy): boolean;
21
+ export declare function canReorderQueuedMaterial(material: MaterialForPolicy): boolean;
22
+ export declare function canSetMaterialInQueue(material: MaterialForPolicy, queue: string): boolean;
23
+ export declare function quarantineRoute(material: Readonly<IInProcessMaterial>): {
24
+ readonly process: number;
25
+ readonly path: number;
26
+ };
21
27
  export {};
@@ -46,8 +46,10 @@ function hasLoadStationAction(material) {
46
46
  }
47
47
  }
48
48
  export function materialOperationState(material) {
49
+ if (material.action.automatedTransfer === true)
50
+ return { kind: "AutomationControlled" };
49
51
  const cancellationId = nonBlankCancellationId(material);
50
- if (cancellationId !== null || hasLoadStationAction(material)) {
52
+ if (material.action.loadCancellationId !== undefined || hasLoadStationAction(material)) {
51
53
  return { kind: "ActiveLoadStationOperation", cancellationId };
52
54
  }
53
55
  if (material.location.type === LocType.InQueue && material.action.type === ActionType.Waiting) {
@@ -70,7 +72,11 @@ export function canCancelLoad(material) {
70
72
  return state.kind === "ActiveLoadStationOperation" && state.cancellationId !== null;
71
73
  }
72
74
  export function canSignalQuarantine(material) {
73
- return material !== null && materialOperationState(material).kind === "AutomationControlled";
75
+ return (material !== null &&
76
+ material.materialID >= 0 &&
77
+ (materialOperationState(material).kind === "AutomationControlled" ||
78
+ material.action.type === ActionType.UnloadToInProcess ||
79
+ material.action.type === ActionType.UnloadToCompletedMaterial));
74
80
  }
75
81
  export function canDirectlyQuarantine(material) {
76
82
  return material !== null && materialOperationState(material).kind === "HumanControlledQueue";
@@ -87,3 +93,21 @@ export function canAddOrMoveMaterialToQueue(material) {
87
93
  const state = materialOperationState(material);
88
94
  return state.kind === "AddToQueueProposal" || state.kind === "HumanControlledQueue";
89
95
  }
96
+ export function canReorderQueuedMaterial(material) {
97
+ // Priority changes within the source queue remain allowed during human station work.
98
+ return (material !== null &&
99
+ material.location.type === LocType.InQueue &&
100
+ material.action.automatedTransfer !== true);
101
+ }
102
+ export function canSetMaterialInQueue(material, queue) {
103
+ return material?.location.type === LocType.InQueue && material.location.currentQueue === queue
104
+ ? canReorderQueuedMaterial(material)
105
+ : canAddOrMoveMaterialToQueue(material);
106
+ }
107
+ export function quarantineRoute(material) {
108
+ return material.action.automatedTransfer === true &&
109
+ (material.action.type === ActionType.Loading ||
110
+ material.action.type === ActionType.LoadingToBasket)
111
+ ? { process: material.action.processAfterLoad ?? 0, path: material.action.pathAfterLoad ?? 0 }
112
+ : { process: material.process, path: material.path };
113
+ }
@@ -4,9 +4,9 @@ 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";
10
11
  export { RouteLocation } from "./components/routes.js";
11
12
  export { authenticatedFetch } from "./network/backend.js";
12
- export type { BasketMovementCompletionCommand, BasketMovementCompletionReceipt, BasketLocationCorrectionCommand, SubmitBasketLocationCorrection, SubmitBasketMovementCompletion, } from "./components/station-monitor/BasketMovementArrival.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
- .download-loader {
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-DWZg8nkq.js"></script>
47
10
  <link rel="stylesheet" crossorigin href="/assets/index-DXfAwuj-.css">
48
11
  </head>
49
12
  <body>
package/dist/index.js CHANGED
@@ -33,12 +33,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  import { bootstrapInsight } from "./extension.js";
34
34
  import { ApiException } from "./network/api.js";
35
35
  async function main() {
36
- const developmentProps = import.meta.env.DEV
37
- ? {
38
- submitBasketLoadStationCommand: async () => "accepted",
39
- }
40
- : {};
41
- await bootstrapInsight(developmentProps);
36
+ await bootstrapInsight();
42
37
  }
43
38
  main().catch((e) => {
44
39
  console.log(e);