@onehat/ui 0.4.26 → 0.4.29

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Accordion/Accordion.js +4 -1
  3. package/src/Components/Buttons/SquareButton.js +20 -16
  4. package/src/Components/Form/Field/Color.js +1 -0
  5. package/src/Components/Form/Field/Combo/Combo.js +4 -4
  6. package/src/Components/Form/Field/Date.js +1 -0
  7. package/src/Components/Form/Field/Input.js +1 -1
  8. package/src/Components/Form/Field/Number.js +1 -0
  9. package/src/Components/Form/Field/Slider.js +1 -0
  10. package/src/Components/Form/Form.js +4 -1
  11. package/src/Components/Grid/Grid.js +1 -1
  12. package/src/Components/Hoc/Secondary/withSecondaryEditor.js +39 -10
  13. package/src/Components/Hoc/Secondary/withSecondarySideEditor.js +23 -7
  14. package/src/Components/Hoc/Secondary/withSecondaryWindowedEditor.js +23 -17
  15. package/src/Components/Hoc/withFilters.js +42 -1
  16. package/src/Components/Hoc/withModal.js +2 -1
  17. package/src/Components/Hoc/withPdfButtons.js +7 -1
  18. package/src/Components/Icons/ClipboardCheck.js +2 -2
  19. package/src/Components/Icons/LowPriority.js +1 -1
  20. package/src/Components/Layout/CenterBox.js +3 -1
  21. package/src/Components/Layout/ScreenHeader.js +73 -0
  22. package/src/Components/Layout/TextWithTooltip.js +22 -0
  23. package/src/Components/Panel/Header.js +2 -0
  24. package/src/Components/Panel/Panel.js +8 -3
  25. package/src/Components/Screens/Manager.js +19 -51
  26. package/src/Components/Screens/ReportsManager.js +62 -0
  27. package/src/Components/Toolbar/Pagination.js +1 -0
  28. package/src/Components/Tree/Tree.js +7 -1
  29. package/src/Components/Tree/TreeNode.js +3 -1
  30. package/src/Constants/ScreenModes.js +2 -0
  31. package/src/Functions/Cypress/dom_functions.js +43 -10
  32. package/src/Styles/Global.css +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.26",
3
+ "version": "0.4.29",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -126,7 +126,10 @@ export default function Accordion(props) {
126
126
  return <ScrollView
127
127
  ref={scrollViewRef}
128
128
  keyboardShouldPersistTaps="always"
129
- className="flex-1 w-full"
129
+ className="Accordion-ScrollView flex-1 w-full"
130
+ contentContainerStyle={{
131
+ height: '100%',
132
+ }}
130
133
  >
131
134
  <VStackNative
132
135
  {...propsToPass}
@@ -7,14 +7,13 @@ export default function SquareButton(props) {
7
7
  const {
8
8
  text,
9
9
  isActive = false,
10
- activeColor,
10
+ activeClassName,
11
11
  invertColorWhenActive = false,
12
12
  showText = true,
13
13
  disableInteractions = false,
14
14
  fontSize = '20px',
15
15
  ...propsToPass
16
16
  } = props,
17
- bg = isActive ? activeColor || '#56a6f8' : '#fff',
18
17
  color = invertColorWhenActive && isActive ? '#fff' : '#000';
19
18
 
20
19
  if (!props.icon) {
@@ -24,21 +23,26 @@ export default function SquareButton(props) {
24
23
  throw Error('text missing. If you want to hide the text, use showText={false}');
25
24
  }
26
25
 
26
+ let className = `
27
+ SquareButton
28
+ rounded-md
29
+ p-2
30
+ h-[100px]
31
+ w-[100px]
32
+ flex
33
+ flex-col
34
+ justify-center
35
+ items-center
36
+ bg-grey-200
37
+ hover:bg-grey-400
38
+ disabled:bg-grey-100
39
+ `;
40
+ if (isActive && activeClassName) {
41
+ className += ' ' + activeClassName;
42
+ }
43
+
27
44
  return <IconButton
28
- className={`
29
- SquareButton
30
- rounded-md
31
- p-2
32
- bg-[${bg}]
33
- hover:bg-[${bg}]
34
- disabled:bg-[${bg}]
35
- h-[100px]
36
- w-[100px]
37
- flex
38
- flex-col
39
- justify-center
40
- items-center
41
- `}
45
+ className={className}
42
46
  style={{
43
47
  // backgroundColor: bg,
44
48
  }}
@@ -182,6 +182,7 @@ export function ColorElement(props) {
182
182
  border-bottom-right-radius-6
183
183
  ${styles.FORM_COLOR_INPUT_CLASSNAME}
184
184
  `}
185
+ textAlignIsCenter={true}
185
186
  onLayout={(e) => {
186
187
  // On web, this is not needed, but on RN it might be, so leave it in for now
187
188
  const {
@@ -577,6 +577,7 @@ export function ComboComponent(props) {
577
577
  input = disableDirectEntry ?
578
578
  <Pressable
579
579
  {...testProps('toggleMenuBtn')}
580
+ ref={inputRef}
580
581
  onPress={toggleMenu}
581
582
  className={`
582
583
  Combo-toggleMenuBtn
@@ -587,7 +588,8 @@ export function ComboComponent(props) {
587
588
  justify-center
588
589
  items-center
589
590
  m-0
590
- p-1
591
+ p-2
592
+ bg-white
591
593
  border
592
594
  border-grey-400
593
595
  rounded-r-none
@@ -596,13 +598,10 @@ export function ComboComponent(props) {
596
598
  >
597
599
  {inputIconElement}
598
600
  <TextNative
599
- ref={inputRef}
600
601
  numberOfLines={1}
601
602
  ellipsizeMode="head"
602
603
  className={`
603
604
  Combo-TextNative
604
- h-auto
605
- self-stretch
606
605
  flex-1
607
606
  ${_.isEmpty(textInputValue) ? "text-grey-400" : "text-black"}
608
607
  ${styles.FORM_COMBO_INPUT_CLASSNAME}
@@ -878,6 +877,7 @@ export function ComboComponent(props) {
878
877
  dropdownMenu-Box
879
878
  flex-1
880
879
  overflow-auto
880
+ bg-white
881
881
  p-0
882
882
  rounded-none
883
883
  border
@@ -378,6 +378,7 @@ export const DateElement = forwardRef((props, ref) => {
378
378
  `}
379
379
  autoSubmitDelay={1000}
380
380
  placeholder={placeholder}
381
+ textAlignIsCenter={true}
381
382
  {..._input}
382
383
  />;
383
384
  }
@@ -29,7 +29,7 @@ const InputElement = forwardRef((props, ref) => {
29
29
  rightIcon,
30
30
  rightIconHandler,
31
31
  placeholder,
32
- textAlignIsCenter = true,
32
+ textAlignIsCenter = false,
33
33
  className,
34
34
  ...propsToPass
35
35
  } = props,
@@ -171,6 +171,7 @@ function NumberElement(props) {
171
171
  text-center
172
172
  rounded-none
173
173
  `}
174
+ textAlignIsCenter={true}
174
175
  style={{
175
176
  flex: 3
176
177
  }}
@@ -182,6 +182,7 @@ const
182
182
  isDisabled={isDisabled}
183
183
  disableAutoFlex={true}
184
184
  className={inputClassName}
185
+ textAlignIsCenter={true}
185
186
  {...props._input}
186
187
  />
187
188
  <HStack className="flex-1">
@@ -1350,12 +1350,15 @@ function Form(props) {
1350
1350
  {editorType !== EDITOR_TYPE__INLINE &&
1351
1351
  <ScrollView
1352
1352
  className={`
1353
- ScrollView
1353
+ Form-ScrollView
1354
1354
  w-full
1355
1355
  flex-1
1356
1356
  pb-1
1357
1357
  web:min-h-[${minHeight}px]
1358
1358
  `}
1359
+ contentContainerStyle={{
1360
+ height: '100%',
1361
+ }}
1359
1362
  >
1360
1363
  {modeHeader}
1361
1364
  {formHeader}
@@ -417,7 +417,7 @@ function GridComponent(props) {
417
417
  if (canUser && !canUser(VIEW)) { // permissions
418
418
  return;
419
419
  }
420
- onView(true);
420
+ onView(!props.isEditorViewOnly);
421
421
  }
422
422
  } else {
423
423
  if (onEdit) {
@@ -1,4 +1,4 @@
1
- import { useEffect, useState, useRef, } from 'react';
1
+ import { forwardRef, useEffect, useState, useRef, } from 'react';
2
2
  import {
3
3
  ADD,
4
4
  EDIT,
@@ -11,6 +11,7 @@ import {
11
11
  EDITOR_MODE__ADD,
12
12
  EDITOR_MODE__EDIT,
13
13
  EDITOR_TYPE__SIDE,
14
+ EDITOR_TYPE__INLINE,
14
15
  } from '../../../Constants/Editor.js';
15
16
  import Button from '../../Buttons/Button.js';
16
17
  import UiGlobals from '../../../UiGlobals.js';
@@ -20,10 +21,10 @@ import _ from 'lodash';
20
21
  // This HOC will eventually get out of sync with that one, and may need to be updated.
21
22
 
22
23
  export default function withSecondaryEditor(WrappedComponent, isTree = false) {
23
- return (props) => {
24
+ return forwardRef((props, ref) => {
24
25
 
25
26
  if (props.secondaryDisableWithEditor) {
26
- return <WrappedComponent {...props} isTree={isTree} />;
27
+ return <WrappedComponent {...props} ref={ref} isTree={isTree} />;
27
28
  }
28
29
 
29
30
  let [secondaryEditorMode, secondarySetEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -52,6 +53,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
52
53
  secondaryNewEntityDisplayValue,
53
54
  secondaryNewEntityDisplayProperty, // in case the field to set for newEntityDisplayValue is different from model
54
55
  secondaryDefaultValues,
56
+ secondaryStayInEditModeOnSelectionChange = false,
55
57
 
56
58
  // withComponent
57
59
  self,
@@ -132,7 +134,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
132
134
  // NOTE: This is a hack to prevent adding a new record while the repository is still loading.
133
135
  // This can happen when the repository is still loading, and the user clicks the 'Add' button.
134
136
  setTimeout(() => {
135
- doAdd(e, values);
137
+ secondaryDoAdd(e, values);
136
138
  }, 500);
137
139
  return;
138
140
  }
@@ -169,7 +171,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
169
171
  if (!secondarySelection[0]) {
170
172
  throw Error('Must select a parent node.');
171
173
  }
172
- addValues.parentId = secondarySelection[0].id;
174
+ const parent = secondarySelection[0];
175
+ addValues.parentId = parent.id;
176
+ addValues.depth = parent.depth +1;
173
177
  } else {
174
178
  // Set repository to sort by id DESC and switch to page 1, so this new entity is guaranteed to show up on the current page, even after saving
175
179
  const currentSorter = SecondaryRepository.sorters[0];
@@ -200,10 +204,13 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
200
204
  setIsSaving(false);
201
205
  setSecondaryIsIgnoreNextSelectionChange(true);
202
206
  secondarySetSelection([entity]);
207
+ if (getListeners().onAfterAdd) {
208
+ await getListeners().onAfterAdd(entity);
209
+ }
203
210
  if (SecondaryRepository.isAutoSave) {
204
211
  // for isAutoSave Repositories, submit the handers right away
205
- if (getListeners().onAfterAdd) {
206
- await getListeners().onAfterAdd(entity);
212
+ if (getListeners().onAfterAddSave) {
213
+ await getListeners().onAfterAddSave(entity);
207
214
  }
208
215
  if (secondaryOnAdd) {
209
216
  await secondaryOnAdd(entity);
@@ -331,6 +338,13 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
331
338
  showPermissionsError(VIEW, secondaryModel);
332
339
  return;
333
340
  }
341
+ if (secondaryEditorType === EDITOR_TYPE__INLINE) {
342
+ alert('Cannot view in inline editor.');
343
+ return; // inline editor doesn't have a view mode
344
+ }
345
+
346
+ // check permissions for view
347
+
334
348
  if (secondarySelection.length !== 1) {
335
349
  return;
336
350
  }
@@ -350,6 +364,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
350
364
  showPermissionsError(DUPLICATE, secondaryModel);
351
365
  return;
352
366
  }
367
+
368
+ // check permissions for duplicate
369
+
370
+
353
371
  if (secondarySelection.length !== 1) {
354
372
  return;
355
373
  }
@@ -436,8 +454,8 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
436
454
  if (secondaryOnAdd) {
437
455
  await secondaryOnAdd(secondarySelection);
438
456
  }
439
- if (getListeners().onAfterAdd) {
440
- await getListeners().onAfterAdd(secondarySelection);
457
+ if (getListeners().onAfterAddSave) {
458
+ await getListeners().onAfterAddSave(secondarySelection);
441
459
  }
442
460
  setIsAdding(false);
443
461
  if (!canUser || canUser(EDIT, secondaryModel)) {
@@ -500,6 +518,16 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
500
518
  });
501
519
  },
502
520
  calculateEditorMode = (secondaryIsIgnoreNextSelectionChange = false) => {
521
+
522
+ let doStayInEditModeOnSelectionChange = secondaryStayInEditModeOnSelectionChange;
523
+ if (!_.isNil(UiGlobals.stayInEditModeOnSelectionChange)) {
524
+ // allow global override to for this property
525
+ doStayInEditModeOnSelectionChange = UiGlobals.stayInEditModeOnSelectionChange;
526
+ }
527
+ if (doStayInEditModeOnSelectionChange) {
528
+ secondaryIsIgnoreNextSelectionChange = true;
529
+ }
530
+
503
531
  // calculateEditorMode gets called only on selection changes
504
532
  let mode;
505
533
  if (secondaryEditorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
@@ -579,6 +607,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
579
607
 
580
608
  return <WrappedComponent
581
609
  {...props}
610
+ ref={ref}
582
611
  secondaryDisableWithEditor={false}
583
612
  secondaryCurrentRecord={secondaryCurrentRecord}
584
613
  secondarySetCurrentRecord={secondarySetCurrentRecord}
@@ -612,5 +641,5 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
612
641
  secondarySetSelection={secondarySetSelectionDecorated}
613
642
  isTree={isTree}
614
643
  />;
615
- };
644
+ });
616
645
  }
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'react';
1
2
  import {
2
3
  EDITOR_TYPE__SIDE,
3
4
  } from '../../../Constants/Editor.js';
@@ -10,20 +11,21 @@ import _ from 'lodash';
10
11
 
11
12
 
12
13
  function withAdditionalProps(WrappedComponent) {
13
- return (props) => {
14
+ return forwardRef((props, ref) => {
14
15
  // provide the editorType to withEditor
15
16
  return <WrappedComponent
16
17
  editorType={EDITOR_TYPE__SIDE}
17
18
  {...props}
19
+ ref={ref}
18
20
  />;
19
- };
21
+ });
20
22
  }
21
23
 
22
24
  // NOTE: Effectivtly, the HOC composition is:
23
25
  // withAdditionalProps(withSecondaryEditor(withSecondarySideEditor))
24
26
 
25
27
  export default function withSecondarySideEditor(WrappedComponent, isTree = false) {
26
- return withAdditionalProps(withSecondaryEditor((props) => {
28
+ const SideEditor = forwardRef((props, ref) => {
27
29
  const {
28
30
  SecondaryEditor,
29
31
  secondaryEditorProps = {},
@@ -36,6 +38,7 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
36
38
  secondarySelectorId,
37
39
  secondarySelectorSelected,
38
40
  secondarySelectorSelectedField,
41
+ style,
39
42
 
40
43
  ...propsToPass
41
44
  } = props;
@@ -44,8 +47,23 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
44
47
  throw Error('SecondaryEditor is not defined');
45
48
  }
46
49
 
50
+ if (isResizable) {
51
+ secondaryEditorProps.w = 500;
52
+ secondaryEditorProps.isResizable = true;
53
+ } else {
54
+ secondaryEditorProps.flex = secondarySideFlex;
55
+ }
56
+
57
+ if (!secondaryEditorProps.className) {
58
+ secondaryEditorProps.className = '';
59
+ }
60
+ secondaryEditorProps.className += ' border-l-1 border-l-grey-300';
61
+
47
62
  return <Container
63
+ parent={self}
64
+ reference="SideEditor"
48
65
  center={<WrappedComponent
66
+ ref={ref}
49
67
  isTree={isTree}
50
68
  isSideEditor={true}
51
69
  {...props}
@@ -53,13 +71,11 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
53
71
  east={<Editor
54
72
  {...propsToPass}
55
73
  editorType={EDITOR_TYPE__SIDE}
56
- flex={secondarySideFlex}
57
- borderLeftWidth={1}
58
- borderLeftColor="#ccc"
59
74
  {...secondaryEditorProps}
60
75
  parent={self}
61
76
  reference="secondaryEditor"
62
77
  />}
63
78
  />;
64
- }));
79
+ });
80
+ return withAdditionalProps(withSecondaryEditor(SideEditor, isTree));
65
81
  }
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'react';
1
2
  import {
2
3
  Modal, ModalBackdrop, ModalHeader, ModalContent, ModalCloseButton, ModalBody, ModalFooter,
3
4
  } from '@project-components/Gluestack';
@@ -13,20 +14,21 @@ import _ from 'lodash';
13
14
 
14
15
 
15
16
  function withAdditionalProps(WrappedComponent) {
16
- return (props) => {
17
+ return forwardRef((props, ref) => {
17
18
  // provide the editorType to withEditor
18
19
  return <WrappedComponent
19
20
  editorType={EDITOR_TYPE__WINDOWED}
20
21
  {...props}
22
+ ref={ref}
21
23
  />;
22
- };
24
+ });
23
25
  }
24
26
 
25
27
  // NOTE: Effectivtly, the HOC composition is:
26
28
  // withAdditionalProps(withSecondaryEditor(withSecondaryWindowedEditor))
27
29
 
28
30
  export default function withSecondaryWindowedEditor(WrappedComponent, isTree = false) {
29
- return withAdditionalProps(withSecondaryEditor((props) => {
31
+ const WindowedEditor = forwardRef((props, ref) => {
30
32
  const {
31
33
  secondaryIsEditorShown = false,
32
34
  secondarySetIsEditorShown,
@@ -41,6 +43,7 @@ export default function withSecondaryWindowedEditor(WrappedComponent, isTree = f
41
43
  secondarySelectorSelected,
42
44
  secondarySelectorSelectedField,
43
45
  h,
46
+ style,
44
47
 
45
48
  ...propsToPass
46
49
  } = props;
@@ -65,25 +68,28 @@ export default function withSecondaryWindowedEditor(WrappedComponent, isTree = f
65
68
  }
66
69
 
67
70
  return <>
68
- <WrappedComponent {...props} />
71
+ <WrappedComponent {...props} ref={ref} />
69
72
  {secondaryIsEditorShown &&
70
73
  <Modal
71
74
  isOpen={true}
72
75
  onClose={() => secondarySetIsEditorShown(false)}
76
+ className="withSecondaryEditor-Modal"
73
77
  >
74
- <ModalBackdrop />
75
- <ModalContent>
76
- <ModalBody>
77
- <SecondaryEditor
78
- editorType={EDITOR_TYPE__WINDOWED}
79
- {...propsToPass}
80
- {...secondaryEditorProps}
81
- parent={self}
82
- reference="secondaryEditor"
83
- />
84
- </ModalBody>
85
- </ModalContent>
78
+ <ModalBackdrop className="withSecondaryEditor-ModalBackdrop" />
79
+ <SecondaryEditor
80
+ editorType={EDITOR_TYPE__WINDOWED}
81
+ {...propsToPass}
82
+ {...secondaryEditorProps}
83
+ parent={self}
84
+ reference="secondaryEditor"
85
+ className={`
86
+ bg-white
87
+ shadow-lg
88
+ rounded-lg
89
+ `}
90
+ />
86
91
  </Modal>}
87
92
  </>;
88
- }, isTree));
93
+ });
94
+ return withAdditionalProps(withSecondaryEditor(WindowedEditor, isTree));
89
95
  }
@@ -6,6 +6,7 @@ import {
6
6
  } from '@project-components/Gluestack';
7
7
  import {
8
8
  ScrollView,
9
+ Platform,
9
10
  } from 'react-native'
10
11
  import {
11
12
  EDITOR_TYPE__PLAIN,
@@ -35,6 +36,8 @@ import _ from 'lodash';
35
36
  //
36
37
  // Model defaultFilters should adjust to this new arrangement
37
38
 
39
+ const isWindows = Platform.OS === 'windows';
40
+
38
41
  export default function withFilters(WrappedComponent) {
39
42
  return forwardRef((props, ref) => {
40
43
 
@@ -140,9 +143,11 @@ export default function withFilters(WrappedComponent) {
140
143
 
141
144
  const
142
145
  filterCallbackRef = useRef(),
146
+ scrollViewRef = useRef(),
143
147
  [filters, setFiltersRaw] = useState(formattedStartingFilters), // array of formatted filters
144
148
  [slots, setSlots] = useState(startingSlots), // array of field names user is currently filtering on; blank slots have a null entry in array
145
149
  [previousFilterNames, setPreviousFilterNames] = useState([]), // names of filters the repository used last query
150
+ [isHorizontalScrollbarShown, setIsHorizontalScrollbarShown] = useState(false),
146
151
  setFilters = (filters, doSetSlots = true, save = true) => {
147
152
  setFiltersRaw(filters);
148
153
 
@@ -349,6 +354,16 @@ export default function withFilters(WrappedComponent) {
349
354
  w: 500,
350
355
  });
351
356
  },
357
+ onContentSizeChange = (contentWidth, contentHeight) => {
358
+ if (!isWindows) {
359
+ return;
360
+ }
361
+ if (scrollViewRef.current) {
362
+ scrollViewRef.current.measure((x, y, width, height, pageX, pageY) => {
363
+ setIsHorizontalScrollbarShown(contentWidth > width);
364
+ });
365
+ }
366
+ },
352
367
  buildModalBody = (modalFilters, modalSlots) => {
353
368
 
354
369
  const
@@ -603,6 +618,25 @@ export default function withFilters(WrappedComponent) {
603
618
  })();
604
619
  }, [filters]);
605
620
 
621
+ useEffect(() => {
622
+ if (!isWindows) {
623
+ return;
624
+ }
625
+
626
+ // NOTE: On Windows machines, I was getting horizontal scrollbars when the ScrollView
627
+ // was not wide enough to contain all the filters. This workaround adds pb-5 to the ScrollView
628
+ // when the scrollbar is shown.
629
+
630
+ if (scrollViewRef.current) {
631
+ scrollViewRef.current.addEventListener('contentSizeChange', onContentSizeChange);
632
+ }
633
+ return () => {
634
+ if (scrollViewRef.current) {
635
+ scrollViewRef.current.removeEventListener('contentSizeChange', onContentSizeChange);
636
+ }
637
+ };
638
+ }, []);
639
+
606
640
  if (!isReady) {
607
641
  return null;
608
642
  }
@@ -615,9 +649,16 @@ export default function withFilters(WrappedComponent) {
615
649
  const
616
650
  renderedFilters = renderFilters(),
617
651
  hasFilters = !!renderedFilters.length,
652
+ scrollViewClass = isWindows && isHorizontalScrollbarShown ? 'pb-5' : '',
618
653
  toolbar = <Toolbar>
619
654
  <HStack className="withFilters-scrollViewContainer flex-1 items-center">
620
- <ScrollView className="ScrollView" horizontal={true} contentContainerStyle={{ alignItems: 'center' }}>
655
+ <ScrollView
656
+ ref={scrollViewRef}
657
+ className={`withFilters-ScrollView ${scrollViewClass}`}
658
+ horizontal={true}
659
+ contentContainerStyle={{ alignItems: 'center' }}
660
+ onContentSizeChange={onContentSizeChange}
661
+ >
621
662
  <Text
622
663
  className={`
623
664
  withFilters-filtersLabel
@@ -163,13 +163,14 @@ export default function withModal(WrappedComponent) {
163
163
  <Panel
164
164
  title={title}
165
165
  isCollapsible={false}
166
- className="bg-white overflow-auto"
166
+ className="withModal-Panel bg-white"
167
167
  h={h > windowHeight ? windowHeight : h}
168
168
  w={w > windowWidth ? windowWidth : w}
169
169
  isWindow={true}
170
170
  disableAutoFlex={true}
171
171
  onClose={canClose ? hideModal : null}
172
172
  footer={footer}
173
+ isScrollable={true}
173
174
  >{modalBody}</Panel>
174
175
  }
175
176
  }
@@ -83,7 +83,13 @@ export default function withPdfButtons(WrappedComponent) {
83
83
 
84
84
  if (!_.isEmpty(ancillaryItems)) {
85
85
  const
86
- ancillaryItemsClone = _.cloneDeep(ancillaryItems),
86
+ ancillaryItemsClone = _.cloneDeepWith(ancillaryItems, (value) => {
87
+ // Exclude the 'parent' property from being cloned, as it would introduce an infinitely recursive loop
88
+ if (value && value.parent) {
89
+ const { parent, ...rest } = value;
90
+ return rest;
91
+ }
92
+ }),
87
93
  items = [];
88
94
  _.each(ancillaryItemsClone, (ancillaryItem) => { // clone, as we don't want to alter the item by reference
89
95
  let name;
@@ -4,8 +4,8 @@ import { Path, Svg } from 'react-native-svg';
4
4
 
5
5
  const SvgComponent = createIcon({
6
6
  Root: Svg,
7
- viewBox: '0 0 9.51 12.68',
8
- path: <Path d="M4.76 0C3.72 0 2.84.66 2.52 1.59h-.93C.72 1.59 0 2.3 0 3.18v7.93c0 .87.71 1.59 1.59 1.59h6.34c.87 0 1.59-.71 1.59-1.59V3.17c0-.87-.71-1.59-1.59-1.59H7A2.384 2.384 0 004.76-.01zm0 1.59c.44 0 .79.35.79.79s-.35.79-.79.79-.79-.35-.79-.79.35-.79.79-.79zm2.8 5.18L4.39 9.94c-.23.23-.61.23-.84 0L1.96 8.35c-.23-.23-.23-.61 0-.84s.61-.23.84 0l1.16 1.16 2.75-2.75c.23-.23.61-.23.84 0s.23.61 0 .84z" />
7
+ viewBox: '0 0 384 512',
8
+ path: <Path d="M192 0c-41.8 0-77.4 26.7-90.5 64L64 64C28.7 64 0 92.7 0 128L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64l-37.5 0C269.4 26.7 233.8 0 192 0zm0 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM305 273L177 401c-9.4 9.4-24.6 9.4-33.9 0L79 337c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L271 239c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z" />
9
9
  });
10
10
 
11
11
  export default SvgComponent
@@ -3,7 +3,7 @@ import { Path, Svg } from 'react-native-svg';
3
3
 
4
4
  const SvgComponent = createIcon({
5
5
  Root: Svg,
6
- viewBox: '0 0 512 512',
6
+ viewBox: '0 0 101.44 83.8',
7
7
  path: <Path d="M58.92 4.5L99.9 68.86c4.12 6.47-.53 14.94-8.2 14.94H9.74c-7.67 0-12.32-8.47-8.2-14.94L42.52 4.5c3.82-6 12.58-6 16.4 0zm-8.2 68.21c3.24 0 5.34-2.34 5.34-5.46-.06-3.18-2.16-5.46-5.34-5.46s-5.4 2.28-5.4 5.46 2.16 5.46 5.4 5.46zm3.42-13.92l1.32-27.18h-9.54l1.38 27.18h6.84z" strokeWidth={0} />
8
8
  });
9
9
 
@@ -1,4 +1,6 @@
1
- import { Box } from "../Gluestack";
1
+ import {
2
+ Box
3
+ } from '@project-components/Gluestack';
2
4
 
3
5
  export default function CenterBox(props) {
4
6
  let className = `
@@ -0,0 +1,73 @@
1
+ import {
2
+ HStack,
3
+ Icon,
4
+ Text,
5
+ VStack,
6
+ } from '@project-components/Gluestack';
7
+ import {
8
+ SCREEN_MODES__FULL,
9
+ SCREEN_MODES__SIDE,
10
+ } from '../../Constants/ScreenModes.js'
11
+ import FullWidth from '../Icons/FullWidth';
12
+ import SideBySide from '../Icons/SideBySide';
13
+ import UiGlobals from '../../UiGlobals.js';
14
+ import IconButton from '../Buttons/IconButton';
15
+ import testProps from '../../Functions/testProps.js';
16
+ import _ from 'lodash';
17
+
18
+ export default function ScreenHeader(props) {
19
+ const {
20
+ title,
21
+ icon,
22
+ useModeIcons = false,
23
+ allowSideBySide = false,
24
+ actualMode,
25
+ onFullWidth,
26
+ onSideBySide,
27
+ } = props,
28
+ textProps = {},
29
+ styles = UiGlobals.styles;
30
+ if (styles.MANAGER_SCREEN_TITLE) {
31
+ textProps.style = {
32
+ fontFamily: styles.MANAGER_SCREEN_TITLE,
33
+ };
34
+ }
35
+ return <HStack className="ScreenHeader-HStack h-[80px] items-center border-b-[2px] border-b-[#ccc]">
36
+ {icon &&
37
+ <Icon
38
+ as={icon}
39
+ className={`
40
+ ml-5
41
+ text-black
42
+ `}
43
+ size="xl"
44
+ />}
45
+ <Text {...textProps} className="ScreenHeader-Text pl-4 text-[26px] font-[700]">{title}</Text>
46
+ {useModeIcons && allowSideBySide &&
47
+ <>
48
+ <IconButton
49
+ {...testProps('fullModeBtn')}
50
+ icon={FullWidth}
51
+ _icon={{
52
+ size: 'xl',
53
+ className: 'text-black',
54
+ }}
55
+ isDisabled={actualMode === SCREEN_MODES__FULL}
56
+ onPress={onFullWidth}
57
+ tooltip="To full width"
58
+ className="ml-5"
59
+ />
60
+ <IconButton
61
+ {...testProps('sideModeBtn')}
62
+ icon={SideBySide}
63
+ _icon={{
64
+ size: 'xl',
65
+ className: 'text-black',
66
+ }}
67
+ isDisabled={actualMode === SCREEN_MODES__SIDE}
68
+ onPress={onSideBySide}
69
+ tooltip="To side editor"
70
+ />
71
+ </>}
72
+ </HStack>;
73
+ }
@@ -0,0 +1,22 @@
1
+ import {
2
+ TextNative,
3
+ Tooltip, TooltipContent, TooltipText,
4
+ } from '@project-components/Gluestack';
5
+
6
+ export default function TextWithTooltip(props) {
7
+ const {
8
+ tooltip,
9
+ children,
10
+ ...propsToPass
11
+ } = props;
12
+ return <Tooltip
13
+ placement="bottom"
14
+ trigger={(triggerProps) => {
15
+ return <TextNative {...triggerProps} {...propsToPass}>{children}</TextNative>
16
+ }}
17
+ >
18
+ <TooltipContent>
19
+ <TooltipText>{tooltip}</TooltipText>
20
+ </TooltipContent>
21
+ </Tooltip>;
22
+ }
@@ -15,6 +15,7 @@ import {
15
15
  UI_MODE_NATIVE,
16
16
  } from '../../Constants/UiModes.js';
17
17
  import UiGlobals from '../../UiGlobals.js';
18
+ import testProps from '../../Functions/testProps.js';
18
19
  import Minus from '../Icons/Minus.js';
19
20
  import Plus from '../Icons/Plus.js';
20
21
  import Xmark from '../Icons/Xmark.js';
@@ -55,6 +56,7 @@ export default function Header(props) {
55
56
  closeClassName += ' mb-1';
56
57
  }
57
58
  closeBtn = <IconButton
59
+ {...testProps('closeBtn')}
58
60
  onPress={onClose}
59
61
  icon={Xmark}
60
62
  _icon={{
@@ -120,7 +120,7 @@ function Panel(props) {
120
120
  }
121
121
 
122
122
  // frame
123
- className += ' border-primary-600' + (isWindow ? ' rounded-lg shadow-lg ' : '') + (frame ? ' border-2' : ' border-none');
123
+ className += ' border-grey-300' + (isWindow ? ' rounded-lg shadow-lg ' : '') + (frame ? ' border-2' : ' border-none');
124
124
 
125
125
  if (props.className) {
126
126
  className += ' ' + props.className;
@@ -143,8 +143,13 @@ function Panel(props) {
143
143
  overflow-hidden
144
144
  `}
145
145
  >
146
- {isScrollable ?
147
- <ScrollView className="Panel-ScrollView">
146
+ {isScrollable ?
147
+ <ScrollView
148
+ className="Panel-ScrollView"
149
+ contentContainerStyle={{
150
+ height: '100%',
151
+ }}
152
+ >
148
153
  {children}
149
154
  </ScrollView> :
150
155
  children}
@@ -1,26 +1,22 @@
1
1
  import {
2
- HStack,
3
- Text,
4
2
  VStackNative,
5
3
  } from '@project-components/Gluestack';
6
4
  import React, { useState, useEffect, } from 'react';
5
+ import {
6
+ SCREEN_MODES__FULL,
7
+ SCREEN_MODES__SIDE,
8
+ } from '../../Constants/ScreenModes.js'
7
9
  import withComponent from '../Hoc/withComponent.js';
8
10
  import testProps from '../../Functions/testProps.js';
9
- import UiGlobals from '../../UiGlobals.js';
10
- import IconButton from '../Buttons/IconButton';
11
- import FullWidth from '../Icons/FullWidth';
12
- import SideBySide from '../Icons/SideBySide';
11
+ import ScreenHeader from '../Layout/ScreenHeader';
13
12
  import getSaved from '../../Functions/getSaved.js';
14
13
  import setSaved from '../../Functions/setSaved.js';
15
14
  import _ from 'lodash';
16
15
 
17
- const
18
- MODE_FULL = 'MODE_FULL',
19
- MODE_SIDE = 'MODE_SIDE';
20
-
21
16
  function ManagerScreen(props) {
22
17
  const {
23
18
  title,
19
+ icon,
24
20
  sideModeComponent,
25
21
  fullModeComponent,
26
22
  onChangeMode,
@@ -28,15 +24,14 @@ function ManagerScreen(props) {
28
24
  // withComponent
29
25
  self,
30
26
  } = props,
31
- styles = UiGlobals.styles,
32
27
  id = props.id || props.self?.path,
33
28
  [isRendered, setIsRendered] = useState(false),
34
29
  [isModeSet, setIsModeSet] = useState(false),
35
30
  [allowSideBySide, setAllowSideBySide] = useState(false),
36
- [mode, setModeRaw] = useState(MODE_FULL),
37
- actualMode = (!allowSideBySide || mode === MODE_FULL) ? MODE_FULL : MODE_SIDE,
31
+ [mode, setModeRaw] = useState(SCREEN_MODES__FULL),
32
+ actualMode = (!allowSideBySide || mode === SCREEN_MODES__FULL) ? SCREEN_MODES__FULL : SCREEN_MODES__SIDE,
38
33
  setMode = (newMode) => {
39
- if (!allowSideBySide && newMode === MODE_SIDE) {
34
+ if (!allowSideBySide && newMode === SCREEN_MODES__SIDE) {
40
35
  return;
41
36
  }
42
37
  if (newMode === mode) {
@@ -83,49 +78,22 @@ function ManagerScreen(props) {
83
78
  self.mode = actualMode;
84
79
  }
85
80
 
86
- const
87
- whichComponent = actualMode === MODE_FULL ? fullModeComponent : sideModeComponent,
88
- textProps = {};
89
- if (styles.MANAGER_SCREEN_TITLE) {
90
- textProps.style = {
91
- fontFamily: styles.MANAGER_SCREEN_TITLE,
92
- };
93
- }
81
+ const whichComponent = actualMode === SCREEN_MODES__FULL ? fullModeComponent : sideModeComponent;
94
82
 
95
83
  return <VStackNative
96
84
  {...testProps(self)}
97
85
  onLayout={onLayout}
98
86
  className="max-h-screen overflow-hidden flex-1 w-full"
99
87
  >
100
- <HStack className="h-[80px] items-center border-b-[2px] border-b-[#ccc]">
101
- <Text {...textProps} className="pl-5 text-[26px] font-[700]">{title}</Text>
102
- {allowSideBySide &&
103
- <>
104
- <IconButton
105
- {...testProps('fullModeBtn')}
106
- icon={FullWidth}
107
- _icon={{
108
- size: 'xl',
109
- className: 'text-black',
110
- }}
111
- isDisabled={actualMode === MODE_FULL}
112
- onPress={() => setMode(MODE_FULL)}
113
- tooltip="To full width"
114
- className="ml-5"
115
- />
116
- <IconButton
117
- {...testProps('sideModeBtn')}
118
- icon={SideBySide}
119
- _icon={{
120
- size: 'xl',
121
- className: 'text-black',
122
- }}
123
- isDisabled={actualMode === MODE_SIDE}
124
- onPress={() => setMode(MODE_SIDE)}
125
- tooltip="To side editor"
126
- />
127
- </>}
128
- </HStack>
88
+ <ScreenHeader
89
+ title={title}
90
+ icon={icon}
91
+ useModeIcons={true}
92
+ actualMode={actualMode}
93
+ allowSideBySide={allowSideBySide}
94
+ onFullWidth={() => setMode(SCREEN_MODES__FULL)}
95
+ onSideBySide={() => setMode(SCREEN_MODES__SIDE)}
96
+ />
129
97
  {isRendered && isModeSet && whichComponent}
130
98
  </VStackNative>;
131
99
  }
@@ -0,0 +1,62 @@
1
+ import { useState, } from 'react';
2
+ import {
3
+ HStack,
4
+ ScrollView,
5
+ VStack,
6
+ VStackNative,
7
+ } from '@project-components/Gluestack';
8
+ import ChartPie from '../Icons/ChartPie.js';
9
+ import ScreenHeader from '../Layout/ScreenHeader.js';
10
+
11
+ const CONTAINER_THRESHOLD = 1100;
12
+
13
+ export default function ReportsManager(props) {
14
+ const {
15
+ reports = [],
16
+ isActive = false,
17
+ } = props,
18
+ [containerWidth, setContainerWidth] = useState(),
19
+ onLayout = (e) => {
20
+ setContainerWidth(e.nativeEvent.layout.width);
21
+ };
22
+
23
+ if (!isActive) {
24
+ return null;
25
+ }
26
+
27
+ let reportElements = [];
28
+ if (containerWidth) {
29
+ if (containerWidth >= CONTAINER_THRESHOLD) {
30
+ // two column layout
31
+ const
32
+ reportsPerColumn = Math.ceil(reports.length / 2),
33
+ col1Reports = reports.slice(0, reportsPerColumn),
34
+ col2Reports = reports.slice(reportsPerColumn);
35
+ reportElements = <HStack className="gap-3">
36
+ <VStack className="flex-1">
37
+ {col1Reports}
38
+ </VStack>
39
+ <VStack className="flex-1">
40
+ {col2Reports}
41
+ </VStack>
42
+ </HStack>;
43
+ } else {
44
+ // one column layout
45
+ reportElements = reports;
46
+ }
47
+ }
48
+
49
+ return <VStack
50
+ className="overflow-hidden flex-1 w-full"
51
+ >
52
+ <ScreenHeader
53
+ title="Reports"
54
+ icon={ChartPie}
55
+ />
56
+ <ScrollView className="flex-1 w-full">
57
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
58
+ {containerWidth && reportElements}
59
+ </VStackNative>
60
+ </ScrollView>
61
+ </VStack>;
62
+ }
@@ -126,6 +126,7 @@ export default function Pagination(props) {
126
126
  text-center
127
127
  bg-grey-100
128
128
  `}
129
+ textAlignIsCenter={true}
129
130
  tooltip="Set Page"
130
131
  />);
131
132
  items.push(<Text
@@ -1396,7 +1396,13 @@ function TreeComponent(props) {
1396
1396
  }}
1397
1397
  className="Tree-deselector w-full flex-1 p-1 bg-white"
1398
1398
  >
1399
- <ScrollView {...testProps('ScrollView')} className="ScrollView flex-1 w-full">
1399
+ <ScrollView
1400
+ {...testProps('ScrollView')}
1401
+ className="Tree-ScrollView flex-1 w-full"
1402
+ contentContainerStyle={{
1403
+ height: '100%',
1404
+ }}
1405
+ >
1400
1406
  {!treeNodes?.length ?
1401
1407
  <CenterBox>
1402
1408
  {Repository.isLoading ? <Loading /> : <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} />}
@@ -109,7 +109,9 @@ export default function TreeNode(props) {
109
109
  text-ellipsis
110
110
  ${styles.TREE_NODE_CLASSNAME}
111
111
  `}
112
- style={{ userSelect: 'none', }}
112
+ style={{
113
+ // userSelect: 'none',
114
+ }}
113
115
  >{text}</TextNative> : null}
114
116
 
115
117
  {content}
@@ -0,0 +1,2 @@
1
+ export const SCREEN_MODES__FULL = 'SCREEN_MODES__FULL';
2
+ export const SCREEN_MODES__SIDE = 'SCREEN_MODES__SIDE';
@@ -24,9 +24,11 @@ export function getDomNodes(selectors, options = {}) {
24
24
 
25
25
  /**
26
26
  * Builds selector string for data-testid attributes.
27
- * @argument {string | string[]} selectors - data-testid attribute values
28
- * If an array is given, these will be considered nested selectors.
27
+ * It leaves classname, id, and attribute selectors unchanged.
28
+ *
29
+ * If selectors is an array, these will be considered nested selectors.
29
30
  * e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
31
+ * @argument {string|string[]} selectors - data-testid attribute values
30
32
  * @return {string}
31
33
  */
32
34
  export function getTestIdSelectors(selectors, isGetFirst = false) {
@@ -34,16 +36,47 @@ export function getTestIdSelectors(selectors, isGetFirst = false) {
34
36
  selectors = [selectors];
35
37
  }
36
38
  const selectorParts = _.map(selectors, (selector) => {
37
- if (selector.match(/=/)) { // selector is something like [role="switch"], so don't use data-testid
38
- return selector;
39
- }
40
- if (selector.match(/^\./)) { // selector is something like .my-class, so don't use data-testid
41
- return selector;
39
+ if (!selector.match(/^\./) // className, like .my-class
40
+ && !selector.match(/^#/) // id, like @my-id
41
+ && !selector.match(/=/) // attribute, like [role="switch"]
42
+ ){
43
+ selector = '[data-testid="' + selector + '"]';
42
44
  }
43
- if (selector.match(/^#/)) { // selector is something like #my-id, so don't use data-testid
44
- return selector;
45
+ if (isGetFirst) {
46
+ selector += ':first';
45
47
  }
46
- return '[data-testid="' + selector + '"]' + (isGetFirst ? ':first' : '');
48
+ return selector;
47
49
  });
48
50
  return selectorParts.join(' ');
49
51
  }
52
+
53
+ export function drag(draggableSelectors, droppableSelectors, options = {}) {
54
+ if (typeof options.force === 'undefined') {
55
+ options.force = true;
56
+ }
57
+
58
+ // getDomNode(getTestIdSelectors(droppableSelectors)).then((node) => {
59
+ // const selectors = getTestIdSelectors(droppableSelectors);
60
+ // debugger;
61
+ // });
62
+
63
+ options = {
64
+ source: { // applies to the element being dragged
65
+ x: 10,
66
+ // y: 100,
67
+ position: 'left',
68
+ },
69
+ target: { // applies to the drop target
70
+ position: 'left',
71
+ x: 20,
72
+ },
73
+ force: true, // applied to both the source and target element
74
+ };
75
+
76
+
77
+ return getDomNode(draggableSelectors)
78
+ .drag(getTestIdSelectors(droppableSelectors), options)
79
+ .then((success) => {
80
+ debugger;
81
+ });
82
+ }
@@ -1,6 +1,6 @@
1
1
  html, body {
2
- /* -webkit-user-select: none;
3
- user-select: none; */
2
+ -webkit-user-select: none;
3
+ user-select: none;
4
4
  }
5
5
 
6
6
  /* to fix the inline editor */