@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.
- package/package.json +1 -1
- package/src/Components/Accordion/Accordion.js +65 -6
- package/src/Components/Container/Container.js +10 -4
- package/src/Components/Form/Field/Combo/Combo.js +10 -4
- package/src/Components/Form/Field/Tag/Tag.js +6 -0
- package/src/Components/Form/Form.js +8 -3
- package/src/Components/Grid/Grid.js +232 -154
- package/src/Components/Grid/GridRow.js +7 -3
- package/src/Components/Hoc/withPresetButtons.js +18 -6
- package/src/Components/Icons/ArrowsLeftRight.js +10 -0
- package/src/Components/Icons/Bar.js +10 -0
- package/src/Components/Icons/Box.js +11 -0
- package/src/Components/Icons/BoxOpen.js +11 -0
- package/src/Components/Icons/Bucket.js +10 -0
- package/src/Components/Icons/Bump.js +21 -0
- package/src/Components/Icons/Calculator.js +12 -0
- package/src/Components/Icons/Dots.js +20 -0
- package/src/Components/Icons/Fleets.js +26 -0
- package/src/Components/Icons/Lock.js +11 -0
- package/src/Components/Icons/Microchip.js +12 -0
- package/src/Components/Icons/Num1.js +10 -0
- package/src/Components/Icons/Num2.js +10 -0
- package/src/Components/Icons/Num3.js +10 -0
- package/src/Components/Icons/Num4.js +10 -0
- package/src/Components/Icons/OilCan.js +11 -0
- package/src/Components/Icons/Operations.js +10 -0
- package/src/Components/Icons/OverduePms.js +10 -0
- package/src/Components/Icons/SackDollar.js +11 -0
- package/src/Components/Icons/ShortBar.js +15 -0
- package/src/Components/Icons/Tower.js +10 -0
- package/src/Components/Icons/UpcomingPms.js +10 -0
- package/src/Components/Layout/ScreenHeader.js +35 -3
- package/src/Components/Layout/SetupButton.js +31 -0
- package/src/Components/Layout/UserIndicator.js +35 -0
- package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
- package/src/Components/Pms/Editor/MetersEditor.js +173 -0
- package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
- package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
- package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
- package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
- package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
- package/src/Components/Pms/Screens/PmsManager.js +136 -0
- package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
- package/src/Components/Screens/Manager.js +3 -0
- package/src/Components/Screens/ReportsManager.js +51 -26
- package/src/Components/Tree/Tree.js +15 -6
- package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
- package/src/Components/Viewer/TextWithLinks.js +9 -1
- package/src/Components/Viewer/Viewer.js +38 -30
- package/src/Functions/flatten.js +39 -0
- 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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
58
|
-
<
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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>
|