@onehat/ui 0.4.102 → 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 (46) 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/Form.js +8 -3
  5. package/src/Components/Grid/Grid.js +230 -154
  6. package/src/Components/Hoc/withPresetButtons.js +18 -6
  7. package/src/Components/Icons/ArrowsLeftRight.js +10 -0
  8. package/src/Components/Icons/Bar.js +10 -0
  9. package/src/Components/Icons/Box.js +11 -0
  10. package/src/Components/Icons/BoxOpen.js +11 -0
  11. package/src/Components/Icons/Bucket.js +10 -0
  12. package/src/Components/Icons/Bump.js +21 -0
  13. package/src/Components/Icons/Calculator.js +12 -0
  14. package/src/Components/Icons/Dots.js +20 -0
  15. package/src/Components/Icons/Fleets.js +26 -0
  16. package/src/Components/Icons/Microchip.js +12 -0
  17. package/src/Components/Icons/Num1.js +10 -0
  18. package/src/Components/Icons/Num2.js +10 -0
  19. package/src/Components/Icons/Num3.js +10 -0
  20. package/src/Components/Icons/Num4.js +10 -0
  21. package/src/Components/Icons/OilCan.js +11 -0
  22. package/src/Components/Icons/Operations.js +10 -0
  23. package/src/Components/Icons/OverduePms.js +10 -0
  24. package/src/Components/Icons/SackDollar.js +11 -0
  25. package/src/Components/Icons/ShortBar.js +15 -0
  26. package/src/Components/Icons/Tower.js +10 -0
  27. package/src/Components/Icons/UpcomingPms.js +10 -0
  28. package/src/Components/Layout/ScreenHeader.js +35 -3
  29. package/src/Components/Layout/SetupButton.js +31 -0
  30. package/src/Components/Layout/UserIndicator.js +35 -0
  31. package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
  32. package/src/Components/Pms/Editor/MetersEditor.js +173 -0
  33. package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
  34. package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
  35. package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
  36. package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
  37. package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
  38. package/src/Components/Pms/Screens/PmsManager.js +136 -0
  39. package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
  40. package/src/Components/Screens/Manager.js +3 -0
  41. package/src/Components/Tree/Tree.js +15 -6
  42. package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
  43. package/src/Components/Viewer/TextWithLinks.js +9 -1
  44. package/src/Components/Viewer/Viewer.js +38 -30
  45. package/src/Functions/flatten.js +39 -0
  46. 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}
@@ -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>
@@ -29,6 +29,7 @@ import {
29
29
  METER_TYPES__MILES_UNITS,
30
30
  METER_TYPES__MILES_TEXT,
31
31
  } from '../../Constants/MeterTypes.js';
32
+ import flatten from '../../Functions/flatten.js';
32
33
  import Button from '../Buttons/Button.js';
33
34
  import Json from '../Form/Field/Json.js';
34
35
  import Panel from '../Panel/Panel.js';
@@ -38,6 +39,7 @@ import testProps from '../../Functions/testProps.js';
38
39
  import moment from 'moment';
39
40
  import _ from 'lodash';
40
41
 
42
+
41
43
  export default function PmCalcDebugViewer(props) {
42
44
 
43
45
  const {
@@ -45,34 +47,7 @@ export default function PmCalcDebugViewer(props) {
45
47
  onClose,
46
48
  } = props,
47
49
  json = metersPmSchedule?.properties.meters_pm_schedules__debug_json.json,
48
- flatten = (obj, prefix = '', result = {}) => {
49
- for (const key in obj) {
50
- if (obj.hasOwnProperty(key)) {
51
- const
52
- newKey = prefix ? `${prefix}.${key}` : key,
53
- value = obj[key];
54
-
55
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
56
- // Recursively flatten nested objects
57
- flatten(value, newKey, result);
58
- } else if (Array.isArray(value)) {
59
- // Flatten arrays using index as key
60
- value.forEach((item, index) => {
61
- const arrayKey = `${newKey}.${index}`;
62
- if (item !== null && typeof item === 'object') {
63
- flatten(item, arrayKey, result);
64
- } else {
65
- result[arrayKey] = item;
66
- }
67
- });
68
- } else {
69
- // Assign primitive values and null
70
- result[newKey] = value;
71
- }
72
- }
73
- }
74
- return result;
75
- },
50
+ record = flatten(json),
76
51
  ucfirst = (string) => {
77
52
  return string.charAt(0).toUpperCase() + string.slice(1);
78
53
  },
@@ -90,37 +65,85 @@ export default function PmCalcDebugViewer(props) {
90
65
  return 'Unknown Mode';
91
66
  }
92
67
  },
93
- flattenedJson = flatten(json),
68
+ formatBooleanValue = (value, record, self) => {
69
+ if (value === null || value === undefined) {
70
+ return 'N/A';
71
+ }
72
+ return value ? 'Yes' : 'No';
73
+ },
94
74
  formatDaysValue = (value, record, self) => {
95
75
  if (value === null || value === undefined) {
96
- return value;
76
+ return 'N/A';
97
77
  }
98
78
  return parseInt(_.round(value), 10);
99
79
  },
100
80
  formatMeterValue = (value, record, self) => {
101
81
  if (value === null || value === undefined) {
102
- return value;
82
+ return 'N/A';
103
83
  }
104
84
  let ret;
105
85
  const meterType = parseInt(record?.meter_type, 10);
86
+ let units = meterType === METER_TYPES__HOURS ? METER_TYPES__HOURS_UNITS : meterType === METER_TYPES__MILES ? METER_TYPES__MILES_UNITS : '';
87
+ if (value === 1) {
88
+ units = units.replace(/s$/, ''); // remove plural 's' if value is 1
89
+ }
106
90
  switch(meterType) {
107
91
  case METER_TYPES__HOURS:
108
- ret = `${value} ${METER_TYPES__HOURS_UNITS}`;
92
+ ret = `${value} ${units}`;
109
93
  break;
110
94
  case METER_TYPES__MILES:
111
- ret = `${value} ${METER_TYPES__MILES_UNITS}`;
95
+ ret = `${value} ${units}`;
112
96
  break;
113
97
  }
114
98
  return ret;
115
99
  },
116
100
  formatDateValue = (value, record, self) => {
117
101
  if (value === null || value === undefined) {
118
- return value;
102
+ return 'N/A';
119
103
  }
120
104
 
121
105
  // convert from datetime to pretty-printed date
122
106
  return moment(value).format(MOMENT_DATE_FORMAT_6);
123
107
  },
108
+ formatStatusValue = (value, record, self) => {
109
+ let ret = value,
110
+ classNames = null;
111
+
112
+ switch(record['pm_status_id']) {
113
+ case PM_STATUSES__OVERDUE:
114
+ classNames = [
115
+ 'text-red-500',
116
+ 'font-bold',
117
+ ];
118
+ break;
119
+ case PM_STATUSES__PM_DUE:
120
+ classNames = [
121
+ 'text-[#c89903]',
122
+ ];
123
+ break;
124
+ case PM_STATUSES__DELAYED:
125
+ classNames = [
126
+ 'text-green-500',
127
+ ];
128
+ break;
129
+ case PM_STATUSES__WILL_CALL:
130
+ classNames = [
131
+ 'text-blue-500',
132
+ ];
133
+ break;
134
+ }
135
+ if (classNames) {
136
+ // special formatting
137
+ ret = <Text
138
+ className={clsx(
139
+ classNames,
140
+ 'px-3',
141
+ 'py-2',
142
+ )}
143
+ >{value}</Text>;
144
+ }
145
+ return ret;
146
+ },
124
147
  items = [
125
148
  {
126
149
  "type": "Column",
@@ -131,133 +154,138 @@ export default function PmCalcDebugViewer(props) {
131
154
  "items": [
132
155
  {
133
156
  "type": "FieldSet",
134
- "title": "General",
135
- "reference": "general",
157
+ "title": "Overview",
158
+ "reference": "overview",
136
159
  "defaults": {},
137
160
  "items": [
138
- {
161
+ record['pmSchedule.name'] && {
139
162
  label: 'PM Schedule',
140
163
  name: 'pmSchedule.name',
141
164
  },
142
- {
165
+ record['pm_status_name'] && {
143
166
  label: 'Status',
144
167
  name: 'pm_status_name',
168
+ viewerFormatter: formatStatusValue,
145
169
  },
146
- {
170
+ record['nextPmDate'] && {
147
171
  label: 'Next PM Date',
148
172
  name: 'nextPmDate',
149
173
  viewerFormatter: formatDateValue,
150
174
  },
151
- json?.overduePms > 0 && {
152
- label: 'Overdue PMs',
153
- name: 'overduePms',
154
- },
155
- ],
156
- },
157
- {
158
- "type": "FieldSet",
159
- "title": "Calculation Details",
160
- "reference": "calcDetails",
161
- "defaults": {},
162
- "items": [
163
- {
164
- label: 'Controlling Method',
165
- name: 'controllingMethod',
166
- tooltip: 'Indicates whether the calculation was based on days or usage (meter). ' +
167
- 'If both methods are applicable, the one resulting in the earlier PM date is chosen.',
168
- },
169
- (json?.pm_status_id === PM_STATUSES__OVERDUE || json?.pm_status_id === PM_STATUSES__PM_DUE) && {
170
- label: 'Grace Period End Date',
175
+ record['pm_status_id'] && record['pm_status_id'] === PM_STATUSES__OVERDUE && {
176
+ label: 'Grace Period Ends',
171
177
  name: 'maxGracePeriodDateTime',
172
178
  viewerFormatter: formatDateValue,
173
179
  },
174
- {
175
- label: 'Last Reset Date',
176
- name: 'resetDate',
177
- tooltip: 'Indicates whether the calculation was based on days or usage (meter). ' +
178
- 'If both methods are applicable, the one resulting in the earlier PM date is chosen.',
179
- viewerFormatter: formatDateValue,
180
- },
181
- json?.workOrder?.title && {
182
- label: 'Work Order',
183
- name: 'workOrder.title',
184
- },
185
- json?.workOrder?.service_order && {
186
- label: 'Work Order',
187
- name: 'workOrder.service_order',
188
- },
189
- {
190
- label: 'Meter Accrued Since Last PM',
191
- name: 'meterAccruedSinceLatestPm',
192
- viewerFormatter: formatMeterValue,
193
- },
194
- {
195
- label: 'Meter Remaining Until Next PM',
196
- name: 'meterRemainingUntilNextPm',
197
- viewerFormatter: formatMeterValue,
198
- },
199
- {
200
- label: 'Days Since Last PM',
201
- name: 'daysSinceLatestPm',
202
- viewerFormatter: formatDaysValue,
203
- },
204
- {
205
- label: 'Days Left Until Next PM',
206
- name: 'daysLeft',
207
- viewerFormatter: formatDaysValue,
180
+ record['pm_status_id'] && record['pm_status_id'] === PM_STATUSES__OVERDUE && {
181
+ label: 'Overdue by # Cycles',
182
+ name: 'overduePms',
208
183
  },
209
- // json?.isDelayed && {
210
- // label: 'Is Delayed',
211
- // name: 'isDelayed',
212
- // },
213
- // json?.isOverride && {
214
- // label: 'Is Override',
215
- // name: 'isOverride',
216
- // },
217
- ]
184
+ ],
218
185
  },
219
- {
186
+ record['calculationMode'] && {
220
187
  "type": "FieldSet",
221
- "title": "PM Schedule Config",
222
- "reference": "pmSchedule",
188
+ "title": "How it was calculated",
189
+ "reference": "calcDetails",
223
190
  "defaults": {},
224
191
  "items": [
225
- json?.pmSchedule?.interval_days && {
226
- label: 'Interval Days',
227
- name: 'pmSchedule.interval_days',
228
- },
229
- json?.pmSchedule?.interval_meter && {
230
- label: 'Interval Meter',
231
- name: 'pmSchedule.interval_meter',
232
- viewerFormatter: formatMeterValue,
233
- },
234
192
  {
235
- label: 'Mode',
236
- name: 'pmScheduleMode',
193
+ label: 'Calculation Mode',
194
+ name: 'calculationMode',
237
195
  },
238
- ]
239
- },
240
- {
241
- "type": "FieldSet",
242
- "title": "Equipment",
243
- "reference": "equipment",
244
- "defaults": {},
245
- "items": [
246
196
  {
247
197
  label: 'In Service Date',
248
198
  name: 'inServiceDate',
249
199
  viewerFormatter: formatDateValue,
250
200
  },
251
- {
252
- label: 'Latest Meter Reading',
253
- name: 'latestMeterReading',
254
- viewerFormatter: formatMeterValue,
255
- },
256
- {
257
- label: 'Avg Daily Meter',
258
- name: 'avgDailyMeter',
259
- viewerFormatter: formatMeterValue,
260
- },
201
+ ...(record['isOverride'] ? [
202
+ // these items are only for overrides
203
+ {
204
+ label: 'Is Override',
205
+ name: 'isOverride',
206
+ viewerFormatter: formatBooleanValue,
207
+ },
208
+ {
209
+ label: 'Override Event Date',
210
+ name: 'overrideEventDate',
211
+ viewerFormatter: formatDateValue,
212
+ },
213
+ ] : []),
214
+ ...(!record['isOverride'] ? [
215
+ // these items are only for non-overrides
216
+ {
217
+ label: 'Last Reset Date',
218
+ name: 'resetDate',
219
+ viewerFormatter: formatDateValue,
220
+ },
221
+ record['workOrder.title'] && { // Gingerich
222
+ label: 'Work Order',
223
+ name: 'workOrder.title',
224
+ },
225
+ record['workOrder.service_order'] && { // MH
226
+ label: 'Service Order',
227
+ name: 'workOrder.service_order',
228
+ },
229
+ {
230
+ label: 'Interval Days',
231
+ name: 'intervalDays',
232
+ },
233
+ {
234
+ label: 'Days Left Until Next PM',
235
+ name: 'daysLeft',
236
+ viewerFormatter: formatDaysValue,
237
+ },
238
+ {
239
+ label: 'Interval Meter',
240
+ name: 'intervalMeter',
241
+ viewerFormatter: formatMeterValue,
242
+ },
243
+ typeof record['latestMeterReading.value'] !== 'undefined' && { // typeof so it allows 0
244
+ label: 'Latest Meter Reading',
245
+ name: 'latestMeterReading',
246
+ viewerFormatter: (value, record) => {
247
+ const
248
+ meterValue = formatMeterValue(record['latestMeterReading.value'], record),
249
+ meterDate = formatDateValue(record['latestMeterReading.date'], record);
250
+ return `${meterValue} on ${meterDate}`;
251
+ }
252
+ },
253
+ {
254
+ label: 'Meter Accrued Since Latest PM',
255
+ name: 'meterAccruedSinceLatestPm',
256
+ viewerFormatter: formatMeterValue,
257
+ },
258
+ {
259
+ label: 'Avg Daily Meter',
260
+ name: 'avgDailyMeter',
261
+ viewerFormatter: formatMeterValue,
262
+ },
263
+ {
264
+ label: 'Meter Until Next PM',
265
+ name: 'meterRemainingUntilNextPm',
266
+ viewerFormatter: formatMeterValue,
267
+ },
268
+ {
269
+ label: 'Controlling Method',
270
+ name: 'controllingMethod',
271
+ tooltip: 'Indicates whether the calculation was based on days or usage (meter). ' +
272
+ 'If both methods are applicable, the one resulting in the earlier PM date is chosen.',
273
+ },
274
+ ] : []),
275
+
276
+ ...(record['isOverride'] ? [
277
+ // these items are only for delays
278
+ {
279
+ label: 'Is Delayed',
280
+ name: 'isDelayed',
281
+ viewerFormatter: formatBooleanValue,
282
+ },
283
+ {
284
+ label: 'Delay Threshold Date',
285
+ name: 'delayThresholdDate',
286
+ viewerFormatter: formatDateValue,
287
+ },
288
+ ] : []),
261
289
  ]
262
290
  },
263
291
 
@@ -265,7 +293,7 @@ export default function PmCalcDebugViewer(props) {
265
293
  }
266
294
  ];
267
295
 
268
- flattenedJson.pmScheduleMode = getPmScheduleMode(json?.pmSchedule.pm_schedule_mode_id);
296
+ record.pmScheduleMode = getPmScheduleMode(json?.pmSchedule.pm_schedule_mode_id);
269
297
 
270
298
  return <Panel
271
299
  title="PM Calculation Info"
@@ -282,18 +310,8 @@ export default function PmCalcDebugViewer(props) {
282
310
  </Footer>}
283
311
  >
284
312
  <Viewer
285
- record={flattenedJson}
313
+ record={record}
286
314
  items={items}
287
315
  />
288
- {/* <VStack className="PmCalcDebugViewer-VStack p-4">
289
-
290
- <Text>Equipment: {metersPmSchedule.meters__nickname}</Text>
291
- <Json
292
- value={metersPmSchedule.meters_pm_schedules__debug_json}
293
- displaySize="expanded"
294
- editable={false}
295
- collapsed={2}
296
- />
297
- </VStack> */}
298
316
  </Panel>;
299
317
  }
@@ -49,13 +49,21 @@ function TextWithLinksElement(props) {
49
49
  modifiedText = modifiedText.replace(link, key);
50
50
  });
51
51
 
52
- const
52
+ let
53
53
  textClassName = clsx(
54
54
  'TextWithLinks-Text',
55
55
  'text-base',
56
56
  'overflow-hidden',
57
+ styles.FORM_TEXT_CLASSNAME,
57
58
  ),
58
59
  textSegments = modifiedText.split(/(link_\d+)/);
60
+ if (props.className) {
61
+ const
62
+ classes = props.className.split(' '),
63
+ newClasses = _.reject(classes, (c) => c === 'overflow-auto'),
64
+ className = newClasses.join(' ');
65
+ textClassName += ' ' + className;
66
+ }
59
67
  if (textSegments.length === 1) {
60
68
  return <Text className={textClassName}>{modifiedText}</Text>;
61
69
  }