@evoke-platform/ui-components 1.13.0 → 1.15.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/custom/CriteriaBuilder/CriteriaBuilder.js +2 -2
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +0 -15
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +0 -10
- package/dist/published/components/custom/CriteriaBuilder/utils.js +2 -161
- package/dist/published/components/custom/Form/utils.js +3 -2
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +25 -27
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +132 -101
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +105 -185
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +18 -26
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -18
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +96 -169
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +57 -13
- package/dist/published/components/custom/FormV2/components/HtmlView.js +5 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +61 -29
- package/dist/published/components/custom/FormV2/components/utils.d.ts +23 -4
- package/dist/published/components/custom/FormV2/components/utils.js +136 -26
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +28 -14
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +41 -48
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +56 -19
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
- package/dist/published/components/custom/index.d.ts +2 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/components/custom/types.d.ts +15 -0
- package/dist/published/components/custom/types.js +1 -0
- package/dist/published/components/custom/util.d.ts +10 -0
- package/dist/published/components/custom/util.js +161 -1
- package/dist/published/index.d.ts +2 -2
- package/dist/published/index.js +1 -1
- package/package.json +3 -4
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import { Close, ExpandMore } from '@mui/icons-material';
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
4
|
import React, { useEffect, useState } from 'react';
|
|
4
5
|
import { useFormContext } from '../../../../../theme/hooks';
|
|
5
6
|
import { Autocomplete, IconButton, Paper, TextField, Typography } from '../../../../core';
|
|
6
7
|
import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
|
|
7
8
|
const UserProperty = (props) => {
|
|
8
9
|
const { id, error, value, readOnly, hasDescription } = props;
|
|
9
|
-
const {
|
|
10
|
+
const { handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
|
|
10
11
|
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
11
12
|
const apiServices = useApiServices();
|
|
12
|
-
const [options, setOptions] = useState(
|
|
13
|
+
const [options, setOptions] = useState([]);
|
|
13
14
|
const [openOptions, setOpenOptions] = useState(false);
|
|
14
|
-
const [users, setUsers] = useState();
|
|
15
15
|
const [userValue, setUserValue] = useState();
|
|
16
16
|
useEffect(() => {
|
|
17
17
|
if (value && typeof value == 'object' && 'name' in value && 'id' in value) {
|
|
@@ -21,25 +21,21 @@ const UserProperty = (props) => {
|
|
|
21
21
|
setUserValue(undefined);
|
|
22
22
|
}
|
|
23
23
|
}, [value]);
|
|
24
|
+
const { data: users } = useQuery({
|
|
25
|
+
queryKey: ['users'],
|
|
26
|
+
queryFn: () => apiServices.get(getPrefixedUrl(`/users`)),
|
|
27
|
+
staleTime: Infinity,
|
|
28
|
+
meta: {
|
|
29
|
+
errorMessage: 'Error fetching users: ',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
24
32
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
value: user.id,
|
|
32
|
-
})));
|
|
33
|
-
setFetchedOptions({
|
|
34
|
-
[`${id}Options`]: (userList ?? []).map((user) => ({
|
|
35
|
-
label: user.name,
|
|
36
|
-
value: user.id,
|
|
37
|
-
})),
|
|
38
|
-
});
|
|
39
|
-
setLoadingOptions(false);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}, [id]);
|
|
33
|
+
setOptions((users ?? []).map((user) => ({
|
|
34
|
+
label: user.name,
|
|
35
|
+
value: user.id,
|
|
36
|
+
})));
|
|
37
|
+
setLoadingOptions(false);
|
|
38
|
+
}, [users]);
|
|
43
39
|
async function handleChangeUserProperty(id, value) {
|
|
44
40
|
const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
|
|
45
41
|
try {
|
|
@@ -1,53 +1,41 @@
|
|
|
1
1
|
import { useApiServices, useApp, useNavigate, } from '@evoke-platform/context';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
3
|
import cleanDeep from 'clean-deep';
|
|
3
|
-
import { cloneDeep, debounce, isEmpty,
|
|
4
|
+
import { cloneDeep, debounce, isEmpty, isNil } from 'lodash';
|
|
4
5
|
import Handlebars from 'no-eval-handlebars';
|
|
5
|
-
import React, {
|
|
6
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
7
|
import { Close } from '../../../../../../icons';
|
|
7
8
|
import { useFormContext } from '../../../../../../theme/hooks';
|
|
8
9
|
import { Autocomplete, Button, IconButton, Link, ListItem, Paper, Snackbar, TextField, Tooltip, Typography, } from '../../../../../core';
|
|
9
10
|
import { Box } from '../../../../../layout';
|
|
10
|
-
import { getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
|
|
11
|
+
import { getDefaultPages, getPrefixedUrl, transformToWhere, useFormById } from '../../utils';
|
|
11
12
|
import RelatedObjectInstance from './RelatedObjectInstance';
|
|
12
13
|
const ObjectPropertyInput = (props) => {
|
|
13
14
|
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, relatedObjectId, } = props;
|
|
14
15
|
const { fetchedOptions, setFetchedOptions, fieldHeight, handleChange: handleChangeObjectField, onAutosave: onAutosave, instance, } = useFormContext();
|
|
15
16
|
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
17
|
+
const [debouncedDropdownInput, setDebouncedDropdownInput] = useState();
|
|
16
18
|
const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
|
|
17
19
|
const [openCreateDialog, setOpenCreateDialog] = useState(false);
|
|
18
|
-
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
19
|
-
const [navigationSlug, setNavigationSlug] = useState(fetchedOptions[`${id}NavigationSlug`]);
|
|
20
|
-
const [relatedObject, setRelatedObject] = useState(fetchedOptions[`${id}RelatedObject`]);
|
|
21
20
|
const [dropdownInput, setDropdownInput] = useState();
|
|
22
21
|
const [openOptions, setOpenOptions] = useState(false);
|
|
23
|
-
const [hasFetched, setHasFetched] = useState(fetchedOptions[`${id}OptionsHaveFetched`] || false);
|
|
24
|
-
const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
|
|
25
22
|
const [layout, setLayout] = useState(fetchedOptions[`${id}ViewLayout`]);
|
|
26
|
-
const [form, setForm] = useState();
|
|
27
23
|
const [snackbarError, setSnackbarError] = useState({
|
|
28
24
|
showAlert: false,
|
|
29
25
|
isError: true,
|
|
30
26
|
});
|
|
27
|
+
const { data: relatedObject } = useQuery({
|
|
28
|
+
queryKey: [relatedObjectId, 'sanitized'],
|
|
29
|
+
queryFn: () => apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/effective?sanitizedVersion=true`)),
|
|
30
|
+
enabled: !!relatedObjectId,
|
|
31
|
+
staleTime: Infinity,
|
|
32
|
+
});
|
|
31
33
|
const action = relatedObject?.actions?.find((action) => action.id === createActionId);
|
|
32
34
|
const apiServices = useApiServices();
|
|
33
35
|
const navigateTo = useNavigate();
|
|
34
|
-
const updatedCriteria = useMemo(() => {
|
|
35
|
-
let criteria = filter ? { where: transformToWhere(filter) } : undefined;
|
|
36
|
-
if (dropdownInput) {
|
|
37
|
-
const nameQuery = transformToWhere({
|
|
38
|
-
name: {
|
|
39
|
-
like: dropdownInput,
|
|
40
|
-
options: 'i',
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
criteria = {
|
|
44
|
-
...criteria,
|
|
45
|
-
where: criteria?.where ? { and: [criteria.where, nameQuery] } : nameQuery,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return criteria;
|
|
49
|
-
}, [filter, dropdownInput]);
|
|
50
36
|
const listboxRef = useRef(null);
|
|
37
|
+
const formIdToFetch = formId || action?.defaultFormId;
|
|
38
|
+
const { data: form } = useFormById(formIdToFetch ?? '', apiServices, 'Error fetching form: ');
|
|
51
39
|
useEffect(() => {
|
|
52
40
|
if (relatedObject) {
|
|
53
41
|
let defaultViewLayout;
|
|
@@ -85,143 +73,103 @@ const ObjectPropertyInput = (props) => {
|
|
|
85
73
|
}
|
|
86
74
|
}
|
|
87
75
|
}, [displayOption, relatedObject, viewLayout]);
|
|
76
|
+
const debouncedSetDropdownInput = useMemo(() => debounce((value) => {
|
|
77
|
+
setDebouncedDropdownInput(value);
|
|
78
|
+
}, 200), []);
|
|
88
79
|
useEffect(() => {
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
debouncedSetDropdownInput(dropdownInput);
|
|
81
|
+
return () => {
|
|
82
|
+
debouncedSetDropdownInput.cancel();
|
|
83
|
+
};
|
|
84
|
+
}, [dropdownInput, debouncedSetDropdownInput]);
|
|
85
|
+
const updatedCriteria = useMemo(() => {
|
|
86
|
+
let criteria = filter ? { where: transformToWhere(filter) } : undefined;
|
|
87
|
+
if (debouncedDropdownInput) {
|
|
88
|
+
const nameQuery = transformToWhere({
|
|
89
|
+
name: {
|
|
90
|
+
like: debouncedDropdownInput,
|
|
91
|
+
options: 'i',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
criteria = {
|
|
95
|
+
...criteria,
|
|
96
|
+
where: criteria?.where ? { and: [criteria.where, nameQuery] } : nameQuery,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return criteria;
|
|
100
|
+
}, [filter, debouncedDropdownInput]);
|
|
101
|
+
const { data: defaultInstances, isLoading: isLoadingDefaultInstances } = useQuery({
|
|
102
|
+
queryKey: ['defaultInstances', relatedObjectId, updatedCriteria],
|
|
103
|
+
queryFn: async () => {
|
|
91
104
|
const updatedFilter = cleanDeep({
|
|
92
105
|
where: transformToWhere({ $and: [defaultValueCriteria, updatedCriteria?.where ?? {}] }),
|
|
93
106
|
order: orderBy && sortBy ? encodeURIComponent(sortBy + ' ' + orderBy) : undefined,
|
|
94
107
|
limit: 1,
|
|
95
108
|
});
|
|
96
109
|
if (updatedFilter.where) {
|
|
97
|
-
|
|
98
|
-
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), async (error, instances) => {
|
|
99
|
-
if (error) {
|
|
100
|
-
console.error(error);
|
|
101
|
-
setLoadingOptions(false);
|
|
102
|
-
}
|
|
103
|
-
if (instances && instances.length > 0) {
|
|
104
|
-
setSelectedInstance(instances[0]);
|
|
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
|
-
}
|
|
119
|
-
}
|
|
120
|
-
setLoadingOptions(false);
|
|
121
|
-
});
|
|
110
|
+
return apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`));
|
|
122
111
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
(fetchedOptions?.[`${id}Options`]).length === 0) &&
|
|
128
|
-
!hasFetched) ||
|
|
129
|
-
!isEqual(fetchedOptions?.[`${id}UpdatedCriteria`], updatedCriteria)) {
|
|
130
|
-
setFetchedOptions({ [`${id}UpdatedCriteria`]: updatedCriteria });
|
|
131
|
-
setLoadingOptions(true);
|
|
132
|
-
const updatedFilter = cloneDeep(updatedCriteria) || {};
|
|
133
|
-
updatedFilter.limit = 100;
|
|
134
|
-
const { propertyId, direction } = layout?.sort ?? {
|
|
135
|
-
propertyId: 'name',
|
|
136
|
-
direction: 'asc',
|
|
137
|
-
};
|
|
138
|
-
updatedFilter.order = `${propertyId} ${direction}`;
|
|
139
|
-
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${JSON.stringify(updatedFilter)}`), (error, instances) => {
|
|
140
|
-
if (error) {
|
|
141
|
-
console.error(error);
|
|
142
|
-
setLoadingOptions(false);
|
|
143
|
-
}
|
|
144
|
-
if (instances) {
|
|
145
|
-
setOptions(instances);
|
|
146
|
-
setLoadingOptions(false);
|
|
147
|
-
// so if you go off a section too quickly and it doesn't fetch it re-fetches but doesn't cause an infinite loop
|
|
148
|
-
setHasFetched(true);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}, [
|
|
153
|
-
relatedObjectId,
|
|
154
|
-
updatedCriteria,
|
|
155
|
-
layout,
|
|
156
|
-
fetchedOptions?.[`${id}Options`],
|
|
157
|
-
fetchedOptions?.[`${id}UpdatedCriteria`],
|
|
158
|
-
hasFetched,
|
|
159
|
-
id,
|
|
160
|
-
]);
|
|
161
|
-
const debouncedGetDropdownOptions = useCallback(debounce(getDropdownOptions, 200), [getDropdownOptions]);
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (displayOption === 'dropdown') {
|
|
164
|
-
debouncedGetDropdownOptions();
|
|
165
|
-
return () => debouncedGetDropdownOptions.cancel();
|
|
166
|
-
}
|
|
167
|
-
}, [displayOption, debouncedGetDropdownOptions]);
|
|
168
|
-
useEffect(() => {
|
|
169
|
-
setSelectedInstance(initialValue);
|
|
170
|
-
}, [initialValue]);
|
|
112
|
+
},
|
|
113
|
+
enabled: !isEmpty(defaultValueCriteria) && !selectedInstance && (!instance || !instance[id]),
|
|
114
|
+
staleTime: Infinity,
|
|
115
|
+
});
|
|
171
116
|
useEffect(() => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
let evokeForm;
|
|
178
|
-
if (formId || action?.defaultFormId) {
|
|
179
|
-
evokeForm = await apiServices.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`));
|
|
117
|
+
if (defaultInstances?.[0]) {
|
|
118
|
+
setSelectedInstance(defaultInstances[0]);
|
|
119
|
+
(async () => {
|
|
120
|
+
try {
|
|
121
|
+
handleChangeObjectField && (await handleChangeObjectField(id, defaultInstances[0]));
|
|
180
122
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
[`${id}Form`]: evokeForm,
|
|
185
|
-
});
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error('Failed to update field:', error);
|
|
125
|
+
return;
|
|
186
126
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.error('Error fetching form:', error);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
fetchForm();
|
|
193
|
-
}, [action, formId, id, apiServices, fetchedOptions]);
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (!fetchedOptions[`${id}RelatedObject`]) {
|
|
196
|
-
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/effective?sanitizedVersion=true`), (error, object) => {
|
|
197
|
-
if (error) {
|
|
198
|
-
console.error(error);
|
|
127
|
+
try {
|
|
128
|
+
await onAutosave?.(id);
|
|
199
129
|
}
|
|
200
|
-
|
|
201
|
-
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('Autosave failed:', error);
|
|
202
132
|
}
|
|
203
|
-
});
|
|
133
|
+
})();
|
|
204
134
|
}
|
|
205
|
-
}, [
|
|
135
|
+
}, [defaultInstances]);
|
|
136
|
+
// Construct filter from debounced criteria
|
|
137
|
+
const updatedFilter = useMemo(() => {
|
|
138
|
+
const filter = cloneDeep(updatedCriteria) || {};
|
|
139
|
+
filter.limit = 100;
|
|
140
|
+
const { propertyId, direction } = layout?.sort ?? {
|
|
141
|
+
propertyId: 'name',
|
|
142
|
+
direction: 'asc',
|
|
143
|
+
};
|
|
144
|
+
filter.order = `${propertyId} ${direction}`;
|
|
145
|
+
return filter;
|
|
146
|
+
}, [updatedCriteria, layout]);
|
|
147
|
+
const {
|
|
148
|
+
// Sets the default value of options to an empty array
|
|
149
|
+
data: options = [], isLoading: isLoadingOptions, } = useQuery({
|
|
150
|
+
queryKey: ['dropdownOptions', relatedObjectId, updatedFilter],
|
|
151
|
+
queryFn: () => apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${JSON.stringify(updatedFilter)}`)),
|
|
152
|
+
staleTime: 300000,
|
|
153
|
+
// Keep old data while fetching new data
|
|
154
|
+
placeholderData: (previousData) => previousData,
|
|
155
|
+
enabled: displayOption === 'dropdown',
|
|
156
|
+
});
|
|
206
157
|
useEffect(() => {
|
|
207
|
-
(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
else {
|
|
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
|
-
});
|
|
221
|
-
}
|
|
158
|
+
setSelectedInstance(initialValue);
|
|
159
|
+
}, [initialValue]);
|
|
160
|
+
const loadingOptions = isLoadingOptions || isLoadingDefaultInstances;
|
|
161
|
+
const { data: navigationSlug } = useQuery({
|
|
162
|
+
queryKey: ['navigationSlug', id, relatedObjectId],
|
|
163
|
+
queryFn: async () => {
|
|
164
|
+
const pages = await getDefaultPages([{ ...fieldDefinition, objectId: relatedObjectId }], defaultPages, findDefaultPageSlugFor);
|
|
165
|
+
if (relatedObjectId && pages[relatedObjectId]) {
|
|
166
|
+
return pages[relatedObjectId];
|
|
222
167
|
}
|
|
223
|
-
|
|
224
|
-
|
|
168
|
+
return null;
|
|
169
|
+
},
|
|
170
|
+
enabled: !!(relatedObjectId && defaultPages && findDefaultPageSlugFor),
|
|
171
|
+
staleTime: Infinity,
|
|
172
|
+
});
|
|
225
173
|
const handleClose = () => {
|
|
226
174
|
setOpenCreateDialog(false);
|
|
227
175
|
};
|
|
@@ -229,27 +177,6 @@ const ObjectPropertyInput = (props) => {
|
|
|
229
177
|
const template = Handlebars.compileAST(expression);
|
|
230
178
|
return instance ? template(instance) : undefined;
|
|
231
179
|
};
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
if (relatedObject && !fetchedOptions[`${id}RelatedObject`]) {
|
|
234
|
-
setFetchedOptions({
|
|
235
|
-
[`${id}RelatedObject`]: relatedObject,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
if (options &&
|
|
239
|
-
(!fetchedOptions[`${id}Options`] || fetchedOptions[`${id}Options`].length === 0) &&
|
|
240
|
-
hasFetched &&
|
|
241
|
-
!fetchedOptions[`${id}OptionsHaveFetched`]) {
|
|
242
|
-
setFetchedOptions({
|
|
243
|
-
[`${id}Options`]: options,
|
|
244
|
-
[`${id}OptionsHaveFetched`]: hasFetched,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
if (navigationSlug && !fetchedOptions[`${id}NavigationSlug`]) {
|
|
248
|
-
setFetchedOptions({
|
|
249
|
-
[`${id}NavigationSlug`]: navigationSlug,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, id]);
|
|
253
180
|
const dropdownOptions = [
|
|
254
181
|
...options.map((o) => ({ label: o.name, value: o.id })),
|
|
255
182
|
...(mode !== 'existingOnly' && relatedObject?.actions?.some((a) => a.id === createActionId)
|
|
@@ -422,7 +349,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
422
349
|
}
|
|
423
350
|
}, selectOnFocus: false, onBlur: () => {
|
|
424
351
|
if (dropdownInput) {
|
|
425
|
-
|
|
352
|
+
setDropdownInput(undefined);
|
|
426
353
|
}
|
|
427
354
|
}, renderInput: (params) => (React.createElement(TextField, { ...params, placeholder: selectedInstance?.id || readOnly ? '' : 'Select', readOnly: !loadingOptions && !selectedInstance?.id && readOnly, onChange: (event) => setDropdownInput(event.target.value), sx: {
|
|
428
355
|
...(!loadingOptions && selectedInstance?.id
|
|
@@ -529,7 +456,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
529
456
|
event.stopPropagation();
|
|
530
457
|
setOpenCreateDialog(true);
|
|
531
458
|
}, "aria-label": `Add` }, "Add")))),
|
|
532
|
-
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption,
|
|
459
|
+
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption, filter: updatedCriteria, layout: layout, formId: formIdToFetch, actionId: createActionId, setSnackbarError: setSnackbarError }),
|
|
533
460
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
534
461
|
isError: snackbarError.isError,
|
|
535
462
|
showAlert: false,
|
|
@@ -15,8 +15,6 @@ export type RelatedObjectInstanceProps = BaseProps & {
|
|
|
15
15
|
isError: boolean;
|
|
16
16
|
}>>;
|
|
17
17
|
displayOption?: 'dropdown' | 'dialogBox';
|
|
18
|
-
setOptions: (options: ObjectInstance[]) => void;
|
|
19
|
-
options: ObjectInstance[];
|
|
20
18
|
filter?: Record<string, unknown>;
|
|
21
19
|
layout?: TableViewLayout;
|
|
22
20
|
formId?: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import { DialogActions } from '@mui/material';
|
|
3
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
4
|
import { isEmpty } from 'lodash';
|
|
4
|
-
import React, { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
6
|
import { Close } from '../../../../../../icons';
|
|
6
7
|
import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
|
|
7
8
|
import { Button, Dialog, DialogContent, DialogTitle, FormControlLabel, IconButton, Radio, RadioGroup, } from '../../../../../core';
|
|
@@ -12,7 +13,7 @@ import FormRendererContainer from '../../../FormRendererContainer';
|
|
|
12
13
|
import Body from '../../Body';
|
|
13
14
|
import Footer from '../../Footer';
|
|
14
15
|
import { AccordionActions } from '../../Header';
|
|
15
|
-
import { formatSubmission, getPrefixedUrl } from '../../utils';
|
|
16
|
+
import { extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getPrefixedUrl, getUnnestedEntries, } from '../../utils';
|
|
16
17
|
import InstanceLookup from './InstanceLookup';
|
|
17
18
|
const styles = {
|
|
18
19
|
actionButtons: {
|
|
@@ -29,8 +30,8 @@ const styles = {
|
|
|
29
30
|
},
|
|
30
31
|
};
|
|
31
32
|
const RelatedObjectInstance = (props) => {
|
|
32
|
-
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId, setSnackbarError,
|
|
33
|
-
const { handleChange: handleChangeObjectField, onAutosave, richTextEditor, fieldHeight, width } = useFormContext();
|
|
33
|
+
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId, setSnackbarError, } = props;
|
|
34
|
+
const { handleChange: handleChangeObjectField, onAutosave, richTextEditor, fieldHeight, width, form, parameters, } = useFormContext();
|
|
34
35
|
const [selectedRow, setSelectedRow] = useState();
|
|
35
36
|
const [relationType, setRelationType] = useState(displayOption === 'dropdown' || mode === 'newOnly' ? 'new' : 'existing');
|
|
36
37
|
const apiServices = useApiServices();
|
|
@@ -40,11 +41,37 @@ const RelatedObjectInstance = (props) => {
|
|
|
40
41
|
defaultWidth: width,
|
|
41
42
|
});
|
|
42
43
|
const { isXs, isSm } = breakpoints;
|
|
44
|
+
const queryClient = useQueryClient();
|
|
45
|
+
const flattenFormEntries = useMemo(() => getUnnestedEntries(form?.entries || []), [form?.entries]);
|
|
46
|
+
const [uniquePresetValues, setUniquePresetValues] = useState([]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const allCriterias = extractAllCriteria(flattenFormEntries, parameters || []);
|
|
49
|
+
const uniquePresetValues = new Set();
|
|
50
|
+
for (const criteria of allCriterias) {
|
|
51
|
+
const presetValues = extractPresetValuesFromCriteria(criteria);
|
|
52
|
+
presetValues.forEach((value) => uniquePresetValues.add(value));
|
|
53
|
+
}
|
|
54
|
+
extractPresetValuesFromDynamicDefaultValues(flattenFormEntries).map((value) => uniquePresetValues.add(value));
|
|
55
|
+
setUniquePresetValues(Array.from(uniquePresetValues));
|
|
56
|
+
}, [flattenFormEntries, parameters]);
|
|
43
57
|
const linkExistingInstance = async () => {
|
|
44
58
|
if (selectedRow && handleChangeObjectField) {
|
|
45
|
-
setSelectedInstance(selectedRow);
|
|
46
59
|
try {
|
|
47
|
-
await
|
|
60
|
+
const expandedInstance = await apiServices.get(getPrefixedUrl(`/objects/${selectedRow.objectId}/instances/${selectedRow.id}`), {
|
|
61
|
+
params: {
|
|
62
|
+
expand: uniquePresetValues
|
|
63
|
+
?.filter((value) => value.startsWith(`{{{input.${id}.`) || value.startsWith(`{{input.${id}.`))
|
|
64
|
+
.map((value) => {
|
|
65
|
+
return value
|
|
66
|
+
.replace(/{{{|}}}|{{|}}/g, '')
|
|
67
|
+
.split('.')
|
|
68
|
+
.slice(2)
|
|
69
|
+
.join('.');
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
setSelectedInstance(expandedInstance);
|
|
74
|
+
await handleChangeObjectField(id, expandedInstance);
|
|
48
75
|
}
|
|
49
76
|
catch (error) {
|
|
50
77
|
console.error('Failed to update field:', error);
|
|
@@ -74,8 +101,21 @@ const RelatedObjectInstance = (props) => {
|
|
|
74
101
|
actionId: actionId,
|
|
75
102
|
input: submission,
|
|
76
103
|
});
|
|
104
|
+
const expandedInstance = await apiServices.get(getPrefixedUrl(`/objects/${relatedObject.id}/instances/${response.id}`), {
|
|
105
|
+
params: {
|
|
106
|
+
expand: uniquePresetValues
|
|
107
|
+
?.filter((value) => value.startsWith(`{{{input.${id}.`) || value.startsWith(`{{input.${id}.`))
|
|
108
|
+
.map((value) => {
|
|
109
|
+
return value
|
|
110
|
+
.replace(/{{{|}}}|{{|}}/g, '')
|
|
111
|
+
.split('.')
|
|
112
|
+
.slice(2)
|
|
113
|
+
.join('.');
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
77
117
|
try {
|
|
78
|
-
handleChangeObjectField && (await handleChangeObjectField(id,
|
|
118
|
+
handleChangeObjectField && (await handleChangeObjectField(id, expandedInstance));
|
|
79
119
|
}
|
|
80
120
|
catch (error) {
|
|
81
121
|
console.error('Failed to update field:', error);
|
|
@@ -87,13 +127,17 @@ const RelatedObjectInstance = (props) => {
|
|
|
87
127
|
catch (error) {
|
|
88
128
|
console.error('Autosave failed:', error);
|
|
89
129
|
}
|
|
90
|
-
setSelectedInstance(
|
|
130
|
+
setSelectedInstance(expandedInstance);
|
|
91
131
|
setSnackbarError({
|
|
92
132
|
showAlert: true,
|
|
93
133
|
message: 'New instance created',
|
|
94
134
|
isError: false,
|
|
95
135
|
});
|
|
96
|
-
|
|
136
|
+
// Clear option cache to then fetch newly created instance
|
|
137
|
+
queryClient.invalidateQueries({
|
|
138
|
+
queryKey: ['dropdownOptions', relatedObject.id],
|
|
139
|
+
exact: false,
|
|
140
|
+
});
|
|
97
141
|
onClose();
|
|
98
142
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
143
|
}
|
|
@@ -113,14 +157,14 @@ const RelatedObjectInstance = (props) => {
|
|
|
113
157
|
}
|
|
114
158
|
};
|
|
115
159
|
const shouldShowRadioButtons = displayOption !== 'dropdown' && mode !== 'existingOnly' && mode !== 'newOnly' && actionId;
|
|
116
|
-
const RadioButtons = () => shouldShowRadioButtons ? (React.createElement(RadioGroup, { row: true, "aria-label": "Relation Type", onChange: (event) => {
|
|
160
|
+
const RadioButtons = useCallback(() => shouldShowRadioButtons ? (React.createElement(RadioGroup, { row: true, "aria-label": "Relation Type", onChange: (event) => {
|
|
117
161
|
const { value } = event.target;
|
|
118
162
|
if (value === 'new' || value === 'existing') {
|
|
119
163
|
setRelationType(value);
|
|
120
164
|
}
|
|
121
165
|
}, value: relationType },
|
|
122
166
|
React.createElement(FormControlLabel, { value: "existing", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "Existing" }),
|
|
123
|
-
React.createElement(FormControlLabel, { value: "new", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "New" }))) : null;
|
|
167
|
+
React.createElement(FormControlLabel, { value: "new", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "New" }))) : null, [shouldShowRadioButtons, relationType]);
|
|
124
168
|
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 },
|
|
125
169
|
relationType === 'new' ? (React.createElement("div", { ref: validationErrorsRef }, !isEmpty(bodyProps.errors) && bodyProps.shouldShowValidationErrors ? (React.createElement(FormRenderer.ValidationErrors, { errors: bodyProps.errors, sx: {
|
|
126
170
|
my: isSm || isXs ? 2 : 3,
|
|
@@ -146,7 +190,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
146
190
|
maxWidth: '950px',
|
|
147
191
|
width: '100%',
|
|
148
192
|
},
|
|
149
|
-
} },
|
|
193
|
+
} }, open && (React.createElement(React.Fragment, null,
|
|
150
194
|
React.createElement(DialogTitle, { sx: {
|
|
151
195
|
padding: 3,
|
|
152
196
|
borderBottom: '1px solid #e9ecef',
|
|
@@ -176,6 +220,6 @@ const RelatedObjectInstance = (props) => {
|
|
|
176
220
|
marginLeft: '8px',
|
|
177
221
|
width: '85px',
|
|
178
222
|
'&:hover': { boxShadow: 'none' },
|
|
179
|
-
}, onClick: linkExistingInstance, variant: "contained", disabled: !selectedRow, "aria-label": `Add` }, "Add")))))));
|
|
223
|
+
}, onClick: linkExistingInstance, variant: "contained", disabled: !selectedRow, "aria-label": `Add` }, "Add")))))))));
|
|
180
224
|
};
|
|
181
225
|
export default RelatedObjectInstance;
|
|
@@ -5,7 +5,11 @@ import TableUp from 'quill-table-up';
|
|
|
5
5
|
import 'quill-table-up/index.css';
|
|
6
6
|
import { Box } from '@mui/material';
|
|
7
7
|
import DOMPurify from 'dompurify';
|
|
8
|
+
const fontSizeArr = ['8px', '10px', '12px', '14px', '16px', '18px', '20px', '22px', '24px', '26px', '28px'];
|
|
8
9
|
Quill.register({ [`modules/${TableUp.moduleName}`]: TableUp }, true);
|
|
10
|
+
const Size = Quill.import('attributors/style/size');
|
|
11
|
+
Size.whitelist = fontSizeArr;
|
|
12
|
+
Quill.register(Size, true);
|
|
9
13
|
const HtmlView = ({ value }) => {
|
|
10
14
|
const containerRef = useRef(null);
|
|
11
15
|
const quillRef = useRef(null);
|
|
@@ -32,7 +36,7 @@ const HtmlView = ({ value }) => {
|
|
|
32
36
|
return (React.createElement(Box, { sx: {
|
|
33
37
|
width: '100%',
|
|
34
38
|
height: '100%',
|
|
35
|
-
'.ql-container': {
|
|
39
|
+
'.ql-container.ql-snow': {
|
|
36
40
|
border: 'none',
|
|
37
41
|
minHeight: 20,
|
|
38
42
|
},
|