@evoke-platform/ui-components 1.8.0-dev.0 → 1.8.0-dev.10
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/core/TextField/TextField.js +3 -2
- package/dist/published/components/custom/DataGrid/DataGrid.d.ts +2 -0
- package/dist/published/components/custom/DataGrid/DataGrid.js +3 -1
- package/dist/published/components/custom/DataGrid/Toolbar.d.ts +2 -0
- package/dist/published/components/custom/DataGrid/Toolbar.js +4 -3
- package/dist/published/components/custom/DataGrid/index.d.ts +1 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +4 -0
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +4 -0
- package/dist/published/components/custom/Form/utils.js +76 -44
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +6 -2
- package/dist/published/components/custom/FormV2/FormRenderer.js +13 -14
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +7 -2
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +61 -109
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +9 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +12 -24
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +5 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +80 -30
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +51 -27
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +5 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +45 -7
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +8 -6
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +3 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +1 -3
- package/dist/published/components/custom/FormV2/components/types.d.ts +7 -1
- package/dist/published/components/custom/FormV2/components/utils.d.ts +27 -2
- package/dist/published/components/custom/FormV2/components/utils.js +108 -2
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +173 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +96 -0
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +16 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +394 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/index.d.ts +1 -1
- package/dist/published/stories/FormRenderer.stories.d.ts +7 -0
- package/dist/published/stories/FormRenderer.stories.js +65 -0
- package/dist/published/stories/FormRendererContainer.stories.d.ts +7 -0
- package/dist/published/stories/FormRendererContainer.stories.js +56 -0
- package/dist/published/stories/FormRendererData.d.ts +116 -0
- package/dist/published/stories/FormRendererData.js +925 -0
- package/dist/published/stories/sharedMswHandlers.d.ts +1 -0
- package/dist/published/stories/sharedMswHandlers.js +100 -0
- package/dist/published/theme/hooks.d.ts +4 -0
- package/package.json +12 -4
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
|
-
import { LocalDateTime } from '@js-joda/core';
|
|
3
2
|
import axios from 'axios';
|
|
4
3
|
import { get, isArray, isEmpty, isEqual, merge, omit, pick, set, uniq } from 'lodash';
|
|
5
4
|
import React, { useEffect, useState } from 'react';
|
|
@@ -7,10 +6,10 @@ import { Skeleton, Snackbar } from '../../core';
|
|
|
7
6
|
import { Box } from '../../layout';
|
|
8
7
|
import ErrorComponent from '../ErrorComponent';
|
|
9
8
|
import { evalDefaultVals, processValueUpdate } from './components/DefaultValues';
|
|
10
|
-
import { convertDocToEntries, encodePageSlug, formatDataToDoc, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault,
|
|
9
|
+
import { convertDocToEntries, deleteDocuments, encodePageSlug, formatDataToDoc, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, } from './components/utils';
|
|
11
10
|
import FormRenderer from './FormRenderer';
|
|
12
11
|
function FormRendererContainer(props) {
|
|
13
|
-
const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, } = props;
|
|
12
|
+
const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, onClose, onSubmit, associatedObject, hideButtons, } = props;
|
|
14
13
|
const apiServices = useApiServices();
|
|
15
14
|
const navigateTo = useNavigate();
|
|
16
15
|
const { id: appId, defaultPages } = useApp();
|
|
@@ -98,10 +97,11 @@ function FormRendererContainer(props) {
|
|
|
98
97
|
return;
|
|
99
98
|
if (formId || action?.defaultFormId) {
|
|
100
99
|
apiServices
|
|
101
|
-
.get(getPrefixedUrl(
|
|
100
|
+
.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
|
|
102
101
|
.then((evokeForm) => {
|
|
103
102
|
if (evokeForm?.actionId === actionId) {
|
|
104
|
-
|
|
103
|
+
const form = onClose ? { ...evokeForm, name: '' } : evokeForm;
|
|
104
|
+
setForm(form);
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
107
|
setError(true);
|
|
@@ -111,24 +111,50 @@ function FormRendererContainer(props) {
|
|
|
111
111
|
onError(error);
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
|
-
else if (action
|
|
114
|
+
else if (action) {
|
|
115
115
|
apiServices
|
|
116
|
-
.get(getPrefixedUrl(
|
|
116
|
+
.get(getPrefixedUrl('/forms'), {
|
|
117
|
+
params: {
|
|
118
|
+
filter: {
|
|
119
|
+
where: {
|
|
120
|
+
actionId: action.id,
|
|
121
|
+
objectId: objectId,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
})
|
|
117
126
|
.then((matchingForms) => {
|
|
118
127
|
if (matchingForms.length === 1) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
const form = onClose ? { ...matchingForms[0], name: '' } : matchingForms[0];
|
|
129
|
+
setForm(form);
|
|
130
|
+
// use this default form if no delete form is found
|
|
131
|
+
}
|
|
132
|
+
else if (action.type === 'delete' && instance) {
|
|
133
|
+
setForm({
|
|
134
|
+
id: '',
|
|
135
|
+
name: '',
|
|
136
|
+
entries: [
|
|
137
|
+
{
|
|
138
|
+
type: 'content',
|
|
139
|
+
html: `<p>You are about to delete <strong>${instance.name}</strong>. Deleted records can't be restored. Are you sure you want to continue?</p>`,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
objectId: objectId,
|
|
143
|
+
actionId: '_delete',
|
|
144
|
+
display: {
|
|
145
|
+
submitLabel: 'Delete',
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else if (instance || action.type === 'create') {
|
|
150
|
+
setError(true);
|
|
125
151
|
}
|
|
126
152
|
})
|
|
127
153
|
.catch((error) => {
|
|
128
154
|
onError(error);
|
|
129
155
|
});
|
|
130
156
|
}
|
|
131
|
-
}, [action]);
|
|
157
|
+
}, [action, objectId, instance]);
|
|
132
158
|
useEffect(() => {
|
|
133
159
|
if (form?.id === 'documentForm') {
|
|
134
160
|
setParameters([
|
|
@@ -174,54 +200,6 @@ function FormRendererContainer(props) {
|
|
|
174
200
|
};
|
|
175
201
|
getInitialValues();
|
|
176
202
|
}, [form, instance, sanitizedObject]);
|
|
177
|
-
const uploadDocuments = async (files, metadata) => {
|
|
178
|
-
const allDocuments = [];
|
|
179
|
-
const formData = new FormData();
|
|
180
|
-
for (const [index, file] of files.entries()) {
|
|
181
|
-
if ('size' in file) {
|
|
182
|
-
formData.append(`files[${index}]`, file);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
allDocuments.push(file);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (metadata) {
|
|
189
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
190
|
-
formData.append(key, value);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const docs = await apiServices?.post(getPrefixedUrl(`/objects/${form?.objectId}/instances/${instanceId}/documents`), formData);
|
|
194
|
-
return allDocuments.concat(docs?.map((doc) => ({
|
|
195
|
-
id: doc.id,
|
|
196
|
-
name: doc.name,
|
|
197
|
-
})) ?? []);
|
|
198
|
-
};
|
|
199
|
-
const deleteDocuments = async (submittedFields, requestSuccess, action) => {
|
|
200
|
-
const documentProperties = action?.parameters
|
|
201
|
-
? action.parameters.filter((param) => param.type === 'document')
|
|
202
|
-
: sanitizedObject?.properties?.filter((prop) => prop.type === 'document');
|
|
203
|
-
for (const docProperty of documentProperties ?? []) {
|
|
204
|
-
const savedValue = submittedFields[docProperty.id];
|
|
205
|
-
const originalValue = instance?.[docProperty.id];
|
|
206
|
-
const documentsToRemove = requestSuccess
|
|
207
|
-
? (originalValue?.filter((file) => !savedValue?.some((f) => f.id === file.id)) ?? [])
|
|
208
|
-
: (savedValue?.filter((file) => !originalValue?.some((f) => f.id === file.id)) ?? []);
|
|
209
|
-
for (const doc of documentsToRemove) {
|
|
210
|
-
try {
|
|
211
|
-
await apiServices?.delete(getPrefixedUrl(`/objects/${form?.objectId}/instances/${instanceId}/documents/${doc.id}`));
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
if (error) {
|
|
215
|
-
setSnackbarError({
|
|
216
|
-
showAlert: true,
|
|
217
|
-
message: `An error occurred while removing document '${doc.name}'`,
|
|
218
|
-
isError: true,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
203
|
const onSubmissionSuccess = (updatedInstance) => {
|
|
226
204
|
setSnackbarError({
|
|
227
205
|
showAlert: true,
|
|
@@ -247,47 +225,7 @@ function FormRendererContainer(props) {
|
|
|
247
225
|
const saveHandler = async (submission) => {
|
|
248
226
|
if (!form)
|
|
249
227
|
return;
|
|
250
|
-
|
|
251
|
-
for (const [key, value] of Object.entries(submission)) {
|
|
252
|
-
if (isArray(value)) {
|
|
253
|
-
const fileInArray = value.some((item) => item instanceof File);
|
|
254
|
-
if (fileInArray) {
|
|
255
|
-
try {
|
|
256
|
-
const uploadedDocuments = await uploadDocuments(value, {
|
|
257
|
-
type: '',
|
|
258
|
-
view_permission: '',
|
|
259
|
-
});
|
|
260
|
-
submission[key] = uploadedDocuments;
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
if (err) {
|
|
264
|
-
setSnackbarError({
|
|
265
|
-
showAlert: true,
|
|
266
|
-
message: `An error occurred while uploading associated documents`,
|
|
267
|
-
isError: true,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
// if there are address fields with no value address needs to be set to undefined to be able to submit
|
|
274
|
-
}
|
|
275
|
-
else if (typeof value === 'object' && value !== null) {
|
|
276
|
-
if (Object.values(value).every((v) => v === undefined)) {
|
|
277
|
-
submission[key] = undefined;
|
|
278
|
-
// only submit the name and id of a related object
|
|
279
|
-
}
|
|
280
|
-
else if ('id' in value && 'name' in value) {
|
|
281
|
-
submission[key] = pick(value, 'id', 'name');
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
else if ((value === '' && !document) || value === undefined) {
|
|
285
|
-
submission[key] = null;
|
|
286
|
-
}
|
|
287
|
-
else if (value instanceof LocalDateTime) {
|
|
288
|
-
submission[key] = normalizeDateTime(value);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
228
|
+
submission = await formatSubmission(submission, apiServices, objectId, instanceId, setSnackbarError);
|
|
291
229
|
if (document) {
|
|
292
230
|
submission = formatDataToDoc(submission);
|
|
293
231
|
}
|
|
@@ -331,9 +269,9 @@ function FormRendererContainer(props) {
|
|
|
331
269
|
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
332
270
|
.map((property) => property.id) ?? []),
|
|
333
271
|
});
|
|
334
|
-
if (response) {
|
|
272
|
+
if (response && sanitizedObject && instance) {
|
|
335
273
|
onSubmissionSuccess(response);
|
|
336
|
-
deleteDocuments(submission, !!response, action ?? undefined);
|
|
274
|
+
deleteDocuments(submission, !!response, apiServices, sanitizedObject, instance, action ?? undefined, setSnackbarError);
|
|
337
275
|
}
|
|
338
276
|
}
|
|
339
277
|
}
|
|
@@ -393,7 +331,17 @@ function FormRendererContainer(props) {
|
|
|
393
331
|
const fieldValue = instanceData?.[fieldId] ??
|
|
394
332
|
instanceData?.metadata?.[fieldId];
|
|
395
333
|
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
396
|
-
if (
|
|
334
|
+
if (associatedObject?.propertyId === fieldId && associatedObject?.instanceId && parameter) {
|
|
335
|
+
try {
|
|
336
|
+
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`));
|
|
337
|
+
result[associatedObject.propertyId] = instance;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
console.error(error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else if (entry.type !== 'readonlyField' &&
|
|
344
|
+
isEmptyWithDefault(fieldValue, entry, instanceData)) {
|
|
397
345
|
if (fieldId && parameters && parameters.length > 0) {
|
|
398
346
|
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
399
347
|
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
@@ -463,9 +411,13 @@ function FormRendererContainer(props) {
|
|
|
463
411
|
})();
|
|
464
412
|
}
|
|
465
413
|
const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
|
|
466
|
-
return !error ? (React.createElement(
|
|
467
|
-
|
|
468
|
-
|
|
414
|
+
return !error ? (React.createElement(Box, { sx: {
|
|
415
|
+
backgroundColor: '#ffffff',
|
|
416
|
+
borderRadius: '6px',
|
|
417
|
+
padding: '0px',
|
|
418
|
+
border: !isLoading && !onClose ? '1px solid #dbe0e4' : undefined,
|
|
419
|
+
} },
|
|
420
|
+
!isLoading ? (React.createElement(FormRenderer, { onSubmit: onSubmit ?? saveHandler, hideButtons: hideButtons ?? (document && !hasDocumentUpdateAccess), richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, stickyFooter: stickyFooter, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, onCancel: onClose ?? onCancel, associatedObject: associatedObject })) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
469
421
|
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
470
422
|
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
471
423
|
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
@@ -20,6 +20,10 @@ type FormContextType = {
|
|
|
20
20
|
fieldHeight?: 'small' | 'medium';
|
|
21
21
|
triggerFieldReset?: boolean;
|
|
22
22
|
showSubmitError?: boolean;
|
|
23
|
+
associatedObject?: {
|
|
24
|
+
instanceId?: string;
|
|
25
|
+
propertyId?: string;
|
|
26
|
+
};
|
|
23
27
|
};
|
|
24
28
|
export declare const FormContext: import("react").Context<FormContextType>;
|
|
25
29
|
export {};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { Action,
|
|
1
|
+
import { Action, InputParameter, Obj } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { FieldValues } from 'react-hook-form';
|
|
3
4
|
export type ActionDialogProps = {
|
|
4
5
|
open: boolean;
|
|
5
6
|
onClose: () => void;
|
|
6
7
|
action: Action;
|
|
7
|
-
|
|
8
|
-
handleSubmit: (actionType: ActionType, input: Record<string, unknown> | undefined, instanceId?: string, setSubmitting?: (value: boolean) => void) => void;
|
|
8
|
+
handleSubmit: (action: Action, input: FieldValues, instanceId?: string, setSubmitting?: (value: boolean) => void) => void;
|
|
9
9
|
object: Obj;
|
|
10
10
|
instanceId?: string;
|
|
11
|
-
relatedParameter
|
|
12
|
-
|
|
11
|
+
relatedParameter: InputParameter;
|
|
12
|
+
relatedFormId?: string;
|
|
13
|
+
associatedObject?: {
|
|
14
|
+
instanceId?: string;
|
|
15
|
+
propertyId?: string;
|
|
16
|
+
};
|
|
13
17
|
};
|
|
14
18
|
export declare const ActionDialog: (props: ActionDialogProps) => React.JSX.Element;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import { Close } from '@mui/icons-material';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useFormContext } from '../../../../../../theme/hooks';
|
|
4
5
|
import { Dialog, DialogContent, DialogTitle, IconButton, Skeleton } from '../../../../../core';
|
|
5
6
|
import { Box } from '../../../../../layout';
|
|
6
7
|
import ErrorComponent from '../../../../ErrorComponent';
|
|
8
|
+
import FormRendererContainer from '../../../FormRendererContainer';
|
|
7
9
|
import { getPrefixedUrl } from '../../utils';
|
|
8
10
|
const styles = {
|
|
9
11
|
button: {
|
|
@@ -36,12 +38,11 @@ const styles = {
|
|
|
36
38
|
},
|
|
37
39
|
};
|
|
38
40
|
export const ActionDialog = (props) => {
|
|
39
|
-
const { open, onClose, action, object, instanceId,
|
|
41
|
+
const { open, onClose, action, object, instanceId, relatedFormId, relatedParameter, handleSubmit, associatedObject, } = props;
|
|
40
42
|
const [loading, setLoading] = useState(false);
|
|
41
43
|
const [hasAccess, setHasAccess] = useState();
|
|
42
|
-
const
|
|
44
|
+
const { stickyFooter, fieldHeight, richTextEditor } = useFormContext();
|
|
43
45
|
const apiServices = useApiServices();
|
|
44
|
-
const isDeleteAction = action.type === 'delete';
|
|
45
46
|
useEffect(() => {
|
|
46
47
|
if (instanceId) {
|
|
47
48
|
setLoading(true);
|
|
@@ -57,31 +58,18 @@ export const ActionDialog = (props) => {
|
|
|
57
58
|
setLoading(false);
|
|
58
59
|
}
|
|
59
60
|
}, [object, instanceId]);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
id: '',
|
|
64
|
-
name: '',
|
|
65
|
-
entries: [
|
|
66
|
-
{
|
|
67
|
-
type: 'content',
|
|
68
|
-
html: `<p>You are about to delete ${instanceInput?.name}. Deleted records can't be restored. Are you sure you want to continue?</p>`,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
objectId: object.id,
|
|
72
|
-
actionId: '_delete',
|
|
73
|
-
display: {
|
|
74
|
-
submitLabel: 'Delete',
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
: relatedForm);
|
|
78
|
-
}, [relatedForm, action, form]);
|
|
61
|
+
const handleFormSave = async (data) => {
|
|
62
|
+
return handleSubmit(action, data, instanceId);
|
|
63
|
+
};
|
|
79
64
|
return (React.createElement(Dialog, { maxWidth: 'md', fullWidth: true, open: open, onClose: (e, reason) => reason !== 'backdropClick' && onClose() },
|
|
80
|
-
React.createElement(DialogTitle, { sx: styles.dialogTitle },
|
|
65
|
+
React.createElement(DialogTitle, { sx: { ...styles.dialogTitle, borderBottom: action.type === 'delete' ? undefined : '1px solid #e9ecef' } },
|
|
81
66
|
React.createElement(IconButton, { sx: styles.closeIcon, onClick: onClose },
|
|
82
67
|
React.createElement(Close, { fontSize: "small" })),
|
|
83
68
|
action && hasAccess && !loading ? action?.name : ''),
|
|
84
|
-
React.createElement(DialogContent, { sx: { paddingBottom: loading ? undefined : '0px' } }, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } }
|
|
69
|
+
React.createElement(DialogContent, { sx: { paddingBottom: loading ? undefined : '0px' } }, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } },
|
|
70
|
+
React.createElement(FormRendererContainer, { instanceId: instanceId, formId: relatedFormId, display: { fieldHeight: fieldHeight ?? 'medium' }, actionId: action.id, stickyFooter: stickyFooter,
|
|
71
|
+
// relatedParameter will have an objectId here
|
|
72
|
+
objectId: relatedParameter.objectId, onClose: onClose, onSubmit: handleFormSave, richTextEditor: richTextEditor, associatedObject: associatedObject }))) : (React.createElement(React.Fragment, null, loading ? (React.createElement(React.Fragment, null,
|
|
85
73
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
|
86
74
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
|
87
75
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }))) : (React.createElement(ErrorComponent, { code: 'AccessDenied', message: 'You do not have permission to perform this action.', styles: { boxShadow: 'none' } })))))));
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { InputParameter, Property, ViewLayoutEntityReference } from '@evoke-platform/context';
|
|
1
|
+
import { InputField, InputParameter, InputParameterReference, Property, ReadonlyField, ViewLayoutEntityReference } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export type ObjectPropertyInputProps = {
|
|
4
4
|
fieldDefinition: InputParameter | Property;
|
|
5
5
|
canUpdateProperty: boolean;
|
|
6
6
|
criteria?: object;
|
|
7
7
|
viewLayout?: ViewLayoutEntityReference;
|
|
8
|
+
entry: InputField | InputParameterReference | ReadonlyField;
|
|
9
|
+
createActionId?: string;
|
|
10
|
+
updateActionId?: string;
|
|
11
|
+
deleteActionId?: string;
|
|
8
12
|
};
|
|
9
13
|
declare const RepeatableField: (props: ObjectPropertyInputProps) => React.JSX.Element;
|
|
10
14
|
export default RepeatableField;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useApiServices, useNotification, } from '@evoke-platform/context';
|
|
2
|
-
import {
|
|
3
|
-
import { get, isEqual, isObject, pick, startCase } from 'lodash';
|
|
2
|
+
import { get, isEqual, pick, startCase } from 'lodash';
|
|
4
3
|
import { DateTime } from 'luxon';
|
|
5
4
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
6
5
|
import sift from 'sift';
|
|
@@ -11,7 +10,7 @@ import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Skel
|
|
|
11
10
|
import { Box } from '../../../../../layout';
|
|
12
11
|
import { getReadableQuery } from '../../../../CriteriaBuilder';
|
|
13
12
|
import { retrieveCustomErrorMessage } from '../../../../Form/utils';
|
|
14
|
-
import {
|
|
13
|
+
import { deleteDocuments, formatSubmission, getPrefixedUrl, transformToWhere } from '../../utils';
|
|
15
14
|
import { ActionDialog } from './ActionDialog';
|
|
16
15
|
import { DocumentViewerCell } from './DocumentViewerCell';
|
|
17
16
|
const styles = {
|
|
@@ -36,7 +35,7 @@ const styles = {
|
|
|
36
35
|
},
|
|
37
36
|
};
|
|
38
37
|
const RepeatableField = (props) => {
|
|
39
|
-
const { fieldDefinition, canUpdateProperty, criteria, viewLayout } = props;
|
|
38
|
+
const { fieldDefinition, canUpdateProperty, criteria, viewLayout, entry, createActionId, updateActionId, deleteActionId, } = props;
|
|
40
39
|
const { fetchedOptions, setFetchedOptions, instance } = useFormContext();
|
|
41
40
|
const { instanceChanges } = useNotification();
|
|
42
41
|
const apiServices = useApiServices();
|
|
@@ -54,11 +53,49 @@ const RepeatableField = (props) => {
|
|
|
54
53
|
const [hasCreateAction, setHasCreateAction] = useState(fetchedOptions[`${fieldDefinition.id}HasCreateAction`] || false);
|
|
55
54
|
const [loading, setLoading] = useState((relatedObject && relatedInstances) || !fieldDefinition ? false : true);
|
|
56
55
|
const [tableViewLayout, setTableViewLayout] = useState(fetchedOptions[`${fieldDefinition.id}TableViewLayout`]);
|
|
56
|
+
const [createForm, setCreateForm] = useState(fetchedOptions[`${fieldDefinition.id}-createForm`]);
|
|
57
|
+
const [updateForm, setUpdateForm] = useState(fetchedOptions[`${fieldDefinition.id}-updateForm`]);
|
|
58
|
+
const [deleteForm, setDeleteForm] = useState(fetchedOptions[`${fieldDefinition.id}-deleteForm`]);
|
|
57
59
|
const [snackbarError, setSnackbarError] = useState({
|
|
58
60
|
showAlert: false,
|
|
59
61
|
isError: false,
|
|
60
62
|
});
|
|
61
|
-
const
|
|
63
|
+
const createAction = relatedObject?.actions?.find((item) => item.id === createActionId);
|
|
64
|
+
const updateAction = relatedObject?.actions?.find((item) => item.id === updateActionId);
|
|
65
|
+
const deleteAction = relatedObject?.actions?.find((item) => item.id === deleteActionId);
|
|
66
|
+
function getForm(setForm, action, formId) {
|
|
67
|
+
if (formId || action?.defaultFormId) {
|
|
68
|
+
apiServices
|
|
69
|
+
.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
|
|
70
|
+
.then((evokeForm) => {
|
|
71
|
+
setForm(evokeForm);
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
console.error(error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (action) {
|
|
78
|
+
apiServices
|
|
79
|
+
.get(getPrefixedUrl('/forms'), {
|
|
80
|
+
params: {
|
|
81
|
+
filter: {
|
|
82
|
+
where: {
|
|
83
|
+
actionId: action.id,
|
|
84
|
+
objectId: fieldDefinition.objectId,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
.then((matchingForms) => {
|
|
90
|
+
if (matchingForms.length === 1) {
|
|
91
|
+
setForm(matchingForms[0]);
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
console.error(error);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
62
99
|
const fetchRelatedInstances = useCallback(async (refetch = false) => {
|
|
63
100
|
let relatedObject;
|
|
64
101
|
if (fieldDefinition.objectId) {
|
|
@@ -168,6 +205,14 @@ const RepeatableField = (props) => {
|
|
|
168
205
|
if (relatedObject)
|
|
169
206
|
fetchCriteriaObjects();
|
|
170
207
|
}, [fetchCriteriaObjects, relatedObject]);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (createAction && !createForm)
|
|
210
|
+
getForm(setCreateForm, createAction); // TODO: pass entry.display?.createForm as a third argument
|
|
211
|
+
if (updateAction && !updateForm)
|
|
212
|
+
getForm(setUpdateForm, updateAction); // TODO: pass entry.display?.updateForm as a third argument
|
|
213
|
+
if (deleteAction && !deleteForm)
|
|
214
|
+
getForm(setDeleteForm, deleteAction); // TODO: pass entry.display?.deleteForm as a third argument
|
|
215
|
+
}, [entry.display, createAction, updateAction, deleteAction]);
|
|
171
216
|
useEffect(() => {
|
|
172
217
|
if (relatedObject?.rootObjectId) {
|
|
173
218
|
// pass true here so while it doesn't refetch on every tab change it does refetch on changes made
|
|
@@ -226,11 +271,7 @@ const RepeatableField = (props) => {
|
|
|
226
271
|
})
|
|
227
272
|
.then((checkAccess) => {
|
|
228
273
|
const action = relatedObject.actions?.find((item) => item.id === '_create');
|
|
229
|
-
if (action &&
|
|
230
|
-
fieldDefinition.relatedPropertyId &&
|
|
231
|
-
// TODO: replace with the entries create form or defaultFormId of the
|
|
232
|
-
// default create action, keeping it like this to get minimum changes out so other can use it
|
|
233
|
-
!!fieldDefinition.createForm) {
|
|
274
|
+
if (action && fieldDefinition.relatedPropertyId) {
|
|
234
275
|
const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
|
|
235
276
|
if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
|
|
236
277
|
setHasCreateAction(checkAccess.result);
|
|
@@ -301,22 +342,17 @@ const RepeatableField = (props) => {
|
|
|
301
342
|
},
|
|
302
343
|
'min-width': '44px',
|
|
303
344
|
}, variant: "text", onClick: () => setReloadOnErrorTrigger((prevState) => !prevState) }, "Retry")));
|
|
304
|
-
const save = async (
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
input = Object.entries(input).reduce((agg, [key, value]) => Object.assign(agg, {
|
|
309
|
-
[key]: value instanceof LocalDateTime ? normalizeDateTime(value) : value,
|
|
310
|
-
}), {});
|
|
311
|
-
}
|
|
312
|
-
if (actionType === 'create') {
|
|
345
|
+
const save = async (action, input, instanceId) => {
|
|
346
|
+
// when save is called we know that fieldDefinition is a parameter and fieldDefinition.objectId is defined
|
|
347
|
+
input = await formatSubmission(input, apiServices, fieldDefinition.objectId, instanceId);
|
|
348
|
+
if (action.type === 'create' && createActionId) {
|
|
313
349
|
const updatedInput = {
|
|
314
350
|
...input,
|
|
315
351
|
[fieldDefinition?.relatedPropertyId]: { id: instance?.id },
|
|
316
352
|
};
|
|
317
353
|
try {
|
|
318
354
|
const instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
|
|
319
|
-
actionId:
|
|
355
|
+
actionId: createActionId,
|
|
320
356
|
input: updatedInput,
|
|
321
357
|
});
|
|
322
358
|
const hasAccess = fieldDefinition?.relatedPropertyId && fieldDefinition.relatedPropertyId in instance;
|
|
@@ -337,13 +373,16 @@ const RepeatableField = (props) => {
|
|
|
337
373
|
else {
|
|
338
374
|
const relatedObjectId = relatedObject?.id;
|
|
339
375
|
try {
|
|
340
|
-
await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${instanceId}/actions`), {
|
|
341
|
-
actionId: `_${
|
|
376
|
+
const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${instanceId}/actions`), {
|
|
377
|
+
actionId: `_${action.type}`,
|
|
342
378
|
input: pick(input, relatedObject?.properties
|
|
343
379
|
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
344
380
|
.map((property) => property.id) ?? []),
|
|
345
381
|
});
|
|
346
|
-
if (
|
|
382
|
+
if (response && relatedObject && instance) {
|
|
383
|
+
deleteDocuments(input, !!response, apiServices, relatedObject, instance, action);
|
|
384
|
+
}
|
|
385
|
+
if (action.type === 'delete') {
|
|
347
386
|
setRelatedInstances((prevInstances) => prevInstances.filter((instance) => instance.id !== instanceId));
|
|
348
387
|
}
|
|
349
388
|
else {
|
|
@@ -357,7 +396,7 @@ const RepeatableField = (props) => {
|
|
|
357
396
|
setSnackbarError({
|
|
358
397
|
showAlert: true,
|
|
359
398
|
message: retrieveCustomErrorMessage(err) ??
|
|
360
|
-
`An error occurred while ${
|
|
399
|
+
`An error occurred while ${action.type === 'delete' ? ' deleting' : ' updating'} an instance`,
|
|
361
400
|
isError: true,
|
|
362
401
|
});
|
|
363
402
|
}
|
|
@@ -501,7 +540,7 @@ const RepeatableField = (props) => {
|
|
|
501
540
|
cursor: 'pointer',
|
|
502
541
|
},
|
|
503
542
|
}
|
|
504
|
-
: {}, onClick:
|
|
543
|
+
: {}, onClick: updateActionId &&
|
|
505
544
|
canUpdateProperty &&
|
|
506
545
|
prop.id === 'name'
|
|
507
546
|
? () => editRow(relatedInstance.id)
|
|
@@ -511,16 +550,27 @@ const RepeatableField = (props) => {
|
|
|
511
550
|
users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
|
|
512
551
|
}),
|
|
513
552
|
canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
|
|
514
|
-
|
|
515
|
-
React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
|
|
553
|
+
updateActionId && (React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
|
|
516
554
|
React.createElement(Tooltip, { title: "Edit" },
|
|
517
555
|
React.createElement(Edit, null)))),
|
|
518
556
|
React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
|
|
519
557
|
React.createElement(Tooltip, { title: "Delete" },
|
|
520
558
|
React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))),
|
|
521
|
-
hasCreateAction && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow, "aria-label": 'Add' }, "Add"))),
|
|
522
|
-
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false),
|
|
523
|
-
(dialogType === 'create'
|
|
559
|
+
hasCreateAction && createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow, "aria-label": 'Add' }, "Add"))),
|
|
560
|
+
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), handleSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
|
|
561
|
+
(dialogType === 'create'
|
|
562
|
+
? createActionId
|
|
563
|
+
: dialogType === 'update'
|
|
564
|
+
? updateActionId
|
|
565
|
+
: deleteActionId)), relatedFormId: dialogType === 'create'
|
|
566
|
+
? createForm?.id
|
|
567
|
+
: dialogType === 'update'
|
|
568
|
+
? updateForm?.id
|
|
569
|
+
: dialogType === 'delete'
|
|
570
|
+
? deleteForm?.id
|
|
571
|
+
: undefined, instanceId: selectedRow, relatedParameter: fieldDefinition, associatedObject: instance?.id && fieldDefinition.relatedPropertyId
|
|
572
|
+
? { instanceId: instance.id, propertyId: fieldDefinition.relatedPropertyId }
|
|
573
|
+
: undefined })),
|
|
524
574
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })));
|
|
525
575
|
};
|
|
526
576
|
export default RepeatableField;
|
|
@@ -41,7 +41,7 @@ const UserProperty = (props) => {
|
|
|
41
41
|
}
|
|
42
42
|
}, [id]);
|
|
43
43
|
function handleChangeUserProperty(id, value) {
|
|
44
|
-
const updatedValue = typeof value
|
|
44
|
+
const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
|
|
45
45
|
handleChange(id, updatedValue);
|
|
46
46
|
}
|
|
47
47
|
return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), PaperComponent: ({ children }) => {
|
|
@@ -193,7 +193,7 @@ const InstanceLookup = (props) => {
|
|
|
193
193
|
setRows([]);
|
|
194
194
|
}
|
|
195
195
|
}, [filter, searchString.length]);
|
|
196
|
-
return (React.createElement(Grid, { container: true, sx: {
|
|
196
|
+
return (React.createElement(Grid, { container: true, sx: { padding: '30px 24px' } },
|
|
197
197
|
React.createElement(Grid, { item: true, xs: 12 }, searchableColumns.length ? (React.createElement(SearchField, { searchString: searchString, setSearchString: setSearchString, filter: filter, setFilter: setFilter, searchableColumns: searchableColumns })) : (React.createElement(Typography, { sx: { fontSize: '16px', fontWeight: '700' } }, "There are no searchable properties configured for this object"))),
|
|
198
198
|
React.createElement(BuilderGrid, { item: 'instances', rows: rows, columns: retrieveColumns(layout), onRowClick: (params) => setSelectedInstance(params.row), initialSort: {
|
|
199
199
|
field: object.viewLayout?.table?.sort?.colId ?? 'name',
|