@onehat/ui 0.4.96 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.96",
3
+ "version": "0.4.97",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -118,77 +118,81 @@ function Container(props) {
118
118
  westWidthRef = useRef(westInitialWidth),
119
119
  [isReady, setIsReady] = useState(false),
120
120
  [isComponentsDisabled, setIsComponentsDisabled] = useState(false),
121
- [localNorthIsCollapsed, setLocalNorthIsCollapsed] = useState(northInitialIsCollapsed),
122
- [localSouthIsCollapsed, setLocalSouthIsCollapsed] = useState(southInitialIsCollapsed),
123
- [localEastIsCollapsed, setLocalEastIsCollapsed] = useState(eastInitialIsCollapsed),
124
- [localWestIsCollapsed, setLocalWestIsCollapsed] = useState(westInitialIsCollapsed),
121
+ localNorthIsCollapsedRef = useRef(northInitialIsCollapsed),
122
+ localSouthIsCollapsedRef = useRef(southInitialIsCollapsed),
123
+ localEastIsCollapsedRef = useRef(eastInitialIsCollapsed),
124
+ localWestIsCollapsedRef = useRef(westInitialIsCollapsed),
125
125
  setNorthIsCollapsed = (bool) => {
126
126
  if (setExternalNorthIsCollapsed) {
127
127
  setExternalNorthIsCollapsed(bool);
128
128
  } else {
129
- setLocalNorthIsCollapsed(bool);
129
+ localNorthIsCollapsedRef.current = bool;
130
130
  }
131
131
 
132
132
  if (id) {
133
133
  setSaved(id + '-northIsCollapsed', bool);
134
134
  }
135
+ forceUpdate();
135
136
  },
136
137
  getNorthIsCollapsed = () => {
137
138
  if (setExternalNorthIsCollapsed) {
138
139
  return northIsCollapsed;
139
140
  }
140
- return localNorthIsCollapsed;
141
+ return localNorthIsCollapsedRef.current;
141
142
  },
142
143
  setSouthIsCollapsed = (bool) => {
143
144
  if (setExternalSouthIsCollapsed) {
144
145
  setExternalSouthIsCollapsed(bool);
145
146
  } else {
146
- setLocalSouthIsCollapsed(bool);
147
+ localSouthIsCollapsedRef.current = bool;
147
148
  }
148
149
 
149
150
  if (id) {
150
151
  setSaved(id + '-southIsCollapsed', bool);
151
152
  }
153
+ forceUpdate();
152
154
  },
153
155
  getSouthIsCollapsed = () => {
154
156
  if (setExternalSouthIsCollapsed) {
155
157
  return southIsCollapsed;
156
158
  }
157
- return localSouthIsCollapsed;
159
+ return localSouthIsCollapsedRef.current;
158
160
  },
159
161
  setEastIsCollapsed = (bool) => {
160
162
  if (setExternalEastIsCollapsed) {
161
163
  setExternalEastIsCollapsed(bool);
162
164
  } else {
163
- setLocalEastIsCollapsed(bool);
165
+ localEastIsCollapsedRef.current = bool;
164
166
  }
165
167
 
166
168
  if (id) {
167
169
  setSaved(id + '-eastIsCollapsed', bool);
168
170
  }
171
+ forceUpdate();
169
172
  },
170
173
  getEastIsCollapsed = () => {
171
174
  if (setExternalEastIsCollapsed) {
172
175
  return eastIsCollapsed;
173
176
  }
174
- return localEastIsCollapsed;
177
+ return localEastIsCollapsedRef.current;
175
178
  },
176
179
  setWestIsCollapsed = (bool) => {
177
180
  if (setExternalWestIsCollapsed) {
178
181
  setExternalWestIsCollapsed(bool);
179
182
  } else {
180
- setLocalWestIsCollapsed(bool);
183
+ localWestIsCollapsedRef.current = bool;
181
184
  }
182
185
 
183
186
  if (id) {
184
187
  setSaved(id + '-westIsCollapsed', bool);
185
188
  }
189
+ forceUpdate();
186
190
  },
187
191
  getWestIsCollapsed = () => {
188
192
  if (setExternalWestIsCollapsed) {
189
193
  return westIsCollapsed;
190
194
  }
191
- return localWestIsCollapsed;
195
+ return localWestIsCollapsedRef.current;
192
196
  },
193
197
  setNorthHeight = (height) => {
194
198
  if (!getNorthIsCollapsed()) {
@@ -348,7 +352,7 @@ function Container(props) {
348
352
  return null;
349
353
  }
350
354
 
351
- let componentProps = {},
355
+ let componentProps = { _panel: { ...center?.props?._panel }, },
352
356
  wrapperProps = null,
353
357
  centerComponent = null,
354
358
  northComponent = null,
@@ -360,15 +364,15 @@ function Container(props) {
360
364
  westComponent = null,
361
365
  westSplitter = null;
362
366
 
363
- componentProps.isCollapsible = false;
364
- componentProps.isDisabled = isDisabled || isComponentsDisabled;
367
+ componentProps._panel.isCollapsible = false;
368
+ componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
365
369
  centerComponent = cloneElement(center, componentProps);
366
370
  if (north) {
367
- componentProps = {};
371
+ componentProps = { _panel: { ...north.props?._panel }, };
368
372
  wrapperProps = {};
369
373
 
370
- componentProps.isDisabled = isDisabled || isComponentsDisabled;
371
- componentProps.className = 'h-full w-full ' + (north.props.className || '');
374
+ componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
375
+ componentProps._panel.className = 'h-full w-full ' + (north.props.className || '');
372
376
  wrapperProps.onLayout = (e) => {
373
377
  const height = parseFloat(e.nativeEvent.layout.height);
374
378
  if (height && height !== northHeight) {
@@ -387,9 +391,9 @@ function Container(props) {
387
391
  wrapperProps.style = { height: northHeight, };
388
392
  }
389
393
  }
390
- componentProps.collapseDirection = VERTICAL;
391
- componentProps.isCollapsed = getNorthIsCollapsed();
392
- componentProps.setIsCollapsed = setNorthIsCollapsed;
394
+ componentProps._panel.collapseDirection = VERTICAL;
395
+ componentProps._panel.isCollapsed = getNorthIsCollapsed();
396
+ componentProps._panel.setIsCollapsed = setNorthIsCollapsed;
393
397
  if (isWeb && northIsResizable) {
394
398
  northSplitter = <Splitter
395
399
  mode={VERTICAL}
@@ -402,11 +406,11 @@ function Container(props) {
402
406
  </BoxNative>;
403
407
  }
404
408
  if (south) {
405
- componentProps = {};
409
+ componentProps = { _panel: { ...south.props?._panel }, };
406
410
  wrapperProps = {};
407
411
 
408
- componentProps.isDisabled = isDisabled || isComponentsDisabled;
409
- componentProps.className = 'h-full w-full ' + (south.props.className || '');
412
+ componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
413
+ componentProps._panel.className = 'h-full w-full ' + (south.props.className || '');
410
414
  wrapperProps.onLayout = (e) => {
411
415
  const height = parseFloat(e.nativeEvent.layout.height);
412
416
  if (height && height !== getSouthHeight()) {
@@ -425,9 +429,9 @@ function Container(props) {
425
429
  wrapperProps.style = { height: southHeight, };
426
430
  }
427
431
  }
428
- componentProps.collapseDirection = VERTICAL;
429
- componentProps.isCollapsed = getSouthIsCollapsed();
430
- componentProps.setIsCollapsed = setSouthIsCollapsed;
432
+ componentProps._panel.collapseDirection = VERTICAL;
433
+ componentProps._panel.isCollapsed = getSouthIsCollapsed();
434
+ componentProps._panel.setIsCollapsed = setSouthIsCollapsed;
431
435
  if (isWeb && southIsResizable) {
432
436
  southSplitter = <Splitter
433
437
  mode={VERTICAL}
@@ -440,11 +444,11 @@ function Container(props) {
440
444
  </BoxNative>;
441
445
  }
442
446
  if (east) {
443
- componentProps = {};
447
+ componentProps = { _panel: { ...east.props?._panel }, };
444
448
  wrapperProps = {};
445
449
 
446
- componentProps.isDisabled = isDisabled || isComponentsDisabled;
447
- componentProps.className = 'h-full w-full ' + (east.props.className || '');
450
+ componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
451
+ componentProps._panel.className = 'h-full w-full ' + (east.props.className || '');
448
452
  wrapperProps.onLayout = (e) => {
449
453
  const width = parseFloat(e.nativeEvent.layout.width);
450
454
  if (width && width !== getEastWidth()) {
@@ -463,9 +467,9 @@ function Container(props) {
463
467
  wrapperProps.style = { width: eastWidth, };
464
468
  }
465
469
  }
466
- componentProps.collapseDirection = HORIZONTAL;
467
- componentProps.isCollapsed = getEastIsCollapsed();
468
- componentProps.setIsCollapsed = setEastIsCollapsed;
470
+ componentProps._panel.collapseDirection = HORIZONTAL;
471
+ componentProps._panel.isCollapsed = getEastIsCollapsed();
472
+ componentProps._panel.setIsCollapsed = setEastIsCollapsed;
469
473
  if (isWeb && eastIsResizable) {
470
474
  eastSplitter = <Splitter
471
475
  mode={HORIZONTAL}
@@ -478,11 +482,11 @@ function Container(props) {
478
482
  </BoxNative>;
479
483
  }
480
484
  if (west) {
481
- componentProps = {};
485
+ componentProps = { _panel: { ...west.props?._panel }, };
482
486
  wrapperProps = {};
483
487
 
484
- componentProps.isDisabled = isDisabled || isComponentsDisabled;
485
- componentProps.className = 'h-full w-full ' + (west.props.className || '');
488
+ componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
489
+ componentProps._panel.className = 'h-full w-full ' + (west.props.className || '');
486
490
  wrapperProps.onLayout = (e) => {
487
491
  const width = parseFloat(e.nativeEvent.layout.width);
488
492
  if (width && width !== getWestWidth()) {
@@ -501,9 +505,9 @@ function Container(props) {
501
505
  wrapperProps.style = { width: westWidth, };
502
506
  }
503
507
  }
504
- componentProps.collapseDirection = HORIZONTAL;
505
- componentProps.isCollapsed = getWestIsCollapsed();
506
- componentProps.setIsCollapsed = setWestIsCollapsed;
508
+ componentProps._panel.collapseDirection = HORIZONTAL;
509
+ componentProps._panel.isCollapsed = getWestIsCollapsed();
510
+ componentProps._panel.setIsCollapsed = setWestIsCollapsed;
507
511
  if (isWeb && westIsResizable) {
508
512
  westSplitter = <Splitter
509
513
  mode={HORIZONTAL}
@@ -517,17 +521,17 @@ function Container(props) {
517
521
  }
518
522
  return <VStack className="Container-all w-full flex-1">
519
523
  {northComponent}
520
- {!northIsCollapsed && !localNorthIsCollapsed && northSplitter}
524
+ {!getNorthIsCollapsed() && northSplitter}
521
525
  <HStack className="Container-mid w-full flex-[100]">
522
526
  {westComponent}
523
- {!westIsCollapsed && !localWestIsCollapsed && westSplitter}
527
+ {!getWestIsCollapsed() && westSplitter}
524
528
  <VStack className="Container-center h-full overflow-auto flex-[100]">
525
529
  {centerComponent}
526
530
  </VStack>
527
- {!eastIsCollapsed && !localEastIsCollapsed && eastSplitter}
531
+ {!getEastIsCollapsed() && eastSplitter}
528
532
  {eastComponent}
529
533
  </HStack>
530
- {!southIsCollapsed && !localSouthIsCollapsed && southSplitter}
534
+ {!getSouthIsCollapsed() && southSplitter}
531
535
  {southComponent}
532
536
  </VStack>;
533
537
  }
@@ -522,6 +522,8 @@ function Form(props) {
522
522
  }
523
523
  let {
524
524
  type,
525
+ editorType: itemEditorType,
526
+ viewerType,
525
527
  title,
526
528
  name,
527
529
  isEditable = true,
@@ -560,12 +562,18 @@ function Form(props) {
560
562
  }
561
563
  if (!type) {
562
564
  if (isEditable) {
563
- const {
564
- type: t,
565
- ...p
566
- } = propertyDef?.editorType;
567
- type = t;
568
- editorTypeProps = p;
565
+ if (itemEditorType) {
566
+ type = itemEditorType;
567
+ } else {
568
+ const {
569
+ type: t,
570
+ ...p
571
+ } = propertyDef?.editorType;
572
+ type = t;
573
+ editorTypeProps = p;
574
+ }
575
+ } else if (viewerType) {
576
+ type = viewerType;
569
577
  } else if (propertyDef?.viewerType) {
570
578
  const {
571
579
  type: t,
@@ -423,7 +423,16 @@ function GridComponent(props) {
423
423
  }
424
424
  },
425
425
  getFooterToolbarItems = () => {
426
- const items = _.map(additionalToolbarButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
426
+ // Process additionalToolbarButtons to evaluate getIsButtonDisabled functions
427
+ const processedButtons = _.map(additionalToolbarButtons, (config) => {
428
+ const processedConfig = { ...config };
429
+ // If the button has an getIsButtonDisabled function, evaluate it with current selection
430
+ if (_.isFunction(config.getIsButtonDisabled)) {
431
+ processedConfig.isDisabled = config.getIsButtonDisabled(selection);
432
+ }
433
+ return processedConfig;
434
+ });
435
+ const items = _.map(processedButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
427
436
  if (canRowsReorder && CURRENT_MODE === UI_MODE_WEB) { // DND is currently web-only TODO: implement for RN
428
437
  items.unshift(<IconButton
429
438
  {...testProps('reorderBtn')}
@@ -476,33 +485,41 @@ function GridComponent(props) {
476
485
  }
477
486
  break;
478
487
  case DOUBLE_CLICK:
479
- if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
480
- onRowClick(item, e); // so reselect it
481
- }
482
-
483
- if (UiGlobals.doubleClickingGridRowOpensEditorInViewMode) { // global setting
484
- if (onView) {
485
- if (canUser && !canUser(VIEW)) { // permissions
486
- return;
487
- }
488
- onView(!props.isEditorViewOnly);
488
+ if (editorType === EDITOR_TYPE__SIDE) {
489
+ // For side-editors, a double-click just acts like a single click
490
+ if (!getIsEditorShown()) {
491
+ onRowClick(item, e); // sets selection
489
492
  }
490
493
  } else {
491
- let canDoEdit = false,
492
- canDoView = false;
493
- if (onEdit && canUser && canUser(EDIT) && (!canRecordBeEdited || canRecordBeEdited(selection)) && !props.disableEdit && !isEditorViewOnly) {
494
- canDoEdit = true;
495
- } else
496
- if (onView && canUser && canUser(VIEW) && !props.disableView) {
497
- canDoView = true;
494
+ if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
495
+ onRowClick(item, e); // so reselect it
498
496
  }
497
+ if (UiGlobals.doubleClickingGridRowOpensEditorInViewMode) { // global setting
498
+ if (onView) {
499
+ if (canUser && !canUser(VIEW)) { // permissions
500
+ return;
501
+ }
502
+ onView(!props.isEditorViewOnly);
503
+ }
504
+ } else {
505
+ let canDoEdit = false,
506
+ canDoView = false;
507
+ if (onEdit && canUser && canUser(EDIT) && (!canRecordBeEdited || canRecordBeEdited(selection)) && !props.disableEdit && !isEditorViewOnly) {
508
+ canDoEdit = true;
509
+ } else
510
+ if (onView && canUser && canUser(VIEW) && !props.disableView) {
511
+ canDoView = true;
512
+ }
499
513
 
500
- if (canDoEdit) {
501
- onEdit();
502
- } else if (canDoView) {
503
- onView();
514
+ if (canDoEdit) {
515
+ onEdit();
516
+ } else if (canDoView) {
517
+ onView();
518
+ }
504
519
  }
520
+
505
521
  }
522
+
506
523
  break;
507
524
  case TRIPLE_CLICK:
508
525
  break;
@@ -1562,10 +1579,19 @@ function GridComponent(props) {
1562
1579
  const
1563
1580
  currentLength = entities?.length || 0,
1564
1581
  wasEmpty = previousEntitiesLength.current === 0,
1565
- isNowPopulated = currentLength > 0;
1582
+ isNowPopulated = currentLength > 0,
1583
+ hasPhantomRecord = entities?.some(entity => entity?.isPhantom);
1584
+
1585
+ // NOTE: The Repository was reloading when a phantom record was added,
1586
+ // and this broke the Editor because selection was being reset to zero.
1587
+ // This is because adjustPageSizeToHeight calls setPageSize,
1588
+ // which calls _onChangePagination, which calls reload.
1589
+ // The reloaded repository doesn’t get the new phantom record,
1590
+ // so it’s not found, and selection goes to zero.
1591
+ // So we skip this adjustment when there is a phantom record.
1566
1592
 
1567
1593
  // Only remeasure the FIRST time rows appear after being empty
1568
- if (autoAdjustPageSizeToHeight && wasEmpty && isNowPopulated && !hasRemeasuredAfterRowsAppeared.current) {
1594
+ if (!hasPhantomRecord && autoAdjustPageSizeToHeight && wasEmpty && isNowPopulated && !hasRemeasuredAfterRowsAppeared.current) {
1569
1595
  // Rows just appeared for the first time - restart measurement cycle to use actual heights
1570
1596
  if (DEBUG) {
1571
1597
  console.log(`${getMeasurementPhase()}, useEffect 5 - rows appeared for first time, restarting measurement cycle`);
@@ -1602,13 +1628,24 @@ function GridComponent(props) {
1602
1628
  // first time through, render a placeholder so we can get container dimensions
1603
1629
  return <VStackNative
1604
1630
  onLayout={(e) => {
1605
- if (DEBUG) {
1606
- console.log(`${getMeasurementPhase()}, placeholder onLayout, call adjustPageSizeToHeight()`);
1607
- }
1608
- const containerHeight = e.nativeEvent.layout.height;
1609
- adjustPageSizeToHeight(containerHeight);
1610
- if (DEBUG) {
1611
- console.log(`${getMeasurementPhase()}, placeholder onLayout, call setIsInited(true)`);
1631
+ const hasPhantomRecord = entities?.some(entity => entity?.isPhantom);
1632
+ if (!hasPhantomRecord) {
1633
+ // NOTE: The Repository was reloading when a phantom record was added,
1634
+ // and this broke the Editor because selection was being reset to zero.
1635
+ // This is because adjustPageSizeToHeight calls setPageSize,
1636
+ // which calls _onChangePagination, which calls reload.
1637
+ // The reloaded repository doesn’t get the new phantom record,
1638
+ // so it’s not found, and selection goes to zero.
1639
+ // So we skip this adjustment when there is a phantom record.
1640
+
1641
+ if (DEBUG) {
1642
+ console.log(`${getMeasurementPhase()}, placeholder onLayout, call adjustPageSizeToHeight()`);
1643
+ }
1644
+ const containerHeight = e.nativeEvent.layout.height;
1645
+ adjustPageSizeToHeight(containerHeight);
1646
+ if (DEBUG) {
1647
+ console.log(`${getMeasurementPhase()}, placeholder onLayout, call setIsInited(true)`);
1648
+ }
1612
1649
  }
1613
1650
  setIsInited(true);
1614
1651
  }}
@@ -1785,14 +1822,11 @@ function GridComponent(props) {
1785
1822
  )}
1786
1823
  >
1787
1824
  {grid}
1788
- {/* Loading overlay during measurement phases to prevent visual flashing */}
1789
- {autoAdjustPageSizeToHeight &&
1790
- (getMeasurementPhase() === PHASES__INITIAL || getMeasurementPhase() === PHASES__MEASURING) &&
1791
- entities?.length > 0 && (
1825
+ {/* Load overlay during initial phase to prevent visual flashing * /
1826
+ autoAdjustPageSizeToHeight && getMeasurementPhase() === PHASES__INITIAL &&
1792
1827
  <VStack className="absolute inset-0 z-10 bg-white">
1793
1828
  <Loading />
1794
- </VStack>
1795
- )}
1829
+ </VStack>*/}
1796
1830
  </VStack>
1797
1831
 
1798
1832
  {listFooterComponent}
@@ -38,6 +38,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
38
38
  disableDuplicate = false,
39
39
  disableView = false,
40
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
41
42
  getRecordIdentifier = (selection) => {
42
43
  if (selection.length > 1) {
43
44
  return 'records?';
@@ -89,6 +90,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
89
90
  editorModeRef = useRef(initialEditorMode),
90
91
  isIgnoreNextSelectionChangeRef = useRef(false),
91
92
  isEditorShownRef = useRef(false),
93
+ defaultValuesRef = useRef(defaultValues),
92
94
  canEditorBeInEditModeRef = useRef(true), // whether the editor is allowed to be in edit mode based on canRecordBeEdited
93
95
  [currentRecord, setCurrentRecord] = useState(null),
94
96
  [isAdding, setIsAdding] = useState(false),
@@ -111,6 +113,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
111
113
  getIsEditorShown = () => {
112
114
  return isEditorShownRef.current;
113
115
  },
116
+ getDefaultValues = () => {
117
+ return defaultValuesRef.current;
118
+ },
119
+ setDefaultValues = (vals) => {
120
+ defaultValuesRef.current = vals;
121
+ },
114
122
  setCanEditorBeInEditMode = (bool) => {
115
123
  canEditorBeInEditModeRef.current = bool;
116
124
  forceUpdate();
@@ -164,6 +172,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
164
172
  getNewEntityDisplayValue = () => {
165
173
  return newEntityDisplayValueRef.current;
166
174
  },
175
+ setNewEntityDisplayValue = (val) => {
176
+ newEntityDisplayValueRef.current = val;
177
+ },
167
178
  doAdd = async (e, values) => {
168
179
  if (canUser && !canUser(ADD)) {
169
180
  showPermissionsError(ADD);
@@ -191,8 +202,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
191
202
  // 2. Use the repository's default values (defined on each property as 'defaultValue'), or
192
203
  // 3. Individually override the repository's default values with submitted 'defaultValues' (given as a prop to this HOC)
193
204
  let defaultValuesToUse = Repository.getSchema().getDefaultValues();
194
- if (defaultValues) {
195
- _.merge(defaultValuesToUse, defaultValues);
205
+ if (getDefaultValues()) {
206
+ _.merge(defaultValuesToUse, getDefaultValues());
196
207
  }
197
208
  addValues = {...defaultValuesToUse};
198
209
  }
@@ -458,7 +469,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
458
469
  const
459
470
  entity = selection[0],
460
471
  idProperty = Repository.getSchema().model.idProperty,
461
- rawValues = _.omit(entity.getOriginalData(), idProperty);
472
+ rawValues = getDuplicateValues ? getDuplicateValues(entity) : _.omit(entity.getOriginalData(), idProperty);
462
473
  rawValues.id = null; // unset the id of the duplicate
463
474
 
464
475
  setIsWaitModalShown(true);
@@ -738,7 +749,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
738
749
  self.duplicate = doDuplicate;
739
750
  self.setIsEditorShown = setIsEditorShown;
740
751
  }
741
- newEntityDisplayValueRef.current = newEntityDisplayValue;
752
+ setNewEntityDisplayValue(newEntityDisplayValue);
753
+ setDefaultValues(defaultValues);
742
754
 
743
755
  if (lastSelection !== selection) {
744
756
  // NOTE: If I don't calculate this on the fly for selection changes,
@@ -7,7 +7,7 @@ export default function TabPanel(props) {
7
7
  _panel = {},
8
8
  _tab = {},
9
9
  } = props;
10
- return <Panel className="w-full flex" {..._panel}>
10
+ return <Panel {...props} className="w-full flex" {..._panel}>
11
11
  <TabBar {...props} {..._tab} />
12
12
  </Panel>;
13
13
  }
@@ -26,7 +26,7 @@ export function TreePanel(props) {
26
26
  }
27
27
  }
28
28
 
29
- return <Panel {..._panel}>
29
+ return <Panel {...props} {..._panel}>
30
30
  <WhichTree {...props} {..._tree} />
31
31
  </Panel>;
32
32
  }
@@ -654,9 +654,23 @@ function TreeComponent(props) {
654
654
  return;
655
655
  }
656
656
 
657
- // Create datum for the new entity and add it to parent's children
658
657
  const newDatum = buildTreeNodeDatum(entity);
659
- 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
+
660
674
 
661
675
  // Update parent to show it has children and expand if needed
662
676
  if (!entity.parent.hasChildren) {
@@ -1052,7 +1066,16 @@ function TreeComponent(props) {
1052
1066
  return items;
1053
1067
  },
1054
1068
  getFooterToolbarItems = () => {
1055
- 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));
1056
1079
  },
1057
1080
  renderTreeNode = (datum) => {
1058
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
+ }
@@ -0,0 +1,25 @@
1
+ import DisplayField from '../Form/Field/DisplayField.js';
2
+ import clsx from 'clsx';
3
+ import {
4
+ MOMENT_DATE_FORMAT_5,
5
+ } from '../../Constants/Dates';
6
+
7
+ export default function TimeViewer(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_5)}
22
+ {...props}
23
+ className={className}
24
+ />;
25
+ }
@@ -114,6 +114,7 @@ function Viewer(props) {
114
114
  }
115
115
  let {
116
116
  type,
117
+ viewerType,
117
118
  title,
118
119
  name,
119
120
  label,
@@ -122,6 +123,7 @@ function Viewer(props) {
122
123
  isHidden = false,
123
124
  isHiddenInViewMode = false,
124
125
  getDynamicProps,
126
+ viewerFormatter = null,
125
127
  ...itemPropsToPass
126
128
  } = item,
127
129
  viewerTypeProps = {};
@@ -137,7 +139,9 @@ function Viewer(props) {
137
139
  }
138
140
  const propertyDef = name && Repository?.getSchema().getPropertyDefinition(name);
139
141
  if (!type) {
140
- if (propertyDef?.viewerType?.type) {
142
+ if (viewerType) {
143
+ type = viewerType;
144
+ } else if (propertyDef?.viewerType?.type) {
141
145
  const
142
146
  {
143
147
  type: t,
@@ -274,6 +278,9 @@ function Viewer(props) {
274
278
  value = record.properties[fkDisplayField].displayValue;
275
279
  }
276
280
  }
281
+ if (viewerFormatter) {
282
+ value = viewerFormatter(value, record, self);
283
+ }
277
284
 
278
285
  let elementClassName = clsx(
279
286
  'Viewer-field',
@@ -255,6 +255,8 @@ import Container from './Container/Container.js';
255
255
  import ContainerColumn from './Container/ContainerColumn.js';
256
256
  import DataMgt from './Screens/DataMgt.js';
257
257
  import Date from './Form/Field/Date.js';
258
+ import DateViewer from './Viewer/DateViewer.js';
259
+ import DateTimeViewer from './Viewer/DateTimeViewer.js';
258
260
  import DateRange from './Filter/DateRange.js';
259
261
  import DisplayField from './Form/Field/DisplayField.js';
260
262
  import ExpandButton from './Buttons/ExpandButton.js';
@@ -278,6 +280,8 @@ import NumberRange from './Filter/NumberRange.js';
278
280
  import Panel from './Panel/Panel.js';
279
281
  // import Picker from './Panel/Picker.js';
280
282
  import PlusMinusButton from './Buttons/PlusMinusButton.js';
283
+ import PmCalcDebugViewer from './Viewer/PmCalcDebugViewer.js';
284
+ import PmStatusesViewer from './Viewer/PmStatusesViewer.js';
281
285
  import RadioGroup from './Form/Field/RadioGroup/RadioGroup.js';
282
286
  // import Slider from './Form/Field/Slider.js'; // Currently, Slider is not compatible with the new React architecture. Temporarily remove it from index.js to prevent issues.
283
287
  import SquareButton from './Buttons/SquareButton.js';
@@ -286,6 +290,7 @@ import Tag from './Form/Field/Tag/Tag.js';
286
290
  import TextArea from './Form/Field/TextArea.js';
287
291
  import Text from './Form/Field/Text.js';
288
292
  import TextWithLinks from './Viewer/TextWithLinks.js';
293
+ import TimeViewer from './Viewer/TimeViewer.js';
289
294
  import TimezonesCombo from './Form/Field/Combo/TimezonesCombo.js';
290
295
  import Toggle from './Form/Field/Toggle.js';
291
296
  import Toolbar from './Toolbar/Toolbar.js';
@@ -550,6 +555,8 @@ const components = {
550
555
  ContainerColumn,
551
556
  DataMgt,
552
557
  Date,
558
+ DateViewer,
559
+ DateTimeViewer,
553
560
  DateRange,
554
561
  DisplayField,
555
562
  ExpandButton,
@@ -573,6 +580,8 @@ const components = {
573
580
  Panel,
574
581
  // Picker,
575
582
  PlusMinusButton,
583
+ PmCalcDebugViewer,
584
+ PmStatusesViewer,
576
585
  RadioGroup,
577
586
  // Slider,
578
587
  SquareButton,
@@ -581,6 +590,7 @@ const components = {
581
590
  Text,
582
591
  TextArea,
583
592
  TextWithLinks,
593
+ TimeViewer,
584
594
  TimezonesCombo,
585
595
  Toggle,
586
596
  Toolbar,
@@ -18,3 +18,4 @@ export const MOMENT_DATE_FORMAT_2 = 'MMM D YYYY, h:mm:ssa'; // pretty datetime
18
18
  export const MOMENT_DATE_FORMAT_3 = 'h:mma'; // pretty time
19
19
  export const MOMENT_DATE_FORMAT_4 = 'YYYY-MM-DD';
20
20
  export const MOMENT_DATE_FORMAT_5 = 'HH:mm:ss';
21
+ export const MOMENT_DATE_FORMAT_6 = 'MMM D, YYYY'; // pretty date
@@ -0,0 +1,2 @@
1
+ export const EDITOR_MODE_ADD = 'EDITOR_MODE_ADD';
2
+ export const EDITOR_MODE_EDIT = 'EDITOR_MODE_EDIT';
@@ -0,0 +1,5 @@
1
+ export const METER_SOURCES__INITIAL_SETUP = 1;
2
+ export const METER_SOURCES__WORK_ORDER = 2;
3
+ export const METER_SOURCES__PM_EVENT = 3;
4
+ export const METER_SOURCES__TELEMETRY = 4;
5
+ export const METER_SOURCES__MANUAL_ENTRY = 5;
@@ -1,4 +1,6 @@
1
1
  export const METER_TYPES__HOURS = 1;
2
+ export const METER_TYPES__HOURS_UNITS = 'hrs';
3
+ export const METER_TYPES__HOURS_TEXT = 'Time (' + METER_TYPES__HOURS_UNITS + ')';
2
4
  export const METER_TYPES__MILES = 2;
3
- export const METER_TYPES__HOURS_TEXT = 'Time (hrs)';
4
- export const METER_TYPES__MILES_TEXT = 'Distance (mi/km)';
5
+ export const METER_TYPES__MILES_UNITS = 'mi-km';
6
+ export const METER_TYPES__MILES_TEXT = 'Distance (' + METER_TYPES__MILES_UNITS + ')';
@@ -0,0 +1,11 @@
1
+ export const PM_EVENT_TYPES__INITIAL = 1;
2
+ export const PM_EVENT_TYPES__WORK_ORDER = 2;
3
+ export const PM_EVENT_TYPES__ALERT = 3;
4
+ export const PM_EVENT_TYPES__COMPLETE = 4;
5
+ export const PM_EVENT_TYPES__RESET = 5;
6
+ export const PM_EVENT_TYPES__DELAY_BY_DAYS = 6;
7
+ export const PM_EVENT_TYPES__DELAY_BY_METER = 7;
8
+ export const PM_EVENT_TYPES__SCHEDULE_PM = 8;
9
+ export const PM_EVENT_TYPES__WILL_CALL = 9;
10
+ export const PM_EVENT_TYPES__ASSIGN_TECHNICIAN = 10;
11
+ export const PM_EVENT_TYPES__COMMENT = 11;
@@ -0,0 +1,4 @@
1
+ export const PM_SCHEDULE_MODES__HISTORICAL_USAGE = 1;
2
+ export const PM_SCHEDULE_MODES__EXPECTED_USAGE = 2;
3
+ export const PM_SCHEDULE_MODES__NO_ESTIMATION = 3;
4
+ export const PM_SCHEDULE_MODES__WILL_CALL = 4;
@@ -0,0 +1,7 @@
1
+ export const PM_STATUSES__OK = 1;
2
+ export const PM_STATUSES__PM_DUE = 2;
3
+ export const PM_STATUSES__DELAYED = 3;
4
+ export const PM_STATUSES__WILL_CALL = 4;
5
+ export const PM_STATUSES__SCHEDULED = 5;
6
+ export const PM_STATUSES__OVERDUE = 6;
7
+ export const PM_STATUSES__COMPLETED = 7;
@@ -819,6 +819,7 @@ function AttachmentsElement(props) {
819
819
  }
820
820
 
821
821
  if (self) {
822
+ self.repository = Attachments;
822
823
  self.getFiles = getFiles;
823
824
  self.setFiles = setFiles;
824
825
  self.clearFiles = clearFiles;