@evoke-platform/ui-components 1.13.0-dev.5 → 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.
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +6 -3
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +25 -27
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +93 -86
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +104 -184
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +51 -32
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +4 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +40 -38
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +95 -169
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +12 -6
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +39 -17
- package/dist/published/components/custom/FormV2/components/types.d.ts +6 -1
- package/dist/published/components/custom/FormV2/components/utils.d.ts +10 -11
- package/dist/published/components/custom/FormV2/components/utils.js +169 -93
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +48 -15
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +38 -46
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +38 -13
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
- package/package.json +3 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useApiServices, useNotification, } from '@evoke-platform/context';
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
211
|
+
const validate = sift(criteria);
|
|
212
|
+
return validate(instance) && checkAccess.result;
|
|
274
213
|
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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 = () =>
|
|
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: () =>
|
|
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
|
-
|
|
359
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
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 ? (!
|
|
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
|
-
|
|
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
|
|
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 {
|
|
12
|
-
const [loadingError,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 (
|
|
24
|
-
|
|
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
|
-
|
|
35
|
+
return prop;
|
|
54
36
|
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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,12 +1,13 @@
|
|
|
1
1
|
import { DocumentParameterValidation } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { DocumentReference } from '../../types';
|
|
4
4
|
type DocumentProps = {
|
|
5
5
|
id: string;
|
|
6
|
+
fieldType?: 'file' | 'document';
|
|
6
7
|
canUpdateProperty: boolean;
|
|
7
8
|
error: boolean;
|
|
8
9
|
validate?: DocumentParameterValidation;
|
|
9
|
-
value: (File |
|
|
10
|
+
value: (File | DocumentReference)[] | undefined;
|
|
10
11
|
hasDescription?: boolean;
|
|
11
12
|
};
|
|
12
13
|
export declare const Document: (props: DocumentProps) => React.JSX.Element;
|
package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { useApiServices } from '@evoke-platform/context';
|
|
2
|
-
import {
|
|
1
|
+
import { useApiServices, } from '@evoke-platform/context';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
3
|
import prettyBytes from 'pretty-bytes';
|
|
4
|
-
import 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';
|
|
8
8
|
import { Skeleton, Snackbar, Typography } from '../../../../../core';
|
|
9
9
|
import { Box, Grid } from '../../../../../layout';
|
|
10
|
-
import { getPrefixedUrl } from '../../utils';
|
|
10
|
+
import { getEntryId, getPrefixedUrl, getUnnestedEntries, uploadFiles } from '../../utils';
|
|
11
11
|
import { DocumentList } from './DocumentList';
|
|
12
12
|
export const Document = (props) => {
|
|
13
|
-
const { id, canUpdateProperty, error, value, validate, hasDescription } = props;
|
|
13
|
+
const { id, fieldType = 'document', canUpdateProperty, error, value, validate, hasDescription } = props;
|
|
14
14
|
const apiServices = useApiServices();
|
|
15
|
-
const {
|
|
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,33 +32,53 @@ export const Document = (props) => {
|
|
|
33
32
|
useEffect(() => {
|
|
34
33
|
setDocuments(value);
|
|
35
34
|
}, [value]);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
const { data: hasUpdatePermission = false, isLoading } = useQuery({
|
|
36
|
+
queryKey: ['hasDocUpdatePermission', object?.id, instance?.id],
|
|
37
|
+
queryFn: async () => {
|
|
38
|
+
// Find the entry to get the configured createActionId
|
|
39
|
+
const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
|
|
40
|
+
const entry = allEntries?.find((entry) => getEntryId(entry) === id);
|
|
41
|
+
const createActionId = entry?.display?.createActionId ?? '_create';
|
|
42
|
+
// For 'file' type properties, check regular object instance permissions
|
|
43
|
+
// For 'document' type properties, check document attachment permissions
|
|
44
|
+
const endpoint = fieldType === 'file'
|
|
45
|
+
? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=execute&field=${createActionId}`)
|
|
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
|
+
});
|
|
57
58
|
const handleUpload = async (files) => {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
if (!files?.length) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let uploadedFiles = files;
|
|
63
|
+
// Get the createActionId from display options, default to '_create'
|
|
64
|
+
const allEntries = getUnnestedEntries(form?.entries ?? []);
|
|
65
|
+
const entry = allEntries?.find((entry) => getEntryId(entry) === id);
|
|
66
|
+
const createActionId = entry?.display?.createActionId ?? '_create';
|
|
67
|
+
// Immediately upload files for 'file' type properties when autosave is not enabled.
|
|
68
|
+
// Linking will happen upon final submission.
|
|
69
|
+
// If autosave is enabled, upload and linking will happen in the autosave handler.
|
|
70
|
+
if (fieldType === 'file' && !onAutosave) {
|
|
71
|
+
const { successfulUploads, errorMessage } = await uploadFiles(files, apiServices, createActionId, undefined, false);
|
|
72
|
+
uploadedFiles = successfulUploads;
|
|
73
|
+
if (errorMessage) {
|
|
74
|
+
setSnackbarError({ message: errorMessage, type: 'error' });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Store uploaded file references (or File objects) in form state
|
|
78
|
+
const newDocuments = [...(documents ?? []), ...uploadedFiles];
|
|
60
79
|
setDocuments(newDocuments);
|
|
61
80
|
try {
|
|
62
|
-
|
|
81
|
+
await handleChange?.(id, newDocuments);
|
|
63
82
|
}
|
|
64
83
|
catch (error) {
|
|
65
84
|
console.error('Failed to update field:', error);
|
|
@@ -127,7 +146,7 @@ export const Document = (props) => {
|
|
|
127
146
|
} }, validate?.maxDocuments === 1
|
|
128
147
|
? `Maximum size is ${formattedMaxSize}.`
|
|
129
148
|
: `The maximum size of each document is ${formattedMaxSize}.`)))))),
|
|
130
|
-
canUpdateProperty &&
|
|
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 })),
|
|
131
150
|
React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
|
|
132
151
|
errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
|
|
133
152
|
React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),
|