@evoke-platform/ui-components 1.10.0-testing.9 → 1.10.1-dev.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 +9 -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 +127 -92
- 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
|
@@ -6,7 +6,7 @@ import { Autocomplete, IconButton, Paper, TextField, Typography } from '../../..
|
|
|
6
6
|
import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
|
|
7
7
|
const UserProperty = (props) => {
|
|
8
8
|
const { id, error, value, readOnly, hasDescription } = props;
|
|
9
|
-
const { fetchedOptions, setFetchedOptions, handleChange, fieldHeight } = useFormContext();
|
|
9
|
+
const { fetchedOptions, setFetchedOptions, handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
|
|
10
10
|
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
11
11
|
const apiServices = useApiServices();
|
|
12
12
|
const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
|
|
@@ -40,9 +40,21 @@ const UserProperty = (props) => {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
}, [id]);
|
|
43
|
-
function handleChangeUserProperty(id, value) {
|
|
43
|
+
async function handleChangeUserProperty(id, value) {
|
|
44
44
|
const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
handleChange && (await handleChange(id, updatedValue));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Failed to update field:', error);
|
|
50
|
+
return; // Exit early if handleChange fails
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await onAutosave?.(id);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('Autosave failed:', error);
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), clearIcon: !loadingOptions && userValue ? (React.createElement(IconButton, { size: "small", disableRipple: true, onKeyDown: (e) => {
|
|
48
60
|
if (e.key === 'Enter') {
|
|
@@ -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`]);
|
|
@@ -23,8 +22,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
23
22
|
const [openOptions, setOpenOptions] = useState(false);
|
|
24
23
|
const [hasFetched, setHasFetched] = useState(fetchedOptions[`${id}OptionsHaveFetched`] || false);
|
|
25
24
|
const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
|
|
26
|
-
const [layout, setLayout] = useState();
|
|
27
|
-
const [appId, setAppId] = useState(fetchedOptions[`${id}AppId`]);
|
|
25
|
+
const [layout, setLayout] = useState(fetchedOptions[`${id}ViewLayout`]);
|
|
28
26
|
const [form, setForm] = useState();
|
|
29
27
|
const [snackbarError, setSnackbarError] = useState({
|
|
30
28
|
showAlert: false,
|
|
@@ -70,10 +68,17 @@ const ObjectPropertyInput = (props) => {
|
|
|
70
68
|
};
|
|
71
69
|
}
|
|
72
70
|
if (viewLayout) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
if (!fetchedOptions[`${id}ViewLayout`]) {
|
|
72
|
+
apiServices
|
|
73
|
+
.get(getPrefixedUrl(`/objects/${viewLayout.objectId}/${displayOption === 'dropdown' ? 'dropdown' : 'table'}Layouts/${viewLayout.id}`))
|
|
74
|
+
.then((layout) => {
|
|
75
|
+
setLayout(layout);
|
|
76
|
+
setFetchedOptions({
|
|
77
|
+
[`${id}ViewLayout`]: layout,
|
|
78
|
+
});
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => setLayout(defaultViewLayout));
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
else {
|
|
79
84
|
setLayout(defaultViewLayout);
|
|
@@ -90,20 +95,33 @@ const ObjectPropertyInput = (props) => {
|
|
|
90
95
|
});
|
|
91
96
|
if (updatedFilter.where) {
|
|
92
97
|
setLoadingOptions(true);
|
|
93
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
98
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), async (error, instances) => {
|
|
94
99
|
if (error) {
|
|
95
100
|
console.error(error);
|
|
96
101
|
setLoadingOptions(false);
|
|
97
102
|
}
|
|
98
103
|
if (instances && instances.length > 0) {
|
|
99
104
|
setSelectedInstance(instances[0]);
|
|
100
|
-
|
|
105
|
+
try {
|
|
106
|
+
handleChangeObjectField && (await handleChangeObjectField(id, instances[0]));
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('Failed to update field:', error);
|
|
110
|
+
setLoadingOptions(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await onAutosave?.(id);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error('Autosave failed:', error);
|
|
118
|
+
}
|
|
101
119
|
}
|
|
102
120
|
setLoadingOptions(false);
|
|
103
121
|
});
|
|
104
122
|
}
|
|
105
123
|
}
|
|
106
|
-
}, [
|
|
124
|
+
}, [relatedObjectId, defaultValueCriteria, sortBy, orderBy]);
|
|
107
125
|
const getDropdownOptions = useCallback(() => {
|
|
108
126
|
if (((!fetchedOptions?.[`${id}Options`] ||
|
|
109
127
|
(fetchedOptions?.[`${id}Options`]).length === 0) &&
|
|
@@ -118,7 +136,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
118
136
|
direction: 'asc',
|
|
119
137
|
};
|
|
120
138
|
updatedFilter.order = `${propertyId} ${direction}`;
|
|
121
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
139
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${JSON.stringify(updatedFilter)}`), (error, instances) => {
|
|
122
140
|
if (error) {
|
|
123
141
|
console.error(error);
|
|
124
142
|
setLoadingOptions(false);
|
|
@@ -131,7 +149,15 @@ const ObjectPropertyInput = (props) => {
|
|
|
131
149
|
}
|
|
132
150
|
});
|
|
133
151
|
}
|
|
134
|
-
}, [
|
|
152
|
+
}, [
|
|
153
|
+
relatedObjectId,
|
|
154
|
+
updatedCriteria,
|
|
155
|
+
layout,
|
|
156
|
+
fetchedOptions?.[`${id}Options`],
|
|
157
|
+
fetchedOptions?.[`${id}UpdatedCriteria`],
|
|
158
|
+
hasFetched,
|
|
159
|
+
id,
|
|
160
|
+
]);
|
|
135
161
|
const debouncedGetDropdownOptions = useCallback(debounce(getDropdownOptions, 200), [getDropdownOptions]);
|
|
136
162
|
useEffect(() => {
|
|
137
163
|
if (displayOption === 'dropdown') {
|
|
@@ -143,41 +169,31 @@ const ObjectPropertyInput = (props) => {
|
|
|
143
169
|
setSelectedInstance(initialValue);
|
|
144
170
|
}, [initialValue]);
|
|
145
171
|
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]);
|
|
172
|
+
// Early return if already fetched
|
|
173
|
+
if (fetchedOptions[`${id}Form`])
|
|
174
|
+
return;
|
|
175
|
+
const fetchForm = async () => {
|
|
176
|
+
try {
|
|
177
|
+
let evokeForm;
|
|
178
|
+
if (formId || action?.defaultFormId) {
|
|
179
|
+
evokeForm = await apiServices.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`));
|
|
171
180
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
if (evokeForm) {
|
|
182
|
+
setForm(evokeForm);
|
|
183
|
+
setFetchedOptions({
|
|
184
|
+
[`${id}Form`]: evokeForm,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.error('Error fetching form:', error);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
fetchForm();
|
|
193
|
+
}, [action, formId, id, apiServices, fetchedOptions]);
|
|
178
194
|
useEffect(() => {
|
|
179
195
|
if (!fetchedOptions[`${id}RelatedObject`]) {
|
|
180
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
196
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/effective?sanitizedVersion=true`), (error, object) => {
|
|
181
197
|
if (error) {
|
|
182
198
|
console.error(error);
|
|
183
199
|
}
|
|
@@ -186,33 +202,26 @@ const ObjectPropertyInput = (props) => {
|
|
|
186
202
|
}
|
|
187
203
|
});
|
|
188
204
|
}
|
|
189
|
-
}, [
|
|
205
|
+
}, [relatedObjectId, fetchedOptions, id]);
|
|
190
206
|
useEffect(() => {
|
|
191
|
-
|
|
192
|
-
if (parameters &&
|
|
207
|
+
(async () => {
|
|
208
|
+
if (parameters && fetchedOptions[`${id}NavigationSlug`] === undefined) {
|
|
193
209
|
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);
|
|
210
|
+
if (relatedObjectId && pages[relatedObjectId]) {
|
|
211
|
+
setNavigationSlug(pages[relatedObjectId]);
|
|
212
|
+
setFetchedOptions({
|
|
213
|
+
[`${id}NavigationSlug`]: pages[relatedObjectId],
|
|
214
|
+
});
|
|
208
215
|
}
|
|
209
216
|
else {
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
// setting the nav slug to null if there is no default page for this object to avoid re-fetching
|
|
218
|
+
setFetchedOptions({
|
|
219
|
+
[`${id}NavigationSlug`]: null,
|
|
220
|
+
});
|
|
212
221
|
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}, [
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
224
|
+
}, [parameters, defaultPages, findDefaultPageSlugFor, relatedObjectId, fetchedOptions]);
|
|
216
225
|
const handleClose = () => {
|
|
217
226
|
setOpenCreateDialog(false);
|
|
218
227
|
};
|
|
@@ -240,12 +249,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
240
249
|
[`${id}NavigationSlug`]: navigationSlug,
|
|
241
250
|
});
|
|
242
251
|
}
|
|
243
|
-
|
|
244
|
-
setFetchedOptions({
|
|
245
|
-
[`${id}AppId`]: appId,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, appId, id]);
|
|
252
|
+
}, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, id]);
|
|
249
253
|
const dropdownOptions = [
|
|
250
254
|
...options.map((o) => ({ label: o.name, value: o.id })),
|
|
251
255
|
...(mode !== 'existingOnly' && relatedObject?.actions?.some((a) => a.id === createActionId)
|
|
@@ -383,19 +387,38 @@ const ObjectPropertyInput = (props) => {
|
|
|
383
387
|
if (selectedInstance?.id) {
|
|
384
388
|
e.preventDefault();
|
|
385
389
|
}
|
|
386
|
-
}, loading: loadingOptions, onChange: (event, value) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
}, loading: loadingOptions, onChange: async (event, value) => {
|
|
391
|
+
try {
|
|
392
|
+
if (isNil(value)) {
|
|
393
|
+
setDropdownInput(undefined);
|
|
394
|
+
setSelectedInstance(undefined);
|
|
395
|
+
handleChangeObjectField && (await handleChangeObjectField(id, null));
|
|
396
|
+
// Trigger autosave immediately upon clearing
|
|
397
|
+
try {
|
|
398
|
+
await onAutosave?.(id);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
console.error('Autosave failed:', error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else if (value?.value === '__new__') {
|
|
405
|
+
setOpenCreateDialog(true);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
const selectedInstance = options.find((o) => o.id === value?.value);
|
|
409
|
+
setSelectedInstance(selectedInstance);
|
|
410
|
+
handleChangeObjectField && (await handleChangeObjectField(id, selectedInstance));
|
|
411
|
+
// Trigger autosave immediately upon selection
|
|
412
|
+
try {
|
|
413
|
+
await onAutosave?.(id);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
console.error('Autosave failed:', error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
394
419
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
setSelectedInstance(selectedInstance);
|
|
398
|
-
handleChangeObjectField(id, selectedInstance);
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.error('Failed to update field:', error);
|
|
399
422
|
}
|
|
400
423
|
}, selectOnFocus: false, onBlur: () => {
|
|
401
424
|
if (dropdownInput) {
|
|
@@ -434,7 +457,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
434
457
|
...params.InputProps,
|
|
435
458
|
startAdornment: selectedInstance?.id ? (React.createElement(Typography, { onClick: () => {
|
|
436
459
|
if (navigationSlug && selectedInstance?.id) {
|
|
437
|
-
navigateTo(`/${
|
|
460
|
+
navigateTo(`/${navigationSlug.replace(':instanceId', selectedInstance.id)}`);
|
|
438
461
|
}
|
|
439
462
|
}, sx: {
|
|
440
463
|
cursor: navigationSlug ? 'pointer' : 'default',
|
|
@@ -464,13 +487,25 @@ const ObjectPropertyInput = (props) => {
|
|
|
464
487
|
? '#999'
|
|
465
488
|
: '#212B36',
|
|
466
489
|
}, variant: "body2", href: navigationSlug && !isModal
|
|
467
|
-
? `${'/app'}
|
|
490
|
+
? `${'/app'}${navigationSlug.replace(':instanceId', selectedInstance?.id ?? '')}`
|
|
468
491
|
: undefined, "aria-label": selectedInstance?.name }, selectedInstance?.name ? selectedInstance?.name : readOnly && 'None')),
|
|
469
492
|
!readOnly && selectedInstance ? (React.createElement(Tooltip, { title: `Unlink` },
|
|
470
493
|
React.createElement("span", null,
|
|
471
|
-
React.createElement(IconButton, { onClick: (event) => {
|
|
494
|
+
React.createElement(IconButton, { onClick: async (event) => {
|
|
472
495
|
event.stopPropagation();
|
|
473
|
-
|
|
496
|
+
try {
|
|
497
|
+
handleChangeObjectField && (await handleChangeObjectField(id, null));
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
console.error('Failed to update field:', error);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
await onAutosave?.(id);
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
console.error('Autosave failed:', error);
|
|
508
|
+
}
|
|
474
509
|
setSelectedInstance(undefined);
|
|
475
510
|
}, sx: { p: 0, marginBottom: '4px' }, "aria-label": `Unlink` },
|
|
476
511
|
React.createElement(Close, { sx: { width: '20px', height: '20px' } }))))) : (React.createElement(Button, { sx: {
|
|
@@ -494,7 +529,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
494
529
|
event.stopPropagation();
|
|
495
530
|
setOpenCreateDialog(true);
|
|
496
531
|
}, "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
|
|
532
|
+
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
533
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
499
534
|
isError: snackbarError.isError,
|
|
500
535
|
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;
|