@evoke-platform/ui-components 1.13.0-dev.6 → 1.13.0-dev.7

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 (26) hide show
  1. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
  2. package/dist/published/components/custom/FormV2/FormRenderer.js +25 -27
  3. package/dist/published/components/custom/FormV2/FormRendererContainer.js +70 -66
  4. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
  5. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
  6. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
  7. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
  8. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
  9. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +104 -184
  10. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +19 -36
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -20
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +95 -169
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +12 -6
  17. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
  18. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +38 -16
  19. package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
  20. package/dist/published/components/custom/FormV2/components/utils.js +25 -25
  21. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +48 -15
  22. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +38 -46
  23. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
  24. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +37 -12
  25. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
  26. package/package.json +3 -2
@@ -1,7 +1,8 @@
1
1
  import { useApiServices, useNotification, } from '@evoke-platform/context';
2
- import { get, isEqual, omit, startCase } from 'lodash';
2
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
3
+ import { get, omit, startCase } from 'lodash';
3
4
  import { DateTime } from 'luxon';
4
- import React, { useCallback, useEffect, useState } from 'react';
5
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
5
6
  import sift from 'sift';
6
7
  import { Edit, ExpandMoreOutlined, TrashCan } from '../../../../../../icons';
7
8
  import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
@@ -9,7 +10,7 @@ import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Skel
9
10
  import { Box } from '../../../../../layout';
10
11
  import { getReadableQuery } from '../../../../CriteriaBuilder';
11
12
  import { retrieveCustomErrorMessage } from '../../../../Form/utils';
12
- import { convertPropertiesToParams, deleteDocuments, formatSubmission, getPrefixedUrl, transformToWhere, } from '../../utils';
13
+ import { convertPropertiesToParams, deleteDocuments, formatSubmission, getPrefixedUrl, transformToWhere, useFormById, } from '../../utils';
13
14
  import { ActionDialog } from './ActionDialog';
14
15
  import { DocumentViewerCell } from './DocumentViewerCell';
15
16
  const styles = {
@@ -35,7 +36,7 @@ const styles = {
35
36
  };
36
37
  const RepeatableField = (props) => {
37
38
  const { fieldDefinition, canUpdateProperty, criteria, viewLayout, entry } = props;
38
- const { fetchedOptions, setFetchedOptions, instance, width } = useFormContext();
39
+ const { instance, width } = useFormContext();
39
40
  const { isBelow } = useWidgetSize({
40
41
  scroll: false,
41
42
  defaultWidth: width,
@@ -43,106 +44,71 @@ const RepeatableField = (props) => {
43
44
  const smallerThanMd = isBelow('md');
44
45
  const { instanceChanges } = useNotification();
45
46
  const apiServices = useApiServices();
46
- const [reloadOnErrorTrigger, setReloadOnErrorTrigger] = useState(true);
47
47
  const [criteriaObjects, setCriteriaObjects] = useState([]);
48
48
  const [selectedInstanceId, setSelectedInstanceId] = useState();
49
49
  const [dialogType, setDialogType] = useState();
50
50
  const [openDialog, setOpenDialog] = useState(false);
51
- const [users, setUsers] = useState(fetchedOptions[`${fieldDefinition.id}Users`] || []);
52
- const [error, setError] = useState(false);
53
- const [relatedInstances, setRelatedInstances] = useState(fetchedOptions[`${fieldDefinition.id}Options`] || []);
54
- const [relatedObject, setRelatedObject] = useState(fetchedOptions[`${fieldDefinition.id}RelatedObject`]);
55
- const [hasCreateAction, setHasCreateAction] = useState(fetchedOptions[`${fieldDefinition.id}HasCreateAction`] || false);
56
- const [loading, setLoading] = useState((relatedObject && relatedInstances) || !fieldDefinition ? false : true);
57
- const [tableViewLayout, setTableViewLayout] = useState(fetchedOptions[`${fieldDefinition.id}TableViewLayout`]);
58
- const [createForm, setCreateForm] = useState(fetchedOptions[`${fieldDefinition.id}-createForm`]);
59
- const [updateForm, setUpdateForm] = useState(fetchedOptions[`${fieldDefinition.id}-updateForm`]);
60
- const [deleteForm, setDeleteForm] = useState(fetchedOptions[`${fieldDefinition.id}-deleteForm`]);
61
51
  const [snackbarError, setSnackbarError] = useState({
62
52
  showAlert: false,
63
53
  isError: false,
64
54
  });
55
+ const queryClient = useQueryClient();
56
+ const { data: relatedObject, isLoading: loadingRelatedObject } = useQuery({
57
+ queryKey: [fieldDefinition?.objectId, 'effective'],
58
+ queryFn: () => apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective`)),
59
+ staleTime: Infinity,
60
+ enabled: !!fieldDefinition.objectId,
61
+ });
62
+ const { data: tableViewLayout, isFetching: isLayoutFetching } = useQuery({
63
+ queryKey: ['tableViewLayout', viewLayout?.id],
64
+ enabled: !!viewLayout?.id && !!relatedObject,
65
+ staleTime: Infinity,
66
+ placeholderData: () => {
67
+ return relatedObject?.viewLayout?.table
68
+ ? {
69
+ id: 'default',
70
+ name: 'Default',
71
+ objectId: relatedObject.id,
72
+ ...relatedObject.viewLayout.table,
73
+ }
74
+ : undefined;
75
+ },
76
+ queryFn: () => apiServices.get(getPrefixedUrl(`/objects/${viewLayout.objectId}/tableLayouts/${viewLayout.id}`)),
77
+ });
78
+ const transformedCriteria = useMemo(() => (criteria ? transformToWhere(criteria) : {}), [criteria]);
79
+ const relatedInstancesQueryKey = [
80
+ instance?.id,
81
+ 'object',
82
+ fieldDefinition?.objectId,
83
+ 'instances',
84
+ fieldDefinition?.relatedPropertyId,
85
+ transformedCriteria,
86
+ ];
87
+ const { data: relatedInstances = [], refetch: refetchRelatedInstances, isLoading: loadingRelatedInstances, isError: relatedInstancesError, } = useQuery({
88
+ queryKey: relatedInstancesQueryKey,
89
+ queryFn: async () => {
90
+ const filterProperty = `${fieldDefinition.relatedPropertyId}.id`;
91
+ const filter = {
92
+ where: { [filterProperty]: instance?.id, ...transformedCriteria },
93
+ limit: 100,
94
+ };
95
+ const instances = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
96
+ params: { filter: JSON.stringify(filter) },
97
+ });
98
+ return instances;
99
+ },
100
+ enabled: !!fieldDefinition.relatedPropertyId && !!fieldDefinition.objectId && !!instance?.id,
101
+ staleTime: Infinity,
102
+ });
65
103
  const createAction = relatedObject?.actions?.find((item) => item.id === entry.display?.createActionId);
66
104
  const updateAction = relatedObject?.actions?.find((item) => item.id === entry.display?.updateActionId);
67
105
  const deleteAction = relatedObject?.actions?.find((item) => item.id === entry.display?.deleteActionId);
68
- function getForm(setForm, action, formId) {
69
- if (formId === '_auto_')
70
- return;
71
- if (formId || action?.defaultFormId) {
72
- apiServices
73
- .get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
74
- .then((evokeForm) => {
75
- setForm(evokeForm);
76
- setFetchedOptions({
77
- [`${fieldDefinition.id}${action?.type === 'delete' ? '-deleteForm' : action?.type === 'create' ? '-createForm' : '-updateForm'}`]: evokeForm,
78
- });
79
- })
80
- .catch((error) => {
81
- console.error(error);
82
- });
83
- }
84
- }
85
- const fetchRelatedInstances = useCallback(async (refetch = false) => {
86
- let relatedObject;
87
- if (fieldDefinition.objectId) {
88
- if (!fetchedOptions[`${fieldDefinition.id}RelatedObject`]) {
89
- try {
90
- relatedObject = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective`));
91
- let defaultTableViewLayout;
92
- if (relatedObject.viewLayout?.table) {
93
- defaultTableViewLayout = {
94
- id: 'default',
95
- name: 'Default',
96
- objectId: relatedObject.id,
97
- ...relatedObject?.viewLayout.table,
98
- };
99
- }
100
- if (viewLayout) {
101
- apiServices
102
- .get(getPrefixedUrl(`/objects/${viewLayout.objectId}/tableLayouts/${viewLayout.id}`))
103
- .then(setTableViewLayout)
104
- .catch((err) => setTableViewLayout(defaultTableViewLayout));
105
- }
106
- else {
107
- setTableViewLayout(defaultTableViewLayout);
108
- }
109
- setRelatedObject(relatedObject);
110
- }
111
- catch (err) {
112
- console.error(err);
113
- }
114
- }
115
- if (fieldDefinition.relatedPropertyId &&
116
- fieldDefinition.objectId &&
117
- instance?.id &&
118
- (!fetchedOptions[`${fieldDefinition.id}Options`] || refetch)) {
119
- const filterProperty = `${fieldDefinition.relatedPropertyId}.id`;
120
- const transformedCriteria = criteria ? transformToWhere(criteria) : {};
121
- const filter = {
122
- where: { [filterProperty]: instance?.id, ...transformedCriteria },
123
- limit: 100,
124
- };
125
- try {
126
- const timeout = setTimeout(() => {
127
- setLoading(false);
128
- }, 300);
129
- setLoading(true);
130
- const instances = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
131
- params: { filter: JSON.stringify(filter) },
132
- });
133
- clearTimeout(timeout);
134
- if (instances) {
135
- setRelatedInstances(instances);
136
- }
137
- }
138
- catch (error) {
139
- setError(true);
140
- }
141
- }
142
- setLoading(false);
143
- }
144
- relatedObject && checkCreateAccess(relatedObject);
145
- }, [fieldDefinition]);
106
+ const createFormId = entry.display?.createFormId || createAction?.defaultFormId;
107
+ const updateFormId = entry.display?.updateFormId || updateAction?.defaultFormId;
108
+ const deleteFormId = entry.display?.deleteFormId || deleteAction?.defaultFormId;
109
+ const { data: createForm } = useFormById(createFormId ?? '', apiServices);
110
+ const { data: updateForm } = useFormById(updateFormId ?? '', apiServices);
111
+ const { data: deleteForm } = useFormById(deleteFormId ?? '', apiServices);
146
112
  const fetchCriteriaObjects = useCallback(async () => {
147
113
  let objectIds = [];
148
114
  const criteriaProperties = relatedObject?.properties?.filter((property) => property.type === 'criteria' && property.objectId) ?? [];
@@ -168,41 +134,21 @@ const RepeatableField = (props) => {
168
134
  }
169
135
  setCriteriaObjects(objects);
170
136
  }, [apiServices, relatedObject, tableViewLayout]);
171
- useEffect(() => {
172
- if (!fetchedOptions[`${fieldDefinition.id}Users`]) {
173
- (async () => {
174
- try {
175
- const users = await apiServices.get(getPrefixedUrl(`/users`));
176
- setFetchedOptions({
177
- [`${fieldDefinition.id}Users`]: users,
178
- });
179
- setUsers(users);
180
- }
181
- catch (error) {
182
- console.error(error);
183
- }
184
- })();
185
- }
186
- }, [apiServices]);
187
- useEffect(() => {
188
- fetchRelatedInstances();
189
- }, [fetchRelatedInstances, reloadOnErrorTrigger, instance]);
137
+ const { data: users } = useQuery({
138
+ queryKey: ['users'],
139
+ queryFn: () => apiServices.get(getPrefixedUrl(`/users`)),
140
+ staleTime: Infinity,
141
+ meta: {
142
+ errorMessage: 'Error fetching users: ',
143
+ },
144
+ });
190
145
  useEffect(() => {
191
146
  if (relatedObject)
192
147
  fetchCriteriaObjects();
193
148
  }, [fetchCriteriaObjects, relatedObject]);
194
- useEffect(() => {
195
- if (createAction && !createForm)
196
- getForm(setCreateForm, createAction, entry.display?.createFormId);
197
- if (updateAction && !updateForm)
198
- getForm(setUpdateForm, updateAction, entry.display?.updateFormId);
199
- if (deleteAction && !deleteForm)
200
- getForm(setDeleteForm, deleteAction, entry.display?.deleteFormId);
201
- }, [entry.display, createAction, updateAction, deleteAction]);
202
149
  useEffect(() => {
203
150
  if (relatedObject?.rootObjectId) {
204
- // pass true here so while it doesn't refetch on every tab change it does refetch on changes made
205
- const callback = () => fetchRelatedInstances(true);
151
+ const callback = () => refetchRelatedInstances();
206
152
  instanceChanges?.subscribe(relatedObject?.rootObjectId, callback);
207
153
  return () => instanceChanges?.unsubscribe(relatedObject?.rootObjectId, callback);
208
154
  }
@@ -249,59 +195,30 @@ const RepeatableField = (props) => {
249
195
  };
250
196
  }
251
197
  };
252
- const checkCreateAccess = useCallback((relatedObject) => {
253
- if (fieldDefinition.objectId &&
254
- canUpdateProperty &&
255
- !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
256
- apiServices
257
- .get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/checkAccess`), {
198
+ const { data: hasCreatePermission } = useQuery({
199
+ queryKey: [fieldDefinition.objectId, entry.display?.createActionId, 'hasCreatePermission '],
200
+ queryFn: async () => {
201
+ const checkAccess = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/checkAccess`), {
258
202
  params: { action: 'execute', field: entry.display?.createActionId, scope: 'data' },
259
- })
260
- .then((checkAccess) => {
261
- const action = relatedObject.actions?.find((item) => item.id === entry.display?.createActionId);
262
- if (action && fieldDefinition.relatedPropertyId) {
263
- const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
264
- if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
265
- setHasCreateAction(checkAccess.result);
266
- }
267
- else {
268
- const validate = sift(criteria);
269
- setHasCreateAction(validate(instance) && checkAccess.result);
270
- }
203
+ });
204
+ const action = relatedObject.actions?.find((item) => item.id === entry.display?.createActionId);
205
+ if (action && fieldDefinition.relatedPropertyId) {
206
+ const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
207
+ if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
208
+ return checkAccess.result;
271
209
  }
272
210
  else {
273
- setHasCreateAction(false);
211
+ const validate = sift(criteria);
212
+ return validate(instance) && checkAccess.result;
274
213
  }
275
- });
276
- }
277
- }, [fieldDefinition, canUpdateProperty, fetchedOptions, entry.display?.createActionId, instance, apiServices]);
278
- useEffect(() => {
279
- // Re-check create access when instance changes to re-evaluate the criteria
280
- relatedObject && checkCreateAccess(relatedObject);
281
- }, [relatedObject, checkCreateAccess]);
282
- useEffect(() => {
283
- const updatedOptions = {};
284
- if ((relatedInstances && !fetchedOptions[`${fieldDefinition.id}Options`]) ||
285
- fetchedOptions[`${fieldDefinition.id}Options`].length === 0 ||
286
- !isEqual(relatedInstances, fetchedOptions[`${fieldDefinition.id}Options`])) {
287
- updatedOptions[`${fieldDefinition.id}Options`] = relatedInstances;
288
- }
289
- if (relatedObject && !fetchedOptions[`${fieldDefinition.id}RelatedObject`]) {
290
- updatedOptions[`${fieldDefinition.id}RelatedObject`] = relatedObject;
291
- }
292
- if (tableViewLayout && !fetchedOptions[`${fieldDefinition.id}TableViewLayout`]) {
293
- updatedOptions[`${fieldDefinition.id}TableViewLayout`] = tableViewLayout;
294
- }
295
- if ((hasCreateAction || hasCreateAction === false) && !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
296
- updatedOptions[`${fieldDefinition.id}HasCreateAction`] = hasCreateAction;
297
- }
298
- else if (!hasCreateAction && relatedObject) {
299
- checkCreateAccess(relatedObject);
300
- }
301
- if (Object.keys(updatedOptions).length > 0) {
302
- setFetchedOptions(updatedOptions);
303
- }
304
- }, [relatedObject, relatedInstances, hasCreateAction, tableViewLayout]);
214
+ }
215
+ else {
216
+ return false;
217
+ }
218
+ },
219
+ staleTime: Infinity,
220
+ enabled: !!fieldDefinition.objectId && canUpdateProperty && !!relatedObject,
221
+ });
305
222
  const deleteRow = (id) => {
306
223
  setDialogType('delete');
307
224
  setSelectedInstanceId(id);
@@ -317,7 +234,7 @@ const RepeatableField = (props) => {
317
234
  setSelectedInstanceId(id);
318
235
  setOpenDialog(true);
319
236
  };
320
- const ErrorComponent = () => loading ? (React.createElement("div", null,
237
+ const ErrorComponent = () => loadingRelatedObject || loadingRelatedInstances ? (React.createElement("div", null,
321
238
  React.createElement(Typography, { sx: {
322
239
  fontSize: '14px',
323
240
  color: '#727c84',
@@ -333,7 +250,7 @@ const RepeatableField = (props) => {
333
250
  backgroundColor: 'transparent',
334
251
  },
335
252
  'min-width': '44px',
336
- }, variant: "text", onClick: () => setReloadOnErrorTrigger((prevState) => !prevState) }, "Retry")));
253
+ }, variant: "text", onClick: () => refetchRelatedInstances() }, "Retry")));
337
254
  const save = async (input) => {
338
255
  const action = relatedObject?.actions?.find((a) => a.id ===
339
256
  (dialogType === 'create'
@@ -355,8 +272,11 @@ const RepeatableField = (props) => {
355
272
  actionId: entry.display?.createActionId,
356
273
  input: updatedInput,
357
274
  });
358
- const hasAccess = fieldDefinition?.relatedPropertyId && fieldDefinition.relatedPropertyId in instance;
359
- hasAccess && setRelatedInstances([...relatedInstances, instance]);
275
+ queryClient.setQueryData(relatedInstancesQueryKey, (oldData) => {
276
+ if (!oldData)
277
+ return [instance];
278
+ return [...oldData, instance];
279
+ });
360
280
  setOpenDialog(false);
361
281
  setDialogType(undefined);
362
282
  setSelectedInstanceId(undefined);
@@ -382,12 +302,12 @@ const RepeatableField = (props) => {
382
302
  if (response && relatedObject && instance) {
383
303
  deleteDocuments(input, !!response, apiServices, relatedObject, instance, action);
384
304
  }
385
- if (action?.type === 'delete') {
386
- setRelatedInstances((prevInstances) => prevInstances.filter((instance) => instance.id !== selectedInstanceId));
387
- }
388
- else {
389
- setRelatedInstances((prevInstances) => prevInstances.map((i) => (i.id === instance?.id ? instance : i)));
390
- }
305
+ queryClient.setQueryData(relatedInstancesQueryKey, (oldData = []) => {
306
+ if (action?.type === 'delete') {
307
+ return oldData.filter((i) => i.id !== selectedInstanceId);
308
+ }
309
+ return oldData.map((i) => (i.id === instance?.id ? instance : i));
310
+ });
391
311
  setOpenDialog(false);
392
312
  setDialogType(undefined);
393
313
  setSelectedInstanceId(undefined);
@@ -461,12 +381,12 @@ const RepeatableField = (props) => {
461
381
  }
462
382
  return value;
463
383
  };
464
- return loading ? (React.createElement(React.Fragment, null,
384
+ return loadingRelatedObject || loadingRelatedInstances || isLayoutFetching ? (React.createElement(React.Fragment, null,
465
385
  React.createElement(Skeleton, null),
466
386
  React.createElement(Skeleton, null),
467
387
  React.createElement(Skeleton, null))) : (React.createElement(React.Fragment, null,
468
388
  React.createElement(Box, { sx: { padding: '10px 0' } },
469
- !relatedInstances?.length ? (!error ? (React.createElement(Typography, { sx: { margin: '-10px 0', color: 'rgb(114 124 132)', fontSize: '14px' } }, "No items added")) : (React.createElement(ErrorComponent, null))) : smallerThanMd ? (React.createElement(React.Fragment, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(Accordion, { key: relatedInstance.id, sx: {
389
+ !relatedInstances?.length ? (!relatedInstancesError ? (React.createElement(Typography, { sx: { margin: '-10px 0', color: 'rgb(114 124 132)', fontSize: '14px' } }, "No items added")) : (React.createElement(ErrorComponent, null))) : smallerThanMd ? (React.createElement(React.Fragment, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(Accordion, { key: relatedInstance.id, sx: {
470
390
  border: '1px solid #dbe0e4',
471
391
  borderTop: index === 0 ? undefined : 'none',
472
392
  boxShadow: 'none',
@@ -556,7 +476,7 @@ const RepeatableField = (props) => {
556
476
  entry.display?.deleteActionId && (React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
557
477
  React.createElement(Tooltip, { title: "Delete" },
558
478
  React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))))),
559
- hasCreateAction && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, disabled: !createAction, onClick: addRow, "aria-label": 'Add' }, "Add"))),
479
+ hasCreatePermission && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, disabled: !createAction, onClick: addRow, "aria-label": 'Add' }, "Add"))),
560
480
  relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), onSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
561
481
  (dialogType === 'create'
562
482
  ? entry.display?.createActionId
@@ -1,62 +1,46 @@
1
1
  import { useApiServices } from '@evoke-platform/context';
2
- import React, { useCallback, useEffect, useState } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import React from 'react';
3
4
  import { useFormContext } from '../../../../../theme/hooks';
4
- import { Button, CircularProgress, Typography } from '../../../../core';
5
+ import { Button, CircularProgress, Skeleton, Typography } from '../../../../core';
5
6
  import { Box } from '../../../../layout';
6
7
  import CriteriaBuilder from '../../../CriteriaBuilder';
7
8
  import { addressProperties, getPrefixedUrl } from '../utils';
8
9
  export default function Criteria(props) {
9
10
  const { value, canUpdateProperty, fieldDefinition, error } = props;
10
11
  const apiServices = useApiServices();
11
- const { fetchedOptions, setFetchedOptions, handleChange, onAutosave } = useFormContext();
12
- const [loadingError, setLoadingError] = useState(false);
13
- const [loading, setLoading] = useState(false);
14
- const [properties, setProperties] = useState(fetchedOptions[`${fieldDefinition.id}Options`] || []);
15
- const fetchProperties = useCallback(async () => {
16
- if (fieldDefinition.objectId && !fetchedOptions[`${fieldDefinition.id}Options`]) {
17
- setLoading(true);
18
- apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective/properties`), { params: { fields: ['properties'] } }, (error, properties) => {
19
- if (error) {
20
- console.error('Error fetching object properties', error);
21
- setLoadingError(true);
12
+ const { handleChange, onAutosave } = useFormContext();
13
+ const { data: properties = [], error: loadingError, refetch: fetchProperties, isFetching: loading, } = useQuery({
14
+ queryKey: [fieldDefinition?.objectId, 'effective properties'],
15
+ queryFn: async () => {
16
+ const properties = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective/properties`));
17
+ return properties.flatMap((prop) => {
18
+ if (prop.type === 'object' || prop.type === 'user') {
19
+ return [
20
+ {
21
+ id: `${prop.id}.id`,
22
+ name: `${prop.name} Id`,
23
+ type: 'string',
24
+ },
25
+ {
26
+ id: `${prop.id}.name`,
27
+ name: `${prop.name} Name`,
28
+ type: 'string',
29
+ },
30
+ ];
22
31
  }
23
- if (properties) {
24
- const flattenProperties = properties.flatMap((prop) => {
25
- if (prop.type === 'object' || prop.type === 'user') {
26
- return [
27
- {
28
- id: `${prop.id}.id`,
29
- name: `${prop.name} Id`,
30
- type: 'string',
31
- },
32
- {
33
- id: `${prop.id}.name`,
34
- name: `${prop.name} Name`,
35
- type: 'string',
36
- },
37
- ];
38
- }
39
- else if (prop.type === 'address') {
40
- return addressProperties(prop);
41
- }
42
- return prop;
43
- });
44
- setProperties(flattenProperties);
45
- setFetchedOptions({
46
- [`${fieldDefinition.id}Options`]: flattenProperties.map((prop) => ({
47
- id: prop.id,
48
- name: prop.name,
49
- })),
50
- });
51
- setLoadingError(false);
32
+ if (prop.type === 'address') {
33
+ return addressProperties(prop);
52
34
  }
53
- setLoading(false);
35
+ return prop;
54
36
  });
55
- }
56
- }, [fieldDefinition.objectId, apiServices]);
57
- useEffect(() => {
58
- fetchProperties();
59
- }, [fetchProperties]);
37
+ },
38
+ staleTime: Infinity,
39
+ enabled: !!fieldDefinition?.objectId,
40
+ meta: {
41
+ errorMessage: 'Error fetching object properties: ',
42
+ },
43
+ });
60
44
  const handleUpdate = async (criteria) => {
61
45
  if (criteria || value) {
62
46
  const newValue = criteria ?? null;
@@ -82,9 +66,12 @@ export default function Criteria(props) {
82
66
  padding: 0,
83
67
  '&:hover': { backgroundColor: 'transparent' },
84
68
  minWidth: '44px',
85
- }, variant: "text", onClick: fetchProperties, disabled: loading }, "Retry"),
69
+ }, variant: "text", onClick: () => fetchProperties(), disabled: loading }, "Retry"),
86
70
  loading && React.createElement(CircularProgress, { size: 20, sx: { paddingLeft: '10px' } })));
87
71
  }
72
+ if (loading) {
73
+ return React.createElement(Skeleton, null);
74
+ }
88
75
  return !!value || canUpdateProperty ? (React.createElement(Box, { sx: { borderRadius: '8px', border: error ? '1px solid #FF0000' : '1px solid #ddd' } },
89
76
  React.createElement(CriteriaBuilder, { criteria: value ?? undefined, properties: properties, setCriteria: handleUpdate, disabled: !canUpdateProperty, hideBorder: true, presetValues: [
90
77
  {
@@ -1,7 +1,7 @@
1
1
  import { useApiServices, } from '@evoke-platform/context';
2
- import { isNil } from 'lodash';
2
+ import { useQuery } from '@tanstack/react-query';
3
3
  import prettyBytes from 'pretty-bytes';
4
- import React, { useCallback, useEffect, useState } from 'react';
4
+ import React, { useEffect, useState } from 'react';
5
5
  import { useDropzone } from 'react-dropzone';
6
6
  import { InfoRounded, UploadCloud } from '../../../../../../icons';
7
7
  import { useFormContext } from '../../../../../../theme/hooks';
@@ -12,10 +12,9 @@ import { DocumentList } from './DocumentList';
12
12
  export const Document = (props) => {
13
13
  const { id, fieldType = 'document', canUpdateProperty, error, value, validate, hasDescription } = props;
14
14
  const apiServices = useApiServices();
15
- const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance, form, } = useFormContext();
15
+ const { object, handleChange, onAutosave: onAutosave, instance, form } = useFormContext();
16
16
  const [snackbarError, setSnackbarError] = useState();
17
17
  const [documents, setDocuments] = useState();
18
- const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
19
18
  let allowedTypesMessage = '';
20
19
  if (validate?.allowedFileExtensions?.length) {
21
20
  if (validate.allowedFileExtensions.length === 1) {
@@ -33,8 +32,9 @@ export const Document = (props) => {
33
32
  useEffect(() => {
34
33
  setDocuments(value);
35
34
  }, [value]);
36
- const checkPermissions = useCallback(() => {
37
- if (canUpdateProperty && !fetchedOptions[`${id}UpdatePermission`] && instance?.id) {
35
+ const { data: hasUpdatePermission = false, isLoading } = useQuery({
36
+ queryKey: ['hasDocUpdatePermission', object?.id, instance?.id],
37
+ queryFn: async () => {
38
38
  // Find the entry to get the configured createActionId
39
39
  const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
40
40
  const entry = allEntries?.find((entry) => getEntryId(entry) === id);
@@ -43,35 +43,18 @@ export const Document = (props) => {
43
43
  // For 'document' type properties, check document attachment permissions
44
44
  const endpoint = fieldType === 'file'
45
45
  ? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=execute&field=${createActionId}`)
46
- : getPrefixedUrl(`/objects/${object?.id}/instances/${instance.id}/documents/checkAccess?action=update`);
47
- apiServices
48
- .get(endpoint)
49
- .then((accessCheck) => {
50
- setFetchedOptions({
51
- [`${id}UpdatePermission`]: accessCheck.result,
52
- });
53
- setHasUpdatePermission(accessCheck.result);
54
- })
55
- .catch(() => {
56
- setFetchedOptions({
57
- [`${id}UpdatePermission`]: false,
58
- });
59
- setHasUpdatePermission(false);
60
- });
61
- }
62
- }, [
63
- canUpdateProperty,
64
- fetchedOptions[`${id}UpdatePermission`],
65
- instance?.id,
66
- object,
67
- id,
68
- apiServices,
69
- form,
70
- fieldType,
71
- ]);
72
- useEffect(() => {
73
- checkPermissions();
74
- }, [checkPermissions]);
46
+ : getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/checkAccess?action=update`);
47
+ try {
48
+ const accessCheck = await apiServices.get(endpoint);
49
+ return accessCheck.result;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ },
55
+ staleTime: Infinity,
56
+ enabled: canUpdateProperty && !!instance?.id && !!object?.id,
57
+ });
75
58
  const handleUpload = async (files) => {
76
59
  if (!files?.length) {
77
60
  return;
@@ -163,7 +146,7 @@ export const Document = (props) => {
163
146
  } }, validate?.maxDocuments === 1
164
147
  ? `Maximum size is ${formattedMaxSize}.`
165
148
  : `The maximum size of each document is ${formattedMaxSize}.`)))))),
166
- canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, fieldType: fieldType, handleChange: handleChange, onAutosave: onAutosave, value: documents, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
149
+ canUpdateProperty && isLoading ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, fieldType: fieldType, handleChange: handleChange, onAutosave: onAutosave, value: documents, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
167
150
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
168
151
  errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
169
152
  React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),