@onehat/ui 0.4.95 → 0.4.97

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.
@@ -30,12 +30,15 @@ export default function withEditor(WrappedComponent, isTree = false) {
30
30
  userCanView = true,
31
31
  canEditorViewOnly = false, // whether the editor can *ever* change state out of 'View' mode
32
32
  canProceedWithCrud, // fn returns bool on if the CRUD operation can proceed
33
+ canRecordBeEdited, // fn(selection) returns bool on if the current record(s) can be edited
34
+ canRecordBeDeleted, // fn(selection) returns bool on if the current record(s) can be deleted
33
35
  disableAdd = false,
34
36
  disableEdit = false,
35
37
  disableDelete = false,
36
38
  disableDuplicate = false,
37
39
  disableView = false,
38
40
  useRemoteDuplicate = false, // call specific copyToNew function on server, rather than simple duplicate on client
41
+ getDuplicateValues, // fn(entity) to get default values for duplication
39
42
  getRecordIdentifier = (selection) => {
40
43
  if (selection.length > 1) {
41
44
  return 'records?';
@@ -87,6 +90,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
87
90
  editorModeRef = useRef(initialEditorMode),
88
91
  isIgnoreNextSelectionChangeRef = useRef(false),
89
92
  isEditorShownRef = useRef(false),
93
+ defaultValuesRef = useRef(defaultValues),
94
+ canEditorBeInEditModeRef = useRef(true), // whether the editor is allowed to be in edit mode based on canRecordBeEdited
90
95
  [currentRecord, setCurrentRecord] = useState(null),
91
96
  [isAdding, setIsAdding] = useState(false),
92
97
  [isSaving, setIsSaving] = useState(false),
@@ -108,6 +113,19 @@ export default function withEditor(WrappedComponent, isTree = false) {
108
113
  getIsEditorShown = () => {
109
114
  return isEditorShownRef.current;
110
115
  },
116
+ getDefaultValues = () => {
117
+ return defaultValuesRef.current;
118
+ },
119
+ setDefaultValues = (vals) => {
120
+ defaultValuesRef.current = vals;
121
+ },
122
+ setCanEditorBeInEditMode = (bool) => {
123
+ canEditorBeInEditModeRef.current = bool;
124
+ forceUpdate();
125
+ },
126
+ getCanEditorBeInEditMode = () => {
127
+ return canEditorBeInEditModeRef.current;
128
+ },
111
129
  setIsWaitModalShown = (bool) => {
112
130
  const
113
131
  dispatch = UiGlobals.redux?.dispatch,
@@ -154,6 +172,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
154
172
  getNewEntityDisplayValue = () => {
155
173
  return newEntityDisplayValueRef.current;
156
174
  },
175
+ setNewEntityDisplayValue = (val) => {
176
+ newEntityDisplayValueRef.current = val;
177
+ },
157
178
  doAdd = async (e, values) => {
158
179
  if (canUser && !canUser(ADD)) {
159
180
  showPermissionsError(ADD);
@@ -181,8 +202,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
181
202
  // 2. Use the repository's default values (defined on each property as 'defaultValue'), or
182
203
  // 3. Individually override the repository's default values with submitted 'defaultValues' (given as a prop to this HOC)
183
204
  let defaultValuesToUse = Repository.getSchema().getDefaultValues();
184
- if (defaultValues) {
185
- _.merge(defaultValuesToUse, defaultValues);
205
+ if (getDefaultValues()) {
206
+ _.merge(defaultValuesToUse, getDefaultValues());
186
207
  }
187
208
  addValues = {...defaultValuesToUse};
188
209
  }
@@ -448,7 +469,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
448
469
  const
449
470
  entity = selection[0],
450
471
  idProperty = Repository.getSchema().model.idProperty,
451
- rawValues = _.omit(entity.getOriginalData(), idProperty);
472
+ rawValues = getDuplicateValues ? getDuplicateValues(entity) : _.omit(entity.getOriginalData(), idProperty);
452
473
  rawValues.id = null; // unset the id of the duplicate
453
474
 
454
475
  setIsWaitModalShown(true);
@@ -624,6 +645,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
624
645
  });
625
646
  },
626
647
  calculateEditorMode = () => {
648
+ if (!getCanEditorBeInEditMode()) { // this is a result of canRecordBeEdited returning false
649
+ return EDITOR_MODE__VIEW;
650
+ }
627
651
 
628
652
  let isIgnoreNextSelectionChange = getIsIgnoreNextSelectionChange(),
629
653
  doStayInEditModeOnSelectionChange = stayInEditModeOnSelectionChange;
@@ -691,7 +715,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
691
715
  useEffect(() => {
692
716
 
693
717
  if (editorType === EDITOR_TYPE__SIDE) {
694
- if (selection?.length) { // || isAdding
718
+ if (selection?.length) { // || isAdding
695
719
  // there is a selection, so show the editor
696
720
  setIsEditorShown(true);
697
721
  } else {
@@ -700,6 +724,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
700
724
  }
701
725
  }
702
726
 
727
+ if (canRecordBeEdited && canRecordBeEdited(selection) === false) {
728
+ setCanEditorBeInEditMode(false);
729
+ } else {
730
+ setCanEditorBeInEditMode(true);
731
+ }
703
732
  setEditorMode(calculateEditorMode());
704
733
  setLastSelection(selection);
705
734
 
@@ -720,7 +749,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
720
749
  self.duplicate = doDuplicate;
721
750
  self.setIsEditorShown = setIsEditorShown;
722
751
  }
723
- newEntityDisplayValueRef.current = newEntityDisplayValue;
752
+ setNewEntityDisplayValue(newEntityDisplayValue);
753
+ setDefaultValues(defaultValues);
724
754
 
725
755
  if (lastSelection !== selection) {
726
756
  // NOTE: If I don't calculate this on the fly for selection changes,
@@ -749,8 +779,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
749
779
  setIsEditorShown={setIsEditorShown}
750
780
  setIsIgnoreNextSelectionChange={setIsIgnoreNextSelectionChange}
751
781
  onAdd={(!userCanEdit || disableAdd) ? null : doAdd}
752
- onEdit={(!userCanEdit || disableEdit) ? null : doEdit}
753
- onDelete={(!userCanEdit || disableDelete) ? null : doDelete}
782
+ onEdit={(!userCanEdit || disableEdit || (canRecordBeEdited && !canRecordBeEdited(selection))) ? null : doEdit}
783
+ onDelete={(!userCanEdit || disableDelete || (canRecordBeDeleted && !canRecordBeDeleted(selection))) ? null : doDelete}
754
784
  onView={doView}
755
785
  onDuplicate={doDuplicate}
756
786
  onEditorSave={doEditorSave}
@@ -48,7 +48,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
48
48
  }
49
49
 
50
50
  const {
51
- // extract and pass
51
+ // for local use
52
52
  contextMenuItems = [],
53
53
  additionalToolbarButtons = [],
54
54
  useUploadDownload = false,
@@ -59,18 +59,18 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
59
59
  downloadHeaders,
60
60
  downloadParams,
61
61
  onChangeColumnsConfig,
62
- canRecordBeEdited,
63
- canRecordBeDeleted,
64
- canRecordBeDuplicated,
65
62
  ...propsToPass
66
63
  } = props,
67
64
  {
68
- // for local use
65
+ // extract and pass down
69
66
  isEditor = false,
70
67
  isTree = false,
71
68
  canDeleteRootNode = false,
72
69
  isSideEditor = false,
73
70
  canEditorViewOnly = false,
71
+ canRecordBeEdited, // fn(selection) returns bool on if the current record(s) can be edited
72
+ canRecordBeDeleted, // fn(selection) returns bool on if the current record(s) can be deleted
73
+ canRecordBeDuplicated, // fn(selection) returns bool on if the current record(s) can be duplicated
74
74
  disableAdd = !isEditor,
75
75
  disableEdit = !isEditor,
76
76
  disableDelete = !isEditor,
@@ -12,6 +12,7 @@ export function GridPanel(props) {
12
12
  isEditor = false,
13
13
  editorType = EDITOR_TYPE__WINDOWED,
14
14
  _panel = {},
15
+ _grid = {},
15
16
  } = props;
16
17
 
17
18
  let WhichGrid = Grid;
@@ -30,7 +31,7 @@ export function GridPanel(props) {
30
31
  }
31
32
 
32
33
  return <Panel {...props} {..._panel}>
33
- <WhichGrid {...props} />
34
+ <WhichGrid {...props} {..._grid} />
34
35
  </Panel>;
35
36
  }
36
37
 
@@ -3,8 +3,11 @@ import Panel from './Panel.js';
3
3
 
4
4
 
5
5
  export default function TabPanel(props) {
6
- const panelProps = props._panel || {};
7
- return <Panel className="w-full flex" {...panelProps}>
8
- <TabBar {...props} {...props._tab} />
6
+ const {
7
+ _panel = {},
8
+ _tab = {},
9
+ } = props;
10
+ return <Panel {...props} className="w-full flex" {..._panel}>
11
+ <TabBar {...props} {..._tab} />
9
12
  </Panel>;
10
13
  }
@@ -11,6 +11,7 @@ export function TreePanel(props) {
11
11
  isEditor = false,
12
12
  editorType = EDITOR_TYPE__WINDOWED,
13
13
  _panel = {},
14
+ _tree = {},
14
15
  } = props;
15
16
 
16
17
  let WhichTree = Tree;
@@ -25,8 +26,8 @@ export function TreePanel(props) {
25
26
  }
26
27
  }
27
28
 
28
- return <Panel {..._panel}>
29
- <WhichTree {...props} {..._panel} />
29
+ return <Panel {...props} {..._panel}>
30
+ <WhichTree {...props} {..._tree} />
30
31
  </Panel>;
31
32
  }
32
33
 
@@ -3,6 +3,7 @@ import {
3
3
  HStack,
4
4
  Pressable,
5
5
  ScrollView,
6
+ Text,
6
7
  VStack,
7
8
  VStackNative,
8
9
  } from '@project-components/Gluestack';
@@ -133,7 +134,17 @@ function TreeComponent(props) {
133
134
  canNodeMoveInternally, // optional fn to customize whether each node can be dragged INternally
134
135
  canNodeMoveExternally, // optional fn to customize whether each node can be dragged EXternally
135
136
  canNodeAcceptDrop, // optional fn to customize whether each node can accept a dropped item: (targetItem, draggedItem) => boolean
136
- getCustomDragProxy, // optional fn to render custom drag preview: (item, selection) => ReactElement
137
+ dragProxyField,
138
+ getCustomDragProxy = (item, selection) => { // optional fn to render custom drag preview: (item, selection) => ReactElement
139
+ let selectionCount = selection?.length || 1,
140
+ displayText = dragProxyField ? item[dragProxyField] : (item.displayValue || 'Selected TreeNode');
141
+ return <VStack className="bg-white border border-gray-300 rounded-lg p-3 shadow-lg max-w-[200px]">
142
+ <Text className="font-semibold text-gray-800">{displayText}</Text>
143
+ {selectionCount > 1 &&
144
+ <Text className="text-sm text-gray-600">(+{selectionCount -1} more item{selectionCount > 2 ? 's' : ''})</Text>
145
+ }
146
+ </VStack>;
147
+ },
137
148
  dragPreviewOptions, // optional object for drag preview positioning options
138
149
  areNodesDragSource = false,
139
150
  nodeDragSourceType,
@@ -643,9 +654,23 @@ function TreeComponent(props) {
643
654
  return;
644
655
  }
645
656
 
646
- // Create datum for the new entity and add it to parent's children
647
657
  const newDatum = buildTreeNodeDatum(entity);
648
- parentDatum.children.push(newDatum);
658
+
659
+ let isInserted = false;
660
+ if (entity._originalData?.previousSiblingId) {
661
+ // Insert datum in the correct position among parent's children
662
+ const siblingDatum = getDatumById(entity._originalData.previousSiblingId);
663
+ if (siblingDatum) {
664
+ const siblingIndex = parentDatum.children.findIndex(child => child.item.id === siblingDatum.item.id);
665
+ parentDatum.children.splice(siblingIndex + 1, 0, newDatum);
666
+ isInserted = true;
667
+ }
668
+ }
669
+ if (!isInserted) {
670
+ // Insert datum at end of parent's children
671
+ parentDatum.children.push(newDatum);
672
+ }
673
+
649
674
 
650
675
  // Update parent to show it has children and expand if needed
651
676
  if (!entity.parent.hasChildren) {
@@ -1041,7 +1066,16 @@ function TreeComponent(props) {
1041
1066
  return items;
1042
1067
  },
1043
1068
  getFooterToolbarItems = () => {
1044
- return _.map(additionalToolbarButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
1069
+ // Process additionalToolbarButtons to evaluate getIsButtonDisabled functions
1070
+ const processedButtons = _.map(additionalToolbarButtons, (config) => {
1071
+ const processedConfig = { ...config };
1072
+ // If the button has an getIsButtonDisabled function, evaluate it with current selection
1073
+ if (_.isFunction(config.getIsButtonDisabled)) {
1074
+ processedConfig.isDisabled = config.getIsButtonDisabled(selection);
1075
+ }
1076
+ return processedConfig;
1077
+ });
1078
+ return _.map(processedButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
1045
1079
  },
1046
1080
  renderTreeNode = (datum) => {
1047
1081
  if (!datum.isVisible) {
@@ -148,6 +148,9 @@ export default function TreeNode(props) {
148
148
  if (props.className) {
149
149
  className += ' ' + props.className;
150
150
  }
151
+ if (nodeProps.className) {
152
+ className += ' ' + nodeProps.className;
153
+ }
151
154
 
152
155
  return <HStackNative
153
156
  {...testProps('node' + (isSelected ? '-selected' : ''))}
@@ -0,0 +1,25 @@
1
+ import DisplayField from '../Form/Field/DisplayField.js';
2
+ import clsx from 'clsx';
3
+ import {
4
+ MOMENT_DATE_FORMAT_1,
5
+ } from '../../Constants/Dates';
6
+
7
+ export default function DateTimeViewer(props) {
8
+ const {
9
+ moment,
10
+ } = props;
11
+ if (!moment || !moment.isValid()) {
12
+ return null;
13
+ }
14
+ let className = clsx(
15
+ 'flex-none',
16
+ );
17
+ if (props.className) {
18
+ className += ' ' + props.className;
19
+ }
20
+ return <DisplayField
21
+ text={moment.format(MOMENT_DATE_FORMAT_1)}
22
+ {...props}
23
+ className={className}
24
+ />;
25
+ }
@@ -0,0 +1,25 @@
1
+ import DisplayField from '../Form/Field/DisplayField.js';
2
+ import clsx from 'clsx';
3
+ import {
4
+ MOMENT_DATE_FORMAT_4,
5
+ } from '../../Constants/Dates';
6
+
7
+ export default function DateViewer(props) {
8
+ const {
9
+ moment,
10
+ } = props;
11
+ if (!moment || !moment.isValid()) {
12
+ return null;
13
+ }
14
+ let className = clsx(
15
+ 'flex-none',
16
+ );
17
+ if (props.className) {
18
+ className += ' ' + props.className;
19
+ }
20
+ return <DisplayField
21
+ text={moment.format(MOMENT_DATE_FORMAT_4)}
22
+ {...props}
23
+ className={className}
24
+ />;
25
+ }
@@ -0,0 +1,299 @@
1
+ import {
2
+ Text,
3
+ VStack,
4
+ } from '@project-components/Gluestack';
5
+ import clsx from 'clsx';
6
+ import {
7
+ MOMENT_DATE_FORMAT_6,
8
+ } from '../../Constants/Dates.js';
9
+ import {
10
+ PM_SCHEDULE_MODES__HISTORICAL_USAGE,
11
+ PM_SCHEDULE_MODES__EXPECTED_USAGE,
12
+ PM_SCHEDULE_MODES__NO_ESTIMATION,
13
+ PM_SCHEDULE_MODES__WILL_CALL,
14
+ } from '../../Constants/PmScheduleModes.js';
15
+ import {
16
+ PM_STATUSES__OK,
17
+ PM_STATUSES__PM_DUE,
18
+ PM_STATUSES__DELAYED,
19
+ PM_STATUSES__WILL_CALL,
20
+ PM_STATUSES__SCHEDULED,
21
+ PM_STATUSES__OVERDUE,
22
+ PM_STATUSES__COMPLETED,
23
+ } from '../../Constants/PmStatuses.js';
24
+ import {
25
+ METER_TYPES__HOURS,
26
+ METER_TYPES__HOURS_UNITS,
27
+ METER_TYPES__HOURS_TEXT,
28
+ METER_TYPES__MILES,
29
+ METER_TYPES__MILES_UNITS,
30
+ METER_TYPES__MILES_TEXT,
31
+ } from '../../Constants/MeterTypes.js';
32
+ import Button from '../Buttons/Button.js';
33
+ import Json from '../Form/Field/Json.js';
34
+ import Panel from '../Panel/Panel.js';
35
+ import Footer from '../Layout/Footer.js';
36
+ import Viewer from '../Viewer/Viewer.js';
37
+ import testProps from '../../Functions/testProps.js';
38
+ import moment from 'moment';
39
+ import _ from 'lodash';
40
+
41
+ export default function PmCalcDebugViewer(props) {
42
+
43
+ const {
44
+ metersPmSchedule,
45
+ onClose,
46
+ } = props,
47
+ 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
+ },
76
+ ucfirst = (string) => {
77
+ return string.charAt(0).toUpperCase() + string.slice(1);
78
+ },
79
+ getPmScheduleMode = (pm_schedule_mode_id) => {
80
+ switch(pm_schedule_mode_id) {
81
+ case PM_SCHEDULE_MODES__HISTORICAL_USAGE:
82
+ return 'Historical Usage';
83
+ case PM_SCHEDULE_MODES__EXPECTED_USAGE:
84
+ return 'Expected Usage';
85
+ case PM_SCHEDULE_MODES__NO_ESTIMATION:
86
+ return 'No Estimation';
87
+ case PM_SCHEDULE_MODES__WILL_CALL:
88
+ return 'Will Call';
89
+ default:
90
+ return 'Unknown Mode';
91
+ }
92
+ },
93
+ flattenedJson = flatten(json),
94
+ formatDaysValue = (value, record, self) => {
95
+ if (value === null || value === undefined) {
96
+ return value;
97
+ }
98
+ return parseInt(_.round(value), 10);
99
+ },
100
+ formatMeterValue = (value, record, self) => {
101
+ if (value === null || value === undefined) {
102
+ return value;
103
+ }
104
+ let ret;
105
+ const meterType = parseInt(record?.meter_type, 10);
106
+ switch(meterType) {
107
+ case METER_TYPES__HOURS:
108
+ ret = `${value} ${METER_TYPES__HOURS_UNITS}`;
109
+ break;
110
+ case METER_TYPES__MILES:
111
+ ret = `${value} ${METER_TYPES__MILES_UNITS}`;
112
+ break;
113
+ }
114
+ return ret;
115
+ },
116
+ formatDateValue = (value, record, self) => {
117
+ if (value === null || value === undefined) {
118
+ return value;
119
+ }
120
+
121
+ // convert from datetime to pretty-printed date
122
+ return moment(value).format(MOMENT_DATE_FORMAT_6);
123
+ },
124
+ items = [
125
+ {
126
+ "type": "Column",
127
+ "flex": 1,
128
+ "defaults": {
129
+ labelWidth: 300,
130
+ },
131
+ "items": [
132
+ {
133
+ "type": "FieldSet",
134
+ "title": "General",
135
+ "reference": "general",
136
+ "defaults": {},
137
+ "items": [
138
+ {
139
+ label: 'PM Schedule',
140
+ name: 'pmSchedule.name',
141
+ },
142
+ {
143
+ label: 'Status',
144
+ name: 'pm_status_name',
145
+ },
146
+ {
147
+ label: 'Next PM Date',
148
+ name: 'nextPmDate',
149
+ viewerFormatter: formatDateValue,
150
+ },
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',
171
+ name: 'maxGracePeriodDateTime',
172
+ viewerFormatter: formatDateValue,
173
+ },
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,
208
+ },
209
+ // json?.isDelayed && {
210
+ // label: 'Is Delayed',
211
+ // name: 'isDelayed',
212
+ // },
213
+ // json?.isOverride && {
214
+ // label: 'Is Override',
215
+ // name: 'isOverride',
216
+ // },
217
+ ]
218
+ },
219
+ {
220
+ "type": "FieldSet",
221
+ "title": "PM Schedule Config",
222
+ "reference": "pmSchedule",
223
+ "defaults": {},
224
+ "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
+ {
235
+ label: 'Mode',
236
+ name: 'pmScheduleMode',
237
+ },
238
+ ]
239
+ },
240
+ {
241
+ "type": "FieldSet",
242
+ "title": "Equipment",
243
+ "reference": "equipment",
244
+ "defaults": {},
245
+ "items": [
246
+ {
247
+ label: 'In Service Date',
248
+ name: 'inServiceDate',
249
+ viewerFormatter: formatDateValue,
250
+ },
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
+ },
261
+ ]
262
+ },
263
+
264
+ ]
265
+ }
266
+ ];
267
+
268
+ flattenedJson.pmScheduleMode = getPmScheduleMode(json?.pmSchedule.pm_schedule_mode_id);
269
+
270
+ return <Panel
271
+ title="PM Calculation Info"
272
+ className="PmCalcViewer-Panel"
273
+ isScrollable={true}
274
+ footer={<Footer className="justify-end">
275
+ <Button
276
+ {...testProps('closeBtn')}
277
+ key="closeBtn"
278
+ onPress={onClose}
279
+ className="text-white"
280
+ text="Close"
281
+ />
282
+ </Footer>}
283
+ >
284
+ <Viewer
285
+ record={flattenedJson}
286
+ items={items}
287
+ />
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
+ </Panel>;
299
+ }
@@ -0,0 +1,51 @@
1
+ import DisplayField from '../Form/Field/DisplayField.js';
2
+ import clsx from 'clsx';
3
+ import {
4
+ PM_STATUSES__OK,
5
+ PM_STATUSES__PM_DUE,
6
+ PM_STATUSES__DELAYED,
7
+ PM_STATUSES__WILL_CALL,
8
+ PM_STATUSES__SCHEDULED,
9
+ PM_STATUSES__OVERDUE,
10
+ PM_STATUSES__COMPLETED,
11
+ } from '../../Constants/PmStatuses.js';
12
+
13
+ export default function PmStatusesViewer(props) {
14
+ let text = '';
15
+ switch(props.id) {
16
+ case PM_STATUSES__OK:
17
+ text = 'OK';
18
+ break;
19
+ case PM_STATUSES__PM_DUE:
20
+ text = 'Due';
21
+ break;
22
+ case PM_STATUSES__DELAYED:
23
+ text = 'Delayed';
24
+ break;
25
+ case PM_STATUSES__WILL_CALL:
26
+ text = 'Will Call';
27
+ break;
28
+ case PM_STATUSES__SCHEDULED:
29
+ text = 'Scheduled';
30
+ break;
31
+ case PM_STATUSES__OVERDUE:
32
+ text = 'Overdue';
33
+ break;
34
+ case PM_STATUSES__COMPLETED:
35
+ text = 'Completed';
36
+ break;
37
+ default:
38
+ text = 'Unknown';
39
+ }
40
+ let className = clsx(
41
+ 'flex-none',
42
+ );
43
+ if (props.className) {
44
+ className += ' ' + props.className;
45
+ }
46
+ return <DisplayField
47
+ text={text}
48
+ {...props}
49
+ className={className}
50
+ />;
51
+ }