@onehat/ui 0.4.101 → 0.4.103

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 (51) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Accordion/Accordion.js +65 -6
  3. package/src/Components/Container/Container.js +10 -4
  4. package/src/Components/Form/Field/Combo/Combo.js +10 -4
  5. package/src/Components/Form/Field/Tag/Tag.js +6 -0
  6. package/src/Components/Form/Form.js +8 -3
  7. package/src/Components/Grid/Grid.js +232 -154
  8. package/src/Components/Grid/GridRow.js +7 -3
  9. package/src/Components/Hoc/withPresetButtons.js +18 -6
  10. package/src/Components/Icons/ArrowsLeftRight.js +10 -0
  11. package/src/Components/Icons/Bar.js +10 -0
  12. package/src/Components/Icons/Box.js +11 -0
  13. package/src/Components/Icons/BoxOpen.js +11 -0
  14. package/src/Components/Icons/Bucket.js +10 -0
  15. package/src/Components/Icons/Bump.js +21 -0
  16. package/src/Components/Icons/Calculator.js +12 -0
  17. package/src/Components/Icons/Dots.js +20 -0
  18. package/src/Components/Icons/Fleets.js +26 -0
  19. package/src/Components/Icons/Lock.js +11 -0
  20. package/src/Components/Icons/Microchip.js +12 -0
  21. package/src/Components/Icons/Num1.js +10 -0
  22. package/src/Components/Icons/Num2.js +10 -0
  23. package/src/Components/Icons/Num3.js +10 -0
  24. package/src/Components/Icons/Num4.js +10 -0
  25. package/src/Components/Icons/OilCan.js +11 -0
  26. package/src/Components/Icons/Operations.js +10 -0
  27. package/src/Components/Icons/OverduePms.js +10 -0
  28. package/src/Components/Icons/SackDollar.js +11 -0
  29. package/src/Components/Icons/ShortBar.js +15 -0
  30. package/src/Components/Icons/Tower.js +10 -0
  31. package/src/Components/Icons/UpcomingPms.js +10 -0
  32. package/src/Components/Layout/ScreenHeader.js +35 -3
  33. package/src/Components/Layout/SetupButton.js +31 -0
  34. package/src/Components/Layout/UserIndicator.js +35 -0
  35. package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
  36. package/src/Components/Pms/Editor/MetersEditor.js +173 -0
  37. package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
  38. package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
  39. package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
  40. package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
  41. package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
  42. package/src/Components/Pms/Screens/PmsManager.js +136 -0
  43. package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
  44. package/src/Components/Screens/Manager.js +3 -0
  45. package/src/Components/Screens/ReportsManager.js +51 -26
  46. package/src/Components/Tree/Tree.js +15 -6
  47. package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
  48. package/src/Components/Viewer/TextWithLinks.js +9 -1
  49. package/src/Components/Viewer/Viewer.js +38 -30
  50. package/src/Functions/flatten.js +39 -0
  51. package/src/Functions/verifyCanCrudPmEvents.js +33 -0
@@ -0,0 +1,136 @@
1
+ import { useState, useEffect, } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { useIsFocused } from '@react-navigation/native';
4
+ import {
5
+ selectTreeSelection,
6
+ } from '@src/Models/Slices/AppSlice';
7
+ import {
8
+ NODE_TYPES__FLEETS,
9
+ NODE_TYPES__EQUIPMENT,
10
+ } from '@src/Constants/NodeTypes.js';
11
+ import oneHatData from '@onehat/data';
12
+ import testProps from '../../../Functions/testProps.js';
13
+ import verifyCanCrud from '../../../Functions/verifyCanCrudPmEvents.js';
14
+ import ManagerScreen from '../../Screens/Manager.js';
15
+ import Bell from '../../Icons/Bell.js';
16
+ import ClockRegular from '../../Icons/ClockRegular.js';
17
+ import OilCan from '../../Icons/OilCan.js';
18
+ import TabBar from '../../Tab/TabBar.js';
19
+ import TreeSpecific from '../Layout/TreeSpecific/TreeSpecific.js';
20
+ import UpcomingPmsGrid from '../Grid/UpcomingPmsGrid.js';
21
+ import PmEventsFilteredGridEditor from '@src/Components/Grid/PmEventsFilteredGridEditor.js';
22
+ import PmEventsFilteredSideGridEditor from '@src/Components/Grid/PmEventsFilteredSideGridEditor.js';
23
+ import _ from 'lodash';
24
+
25
+ const EmptyWrapper = ({ children }) => {
26
+ return <>{children}</>;
27
+ };
28
+
29
+ export default function PmsManager(props) {
30
+ const {
31
+ Wrapper = EmptyWrapper,
32
+ } = props,
33
+ treeSelection = useSelector(selectTreeSelection),
34
+ isActive = useIsFocused(),
35
+ [defaultMeterId, setDefaultMeterId] = useState(null),
36
+ [Equipment] = useState(() => oneHatData.getRepository('Equipment', true)),
37
+ treeNode = treeSelection?.[0],
38
+ isFleet = treeNode?.nodeType === NODE_TYPES__FLEETS,
39
+ isEquipment = treeNode?.nodeType === NODE_TYPES__EQUIPMENT;
40
+
41
+ useEffect(() => {
42
+ if (isEquipment) {
43
+ (async () => {
44
+ // Fetch the primary_meter_id for this Equipment
45
+ const idToUse = treeNode?.actualId || treeNode?.id;
46
+ let entity = Equipment.getById(idToUse);
47
+ if (!entity) {
48
+ entity = await Equipment.loadOneAdditionalEntity(idToUse);
49
+ }
50
+ let defaultMeterId = null;
51
+ if (entity && !entity.equipment__has_multiple_meters) {
52
+ defaultMeterId = entity.equipment__primary_meter_id;
53
+ }
54
+ setDefaultMeterId(defaultMeterId);
55
+ })();
56
+ } else {
57
+ setDefaultMeterId(null);
58
+ }
59
+ }, [treeNode]);
60
+
61
+ if (!isActive) {
62
+ return null;
63
+ }
64
+
65
+ let fleetId = null,
66
+ selectorId = null,
67
+ selectorSelectedField = undefined; // so the default prop assignment will work in Grid (won't work with null)
68
+ if (isFleet) {
69
+ selectorId = 'pm_events__fleet_id';
70
+ selectorSelectedField = 'actualId';
71
+ fleetId = treeNode.actualId;
72
+ } else if (isEquipment) {
73
+ selectorId = 'pm_events__equipment_id';
74
+ selectorSelectedField = 'actualId';
75
+ fleetId = treeNode.equipment__fleet_id;
76
+ }
77
+
78
+ const
79
+ gridProps = {
80
+ reference: 'PmEventsGridEditor',
81
+ canRecordBeEdited: verifyCanCrud,
82
+ canRecordBeDeleted: verifyCanCrud,
83
+ canRecordBeDuplicated: verifyCanCrud,
84
+ selectorId,
85
+ selectorSelected: treeSelection[0],
86
+ selectorSelectedField,
87
+ defaultValues: defaultMeterId ? {
88
+ pm_events__meter_id: defaultMeterId,
89
+ } : undefined,
90
+ };
91
+
92
+ return <TabBar
93
+ {...testProps('PmsManager')}
94
+ reference="PmsManager"
95
+ tabs={[
96
+ {
97
+ title: 'Upcoming PMs',
98
+ icon: Bell,
99
+ ...testProps('UpcomingPmsGrid'),
100
+ content: <Wrapper>
101
+ <TreeSpecific>
102
+ <UpcomingPmsGrid
103
+ reference="UpcomingPmsGrid"
104
+ nodeType={treeNode?.nodeType}
105
+ nodeId={treeNode?.[selectorSelectedField]}
106
+ />
107
+ </TreeSpecific>
108
+ </Wrapper>,
109
+ },
110
+ {
111
+ title: 'PM Events',
112
+ icon: ClockRegular,
113
+ ...testProps('PmsManager'),
114
+ content: <ManagerScreen
115
+ title="PmEvents"
116
+ icon={OilCan}
117
+ reference="PmEventsManager"
118
+ fullModeComponent={<Wrapper>
119
+ <TreeSpecific>
120
+ <PmEventsFilteredGridEditor
121
+ {...gridProps}
122
+ />
123
+ </TreeSpecific>
124
+ </Wrapper>}
125
+ sideModeComponent={<Wrapper>
126
+ <TreeSpecific>
127
+ <PmEventsFilteredSideGridEditor
128
+ {...gridProps}
129
+ />
130
+ </TreeSpecific>
131
+ </Wrapper>}
132
+ />,
133
+ },
134
+ ]}
135
+ />;
136
+ }
@@ -0,0 +1,25 @@
1
+ import UiGlobals from '../../../UiGlobals.js';
2
+ import Panel from '../../Panel/Panel.js';
3
+ import useAdjustedWindowSize from '../../../Hooks/useAdjustedWindowSize.js';
4
+ import BumpPmsEditor from '../Editor/BumpPmsEditor.js';
5
+
6
+ export default function BumpPmsEditorWindow(props) {
7
+ const
8
+ styles = UiGlobals.styles,
9
+ [width, height] = useAdjustedWindowSize(500, 600);
10
+
11
+ return <Panel
12
+ reference="BumpPmsEditorWindow"
13
+ isCollapsible={false}
14
+ model="PmEvents"
15
+ title={'Add "Bump" PM Event'}
16
+ bg="#fff"
17
+ {...props}
18
+ w={width}
19
+ h={height}
20
+ flex={null}
21
+ >
22
+ <BumpPmsEditor {...props} />
23
+ </Panel>;
24
+ }
25
+
@@ -18,6 +18,8 @@ function ManagerScreen(props) {
18
18
  const {
19
19
  title,
20
20
  icon,
21
+ info,
22
+ _info,
21
23
  sideModeComponent,
22
24
  fullModeComponent,
23
25
  onChangeMode,
@@ -88,6 +90,7 @@ function ManagerScreen(props) {
88
90
  <ScreenHeader
89
91
  title={title}
90
92
  icon={icon}
93
+ info={info}
91
94
  useModeIcons={true}
92
95
  actualMode={actualMode}
93
96
  allowSideBySide={allowSideBySide}
@@ -8,44 +8,63 @@ import {
8
8
  import clsx from 'clsx';
9
9
  import ChartPie from '../Icons/ChartPie.js';
10
10
  import ScreenHeader from '../Layout/ScreenHeader.js';
11
+ import TabBar from '../Tab/TabBar.js';
12
+ import _ from 'lodash';
11
13
 
12
14
  const CONTAINER_THRESHOLD = 1100;
13
15
 
14
16
  export default function ReportsManager(props) {
15
17
  const {
16
18
  reports = [],
19
+ reportTabs,
20
+ initialReportTabIx = 0,
21
+ id,
22
+ self,
17
23
  isActive = false,
18
24
  } = props,
19
25
  [containerWidth, setContainerWidth] = useState(),
20
26
  onLayout = (e) => {
21
27
  setContainerWidth(e.nativeEvent.layout.width);
28
+ },
29
+ renderReportLayout = (reportsToRender = []) => {
30
+ if (!containerWidth) {
31
+ return null;
32
+ }
33
+
34
+ if (containerWidth >= CONTAINER_THRESHOLD) {
35
+ const
36
+ reportsPerColumn = Math.ceil(reportsToRender.length / 2),
37
+ col1Reports = reportsToRender.slice(0, reportsPerColumn),
38
+ col2Reports = reportsToRender.slice(reportsPerColumn);
39
+ return <HStack className="gap-3">
40
+ <VStack className="flex-1">
41
+ {col1Reports}
42
+ </VStack>
43
+ <VStack className="flex-1">
44
+ {col2Reports}
45
+ </VStack>
46
+ </HStack>;
47
+ }
48
+
49
+ return reportsToRender;
22
50
  };
23
51
 
24
52
  if (!isActive) {
25
53
  return null;
26
54
  }
27
55
 
28
- let reportElements = [];
29
- if (containerWidth) {
30
- if (containerWidth >= CONTAINER_THRESHOLD) {
31
- // two column layout
32
- const
33
- reportsPerColumn = Math.ceil(reports.length / 2),
34
- col1Reports = reports.slice(0, reportsPerColumn),
35
- col2Reports = reports.slice(reportsPerColumn);
36
- reportElements = <HStack className="gap-3">
37
- <VStack className="flex-1">
38
- {col1Reports}
39
- </VStack>
40
- <VStack className="flex-1">
41
- {col2Reports}
42
- </VStack>
43
- </HStack>;
44
- } else {
45
- // one column layout
46
- reportElements = reports;
47
- }
48
- }
56
+ const
57
+ hasReportTabs = _.isArray(reportTabs) && reportTabs.length > 0,
58
+ tabBarId = `${id || self?.path || 'ReportsManager'}-reportTabs`,
59
+ reportElements = renderReportLayout(reports),
60
+ tabBarTabs = hasReportTabs ? _.map(reportTabs, (tab, ix) => ({
61
+ ...tab,
62
+ content: <ScrollView className="flex-1 w-full" key={`reportTabContent-${ix}`}>
63
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
64
+ {renderReportLayout(tab.reports || [])}
65
+ </VStackNative>
66
+ </ScrollView>,
67
+ })) : [];
49
68
 
50
69
  return <VStack
51
70
  className="overflow-hidden flex-1 w-full"
@@ -54,10 +73,16 @@ export default function ReportsManager(props) {
54
73
  title="Reports"
55
74
  icon={ChartPie}
56
75
  />
57
- <ScrollView className="flex-1 w-full">
58
- <VStackNative className="w-full p-4" onLayout={onLayout}>
59
- {containerWidth && reportElements}
60
- </VStackNative>
61
- </ScrollView>
76
+ {hasReportTabs ?
77
+ <TabBar
78
+ id={tabBarId}
79
+ initialTabIx={initialReportTabIx}
80
+ tabs={tabBarTabs}
81
+ /> :
82
+ <ScrollView className="flex-1 w-full">
83
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
84
+ {containerWidth && reportElements}
85
+ </VStackNative>
86
+ </ScrollView>}
62
87
  </VStack>;
63
88
  }
@@ -401,7 +401,7 @@ function TreeComponent(props) {
401
401
  } else {
402
402
  // closing
403
403
  if (datumContainsSelection(datum)) {
404
- if (forceSelectionOnCollapse) {
404
+ if (forceSelectionOnCollapse || !allowToggleSelection) {
405
405
  // Select the node being collapsed instead of deselecting all
406
406
  setSelection([datum.item]);
407
407
  } else {
@@ -1068,13 +1068,15 @@ function TreeComponent(props) {
1068
1068
  return items;
1069
1069
  },
1070
1070
  getFooterToolbarItems = () => {
1071
- // Process additionalToolbarButtons to evaluate getIsButtonDisabled functions
1071
+ // Process additionalToolbarButtons to evaluate functions
1072
1072
  const processedButtons = _.map(additionalToolbarButtons, (config) => {
1073
1073
  const processedConfig = { ...config };
1074
- // If the button has an getIsButtonDisabled function, evaluate it with current selection
1075
1074
  if (_.isFunction(config.getIsButtonDisabled)) {
1076
1075
  processedConfig.isDisabled = config.getIsButtonDisabled(selection);
1077
1076
  }
1077
+ if (_.isFunction(config.getText)) {
1078
+ processedConfig.text = config.getText(selection);
1079
+ }
1078
1080
  return processedConfig;
1079
1081
  });
1080
1082
  return _.map(processedButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
@@ -1483,6 +1485,16 @@ function TreeComponent(props) {
1483
1485
  }
1484
1486
  }, [selectorId, selectorSelected]);
1485
1487
 
1488
+ const
1489
+ hasDynamicFooterToolbarItems = useMemo(() => _.some(additionalToolbarButtons, (config) => {
1490
+ return _.isFunction(config.getIsButtonDisabled) || _.isFunction(config.getText);
1491
+ }), [additionalToolbarButtons]),
1492
+ memoizedFooterToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [Repository?.hash, additionalToolbarButtons, getTreeNodeData()]),
1493
+ headerToolbarItemComponents = showHeaderToolbar ? useMemo(() => getHeaderToolbarItems(), [Repository?.hash, treeSearchValue, getTreeNodeData()]) : null,
1494
+ footerToolbarItemComponents = hasDynamicFooterToolbarItems
1495
+ ? getFooterToolbarItems()
1496
+ : memoizedFooterToolbarItemComponents;
1497
+
1486
1498
  if (canUser && !canUser('view')) {
1487
1499
  return <CenterBox>
1488
1500
  <Unauthorized />
@@ -1510,9 +1522,6 @@ function TreeComponent(props) {
1510
1522
  self.forceUpdate = forceUpdate;
1511
1523
  }
1512
1524
 
1513
- const
1514
- headerToolbarItemComponents = showHeaderToolbar ? useMemo(() => getHeaderToolbarItems(), [Repository?.hash, treeSearchValue, getTreeNodeData()]) : null,
1515
- footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [Repository?.hash, additionalToolbarButtons, getTreeNodeData()]);
1516
1525
 
1517
1526
  if (!isReady) {
1518
1527
  return <CenterBox>