@evoke-platform/ui-components 1.10.0-testing.9 → 1.10.0
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/Autocomplete/Autocomplete.js +4 -2
- package/dist/published/components/core/Autocomplete/Autocomplete.test.js +112 -3
- package/dist/published/components/core/TextField/TextField.js +1 -1
- package/dist/published/components/core/TextField/TextField.test.js +0 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +24 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +45 -2
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +2 -1
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/tests/Form.test.js +0 -2
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +36 -7
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormField/FormField.d.ts +3 -1
- package/dist/published/components/custom/FormField/FormField.js +17 -5
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -4
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js +0 -2
- package/dist/published/components/custom/FormField/Select/Select.test.js +0 -2
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +46 -8
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +178 -153
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
- package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +8 -5
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +6 -23
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +22 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +115 -87
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +43 -20
- package/dist/published/components/custom/FormV2/components/Header.d.ts +5 -3
- package/dist/published/components/custom/FormV2/components/Header.js +47 -9
- package/dist/published/components/custom/FormV2/components/PropertyProtection.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/PropertyProtection.js +113 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +47 -24
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
- package/dist/published/components/custom/FormV2/components/types.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
- package/dist/published/components/custom/FormV2/components/utils.js +83 -13
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +411 -44
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +983 -16
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +165 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +144 -0
- package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
- package/dist/published/components/custom/index.d.ts +2 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +6 -6
- package/dist/published/index.js +1 -1
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
- package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
- package/dist/published/stories/FormRendererContainer.stories.js +5 -0
- package/dist/published/stories/FormRendererData.d.ts +12 -0
- package/dist/published/stories/FormRendererData.js +26 -1
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
- package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
- package/dist/published/stories/ViewDetailsV2Data.js +203 -0
- package/dist/published/stories/sharedMswHandlers.js +49 -10
- package/dist/published/theme/hooks.d.ts +4 -3
- package/dist/published/types.d.ts +3 -0
- package/package.json +10 -8
|
@@ -7,15 +7,14 @@ import { Close } from '../../../../../../icons';
|
|
|
7
7
|
import { useFormContext } from '../../../../../../theme/hooks';
|
|
8
8
|
import { Autocomplete, Button, IconButton, Link, ListItem, Paper, Snackbar, TextField, Tooltip, Typography, } from '../../../../../core';
|
|
9
9
|
import { Box } from '../../../../../layout';
|
|
10
|
-
import {
|
|
10
|
+
import { getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
|
|
11
11
|
import RelatedObjectInstance from './RelatedObjectInstance';
|
|
12
12
|
const ObjectPropertyInput = (props) => {
|
|
13
|
-
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, } = props;
|
|
14
|
-
const { fetchedOptions, setFetchedOptions, parameters, fieldHeight, handleChange: handleChangeObjectField, instance, } = useFormContext();
|
|
13
|
+
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, relatedObjectId, } = props;
|
|
14
|
+
const { fetchedOptions, setFetchedOptions, parameters, fieldHeight, handleChange: handleChangeObjectField, onAutosave: onAutosave, instance, } = useFormContext();
|
|
15
15
|
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
16
16
|
const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
|
|
17
17
|
const [openCreateDialog, setOpenCreateDialog] = useState(false);
|
|
18
|
-
const [allDefaultPages, setAllDefaultPages] = useState(defaultPages ?? {});
|
|
19
18
|
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
20
19
|
const [navigationSlug, setNavigationSlug] = useState(fetchedOptions[`${id}NavigationSlug`]);
|
|
21
20
|
const [relatedObject, setRelatedObject] = useState(fetchedOptions[`${id}RelatedObject`]);
|
|
@@ -24,7 +23,6 @@ const ObjectPropertyInput = (props) => {
|
|
|
24
23
|
const [hasFetched, setHasFetched] = useState(fetchedOptions[`${id}OptionsHaveFetched`] || false);
|
|
25
24
|
const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
|
|
26
25
|
const [layout, setLayout] = useState();
|
|
27
|
-
const [appId, setAppId] = useState(fetchedOptions[`${id}AppId`]);
|
|
28
26
|
const [form, setForm] = useState();
|
|
29
27
|
const [snackbarError, setSnackbarError] = useState({
|
|
30
28
|
showAlert: false,
|
|
@@ -90,20 +88,33 @@ const ObjectPropertyInput = (props) => {
|
|
|
90
88
|
});
|
|
91
89
|
if (updatedFilter.where) {
|
|
92
90
|
setLoadingOptions(true);
|
|
93
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
91
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), async (error, instances) => {
|
|
94
92
|
if (error) {
|
|
95
93
|
console.error(error);
|
|
96
94
|
setLoadingOptions(false);
|
|
97
95
|
}
|
|
98
96
|
if (instances && instances.length > 0) {
|
|
99
97
|
setSelectedInstance(instances[0]);
|
|
100
|
-
|
|
98
|
+
try {
|
|
99
|
+
handleChangeObjectField && (await handleChangeObjectField(id, instances[0]));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.error('Failed to update field:', error);
|
|
103
|
+
setLoadingOptions(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await onAutosave?.(id);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error('Autosave failed:', error);
|
|
111
|
+
}
|
|
101
112
|
}
|
|
102
113
|
setLoadingOptions(false);
|
|
103
114
|
});
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
|
-
}, [
|
|
117
|
+
}, [relatedObjectId, defaultValueCriteria, sortBy, orderBy]);
|
|
107
118
|
const getDropdownOptions = useCallback(() => {
|
|
108
119
|
if (((!fetchedOptions?.[`${id}Options`] ||
|
|
109
120
|
(fetchedOptions?.[`${id}Options`]).length === 0) &&
|
|
@@ -118,7 +129,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
118
129
|
direction: 'asc',
|
|
119
130
|
};
|
|
120
131
|
updatedFilter.order = `${propertyId} ${direction}`;
|
|
121
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
132
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${JSON.stringify(updatedFilter)}`), (error, instances) => {
|
|
122
133
|
if (error) {
|
|
123
134
|
console.error(error);
|
|
124
135
|
setLoadingOptions(false);
|
|
@@ -131,7 +142,15 @@ const ObjectPropertyInput = (props) => {
|
|
|
131
142
|
}
|
|
132
143
|
});
|
|
133
144
|
}
|
|
134
|
-
}, [
|
|
145
|
+
}, [
|
|
146
|
+
relatedObjectId,
|
|
147
|
+
updatedCriteria,
|
|
148
|
+
layout,
|
|
149
|
+
fetchedOptions?.[`${id}Options`],
|
|
150
|
+
fetchedOptions?.[`${id}UpdatedCriteria`],
|
|
151
|
+
hasFetched,
|
|
152
|
+
id,
|
|
153
|
+
]);
|
|
135
154
|
const debouncedGetDropdownOptions = useCallback(debounce(getDropdownOptions, 200), [getDropdownOptions]);
|
|
136
155
|
useEffect(() => {
|
|
137
156
|
if (displayOption === 'dropdown') {
|
|
@@ -143,41 +162,31 @@ const ObjectPropertyInput = (props) => {
|
|
|
143
162
|
setSelectedInstance(initialValue);
|
|
144
163
|
}, [initialValue]);
|
|
145
164
|
useEffect(() => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else if (action) {
|
|
157
|
-
apiServices
|
|
158
|
-
.get(getPrefixedUrl('/forms'), {
|
|
159
|
-
params: {
|
|
160
|
-
filter: {
|
|
161
|
-
where: {
|
|
162
|
-
actionId: action.id,
|
|
163
|
-
objectId: fieldDefinition.objectId,
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
})
|
|
168
|
-
.then((matchingForms) => {
|
|
169
|
-
if (matchingForms.length === 1) {
|
|
170
|
-
setForm(matchingForms[0]);
|
|
165
|
+
// Early return if already fetched
|
|
166
|
+
if (fetchedOptions[`${id}Form`])
|
|
167
|
+
return;
|
|
168
|
+
const fetchForm = async () => {
|
|
169
|
+
try {
|
|
170
|
+
let evokeForm;
|
|
171
|
+
if (formId || action?.defaultFormId) {
|
|
172
|
+
evokeForm = await apiServices.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`));
|
|
171
173
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
if (evokeForm) {
|
|
175
|
+
setForm(evokeForm);
|
|
176
|
+
setFetchedOptions({
|
|
177
|
+
[`${id}Form`]: evokeForm,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
console.error('Error fetching form:', error);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
fetchForm();
|
|
186
|
+
}, [action, formId, id, apiServices, fetchedOptions]);
|
|
178
187
|
useEffect(() => {
|
|
179
188
|
if (!fetchedOptions[`${id}RelatedObject`]) {
|
|
180
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
189
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/effective?sanitizedVersion=true`), (error, object) => {
|
|
181
190
|
if (error) {
|
|
182
191
|
console.error(error);
|
|
183
192
|
}
|
|
@@ -186,33 +195,26 @@ const ObjectPropertyInput = (props) => {
|
|
|
186
195
|
}
|
|
187
196
|
});
|
|
188
197
|
}
|
|
189
|
-
}, [
|
|
198
|
+
}, [relatedObjectId, fetchedOptions, id]);
|
|
190
199
|
useEffect(() => {
|
|
191
|
-
|
|
192
|
-
if (parameters &&
|
|
200
|
+
(async () => {
|
|
201
|
+
if (parameters && fetchedOptions[`${id}NavigationSlug`] === undefined) {
|
|
193
202
|
const pages = await getDefaultPages(parameters, defaultPages, findDefaultPageSlugFor);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}, [fetchedOptions, parameters, defaultPages, findDefaultPageSlugFor]);
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
if (fieldDefinition.objectId &&
|
|
202
|
-
allDefaultPages &&
|
|
203
|
-
allDefaultPages[fieldDefinition.objectId] &&
|
|
204
|
-
(!fetchedOptions?.[`${id}NavigationSlug`] || !fetchedOptions[`${id}AppId`])) {
|
|
205
|
-
apiServices.get(getPrefixedUrl(`/apps/${allDefaultPages[fieldDefinition.objectId].split('/').slice(1, 2)}/pages/${encodePageSlug(allDefaultPages[fieldDefinition.objectId].split('/').slice(2).join('/'))}`), (error, page) => {
|
|
206
|
-
if (error) {
|
|
207
|
-
console.error(error);
|
|
203
|
+
if (relatedObjectId && pages[relatedObjectId]) {
|
|
204
|
+
setNavigationSlug(pages[relatedObjectId]);
|
|
205
|
+
setFetchedOptions({
|
|
206
|
+
[`${id}NavigationSlug`]: pages[relatedObjectId],
|
|
207
|
+
});
|
|
208
208
|
}
|
|
209
209
|
else {
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
// setting the nav slug to null if there is no default page for this object to avoid re-fetching
|
|
211
|
+
setFetchedOptions({
|
|
212
|
+
[`${id}NavigationSlug`]: null,
|
|
213
|
+
});
|
|
212
214
|
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}, [
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
}, [parameters, defaultPages, findDefaultPageSlugFor, relatedObjectId, fetchedOptions]);
|
|
216
218
|
const handleClose = () => {
|
|
217
219
|
setOpenCreateDialog(false);
|
|
218
220
|
};
|
|
@@ -240,12 +242,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
240
242
|
[`${id}NavigationSlug`]: navigationSlug,
|
|
241
243
|
});
|
|
242
244
|
}
|
|
243
|
-
|
|
244
|
-
setFetchedOptions({
|
|
245
|
-
[`${id}AppId`]: appId,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, appId, id]);
|
|
245
|
+
}, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, id]);
|
|
249
246
|
const dropdownOptions = [
|
|
250
247
|
...options.map((o) => ({ label: o.name, value: o.id })),
|
|
251
248
|
...(mode !== 'existingOnly' && relatedObject?.actions?.some((a) => a.id === createActionId)
|
|
@@ -383,19 +380,38 @@ const ObjectPropertyInput = (props) => {
|
|
|
383
380
|
if (selectedInstance?.id) {
|
|
384
381
|
e.preventDefault();
|
|
385
382
|
}
|
|
386
|
-
}, loading: loadingOptions, onChange: (event, value) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
383
|
+
}, loading: loadingOptions, onChange: async (event, value) => {
|
|
384
|
+
try {
|
|
385
|
+
if (isNil(value)) {
|
|
386
|
+
setDropdownInput(undefined);
|
|
387
|
+
setSelectedInstance(undefined);
|
|
388
|
+
handleChangeObjectField && (await handleChangeObjectField(id, null));
|
|
389
|
+
// Trigger autosave immediately upon clearing
|
|
390
|
+
try {
|
|
391
|
+
await onAutosave?.(id);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.error('Autosave failed:', error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (value?.value === '__new__') {
|
|
398
|
+
setOpenCreateDialog(true);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const selectedInstance = options.find((o) => o.id === value?.value);
|
|
402
|
+
setSelectedInstance(selectedInstance);
|
|
403
|
+
handleChangeObjectField && (await handleChangeObjectField(id, selectedInstance));
|
|
404
|
+
// Trigger autosave immediately upon selection
|
|
405
|
+
try {
|
|
406
|
+
await onAutosave?.(id);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
console.error('Autosave failed:', error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
394
412
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
setSelectedInstance(selectedInstance);
|
|
398
|
-
handleChangeObjectField(id, selectedInstance);
|
|
413
|
+
catch (error) {
|
|
414
|
+
console.error('Failed to update field:', error);
|
|
399
415
|
}
|
|
400
416
|
}, selectOnFocus: false, onBlur: () => {
|
|
401
417
|
if (dropdownInput) {
|
|
@@ -434,7 +450,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
434
450
|
...params.InputProps,
|
|
435
451
|
startAdornment: selectedInstance?.id ? (React.createElement(Typography, { onClick: () => {
|
|
436
452
|
if (navigationSlug && selectedInstance?.id) {
|
|
437
|
-
navigateTo(`/${
|
|
453
|
+
navigateTo(`/${navigationSlug.replace(':instanceId', selectedInstance.id)}`);
|
|
438
454
|
}
|
|
439
455
|
}, sx: {
|
|
440
456
|
cursor: navigationSlug ? 'pointer' : 'default',
|
|
@@ -464,13 +480,25 @@ const ObjectPropertyInput = (props) => {
|
|
|
464
480
|
? '#999'
|
|
465
481
|
: '#212B36',
|
|
466
482
|
}, variant: "body2", href: navigationSlug && !isModal
|
|
467
|
-
? `${'/app'}
|
|
483
|
+
? `${'/app'}${navigationSlug.replace(':instanceId', selectedInstance?.id ?? '')}`
|
|
468
484
|
: undefined, "aria-label": selectedInstance?.name }, selectedInstance?.name ? selectedInstance?.name : readOnly && 'None')),
|
|
469
485
|
!readOnly && selectedInstance ? (React.createElement(Tooltip, { title: `Unlink` },
|
|
470
486
|
React.createElement("span", null,
|
|
471
|
-
React.createElement(IconButton, { onClick: (event) => {
|
|
487
|
+
React.createElement(IconButton, { onClick: async (event) => {
|
|
472
488
|
event.stopPropagation();
|
|
473
|
-
|
|
489
|
+
try {
|
|
490
|
+
handleChangeObjectField && (await handleChangeObjectField(id, null));
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
console.error('Failed to update field:', error);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
await onAutosave?.(id);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
console.error('Autosave failed:', error);
|
|
501
|
+
}
|
|
474
502
|
setSelectedInstance(undefined);
|
|
475
503
|
}, sx: { p: 0, marginBottom: '4px' }, "aria-label": `Unlink` },
|
|
476
504
|
React.createElement(Close, { sx: { width: '20px', height: '20px' } }))))) : (React.createElement(Button, { sx: {
|
|
@@ -494,7 +522,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
494
522
|
event.stopPropagation();
|
|
495
523
|
setOpenCreateDialog(true);
|
|
496
524
|
}, "aria-label": `Add` }, "Add")))),
|
|
497
|
-
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: formId ?? form?.id, actionId: createActionId, setSnackbarError: setSnackbarError
|
|
525
|
+
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: formId ?? form?.id, actionId: createActionId, setSnackbarError: setSnackbarError }),
|
|
498
526
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
499
527
|
isError: snackbarError.isError,
|
|
500
528
|
showAlert: false,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Obj, ObjectInstance, TableViewLayout } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { BaseProps } from '../../types';
|
|
4
4
|
export type RelatedObjectInstanceProps = BaseProps & {
|
|
5
5
|
id: string;
|
|
6
6
|
open: boolean;
|
|
7
7
|
title: string;
|
|
8
|
-
relatedObject
|
|
8
|
+
relatedObject?: Obj;
|
|
9
9
|
setSelectedInstance: (selectedInstance: ObjectInstance) => void;
|
|
10
10
|
handleClose: () => void;
|
|
11
11
|
mode: 'default' | 'existingOnly' | 'newOnly';
|
|
@@ -21,7 +21,6 @@ export type RelatedObjectInstanceProps = BaseProps & {
|
|
|
21
21
|
layout?: TableViewLayout;
|
|
22
22
|
formId?: string;
|
|
23
23
|
actionId?: string;
|
|
24
|
-
fieldDefinition: InputParameter;
|
|
25
24
|
};
|
|
26
25
|
declare const RelatedObjectInstance: (props: RelatedObjectInstanceProps) => React.JSX.Element;
|
|
27
26
|
export default RelatedObjectInstance;
|
|
@@ -6,6 +6,7 @@ import { Close } from '../../../../../../icons';
|
|
|
6
6
|
import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
|
|
7
7
|
import { Button, Dialog, DialogContent, DialogTitle, FormControlLabel, IconButton, Radio, RadioGroup, } from '../../../../../core';
|
|
8
8
|
import Box from '../../../../../layout/Box/Box';
|
|
9
|
+
import ErrorComponent from '../../../../ErrorComponent';
|
|
9
10
|
import FormRenderer from '../../../FormRenderer';
|
|
10
11
|
import FormRendererContainer from '../../../FormRendererContainer';
|
|
11
12
|
import Body from '../../Body';
|
|
@@ -28,8 +29,8 @@ const styles = {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
const RelatedObjectInstance = (props) => {
|
|
31
|
-
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId,
|
|
32
|
-
const { handleChange: handleChangeObjectField, richTextEditor, fieldHeight, width } = useFormContext();
|
|
32
|
+
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId, setSnackbarError, setOptions, options, } = props;
|
|
33
|
+
const { handleChange: handleChangeObjectField, onAutosave, richTextEditor, fieldHeight, width } = useFormContext();
|
|
33
34
|
const [selectedRow, setSelectedRow] = useState();
|
|
34
35
|
const [relationType, setRelationType] = useState(displayOption === 'dropdown' || mode === 'newOnly' ? 'new' : 'existing');
|
|
35
36
|
const apiServices = useApiServices();
|
|
@@ -40,9 +41,22 @@ const RelatedObjectInstance = (props) => {
|
|
|
40
41
|
});
|
|
41
42
|
const { isXs, isSm } = breakpoints;
|
|
42
43
|
const linkExistingInstance = async () => {
|
|
43
|
-
if (selectedRow) {
|
|
44
|
+
if (selectedRow && handleChangeObjectField) {
|
|
44
45
|
setSelectedInstance(selectedRow);
|
|
45
|
-
|
|
46
|
+
try {
|
|
47
|
+
await handleChangeObjectField(id, selectedRow);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error('Failed to update field:', error);
|
|
51
|
+
onClose();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await onAutosave?.(id);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Autosave failed:', error);
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
onClose();
|
|
48
62
|
};
|
|
@@ -56,22 +70,31 @@ const RelatedObjectInstance = (props) => {
|
|
|
56
70
|
}
|
|
57
71
|
submission = await formatSubmission(submission, apiServices, relatedObject.id);
|
|
58
72
|
try {
|
|
59
|
-
await apiServices
|
|
60
|
-
.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
|
|
73
|
+
const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
|
|
61
74
|
actionId: actionId,
|
|
62
75
|
input: submission,
|
|
63
|
-
})
|
|
64
|
-
.then((response) => {
|
|
65
|
-
handleChangeObjectField(id, response);
|
|
66
|
-
setSelectedInstance(response);
|
|
67
|
-
setSnackbarError({
|
|
68
|
-
showAlert: true,
|
|
69
|
-
message: 'New instance created',
|
|
70
|
-
isError: false,
|
|
71
|
-
});
|
|
72
|
-
setOptions(options.concat([response]));
|
|
73
|
-
onClose();
|
|
74
76
|
});
|
|
77
|
+
try {
|
|
78
|
+
handleChangeObjectField && (await handleChangeObjectField(id, response));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error('Failed to update field:', error);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await onAutosave?.(id);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error('Autosave failed:', error);
|
|
89
|
+
}
|
|
90
|
+
setSelectedInstance(response);
|
|
91
|
+
setSnackbarError({
|
|
92
|
+
showAlert: true,
|
|
93
|
+
message: 'New instance created',
|
|
94
|
+
isError: false,
|
|
95
|
+
});
|
|
96
|
+
setOptions(options.concat([response]));
|
|
97
|
+
onClose();
|
|
75
98
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
99
|
}
|
|
77
100
|
catch (err) {
|
|
@@ -98,7 +121,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
98
121
|
}, value: relationType },
|
|
99
122
|
React.createElement(FormControlLabel, { value: "existing", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "Existing" }),
|
|
100
123
|
React.createElement(FormControlLabel, { value: "new", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "New" }))) : null;
|
|
101
|
-
const DialogForm = useCallback(() => (React.createElement(FormRendererContainer, { formId: formId, display: { fieldHeight: fieldHeight ?? 'medium' }, actionId: actionId, objectId:
|
|
124
|
+
const DialogForm = useCallback(() => (React.createElement(FormRendererContainer, { formId: formId, display: { fieldHeight: fieldHeight ?? 'medium' }, actionId: actionId, objectId: relatedObject.id, onSubmit: createNewInstance, onDiscardChanges: onClose, onSubmitError: handleSubmitError, richTextEditor: richTextEditor, renderHeader: () => null, renderBody: (bodyProps) => (React.createElement(DialogContent, { sx: styles.dialogContent },
|
|
102
125
|
relationType === 'new' ? (React.createElement("div", { ref: validationErrorsRef }, !isEmpty(bodyProps.errors) && bodyProps.shouldShowValidationErrors ? (React.createElement(FormRenderer.ValidationErrors, { errors: bodyProps.errors, sx: {
|
|
103
126
|
my: isSm || isXs ? 2 : 3,
|
|
104
127
|
} })) : null)) : null,
|
|
@@ -114,7 +137,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
114
137
|
React.createElement(RadioButtons, null),
|
|
115
138
|
defaultContainer)),
|
|
116
139
|
status === 'ready' && defaultContainer));
|
|
117
|
-
}, sx: { border: 'none' } })), [formId, actionId,
|
|
140
|
+
}, sx: { border: 'none' } })), [formId, actionId, relatedObject, fieldHeight, richTextEditor, RadioButtons]);
|
|
118
141
|
return (React.createElement(Dialog, { fullWidth: true, maxWidth: "md", open: open, onClose: (e, reason) => reason !== 'backdropClick' && handleClose(), sx: {
|
|
119
142
|
background: 'none',
|
|
120
143
|
}, PaperProps: {
|
|
@@ -137,7 +160,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
137
160
|
title,
|
|
138
161
|
React.createElement(IconButton, { onClick: onClose, "aria-label": "Close" },
|
|
139
162
|
React.createElement(Close, { fontSize: "small" })))),
|
|
140
|
-
relationType === 'new' ? (React.createElement(DialogForm, null)) : ((mode === 'default' || mode === 'existingOnly') &&
|
|
163
|
+
relationType === 'new' ? (relatedObject ? (React.createElement(DialogForm, null)) : (React.createElement(ErrorComponent, { code: "Misconfigured" }))) : ((mode === 'default' || mode === 'existingOnly') &&
|
|
141
164
|
relatedObject && (React.createElement(React.Fragment, null,
|
|
142
165
|
React.createElement(DialogContent, { sx: styles.dialogContent },
|
|
143
166
|
shouldShowRadioButtons && React.createElement(RadioButtons, null),
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { Action
|
|
1
|
+
import { Action } from '@evoke-platform/context';
|
|
2
2
|
import { SxProps } from '@mui/material';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { FieldErrors } from 'react-hook-form';
|
|
5
5
|
import { ExpandedSection } from './types';
|
|
6
6
|
export type HeaderProps = {
|
|
7
7
|
hasAccordions: boolean;
|
|
8
|
-
shouldShowValidationErrors
|
|
8
|
+
shouldShowValidationErrors?: boolean;
|
|
9
|
+
validationContainerRef?: React.Ref<HTMLDivElement>;
|
|
9
10
|
title?: string;
|
|
10
11
|
expandedSections?: ExpandedSection[];
|
|
11
12
|
onExpandAll?: () => void;
|
|
12
13
|
onCollapseAll?: () => void;
|
|
13
|
-
form: EvokeForm;
|
|
14
14
|
errors?: FieldErrors;
|
|
15
15
|
action?: Action;
|
|
16
|
+
autosaving?: boolean;
|
|
16
17
|
sx?: SxProps;
|
|
18
|
+
autosaveEnabled?: boolean;
|
|
17
19
|
};
|
|
18
20
|
declare const Header: React.FC<HeaderProps>;
|
|
19
21
|
export type TitleProps = {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { isEmpty } from 'lodash';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { Autorenew } from '../../../../icons';
|
|
3
4
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
4
5
|
import Button from '../../../core/Button/Button';
|
|
5
6
|
import { Typography } from '../../../core/Typography';
|
|
6
7
|
import Box from '../../../layout/Box/Box';
|
|
7
8
|
import ValidationErrors from './ValidationFiles/ValidationErrors';
|
|
8
9
|
const Header = (props) => {
|
|
9
|
-
const { title, errors, hasAccordions, shouldShowValidationErrors,
|
|
10
|
+
const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, autosaveEnabled } = props;
|
|
10
11
|
const { width } = useFormContext();
|
|
11
12
|
const { breakpoints, isBelow } = useWidgetSize({
|
|
12
13
|
scroll: false,
|
|
@@ -14,6 +15,8 @@ const Header = (props) => {
|
|
|
14
15
|
});
|
|
15
16
|
const isSmallerThanMd = isBelow('md');
|
|
16
17
|
const { isXs, isSm } = breakpoints;
|
|
18
|
+
const isSmall = isSm || isXs;
|
|
19
|
+
const displayValidationErrors = shouldShowValidationErrors && !isEmpty(errors);
|
|
17
20
|
return (React.createElement(Box, { sx: {
|
|
18
21
|
paddingX: isSmallerThanMd ? 2 : 3,
|
|
19
22
|
paddingTop: '0px',
|
|
@@ -21,16 +24,25 @@ const Header = (props) => {
|
|
|
21
24
|
alignItems: 'center',
|
|
22
25
|
flexWrap: 'wrap',
|
|
23
26
|
paddingY: isSm || isXs ? 2 : 3,
|
|
24
|
-
|
|
25
|
-
borderBottom: !form.id ? undefined : '1px solid #e9ecef',
|
|
27
|
+
borderBottom: '1px solid #e9ecef',
|
|
26
28
|
gap: isSm || isXs ? 2 : 3,
|
|
27
29
|
...sx,
|
|
28
|
-
} },
|
|
29
|
-
title && (React.createElement(Box, { sx: { flex: '1 1
|
|
30
|
-
React.createElement(Title, { ...props })
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
}, ref: validationContainerRef },
|
|
31
|
+
title && (React.createElement(Box, { sx: { flex: '1 1 auto', minWidth: 0, display: 'flex', alignItems: 'center', gap: 1 } },
|
|
32
|
+
React.createElement(Title, { ...props }),
|
|
33
|
+
props.autosaving && !isSmall && React.createElement(SavingIndicator, null))),
|
|
34
|
+
hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
|
|
35
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
36
|
+
React.createElement(AccordionActions, { ...props })),
|
|
37
|
+
autosaveEnabled && (React.createElement(Box, { sx: {
|
|
38
|
+
width: '96px',
|
|
39
|
+
minWidth: '72px',
|
|
40
|
+
display: 'flex',
|
|
41
|
+
justifyContent: 'flex-end',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
marginLeft: 0.5,
|
|
44
|
+
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } }))))),
|
|
45
|
+
displayValidationErrors ? React.createElement(ValidationErrors, { errors: errors }) : null));
|
|
34
46
|
};
|
|
35
47
|
// Default slot components for convenience
|
|
36
48
|
export const Title = ({ title }) => (React.createElement(Typography, { sx: {
|
|
@@ -60,4 +72,30 @@ export const AccordionActions = ({ onExpandAll, onCollapseAll, expandedSections
|
|
|
60
72
|
fontSize: '14px',
|
|
61
73
|
} }, "Collapse all")));
|
|
62
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* SavingIndicator displays a spinning icon with "Saving" text
|
|
77
|
+
* to indicate that an autosave operation is in progress.
|
|
78
|
+
*/
|
|
79
|
+
const SavingIndicator = () => (React.createElement(Box, { sx: {
|
|
80
|
+
display: 'flex',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
gap: 0.5,
|
|
83
|
+
} },
|
|
84
|
+
React.createElement(Typography, { sx: {
|
|
85
|
+
fontSize: '14px',
|
|
86
|
+
color: 'text.secondary',
|
|
87
|
+
} }, "Saving"),
|
|
88
|
+
React.createElement(Autorenew, { sx: {
|
|
89
|
+
fontSize: '16px',
|
|
90
|
+
color: 'text.secondary',
|
|
91
|
+
animation: 'spin 1s linear infinite',
|
|
92
|
+
'@keyframes spin': {
|
|
93
|
+
'0%': {
|
|
94
|
+
transform: 'rotate(0deg)',
|
|
95
|
+
},
|
|
96
|
+
'100%': {
|
|
97
|
+
transform: 'rotate(360deg)',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
} })));
|
|
63
101
|
export default Header;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PropertyProtection as PropertyProtectionType } from '@evoke-platform/context';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ObjectProperty } from '../../../../types';
|
|
4
|
+
type PropertyProtectionProps = {
|
|
5
|
+
parameter: ObjectProperty;
|
|
6
|
+
protection?: PropertyProtectionType;
|
|
7
|
+
mask?: string;
|
|
8
|
+
canEdit: boolean;
|
|
9
|
+
value: unknown;
|
|
10
|
+
handleChange?: (value: unknown) => void;
|
|
11
|
+
setCurrentDisplayValue: (value: unknown) => void;
|
|
12
|
+
mode: 'mask' | 'full' | 'edit';
|
|
13
|
+
setMode: (mode: 'mask' | 'full' | 'edit') => void;
|
|
14
|
+
};
|
|
15
|
+
declare const PropertyProtection: React.FC<PropertyProtectionProps>;
|
|
16
|
+
export default PropertyProtection;
|