@onehat/ui 0.4.64 → 0.4.66

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.64",
3
+ "version": "0.4.66",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,21 @@
1
+ import {
2
+ VStack,
3
+ } from '@project-components/Gluestack';
4
+ import _ from 'lodash';
5
+
6
+ // This component allows us to stack multiple children in a Container slot (e.g. east)
7
+ // such that the ContainerColumn can be passed props like isResizable,
8
+ // which the Container will translate into classNames for the VStack component.
9
+
10
+ export default function ContainerColumn(props) {
11
+ let className = `
12
+ ContainerColumn
13
+ `;
14
+ if (props.className) {
15
+ className += ` ${props.className}`;
16
+ }
17
+
18
+ return <VStack className={className}>
19
+ {props.children}
20
+ </VStack>;
21
+ }
@@ -767,6 +767,7 @@ export const ComboComponent = forwardRef((props, ref) => {
767
767
  newEntityDisplayProperty={newEntityDisplayProperty}
768
768
  disablePresetButtons={!isEditor}
769
769
  alternateRowBackgrounds={false}
770
+ showSelectHandle={false}
770
771
  onChangeSelection={(selection) => {
771
772
 
772
773
  if (Repository && selection[0]?.isPhantom) {
@@ -46,12 +46,12 @@ export function JsonElement(props) {
46
46
  ${testID}
47
47
  `;
48
48
  if (props.className) {
49
- className += ' ' + props.className;
49
+ className += ' ' + propsToPass.className;
50
50
  }
51
51
  // if (UiGlobals.mode === UI_MODE_WEB) {
52
52
  const src = value ? JSON.parse(value) : {};
53
53
  assembledComponents =
54
- <HStack style={props.style} className={className}>
54
+ <HStack style={propsToPass.style} className={className}>
55
55
  <JsonEditor
56
56
  width="100%"
57
57
  editable={!isViewOnly}
@@ -61,7 +61,7 @@ export function JsonElement(props) {
61
61
  onEdit={(obj) => {
62
62
  setValue(JSON.stringify(obj.updated_src));
63
63
  }}
64
- {...props}
64
+ {...propsToPass}
65
65
  />
66
66
  </HStack>;
67
67
  // }
@@ -132,6 +132,9 @@ function GridComponent(props) {
132
132
  getExpandedRowContent,
133
133
  showHeaders = true,
134
134
  showHovers = true,
135
+ showSelectHandle = true,
136
+ isRowSelectable = true,
137
+ isRowHoverable = true,
135
138
  canColumnsSort = true,
136
139
  canColumnsReorder = true,
137
140
  canColumnsResize = true,
@@ -424,10 +427,10 @@ function GridComponent(props) {
424
427
  } else {
425
428
  let canDoEdit = false,
426
429
  canDoView = false;
427
- if (onEdit && canUser && canUser(EDIT) && (!canRecordBeEdited || canRecordBeEdited(selection))) {
430
+ if (onEdit && canUser && canUser(EDIT) && (!canRecordBeEdited || canRecordBeEdited(selection)) && !props.disableEdit) {
428
431
  canDoEdit = true;
429
432
  } else
430
- if (onView && canUser && canUser(VIEW)) {
433
+ if (onView && canUser && canUser(VIEW) && !props.disableView) {
431
434
  canDoView = true;
432
435
  }
433
436
 
@@ -472,7 +475,13 @@ function GridComponent(props) {
472
475
  onContextMenu(item, e, newSelection);
473
476
  }
474
477
  }}
475
- className="Pressable Row flex-row grow">
478
+ className={`
479
+ Pressable
480
+ Row
481
+ flex-row
482
+ grow
483
+ `}
484
+ >
476
485
  {({
477
486
  hovered,
478
487
  focused,
@@ -493,6 +502,7 @@ function GridComponent(props) {
493
502
  isInlineEditorShown={isInlineEditorShown}
494
503
  areRowsDragSource={areRowsDragSource}
495
504
  showColumnsSelector={showColumnsSelector}
505
+ showSelectHandle={showSelectHandle}
496
506
  />;
497
507
  if (showRowExpander) {
498
508
  // align the header row to content rows by adding a spacer that matches the width of the Grid-rowExpander-expandBtn
@@ -503,12 +513,11 @@ function GridComponent(props) {
503
513
  }
504
514
  return headerRow;
505
515
  }
506
-
507
516
  const
508
517
  rowReorderProps = {},
509
518
  rowDragProps = {};
510
519
  let WhichRow = GridRow;
511
- if (CURRENT_MODE === UI_MODE_WEB) { // DND is currrently web-only TODO: implement for RN
520
+ if (CURRENT_MODE === UI_MODE_WEB) { // DND is currently web-only TODO: implement for RN
512
521
  // Create a method that gets an always-current copy of the selection ids
513
522
  dragSelectionRef.current = selection;
514
523
  const getSelection = () => dragSelectionRef.current;
@@ -567,9 +576,12 @@ function GridComponent(props) {
567
576
  fields={fields}
568
577
  rowProps={rowProps}
569
578
  hideNavColumn={hideNavColumn}
579
+ isRowSelectable={isRowSelectable}
580
+ isRowHoverable={isRowHoverable}
570
581
  isSelected={isSelected}
571
582
  isHovered={hovered}
572
583
  showHovers={showHovers}
584
+ showSelectHandle={showSelectHandle}
573
585
  index={index}
574
586
  alternatingInterval={alternatingInterval}
575
587
  alternateRowBackgrounds={alternateRowBackgrounds}
@@ -20,6 +20,7 @@ import UiGlobals from '../../UiGlobals.js';
20
20
  import useBlocking from '../../Hooks/useBlocking.js';
21
21
  import testProps from '../../Functions/testProps.js';
22
22
  import AngleRight from '../Icons/AngleRight.js';
23
+ import Arcs from '../Icons/Arcs.js';
23
24
  import HeaderReorderHandle from './HeaderReorderHandle.js';
24
25
  import HeaderResizeHandle from './HeaderResizeHandle.js';
25
26
  import HeaderColumnSelectorHandle from './HeaderColumnSelectorHandle.js';
@@ -46,6 +47,7 @@ export default function GridHeaderRow(props) {
46
47
  isInlineEditorShown,
47
48
  areRowsDragSource,
48
49
  showColumnsSelector,
50
+ showSelectHandle,
49
51
  } = props,
50
52
  styles = UiGlobals.styles,
51
53
  sortFn = Repository && Repository.getSortFn(),
@@ -462,10 +464,18 @@ export default function GridHeaderRow(props) {
462
464
  />}
463
465
  </Pressable>;
464
466
  });
467
+ if (showSelectHandle) {
468
+ headerColumns.unshift(<Box
469
+ key="RowSelectHandle"
470
+ className="Spacer-RowSelectHandle px-2 items-center justify-center flex-none w-[40px]"
471
+ >
472
+ <Icon as={Arcs} className={`Arcs w-[20px] h-[20px] text-[#aaa]`} />
473
+ </Box>);
474
+ }
465
475
  if (areRowsDragSource) {
466
476
  headerColumns.unshift(<Box
467
477
  key="spacer"
468
- className="Spacer w-[3px]"
478
+ className="Spacer w-[7px]"
469
479
  />);
470
480
  }
471
481
  if (!hideNavColumn) {
@@ -16,6 +16,7 @@ import { withDragSource, withDropTarget } from '../Hoc/withDnd.js';
16
16
  import testProps from '../../Functions/testProps.js';
17
17
  import AngleRight from '../Icons/AngleRight.js';
18
18
  import RowDragHandle from './RowDragHandle.js';
19
+ import RowSelectHandle from './RowSelectHandle.js';
19
20
  import _ from 'lodash';
20
21
 
21
22
  // This was broken out from Grid simply so we can memoize it
@@ -27,6 +28,9 @@ function GridRow(props) {
27
28
  fields,
28
29
  rowProps,
29
30
  hideNavColumn,
31
+ showSelectHandle,
32
+ isRowSelectable,
33
+ isRowHoverable,
30
34
  isSelected,
31
35
  isHovered,
32
36
  bg,
@@ -55,13 +59,13 @@ function GridRow(props) {
55
59
 
56
60
  let bg = rowProps.bg || props.bg || styles.GRID_ROW_BG,
57
61
  mixWith;
58
- if (isSelected) {
62
+ if (isRowSelectable && isSelected) {
59
63
  if (showHovers && isHovered) {
60
64
  mixWith = styles.GRID_ROW_SELECTED_BG_HOVER;
61
65
  } else {
62
66
  mixWith = styles.GRID_ROW_SELECTED_BG;
63
67
  }
64
- } else if (showHovers && isHovered) {
68
+ } else if (isRowHoverable && showHovers && isHovered) {
65
69
  mixWith = styles.GRID_ROW_BG_HOVER;
66
70
  } else if (alternateRowBackgrounds && index % alternatingInterval === 0) { // i.e. every second line, or every third line
67
71
  mixWith = styles.GRID_ROW_ALTERNATE_BG;
@@ -85,7 +89,8 @@ function GridRow(props) {
85
89
  }
86
90
  const
87
91
  propsToPass = columnProps[key] || {},
88
- colStyle = {};
92
+ colStyle = {},
93
+ whichCursor = showSelectHandle ? 'cursor-text' : 'cursor-pointer'; // when using rowSelectHandle, indicate that the row text is selectable, otherwise indicate that the row itself is selectable
89
94
  let colClassName = `
90
95
  GridRow-column
91
96
  p-1
@@ -93,6 +98,7 @@ function GridRow(props) {
93
98
  border-r-black-100
94
99
  block
95
100
  overflow-auto
101
+ ${whichCursor}
96
102
  ${styles.GRID_ROW_MAX_HEIGHT_EXTRA}
97
103
  `;
98
104
  if (isOnlyOneVisibleColumn) {
@@ -265,6 +271,8 @@ function GridRow(props) {
265
271
 
266
272
  let rowContents = <>
267
273
  {(isDragSource || isDraggable) && <RowDragHandle />}
274
+ {showSelectHandle && <RowSelectHandle />}
275
+
268
276
  {isPhantom &&
269
277
  <Box
270
278
  className={`
@@ -5,11 +5,10 @@ import {
5
5
  import styles from '../../Styles/StyleSheets.js';
6
6
  import GripVertical from '../Icons/GripVertical.js';
7
7
 
8
- function RowDragHandle(props) {
9
- return <VStack
10
- style={styles.ewResize}
11
- className="HeaderReorderHandle bg-grey-100 w-[3px] items-center justify-center"
12
- >
8
+ function RowDragHandle(props) { return <VStack
9
+ style={styles.ewResize}
10
+ className="RowDragHandle bg-grey-100 w-[7px] items-center justify-center select-none"
11
+ >
13
12
  <Icon
14
13
  as={GripVertical}
15
14
  size="xs"
@@ -0,0 +1,18 @@
1
+ import {
2
+ Icon,
3
+ VStack,
4
+ } from '@project-components/Gluestack';
5
+ import Arcs from '../Icons/Arcs.js';
6
+
7
+ function RowSelectHandle(props) {
8
+ return <VStack
9
+ className="RowSelectHandle w-[40px] px-2 items-center justify-center select-none cursor-pointer"
10
+ >
11
+ <Icon
12
+ as={Arcs}
13
+ size="xs"
14
+ className="w-[20px] h-[20px] text-[#ddd]" />
15
+ </VStack>;
16
+ }
17
+
18
+ export default RowSelectHandle;
@@ -63,6 +63,10 @@ function withAlert(WrappedComponent) {
63
63
  text-[18px]
64
64
  flex-none
65
65
  mr-2
66
+ w-full
67
+ break-words
68
+ whitespace-normal
69
+ overflow-wrap-anywhere
66
70
  `}>{message}</Text>
67
71
  </Box>
68
72
  </HStack>;
@@ -1,6 +1,13 @@
1
1
  import { forwardRef, useEffect, useRef, } from 'react';
2
2
  import { useDrag, useDrop, useDragLayer } from 'react-dnd'; // https://react-dnd.github.io/react-dnd/about don't forget the wrapping <DndProvider /> as shown here: https://react-dnd.github.io/react-dnd/docs/api/dnd-provider
3
-
3
+ import {
4
+ Box,
5
+ } from '@project-components/Gluestack';
6
+ import {
7
+ UI_MODE_WEB,
8
+ UI_MODE_NATIVE,
9
+ CURRENT_MODE,
10
+ } from '../../Constants/UiModes.js';
4
11
 
5
12
  // This HOC allows components to be dragged and dropped onto another component.
6
13
  // It can't contrain the movement of the preview item, because react-dnd uses
@@ -32,6 +39,7 @@ export function withDragSource(WrappedComponent) {
32
39
  onDragEnd = null,
33
40
  canDrag = null,
34
41
  isDragging = null,
42
+ getDragProxy,
35
43
  dragCollect = (monitor, props2) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitor and props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
36
44
  // monitor fn determines which props from dnd state get passed
37
45
  return {
@@ -54,7 +62,10 @@ export function withDragSource(WrappedComponent) {
54
62
 
55
63
  return {
56
64
  type: dragSourceType, // Required. This must be either a string or a symbol. Only the drop targets registered for the same type will react to this item.
57
- item: dragSourceItem, // Required (object or function).
65
+ item: {
66
+ ...dragSourceItem,
67
+ getDragProxy,
68
+ }, // Required (object or function).
58
69
  // When an object, it is a plain JavaScript object describing the data being dragged. This is the only information available to the drop targets about the drag source so it's important to pick the minimal data they need to know. You may be tempted to put a complex reference here, but you should try very hard to avoid doing this because it couples the drag sources and drop targets. It's a good idea to use something like { id }.
59
70
  // When a function, it is fired at the beginning of the drag operation and returns an object representing the drag operation (see first bullet). If null is returned, the drag operation is cancelled.
60
71
  previewOptions: dragPreviewOptions, // Optional. A plain JavaScript object describing drag preview options.
@@ -139,6 +150,7 @@ export function withDropTarget(WrappedComponent) {
139
150
  return {
140
151
  canDrop: !!monitor.canDrop(),
141
152
  isOver: !!monitor.isOver(),
153
+ draggedItem: monitor.getItem(), // Pass the dragged item so TreeNode can evaluate custom logic
142
154
  };
143
155
  },
144
156
  } = props,
@@ -171,6 +183,7 @@ export function withDropTarget(WrappedComponent) {
171
183
  {
172
184
  canDrop: stateCanDrop,
173
185
  isOver,
186
+ draggedItem,
174
187
  // didDrop,
175
188
  // clientOffset,
176
189
  // differenceFromInitialOffset,
@@ -187,64 +200,48 @@ export function withDropTarget(WrappedComponent) {
187
200
  ref={ref}
188
201
  isOver={isOver}
189
202
  dropTargetRef={localTargetRef}
203
+ draggedItem={draggedItem} // Pass the dragged item
190
204
  {...props}
191
205
  />;
192
206
  });
193
207
  }
194
208
 
195
209
 
196
- // export function CustomDragLayer(props) {
197
-
198
- // // if (CURRENT_MODE !== UI_MODE_WEB) {
199
- // // throw Error('CustomDragLayer only works in web mode');
200
- // // }
201
-
202
- // const
203
- // {
204
- // onDrag,
205
- // axis = null,
206
- // } = props,
207
- // layer = useDragLayer((monitor) => ({
208
- // isDragging: monitor.isDragging(),
209
- // item: monitor.getItem(),
210
- // currentOffset: monitor.getSourceClientOffset(),
211
- // })),
212
- // { isDragging, item, currentOffset } = layer;
213
-
214
- // useEffect(() => {
215
- // if (layer.isDragging) {
216
- // onDrag(layer);
217
- // }
218
- // }, [layer]);
219
-
220
- // return null;
221
-
222
- // if (!isDragging || !currentOffset) {
223
- // return null;
224
- // }
225
-
226
- // let transform;
227
- // if (axis === 'x') {
228
- // transform = `translate(${currentOffset.x}px, 0)`;
229
- // } else if (axis === 'y') {
230
- // transform = `translate(0, ${currentOffset.y}px)`;
231
- // } else {
232
- // transform = `translate(${currentOffset.x}px, ${currentOffset.y}px)`;
233
- // }
234
-
235
- // return (
236
- // <div id="dragLayer" style={{
237
- // background: '#f00',
238
- // position: 'fixed',
239
- // pointerEvents: 'none',
240
- // zIndex: 10000,
241
- // width: '200px',
242
- // height: '10px',
243
- // left: 0,
244
- // top: 0,
245
- // transform,
246
- // }}>
247
- // {children}
248
- // </div>
249
- // );
250
- // }
210
+ export function GlobalDragProxy() {
211
+ const {
212
+ isDragging,
213
+ item,
214
+ currentOffset,
215
+ } = useDragLayer((monitor) => ({
216
+ isDragging: monitor.isDragging(),
217
+ item: monitor.getItem(),
218
+ currentOffset: monitor.getSourceClientOffset(),
219
+ }));
220
+
221
+ if (!isDragging || !currentOffset || CURRENT_MODE !== UI_MODE_WEB) { // Native uses a native drag layer, so we don't need to render a custom proxy
222
+ return null;
223
+ }
224
+
225
+ const getDragProxy = item?.getDragProxy;
226
+ if (!getDragProxy) {
227
+ // Only render a custom proxy if one is provided - let React DnD handle default case
228
+ return null;
229
+ }
230
+
231
+ let proxyContent = null;
232
+ try {
233
+ proxyContent = getDragProxy(item);
234
+ } catch (error) {
235
+ console.warn('Failed to render custom drag proxy:', error);
236
+ return null; // use default React DnD proxy
237
+ }
238
+
239
+ return <Box
240
+ className="fixed pointer-events-none z-[10000] left-0 top-0"
241
+ style={{
242
+ transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
243
+ }}
244
+ >
245
+ {proxyContent}
246
+ </Box>;
247
+ }
@@ -0,0 +1,10 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ import { Path, Svg } from 'react-native-svg';
3
+
4
+ const SvgComponent = createIcon({
5
+ Root: Svg,
6
+ viewBox: '0 0 53.98 53.98',
7
+ path: <Path d="M14.59 0c1.1 1.66 1.74 3.65 1.74 5.79 0 5.82-4.72 10.54-10.54 10.54-2.14 0-4.13-.64-5.79-1.74 1.89 2.86 5.12 4.75 8.8 4.75 5.82 0 10.54-4.72 10.54-10.54 0-3.68-1.89-6.92-4.75-8.8zM49.23 0c1.1 1.66 1.74 3.65 1.74 5.79 0 5.82-4.72 10.54-10.54 10.54-2.14 0-4.13-.64-5.79-1.74 1.89 2.86 5.12 4.75 8.8 4.75 5.82 0 10.54-4.72 10.54-10.54 0-3.68-1.89-6.92-4.75-8.8zM14.59 34.64c1.1 1.66 1.74 3.65 1.74 5.79 0 5.82-4.72 10.54-10.54 10.54-2.14 0-4.13-.64-5.79-1.74 1.89 2.86 5.12 4.75 8.8 4.75 5.82 0 10.54-4.72 10.54-10.54 0-3.68-1.89-6.92-4.75-8.8zM49.23 34.64c1.1 1.66 1.74 3.65 1.74 5.79 0 5.82-4.72 10.54-10.54 10.54-2.14 0-4.13-.64-5.79-1.74 1.89 2.86 5.12 4.75 8.8 4.75 5.82 0 10.54-4.72 10.54-10.54 0-3.68-1.89-6.92-4.75-8.8z" />,
8
+ });
9
+
10
+ export default SvgComponent
@@ -0,0 +1,11 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ // Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
3
+ import Svg, { Path } from "react-native-svg"
4
+
5
+ const SvgComponent = createIcon({
6
+ Root: Svg,
7
+ viewBox: '0 0 320 512',
8
+ path: <Path d="M0 55.2V426c0 12.2 9.9 22 22 22 6.3 0 12.4-2.7 16.6-7.5l82.6-94.5 58.1 116.3c7.9 15.8 27.1 22.2 42.9 14.3s22.2-27.1 14.3-42.9L179.8 320h118.1c12.2 0 22.1-9.9 22.1-22.1 0-6.3-2.7-12.3-7.4-16.5L38.6 37.9c-4.3-3.8-9.7-5.9-15.4-5.9C10.4 32 0 42.4 0 55.2z" />,
9
+ });
10
+
11
+ export default SvgComponent
@@ -23,6 +23,11 @@ import Panel from '../Panel/Panel.js';
23
23
  import Toolbar from '../Toolbar/Toolbar.js';
24
24
  import _ from 'lodash';
25
25
 
26
+ const
27
+ INITIATE = 'INITIATE',
28
+ PROCESSING = 'PROCESSING',
29
+ RESULTS = 'RESULTS';
30
+
26
31
  // NOTE: This component assumes you have an AppSlice, that has
27
32
  // an 'operationsInProgress' state var and a 'setOperationsInProgress' action.
28
33
 
@@ -37,6 +42,7 @@ function AsyncOperation(props) {
37
42
  Repository,
38
43
  formItems = [],
39
44
  formStartingValues = {},
45
+ _form = {},
40
46
  getProgressUpdates = false,
41
47
  parseProgress, // optional fn, accepts 'response' as arg and returns progress string
42
48
  progressStuckThreshold = null, // e.g. 3, if left blank, doesn't check for stuck state
@@ -49,10 +55,25 @@ function AsyncOperation(props) {
49
55
  alert,
50
56
  } = props,
51
57
  dispatch = useDispatch(),
58
+ isValid = useRef(true),
59
+ setIsValid = (valid) => {
60
+ isValid.current = valid;
61
+ },
62
+ getIsValid = () => {
63
+ return isValid.current;
64
+ },
65
+ mode = useRef(INITIATE),
66
+ setMode = (newMode) => {
67
+ mode.current = newMode;
68
+ },
69
+ getMode = () => {
70
+ return mode.current;
71
+ },
52
72
  initiate = async () => {
53
73
 
54
74
  clearProgress();
55
- setFooter(getFooter('processing'));
75
+ setMode(PROCESSING);
76
+ setFooter(getFooter());
56
77
  setIsInProgress(true);
57
78
 
58
79
  const
@@ -85,17 +106,18 @@ function AsyncOperation(props) {
85
106
  }
86
107
  showResults(results);
87
108
  },
88
- getFooter = (which = 'initiate') => {
109
+ getFooter = (which = getMode()) => {
89
110
  switch(which) {
90
- case 'initiate':
111
+ case INITIATE:
91
112
  return <Toolbar>
92
113
  <Button
93
114
  text="Start"
94
115
  rightIcon={ChevronRight}
95
116
  onPress={() => initiate()}
117
+ isDisabled={!getIsValid()}
96
118
  />
97
119
  </Toolbar>;
98
- case 'processing':
120
+ case PROCESSING:
99
121
  return <Toolbar>
100
122
  <Button
101
123
  text="Please wait"
@@ -103,7 +125,7 @@ function AsyncOperation(props) {
103
125
  variant="link"
104
126
  />
105
127
  </Toolbar>;
106
- case 'results':
128
+ case RESULTS:
107
129
  return <Toolbar>
108
130
  <Button
109
131
  text="Reset"
@@ -152,7 +174,8 @@ function AsyncOperation(props) {
152
174
  },
153
175
  showResults = (results) => {
154
176
  setCurrentTab(1);
155
- setFooter(getFooter('results'));
177
+ setMode(RESULTS);
178
+ setFooter(getFooter());
156
179
  setResults(results);
157
180
  getProgress();
158
181
  },
@@ -206,6 +229,7 @@ function AsyncOperation(props) {
206
229
  },
207
230
  resetToInitialState = () => {
208
231
  setCurrentTab(0);
232
+ setMode(INITIATE);
209
233
  setFooter(getFooter());
210
234
  clearProgress();
211
235
  },
@@ -227,6 +251,10 @@ function AsyncOperation(props) {
227
251
  },
228
252
  });
229
253
  },
254
+ onValidityChange = (isValid) => {
255
+ setIsValid(isValid);
256
+ setFooter(getFooter());
257
+ },
230
258
  unchangedProgressCount = getUnchangedProgressCount();
231
259
 
232
260
  useEffect(() => {
@@ -255,6 +283,8 @@ function AsyncOperation(props) {
255
283
  disableFooter={true}
256
284
  items={formItems}
257
285
  startingValues={formStartingValues}
286
+ onValidityChange={onValidityChange}
287
+ {..._form}
258
288
  />,
259
289
  },
260
290
  {
@@ -3,6 +3,7 @@ import {
3
3
  Box,
4
4
  HStack,
5
5
  Icon,
6
+ Pressable,
6
7
  Text,
7
8
  VStack,
8
9
  VStackNative,
@@ -17,6 +18,7 @@ import {
17
18
  REPORT_TYPES__PDF,
18
19
  } from '../../Constants/ReportTypes.js';
19
20
  import Form from '../Form/Form.js';
21
+ import IconButton from '../Buttons/IconButton.js';
20
22
  import withComponent from '../Hoc/withComponent.js';
21
23
  import withAlert from '../Hoc/withAlert.js';
22
24
  import testProps from '../../Functions/testProps.js';
@@ -37,9 +39,19 @@ function Report(props) {
37
39
  disablePdf = false,
38
40
  disableExcel = false,
39
41
  showReportHeaders = true,
42
+ isQuickReport = false,
43
+ quickReportData = {},
40
44
  alert,
41
45
  } = props,
42
46
  buttons = [],
47
+ onPressQuickReport = () => {
48
+ downloadReport({
49
+ reportId,
50
+ reportType: REPORT_TYPES__EXCEL,
51
+ showReportHeaders,
52
+ data: quickReportData,
53
+ });
54
+ },
43
55
  downloadReport = (args) => {
44
56
  getReport(args);
45
57
  alert('Download started');
@@ -59,6 +71,48 @@ function Report(props) {
59
71
  icon = <Icon as={icon} {...propsIcon} />;
60
72
  }
61
73
 
74
+ if (isQuickReport) {
75
+ let className = `
76
+ Report
77
+ max-w-[100px]
78
+ m-2
79
+ `;
80
+ if (props.className) {
81
+ className += ' ' + props.className;
82
+ }
83
+ return <VStackNative
84
+ {...testProps('QuickReport-' + reportId)}
85
+ className={className}
86
+ >
87
+ <Pressable
88
+ onPress={onPressQuickReport}
89
+ className={`
90
+ flex-1
91
+ items-center
92
+ justify-center
93
+ flex-col
94
+ bg-white
95
+ p-3
96
+ rounded-lg
97
+ border
98
+ border-primary-300
99
+ hover:bg-primary-300
100
+ `}
101
+ >
102
+ {icon}
103
+ <Text
104
+ className={`
105
+ text-black
106
+ text-center
107
+ text-[17px]
108
+ leading-tight
109
+ mt-2
110
+ `}
111
+ >{title}</Text>
112
+ </Pressable>
113
+ </VStackNative>;
114
+ }
115
+
62
116
  if (!disableExcel) {
63
117
  buttons.push({
64
118
  ...testProps('excelBtn'),