@onehat/ui 0.4.120 → 0.4.122

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.120",
3
+ "version": "0.4.122",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,33 @@
1
+ /**
2
+ * COPYRIGHT NOTICE
3
+ * This file is categorized as "Custom Source Code"
4
+ * and is subject to the terms and conditions defined in the
5
+ * "LICENSE.txt" file, which is part of this source code package.
6
+ */
7
+
8
+ import ArrayCombo from './ArrayCombo.js';
9
+ import {
10
+ REPORT_QUEUE_STATUS__ALL,
11
+ REPORT_QUEUE_STATUS__COMPLETED,
12
+ REPORT_QUEUE_STATUS__FAILED,
13
+ REPORT_QUEUE_STATUS__IN_PROCESS,
14
+ REPORT_QUEUE_STATUS__PENDING,
15
+ } from '../../../../Constants/ReportQueueStatuses.js';
16
+
17
+ const data = [
18
+ [REPORT_QUEUE_STATUS__ALL, 'All'],
19
+ [REPORT_QUEUE_STATUS__PENDING, 'Only Pending'],
20
+ [REPORT_QUEUE_STATUS__IN_PROCESS, 'Only In Process'],
21
+ [REPORT_QUEUE_STATUS__FAILED, 'Only Failures'],
22
+ [REPORT_QUEUE_STATUS__COMPLETED, 'Only Completed'],
23
+ ];
24
+
25
+ function ReportQueueStatusesCombo(props) {
26
+ return <ArrayCombo
27
+ data={data}
28
+ disableDirectEntry={true}
29
+ {...props}
30
+ />;
31
+ }
32
+
33
+ export default ReportQueueStatusesCombo;
@@ -0,0 +1,277 @@
1
+
2
+ import { useState, useEffect, } from 'react';
3
+ import {
4
+ Box,
5
+ Text,
6
+ } from '@project-components/Gluestack';
7
+ import clsx from 'clsx';
8
+ import { useSelector, useDispatch } from 'react-redux';
9
+ import oneHatData from '@onehat/data';
10
+ import {
11
+ setIsWaitModalShown,
12
+ } from '../../Models/Slices/SystemSlice';
13
+ import {
14
+ selectUser,
15
+ } from '../../Models/Slices/AuthSlice.js';
16
+ import {
17
+ REPORT_QUEUE_STATUS__ALL,
18
+ } from '../../Constants/ReportQueueStatuses.js';
19
+ import IconButton from '../Buttons/IconButton';
20
+ import withAlert from '../../Components/Hoc/withAlert.js';
21
+ import useForceUpdate from '../../Hooks/useForceUpdate.js';
22
+ import getComponentFromType from '../../Functions/getComponentFromType.js';
23
+ import Rotate from '../Icons/Rotate.js';
24
+ import X from '../Icons/X.js';
25
+
26
+ function ReportsQueue(props) {
27
+ const {
28
+ // withAlert
29
+ alert,
30
+ showInfo,
31
+ } = props,
32
+ dispatch = useDispatch(),
33
+ forceUpdate = useForceUpdate(),
34
+ user = useSelector(selectUser),
35
+ UtilQueuedReports = oneHatData.getRepository('UtilQueuedReports'),
36
+ onRequeueFailedJob = async (entity) => {
37
+ dispatch(setIsWaitModalShown(true));
38
+
39
+ try {
40
+ const result = await UtilQueuedReports._send('POST', 'UtilQueuedReports/requeueFailedJob', {
41
+ util_queued_report_id: entity.id,
42
+ });
43
+ const response = UtilQueuedReports._processServerResponse(result);
44
+ if (response.success) {
45
+
46
+ await entity.reload();
47
+
48
+ forceUpdate();
49
+
50
+ showInfo('Job requeued successfully.');
51
+ }
52
+
53
+ } catch (error) {
54
+ alert('An error occurred while requeuing the job. Please try again.');
55
+ } finally {
56
+ dispatch(setIsWaitModalShown(false));
57
+ }
58
+ },
59
+ onCancel = async (entity) => {
60
+ dispatch(setIsWaitModalShown(true));
61
+
62
+ try {
63
+ const result = await UtilQueuedReports._send('POST', 'UtilQueuedReports/cancelPendingJob', {
64
+ util_queued_report_id: entity.id,
65
+ });
66
+ const response = UtilQueuedReports._processServerResponse(result);
67
+ if (response.success) {
68
+
69
+ await entity.reload();
70
+
71
+ forceUpdate();
72
+
73
+ showInfo('Job cancelled successfully.');
74
+ }
75
+
76
+ } catch (error) {
77
+ alert('An error occurred while cancelling the job. Please try again.');
78
+ } finally {
79
+ dispatch(setIsWaitModalShown(false));
80
+ }
81
+ };
82
+
83
+ useEffect(() => {
84
+
85
+ setTimeout(() => {
86
+ UtilQueuedReports.reload();
87
+ }, 60 * 1000);
88
+
89
+ }, [UtilQueuedReports]);
90
+
91
+ const UtilQueuedReportsFilteredGridEditor = getComponentFromType('UtilQueuedReportsFilteredGridEditor');
92
+
93
+ return <UtilQueuedReportsFilteredGridEditor
94
+ reference="ReportsQueue"
95
+ usePermissions={false}
96
+ Repository={UtilQueuedReports}
97
+
98
+ title="Reports Queue"
99
+ className="w-full h-full"
100
+ searchAllText={false}
101
+ showClearFiltersButton={false}
102
+ customFilters={[
103
+ {
104
+ id: 'status',
105
+ title: 'Status',
106
+ tooltip: 'Select which status to display in the queue.',
107
+ field: 'status',
108
+ type: 'ReportQueueStatusesCombo',
109
+ value: REPORT_QUEUE_STATUS__ALL,
110
+ getRepoFilters: (value) => {
111
+ return [
112
+ {
113
+ name: 'status',
114
+ value,
115
+ },
116
+ ];
117
+ },
118
+ },
119
+ {
120
+ id: 'showAllUsers',
121
+ title: 'All Users?',
122
+ tooltip: 'Should we include queued reports from ALL users, so you can see the overall queue status?',
123
+ field: 'showAllUsers',
124
+ type: 'Toggle',
125
+ value: false,
126
+ getRepoFilters: (value) => {
127
+ return [
128
+ {
129
+ name: 'showAllUsers',
130
+ value,
131
+ },
132
+ ];
133
+ },
134
+ },
135
+ ]}
136
+ columnsConfig={[
137
+ {
138
+ id: 'action',
139
+ header: 'Action',
140
+ w: 70,
141
+ isSortable: false,
142
+ isEditable: false,
143
+ isReorderable: false,
144
+ isResizable: false,
145
+ isHidable: false,
146
+ renderer: (entity, fieldName, cellProps, key) => {
147
+ const
148
+ isUser = entity.util_queued_reports__user_id === user?.id,
149
+ className = clsx(
150
+ cellProps.className,
151
+ 'flex',
152
+ 'items-center',
153
+ 'justify-center',
154
+ );
155
+ let action,
156
+ icon,
157
+ tooltip;
158
+ if (entity.util_queued_reports__is_in_process && isUser) {
159
+ action = onCancel;
160
+ icon = X;
161
+ tooltip = 'Cancel this report';
162
+ } else if (entity.util_queued_reports__success === false && isUser) {
163
+ action = onRequeueFailedJob;
164
+ icon = Rotate;
165
+ tooltip = 'Requeue this failed report';
166
+ } else {
167
+ // no available action
168
+ return <Box {...cellProps} className={className} />;
169
+ }
170
+ return <IconButton
171
+ key={key}
172
+ {...cellProps}
173
+ className={className}
174
+ icon={icon}
175
+ _icon={{
176
+ size: 'xl',
177
+ }}
178
+ onPress={() => action(entity)}
179
+ tooltip={tooltip}
180
+ />;
181
+ },
182
+ },
183
+ {
184
+ "id": "util_queued_reports__report_title",
185
+ "header": "Report", // MOD
186
+ "fieldName": "util_queued_reports__report_title",
187
+ "isSortable": false,
188
+ "isEditable": false,
189
+ "isReorderable": true,
190
+ "isResizable": true,
191
+ "w": 250 // MOD
192
+ },
193
+ {
194
+ "id": "util_queued_reports__report_preset_name",
195
+ "header": "Preset", // MOD
196
+ "fieldName": "util_queued_reports__report_preset_name",
197
+ "isSortable": false,
198
+ "isEditable": false,
199
+ "isReorderable": true,
200
+ "isResizable": true,
201
+ "w": 150
202
+ },
203
+ {
204
+ "id": "util_queued_reports__submitted",
205
+ "header": "Submitted",
206
+ "fieldName": "util_queued_reports__submitted",
207
+ "isSortable": true,
208
+ "isEditable": true,
209
+ "isReorderable": true,
210
+ "isResizable": true,
211
+ "w": 200
212
+ },
213
+ {
214
+ "id": "util_queued_reports__is_in_process",
215
+ "header": "In Process?",
216
+ "fieldName": "util_queued_reports__is_in_process",
217
+ "isSortable": true,
218
+ "isEditable": true,
219
+ "isReorderable": true,
220
+ "isResizable": true,
221
+ "w": 100
222
+ },
223
+ {
224
+ "id": "util_queued_reports__success",
225
+ "header": "Success",
226
+ "fieldName": "util_queued_reports__success",
227
+ "isSortable": true,
228
+ "isEditable": true,
229
+ "isReorderable": true,
230
+ "isResizable": true,
231
+ "w": 100
232
+ },
233
+ {
234
+ "id": "util_queued_reports__run_time",
235
+ "header": "Run Time",
236
+ "fieldName": "util_queued_reports__run_time",
237
+ "isSortable": true,
238
+ "isEditable": true,
239
+ "isReorderable": true,
240
+ "isResizable": true,
241
+ "w": 100
242
+ },
243
+ {
244
+ "id": "users__username",
245
+ "header": "User",
246
+ "fieldName": "users__username",
247
+ "isSortable": false,
248
+ "isEditable": false,
249
+ "isReorderable": false,
250
+ "isResizable": false,
251
+ "w": 100
252
+ },
253
+ ]}
254
+ getRowProps={(item) => {
255
+ const rowProps = {
256
+ borderBottomWidth: 1,
257
+ borderBottomColor: 'trueGray.500',
258
+ };
259
+ if (item.util_queued_reports__is_in_process) {
260
+ rowProps.bg = '#f9eabb';
261
+ } else if (item.util_queued_reports__success === false) {
262
+ rowProps.bg = '#ffd1d1';
263
+ }
264
+ return rowProps;
265
+ }}
266
+ disableAdd={true}
267
+ disableEdit={true}
268
+ disableDelete={true}
269
+ disableDuplicate={true}
270
+ disableView={true}
271
+ disableCopy={true}
272
+
273
+ {...props}
274
+ />;
275
+ }
276
+
277
+ export default withAlert(ReportsQueue);
@@ -63,6 +63,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
63
63
  initialEditorMode = EDITOR_MODE__VIEW,
64
64
  stayInEditModeOnSelectionChange = false,
65
65
  inheritParentEditorMode = true,
66
+ ignoreGlobalStayInEditModeOnSelectionChange = false,
66
67
 
67
68
  // withComponent
68
69
  self,
@@ -863,8 +864,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
863
864
 
864
865
  let isIgnoreNextSelectionChange = getIsIgnoreNextSelectionChange(),
865
866
  doStayInEditModeOnSelectionChange = stayInEditModeOnSelectionChange;
866
- if (!_.isNil(UiGlobals.stayInEditModeOnSelectionChange)) {
867
- // allow global override to for this property
867
+ if (!_.isNil(UiGlobals.stayInEditModeOnSelectionChange) && !ignoreGlobalStayInEditModeOnSelectionChange) {
868
+ // allow global override for this property
868
869
  doStayInEditModeOnSelectionChange = UiGlobals.stayInEditModeOnSelectionChange;
869
870
  }
870
871
  if (doStayInEditModeOnSelectionChange) {
@@ -0,0 +1,12 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ import Svg, { Path } from "react-native-svg"
3
+
4
+ const SvgComponent = createIcon({
5
+ Root: Svg,
6
+ viewBox: '420 310 730 940',
7
+ path: <Path
8
+ d="M972.547 1227.359c-38.672 12.891-84.961 19.336-138.867 19.336-112.5 0-205.469-33.593-278.907-100.781-89.062-80.859-133.593-199.609-133.593-356.25 0-157.812 45.703-277.148 137.109-358.008 74.609-66.015 167.383-99.023 278.32-99.023 111.719 0 205.469 34.961 281.25 104.883 87.5 80.859 131.25 193.945 131.25 339.257 0 76.954-9.375 141.407-28.125 193.36-15.234 49.609-37.695 90.82-67.382 123.633l99.609 93.164-94.336 98.437-104.297-98.437c-31.64 19.14-58.984 32.617-82.031 40.429zM933.875 1071.5l-87.305-83.203 93.164-97.266 87.305 83.203c13.672-28.125 23.242-52.734 28.711-73.828 8.594-31.64 12.891-68.554 12.891-110.742 0-96.875-19.825-171.777-59.473-224.707-39.648-52.93-97.559-79.395-173.73-79.395-71.485 0-128.516 25.391-171.094 76.172-42.578 50.782-63.867 126.758-63.867 227.93 0 118.359 30.468 203.125 91.406 254.297 39.453 33.203 86.719 49.805 141.797 49.805 20.703 0 40.625-2.539 59.765-7.618 10.547-2.734 24.024-7.617 40.43-14.648z"
9
+ />,
10
+ });
11
+
12
+ export default SvgComponent
@@ -120,6 +120,9 @@ function Report(props) {
120
120
  }
121
121
  },
122
122
  manageReportSchedules = async (formData) => {
123
+ let defaultValues = {
124
+ report_schedules__additional_data: additionalData,
125
+ };
123
126
  if (hasFormItems) {
124
127
  // check to make sure there is at least one ReportPreset for this report, since the schedule needs to be based on a preset (which captures the form config)
125
128
  // If not, show alert saying create preset first
@@ -137,6 +140,19 @@ function Report(props) {
137
140
  alert('Please create at least one report preset first, since schedules are based on presets.');
138
141
  return;
139
142
  }
143
+ } else {
144
+ // Form is empty, but the ReportSchedule needs a report_preset_id to work.
145
+ // Get or create one.
146
+ const ReportPresets = oneHatData.getRepository('ReportPresets');
147
+ const result = await ReportPresets._send('POST', 'ReportPresets/getOrCreate', {
148
+ reportId,
149
+ });
150
+ const response = ReportPresets._processServerResponse(result);
151
+ if (!response.success) {
152
+ showInfo('Failed to get or create report preset: ' + (response.message || 'Unknown error'));
153
+ return;
154
+ }
155
+ defaultValues.report_schedules__report_preset_id = response.root.id;
140
156
  }
141
157
  const ReportSchedulesGridEditor = getComponentFromType('ReportSchedulesGridEditor');
142
158
  showModal({
@@ -149,9 +165,7 @@ function Report(props) {
149
165
  hasFormItems,
150
166
  reportId,
151
167
  }}
152
- defaultValues={{
153
- report_schedules__additional_data: additionalData,
154
- }}
168
+ defaultValues={defaultValues}
155
169
  />,
156
170
  canClose: true,
157
171
  whichModal: 'schedulesModal',
@@ -7,7 +7,10 @@ import {
7
7
  } from '@project-components/Gluestack';
8
8
  import clsx from 'clsx';
9
9
  import ChartPie from '../Icons/ChartPie.js';
10
+ import Q from '../Icons/Q.js';
11
+ import getComponentFromType from '../../Functions/getComponentFromType.js';
10
12
  import ScreenHeader from '../Layout/ScreenHeader.js';
13
+ import ReportsQueue from '../Grid/ReportsQueue.js';
11
14
  import TabBar from '../Tab/TabBar.js';
12
15
  import _ from 'lodash';
13
16
 
@@ -18,6 +21,7 @@ export default function ReportsManager(props) {
18
21
  reports = [],
19
22
  reportTabs,
20
23
  initialReportTabIx = 0,
24
+ showQueueTab = false,
21
25
  id,
22
26
  self,
23
27
  isActive = false,
@@ -65,6 +69,14 @@ export default function ReportsManager(props) {
65
69
  </VStackNative>
66
70
  </ScrollView>,
67
71
  })) : [];
72
+
73
+ if (showQueueTab) {
74
+ tabBarTabs.push({
75
+ title: 'Queue',
76
+ icon: Q,
77
+ content: <ReportsQueue />,
78
+ });
79
+ }
68
80
 
69
81
  return <VStack
70
82
  className="overflow-hidden flex-1 w-full"
@@ -284,6 +284,7 @@ import PmCalcDebugViewer from './Viewer/PmCalcDebugViewer.js';
284
284
  import PmStatusesViewer from './Viewer/PmStatusesViewer.js';
285
285
  import RadioGroup from './Form/Field/RadioGroup/RadioGroup.js';
286
286
  import ReportPresetsComboEditor from './Form/Field/Combo/ReportPresetsComboEditor.js';
287
+ import ReportQueueStatusesCombo from './Form/Field/Combo/ReportQueueStatusesCombo.js';
287
288
  // 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.
288
289
  import SquareButton from './Buttons/SquareButton.js';
289
290
  import TabPanel from './Panel/TabPanel.js';
@@ -585,6 +586,7 @@ const components = {
585
586
  PmStatusesViewer,
586
587
  RadioGroup,
587
588
  ReportPresetsComboEditor,
589
+ ReportQueueStatusesCombo,
588
590
  // Slider,
589
591
  SquareButton,
590
592
  TabPanel,
@@ -0,0 +1,5 @@
1
+ export const REPORT_QUEUE_STATUS__ALL = 'ALL';
2
+ export const REPORT_QUEUE_STATUS__PENDING = 'PENDING';
3
+ export const REPORT_QUEUE_STATUS__IN_PROCESS = 'IN_PROCESS';
4
+ export const REPORT_QUEUE_STATUS__COMPLETED = 'COMPLETED';
5
+ export const REPORT_QUEUE_STATUS__FAILED = 'FAILED';
@@ -770,7 +770,7 @@ function AttachmentsElement(props) {
770
770
  wasAlreadyLoaded = AttachmentDirectories.isLoaded,
771
771
  currentConditions = AttachmentDirectories.getParamConditions() || {},
772
772
  newConditions = {
773
- 'conditions[AttachmentDirectories.model]': selectorSelected.schema.name,
773
+ 'conditions[AttachmentDirectories.model]': selectorSelected.schema?.name,
774
774
  'conditions[AttachmentDirectories.modelid]': selectorSelected[selectorSelectedField],
775
775
  };
776
776
  let doReload = false;
@@ -1135,7 +1135,7 @@ function AttachmentsElement(props) {
1135
1135
  maxFileSize={styles.ATTACHMENTS_MAX_FILESIZE}
1136
1136
  autoClean={true}
1137
1137
  uploadConfig={{
1138
- url: Attachments.api.baseURL + Attachments.schema.name + '/uploadAttachment',
1138
+ url: Attachments.api.baseURL + Attachments.schema?.name + '/uploadAttachment',
1139
1139
  method: 'POST',
1140
1140
  headers: Attachments.headers,
1141
1141
  autoUpload,