@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.
Files changed (40) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +2 -2
  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 -27
  8. package/dist/published/components/custom/FormV2/FormRendererContainer.js +132 -101
  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/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +105 -185
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +18 -26
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -18
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +96 -169
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +57 -13
  22. package/dist/published/components/custom/FormV2/components/HtmlView.js +5 -1
  23. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
  24. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +61 -29
  25. package/dist/published/components/custom/FormV2/components/utils.d.ts +23 -4
  26. package/dist/published/components/custom/FormV2/components/utils.js +136 -26
  27. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +28 -14
  28. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +41 -48
  29. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
  30. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +56 -19
  31. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
  32. package/dist/published/components/custom/index.d.ts +2 -0
  33. package/dist/published/components/custom/index.js +1 -0
  34. package/dist/published/components/custom/types.d.ts +15 -0
  35. package/dist/published/components/custom/types.js +1 -0
  36. package/dist/published/components/custom/util.d.ts +10 -0
  37. package/dist/published/components/custom/util.js +161 -1
  38. package/dist/published/index.d.ts +2 -2
  39. package/dist/published/index.js +1 -1
  40. 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 { 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 { 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, 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);
@@ -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, response));
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(response);
130
+ setSelectedInstance(expandedInstance);
91
131
  setSnackbarError({
92
132
  showAlert: true,
93
133
  message: 'New instance created',
94
134
  isError: false,
95
135
  });
96
- setOptions(options.concat([response]));
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
  },
@@ -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;