@evoke-platform/ui-components 1.14.0 → 1.15.1

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.
Files changed (46) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +6 -3
  2. package/dist/published/components/custom/CriteriaBuilder/types.d.ts +0 -15
  3. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +0 -10
  4. package/dist/published/components/custom/CriteriaBuilder/utils.js +2 -161
  5. package/dist/published/components/custom/Form/utils.js +3 -2
  6. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
  7. package/dist/published/components/custom/FormV2/FormRenderer.js +25 -33
  8. package/dist/published/components/custom/FormV2/FormRendererContainer.js +246 -164
  9. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
  10. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
  11. package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +1 -1
  12. package/dist/published/components/custom/FormV2/components/DefaultValues.js +60 -89
  13. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
  14. package/dist/published/components/custom/FormV2/components/FormContext.js +0 -1
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +105 -185
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +2 -2
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +39 -51
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +18 -26
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -18
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -4
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +96 -169
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +63 -15
  28. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
  29. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +67 -36
  30. package/dist/published/components/custom/FormV2/components/utils.d.ts +23 -4
  31. package/dist/published/components/custom/FormV2/components/utils.js +139 -29
  32. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +28 -14
  33. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +40 -46
  34. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
  35. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +56 -19
  36. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
  37. package/dist/published/components/custom/index.d.ts +2 -0
  38. package/dist/published/components/custom/index.js +1 -0
  39. package/dist/published/components/custom/types.d.ts +15 -0
  40. package/dist/published/components/custom/types.js +1 -0
  41. package/dist/published/components/custom/util.d.ts +10 -0
  42. package/dist/published/components/custom/util.js +161 -1
  43. package/dist/published/index.d.ts +2 -2
  44. package/dist/published/index.js +1 -1
  45. package/dist/published/theme/hooks.d.ts +2 -3
  46. package/package.json +4 -4
@@ -1,4 +1,5 @@
1
1
  import { useApiServices } from '@evoke-platform/context';
2
+ import { useQuery } from '@tanstack/react-query';
2
3
  import { isEqual } from 'lodash';
3
4
  import prettyBytes from 'pretty-bytes';
4
5
  import React, { useEffect, useState } from 'react';
@@ -30,7 +31,6 @@ export const DocumentList = (props) => {
30
31
  // Determine property type once at component level
31
32
  const propertyType = object?.properties?.find((p) => p.id === id)?.type;
32
33
  const isFileType = propertyType === 'file';
33
- const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
34
34
  // savedDocuments is either FileInstance[] or DocumentType[], never a mix
35
35
  const [savedDocuments, setSavedDocuments] = useState(fetchedOptions[`${id}SavedDocuments`]);
36
36
  useEffect(() => {
@@ -79,23 +79,23 @@ export const DocumentList = (props) => {
79
79
  }
80
80
  });
81
81
  };
82
- useEffect(() => {
83
- if (!fetchedOptions[`${id}ViewPermission`]) {
84
- checkPermissions();
85
- }
86
- }, [object]);
87
- const checkPermissions = () => {
88
- if (instance?.[id]?.length) {
89
- apiServices
90
- .get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`))
91
- .then((viewPermissionCheck) => {
92
- setFetchedOptions({
93
- [`${id}ViewPermission`]: viewPermissionCheck.result,
94
- });
95
- setHasViewPermission(viewPermissionCheck.result);
96
- });
97
- }
98
- };
82
+ const { data: hasViewPermission = false } = useQuery({
83
+ queryKey: ['hasViewPermission', object?.id, instance?.id],
84
+ queryFn: async () => {
85
+ const endpoint = isFileType
86
+ ? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=read&field=content`)
87
+ : getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/checkAccess?action=view`);
88
+ try {
89
+ const viewPermissionCheck = await apiServices.get(endpoint);
90
+ return viewPermissionCheck.result;
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ },
96
+ enabled: !!instance?.id && !!object?.id && !!instance?.[id]?.length,
97
+ staleTime: Infinity,
98
+ });
99
99
  const isFile = (doc) => doc instanceof File;
100
100
  const fileExists = (doc) => savedDocuments?.find((d) => d.id === doc.id);
101
101
  const handleRemove = async (index) => {
@@ -56,11 +56,9 @@ const styles = {
56
56
  export const Image = (props) => {
57
57
  const { id, canUpdateProperty, error, value, hasDescription } = props;
58
58
  const { handleChange, onAutosave: onAutosave } = useFormContext();
59
- const [image, setImage] = useState();
59
+ const [image, setImage] = useState(value ?? null);
60
60
  useEffect(() => {
61
- if (typeof value === 'string') {
62
- setImage(value);
63
- }
61
+ setImage(value ?? null);
64
62
  }, [value]);
65
63
  const handleUpload = async (file) => {
66
64
  // max file size 300KB
@@ -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 { fetchedOptions, setFetchedOptions, handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
10
+ const { handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
10
11
  const [loadingOptions, setLoadingOptions] = useState(false);
11
12
  const apiServices = useApiServices();
12
- const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
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
- if (!fetchedOptions[`${id}Options`]) {
26
- setLoadingOptions(true);
27
- apiServices.get(getPrefixedUrl(`/users`), (error, userList) => {
28
- setUsers(userList);
29
- setOptions((userList ?? []).map((user) => ({
30
- label: user.name,
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, isEqual, isNil } from 'lodash';
4
+ import { cloneDeep, debounce, isEmpty, isNil } from 'lodash';
4
5
  import Handlebars from 'no-eval-handlebars';
5
- import React, { useCallback, useEffect, useMemo, useRef, useState } from '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
- // setting the default value when there is default criteria
90
- if (!isEmpty(defaultValueCriteria) && !selectedInstance && (!instance || !instance[id])) {
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
- setLoadingOptions(true);
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
- }, [relatedObjectId, defaultValueCriteria, sortBy, orderBy]);
125
- const getDropdownOptions = useCallback(() => {
126
- if (((!fetchedOptions?.[`${id}Options`] ||
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
- // 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}`));
117
+ if (defaultInstances?.[0]) {
118
+ setSelectedInstance(defaultInstances[0]);
119
+ (async () => {
120
+ try {
121
+ handleChangeObjectField && (await handleChangeObjectField(id, defaultInstances[0]));
180
122
  }
181
- if (evokeForm) {
182
- setForm(evokeForm);
183
- setFetchedOptions({
184
- [`${id}Form`]: evokeForm,
185
- });
123
+ catch (error) {
124
+ console.error('Failed to update field:', error);
125
+ return;
186
126
  }
187
- }
188
- catch (error) {
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
- else {
201
- setRelatedObject(object);
130
+ catch (error) {
131
+ console.error('Autosave failed:', error);
202
132
  }
203
- });
133
+ })();
204
134
  }
205
- }, [relatedObjectId, fetchedOptions, id]);
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
- (async () => {
208
- if (fetchedOptions[`${id}NavigationSlug`] === undefined) {
209
- const pages = await getDefaultPages([{ ...fieldDefinition, objectId: relatedObjectId }], defaultPages, findDefaultPageSlugFor);
210
- if (relatedObjectId && pages[relatedObjectId]) {
211
- setNavigationSlug(pages[relatedObjectId]);
212
- setFetchedOptions({
213
- [`${id}NavigationSlug`]: pages[relatedObjectId],
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
- }, [id, fieldDefinition, defaultPages, findDefaultPageSlugFor, relatedObjectId, fetchedOptions]);
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
- getDropdownOptions();
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, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: formId ?? form?.id, actionId: createActionId, setSnackbarError: setSnackbarError }),
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 { isEmpty } from 'lodash';
4
- import React, { useCallback, useRef, useState } from 'react';
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { isEmpty, pick } from 'lodash';
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, setOptions, options, } = props;
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 handleChangeObjectField(id, selectedRow);
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);
@@ -68,14 +95,31 @@ const RelatedObjectInstance = (props) => {
68
95
  // Handle the case where relatedObject is undefined
69
96
  return;
70
97
  }
98
+ const action = relatedObject.actions?.find((act) => act.id === actionId);
99
+ const parameters = action?.parameters || [];
71
100
  submission = await formatSubmission(submission, apiServices, relatedObject.id);
72
101
  try {
73
102
  const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
74
103
  actionId: actionId,
75
- input: submission,
104
+ input: pick(submission, parameters
105
+ .filter((param) => param.type !== 'collection' && !param.formula)
106
+ .map((param) => param.id)),
107
+ });
108
+ const expandedInstance = await apiServices.get(getPrefixedUrl(`/objects/${relatedObject.id}/instances/${response.id}`), {
109
+ params: {
110
+ expand: uniquePresetValues
111
+ ?.filter((value) => value.startsWith(`{{{input.${id}.`) || value.startsWith(`{{input.${id}.`))
112
+ .map((value) => {
113
+ return value
114
+ .replace(/{{{|}}}|{{|}}/g, '')
115
+ .split('.')
116
+ .slice(2)
117
+ .join('.');
118
+ }),
119
+ },
76
120
  });
77
121
  try {
78
- handleChangeObjectField && (await handleChangeObjectField(id, response));
122
+ handleChangeObjectField && (await handleChangeObjectField(id, expandedInstance));
79
123
  }
80
124
  catch (error) {
81
125
  console.error('Failed to update field:', error);
@@ -87,13 +131,17 @@ const RelatedObjectInstance = (props) => {
87
131
  catch (error) {
88
132
  console.error('Autosave failed:', error);
89
133
  }
90
- setSelectedInstance(response);
134
+ setSelectedInstance(expandedInstance);
91
135
  setSnackbarError({
92
136
  showAlert: true,
93
137
  message: 'New instance created',
94
138
  isError: false,
95
139
  });
96
- setOptions(options.concat([response]));
140
+ // Clear option cache to then fetch newly created instance
141
+ queryClient.invalidateQueries({
142
+ queryKey: ['dropdownOptions', relatedObject.id],
143
+ exact: false,
144
+ });
97
145
  onClose();
98
146
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
147
  }
@@ -113,14 +161,14 @@ const RelatedObjectInstance = (props) => {
113
161
  }
114
162
  };
115
163
  const shouldShowRadioButtons = displayOption !== 'dropdown' && mode !== 'existingOnly' && mode !== 'newOnly' && actionId;
116
- const RadioButtons = () => shouldShowRadioButtons ? (React.createElement(RadioGroup, { row: true, "aria-label": "Relation Type", onChange: (event) => {
164
+ const RadioButtons = useCallback(() => shouldShowRadioButtons ? (React.createElement(RadioGroup, { row: true, "aria-label": "Relation Type", onChange: (event) => {
117
165
  const { value } = event.target;
118
166
  if (value === 'new' || value === 'existing') {
119
167
  setRelationType(value);
120
168
  }
121
169
  }, value: relationType },
122
170
  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;
171
+ React.createElement(FormControlLabel, { value: "new", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "New" }))) : null, [shouldShowRadioButtons, relationType]);
124
172
  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
173
  relationType === 'new' ? (React.createElement("div", { ref: validationErrorsRef }, !isEmpty(bodyProps.errors) && bodyProps.shouldShowValidationErrors ? (React.createElement(FormRenderer.ValidationErrors, { errors: bodyProps.errors, sx: {
126
174
  my: isSm || isXs ? 2 : 3,
@@ -146,7 +194,7 @@ const RelatedObjectInstance = (props) => {
146
194
  maxWidth: '950px',
147
195
  width: '100%',
148
196
  },
149
- } },
197
+ } }, open && (React.createElement(React.Fragment, null,
150
198
  React.createElement(DialogTitle, { sx: {
151
199
  padding: 3,
152
200
  borderBottom: '1px solid #e9ecef',
@@ -176,6 +224,6 @@ const RelatedObjectInstance = (props) => {
176
224
  marginLeft: '8px',
177
225
  width: '85px',
178
226
  '&:hover': { boxShadow: 'none' },
179
- }, onClick: linkExistingInstance, variant: "contained", disabled: !selectedRow, "aria-label": `Add` }, "Add")))))));
227
+ }, onClick: linkExistingInstance, variant: "contained", disabled: !selectedRow, "aria-label": `Add` }, "Add")))))))));
180
228
  };
181
229
  export default RelatedObjectInstance;
@@ -1,2 +1,3 @@
1
+ import React from 'react';
1
2
  import { EntryRendererProps } from './types';
2
- export declare function RecursiveEntryRenderer(props: EntryRendererProps): any;
3
+ export declare function RecursiveEntryRenderer(props: EntryRendererProps): React.JSX.Element | null;