@evoke-platform/ui-components 1.10.0-dev.2 → 1.10.0-dev.21

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 (64) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +1 -1
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.d.ts +1 -0
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +430 -0
  4. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +19 -6
  5. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  6. package/dist/published/components/custom/Form/utils.js +1 -0
  7. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +14 -1
  8. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  9. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  10. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  11. package/dist/published/components/custom/FormV2/FormRenderer.js +17 -4
  12. package/dist/published/components/custom/FormV2/FormRendererContainer.js +116 -74
  13. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  14. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  15. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  16. package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
  17. package/dist/published/components/custom/FormV2/components/Footer.js +3 -3
  18. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +1 -1
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +0 -3
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +36 -49
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +16 -4
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
  30. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +109 -81
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +38 -16
  32. package/dist/published/components/custom/FormV2/components/Header.d.ts +13 -3
  33. package/dist/published/components/custom/FormV2/components/Header.js +47 -8
  34. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +44 -35
  35. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
  36. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
  37. package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
  38. package/dist/published/components/custom/FormV2/components/utils.js +11 -14
  39. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +433 -4
  40. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +662 -13
  41. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
  42. package/dist/published/components/custom/FormV2/tests/test-data.js +140 -0
  43. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  44. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
  45. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
  46. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +140 -0
  47. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
  48. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  49. package/dist/published/components/custom/index.d.ts +2 -0
  50. package/dist/published/components/custom/index.js +1 -0
  51. package/dist/published/index.d.ts +6 -6
  52. package/dist/published/index.js +1 -1
  53. package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
  54. package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
  55. package/dist/published/stories/FormRendererContainer.stories.js +5 -0
  56. package/dist/published/stories/FormRendererData.d.ts +12 -0
  57. package/dist/published/stories/FormRendererData.js +27 -44
  58. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  59. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  60. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  61. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  62. package/dist/published/stories/sharedMswHandlers.js +49 -10
  63. package/dist/published/theme/hooks.d.ts +4 -3
  64. package/package.json +4 -2
@@ -1,5 +1,5 @@
1
1
  import { useApiServices, useNotification, } from '@evoke-platform/context';
2
- import { get, isEqual, pick, startCase } from 'lodash';
2
+ import { get, isEqual, omit, startCase } from 'lodash';
3
3
  import { DateTime } from 'luxon';
4
4
  import React, { useCallback, useEffect, useState } from 'react';
5
5
  import sift from 'sift';
@@ -34,7 +34,7 @@ const styles = {
34
34
  },
35
35
  };
36
36
  const RepeatableField = (props) => {
37
- const { fieldDefinition, canUpdateProperty, criteria, viewLayout, entry, createActionId, updateActionId, deleteActionId, } = props;
37
+ const { fieldDefinition, canUpdateProperty, criteria, viewLayout, entry } = props;
38
38
  const { fetchedOptions, setFetchedOptions, instance, width } = useFormContext();
39
39
  const { isBelow } = useWidgetSize({
40
40
  scroll: false,
@@ -62,10 +62,12 @@ const RepeatableField = (props) => {
62
62
  showAlert: false,
63
63
  isError: false,
64
64
  });
65
- const createAction = relatedObject?.actions?.find((item) => item.id === createActionId);
66
- const updateAction = relatedObject?.actions?.find((item) => item.id === updateActionId);
67
- const deleteAction = relatedObject?.actions?.find((item) => item.id === deleteActionId);
65
+ const createAction = relatedObject?.actions?.find((item) => item.id === entry.display?.createActionId);
66
+ const updateAction = relatedObject?.actions?.find((item) => item.id === entry.display?.updateActionId);
67
+ const deleteAction = relatedObject?.actions?.find((item) => item.id === entry.display?.deleteActionId);
68
68
  function getForm(setForm, action, formId) {
69
+ if (formId === '_auto_')
70
+ return;
69
71
  if (formId || action?.defaultFormId) {
70
72
  apiServices
71
73
  .get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
@@ -76,27 +78,6 @@ const RepeatableField = (props) => {
76
78
  console.error(error);
77
79
  });
78
80
  }
79
- else if (action) {
80
- apiServices
81
- .get(getPrefixedUrl('/forms'), {
82
- params: {
83
- filter: {
84
- where: {
85
- actionId: action.id,
86
- objectId: fieldDefinition.objectId,
87
- },
88
- },
89
- },
90
- })
91
- .then((matchingForms) => {
92
- if (matchingForms.length === 1) {
93
- setForm(matchingForms[0]);
94
- }
95
- })
96
- .catch((error) => {
97
- console.error(error);
98
- });
99
- }
100
81
  }
101
82
  const fetchRelatedInstances = useCallback(async (refetch = false) => {
102
83
  let relatedObject;
@@ -209,11 +190,11 @@ const RepeatableField = (props) => {
209
190
  }, [fetchCriteriaObjects, relatedObject]);
210
191
  useEffect(() => {
211
192
  if (createAction && !createForm)
212
- getForm(setCreateForm, createAction); // TODO: pass entry.display?.createForm as a third argument
193
+ getForm(setCreateForm, createAction, entry.display?.createFormId);
213
194
  if (updateAction && !updateForm)
214
- getForm(setUpdateForm, updateAction); // TODO: pass entry.display?.updateForm as a third argument
195
+ getForm(setUpdateForm, updateAction, entry.display?.updateFormId);
215
196
  if (deleteAction && !deleteForm)
216
- getForm(setDeleteForm, deleteAction); // TODO: pass entry.display?.deleteForm as a third argument
197
+ getForm(setDeleteForm, deleteAction, entry.display?.deleteFormId);
217
198
  }, [entry.display, createAction, updateAction, deleteAction]);
218
199
  useEffect(() => {
219
200
  if (relatedObject?.rootObjectId) {
@@ -269,10 +250,10 @@ const RepeatableField = (props) => {
269
250
  if (fieldDefinition.objectId && canUpdateProperty && !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
270
251
  apiServices
271
252
  .get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/checkAccess`), {
272
- params: { action: 'execute', field: '_create', scope: 'data' },
253
+ params: { action: 'execute', field: entry.display?.createActionId, scope: 'data' },
273
254
  })
274
255
  .then((checkAccess) => {
275
- const action = relatedObject.actions?.find((item) => item.id === '_create');
256
+ const action = relatedObject.actions?.find((item) => item.id === entry.display?.createActionId);
276
257
  if (action && fieldDefinition.relatedPropertyId) {
277
258
  const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
278
259
  if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
@@ -346,17 +327,21 @@ const RepeatableField = (props) => {
346
327
  }, variant: "text", onClick: () => setReloadOnErrorTrigger((prevState) => !prevState) }, "Retry")));
347
328
  const save = async (input) => {
348
329
  const action = relatedObject?.actions?.find((a) => a.id ===
349
- (dialogType === 'create' ? createActionId : dialogType === 'update' ? updateActionId : deleteActionId));
330
+ (dialogType === 'create'
331
+ ? entry.display?.createActionId
332
+ : dialogType === 'update'
333
+ ? entry.display?.updateActionId
334
+ : entry.display?.deleteActionId));
350
335
  // when save is called we know that fieldDefinition is a parameter and fieldDefinition.objectId is defined
351
336
  input = await formatSubmission(input, apiServices, fieldDefinition.objectId, selectedInstanceId, action?.type === 'update' ? updateForm : undefined);
352
- if (action?.type === 'create' && createActionId) {
337
+ if (action?.type === 'create' && entry.display?.createActionId) {
353
338
  const updatedInput = {
354
339
  ...input,
355
340
  [fieldDefinition?.relatedPropertyId]: { id: instance?.id },
356
341
  };
357
342
  try {
358
343
  const instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
359
- actionId: createActionId,
344
+ actionId: entry.display?.createActionId,
360
345
  input: updatedInput,
361
346
  });
362
347
  const hasAccess = fieldDefinition?.relatedPropertyId && fieldDefinition.relatedPropertyId in instance;
@@ -379,8 +364,8 @@ const RepeatableField = (props) => {
379
364
  try {
380
365
  const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${selectedInstanceId}/actions`), {
381
366
  actionId: `_${action?.type}`,
382
- input: pick(input, relatedObject?.properties
383
- ?.filter((property) => !property.formula && property.type !== 'collection')
367
+ input: omit(input, relatedObject?.properties
368
+ ?.filter((property) => property.formula || property.type === 'collection')
384
369
  .map((property) => property.id) ?? []),
385
370
  });
386
371
  if (response && relatedObject && instance) {
@@ -519,12 +504,12 @@ const RepeatableField = (props) => {
519
504
  users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' &&
520
505
  ' (Inactive)')))))),
521
506
  canUpdateProperty && (React.createElement(Box, { sx: { mt: 2, display: 'flex', gap: 1 } },
522
- React.createElement(IconButton, { onClick: () => editRow(relatedInstance.id) },
507
+ entry.display?.updateActionId && (React.createElement(IconButton, { onClick: () => editRow(relatedInstance.id) },
523
508
  React.createElement(Tooltip, { title: "Edit" },
524
- React.createElement(Edit, null))),
525
- React.createElement(IconButton, { onClick: () => deleteRow(relatedInstance.id) },
509
+ React.createElement(Edit, null)))),
510
+ entry.display?.deleteActionId && (React.createElement(IconButton, { onClick: () => deleteRow(relatedInstance.id) },
526
511
  React.createElement(Tooltip, { title: "Delete" },
527
- React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))) : (React.createElement(TableContainer, { sx: {
512
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))) : (React.createElement(TableContainer, { sx: {
528
513
  borderRadius: '6px',
529
514
  border: '1px solid #919EAB3D',
530
515
  boxShadow: 'none',
@@ -544,7 +529,7 @@ const RepeatableField = (props) => {
544
529
  cursor: 'pointer',
545
530
  },
546
531
  }
547
- : {}, onClick: updateActionId &&
532
+ : {}, onClick: entry.display?.updateActionId &&
548
533
  canUpdateProperty &&
549
534
  prop.id === 'name'
550
535
  ? () => editRow(relatedInstance.id)
@@ -554,24 +539,26 @@ const RepeatableField = (props) => {
554
539
  users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
555
540
  }),
556
541
  canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
557
- updateActionId && (React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
542
+ entry.display?.updateActionId && (React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
558
543
  React.createElement(Tooltip, { title: "Edit" },
559
544
  React.createElement(Edit, null)))),
560
- React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
545
+ entry.display?.deleteActionId && (React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
561
546
  React.createElement(Tooltip, { title: "Delete" },
562
- React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))),
563
- hasCreateAction && createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow, "aria-label": 'Add' }, "Add"))),
547
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))))),
548
+ hasCreateAction && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, disabled: !createAction, onClick: addRow, "aria-label": 'Add' }, "Add"))),
564
549
  relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), onSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
565
550
  (dialogType === 'create'
566
- ? createActionId
551
+ ? entry.display?.createActionId
567
552
  : dialogType === 'update'
568
- ? updateActionId
569
- : deleteActionId)), relatedFormId: dialogType === 'create'
553
+ ? entry.display?.updateActionId
554
+ : entry.display?.deleteActionId)), relatedFormId: dialogType === 'create'
570
555
  ? createForm?.id
571
556
  : dialogType === 'update'
572
557
  ? updateForm?.id
573
558
  : dialogType === 'delete'
574
- ? deleteForm?.id
559
+ ? entry.display?.deleteFormId === '_auto_'
560
+ ? '_auto_'
561
+ : deleteForm?.id
575
562
  : undefined, instanceId: selectedInstanceId, relatedParameter: fieldDefinition, associatedObject: instance?.id && fieldDefinition.relatedPropertyId
576
563
  ? { instanceId: instance.id, propertyId: fieldDefinition.relatedPropertyId }
577
564
  : undefined })),
@@ -8,7 +8,7 @@ import { addressProperties, getPrefixedUrl } from '../utils';
8
8
  export default function Criteria(props) {
9
9
  const { value, canUpdateProperty, fieldDefinition, error } = props;
10
10
  const apiServices = useApiServices();
11
- const { fetchedOptions, setFetchedOptions, handleChange } = useFormContext();
11
+ const { fetchedOptions, setFetchedOptions, handleChange, onAutosave } = useFormContext();
12
12
  const [loadingError, setLoadingError] = useState(false);
13
13
  const [loading, setLoading] = useState(false);
14
14
  const [properties, setProperties] = useState(fetchedOptions[`${fieldDefinition.id}Options`] || []);
@@ -57,9 +57,22 @@ export default function Criteria(props) {
57
57
  useEffect(() => {
58
58
  fetchProperties();
59
59
  }, [fetchProperties]);
60
- const handleUpdate = (criteria) => {
60
+ const handleUpdate = async (criteria) => {
61
61
  if (criteria || value) {
62
- handleChange(fieldDefinition.id, criteria ?? null);
62
+ const newValue = criteria ?? null;
63
+ try {
64
+ handleChange && (await handleChange(fieldDefinition.id, newValue));
65
+ }
66
+ catch (error) {
67
+ console.error('Failed to update field:', error);
68
+ return;
69
+ }
70
+ try {
71
+ await onAutosave?.(fieldDefinition.id);
72
+ }
73
+ catch (error) {
74
+ console.error('Autosave failed:', error);
75
+ }
63
76
  }
64
77
  };
65
78
  if (loadingError) {
@@ -12,7 +12,7 @@ import { DocumentList } from './DocumentList';
12
12
  export const Document = (props) => {
13
13
  const { id, canUpdateProperty, error, value, validate, hasDescription } = props;
14
14
  const apiServices = useApiServices();
15
- const { fetchedOptions, setFetchedOptions, object, handleChange, instance } = useFormContext();
15
+ const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance, } = useFormContext();
16
16
  const [snackbarError, setSnackbarError] = useState();
17
17
  const [documents, setDocuments] = useState();
18
18
  const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
@@ -49,13 +49,25 @@ export const Document = (props) => {
49
49
  checkPermissions();
50
50
  }, [checkPermissions]);
51
51
  const handleUpload = async (files) => {
52
+ // Store File objects in form state - they will be uploaded during autosave via formatSubmission()
52
53
  const newDocuments = [...(documents ?? []), ...(files ?? [])];
53
54
  setDocuments(newDocuments);
54
- handleChange(id, newDocuments);
55
+ try {
56
+ handleChange && (await handleChange(id, newDocuments));
57
+ }
58
+ catch (error) {
59
+ console.error('Failed to update field:', error);
60
+ return;
61
+ }
62
+ try {
63
+ await onAutosave?.(id);
64
+ }
65
+ catch (error) {
66
+ console.error('Autosave failed:', error);
67
+ }
55
68
  };
56
69
  const uploadDisabled = !!validate?.maxDocuments && (documents?.length ?? 0) >= validate.maxDocuments;
57
70
  const { getRootProps, getInputProps, open, fileRejections } = useDropzone({
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
71
  onDrop: (files) => handleUpload(files),
60
72
  disabled: uploadDisabled,
61
73
  accept: validate?.allowedFileExtensions
@@ -109,7 +121,7 @@ export const Document = (props) => {
109
121
  } }, validate?.maxDocuments === 1
110
122
  ? `Maximum size is ${formattedMaxSize}.`
111
123
  : `The maximum size of each document is ${formattedMaxSize}.`)))))),
112
- canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, handleChange: handleChange, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
124
+ canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, handleChange: handleChange, onAutosave: onAutosave, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
113
125
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
114
126
  errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
115
127
  React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { SavedDocumentReference } from '../../types';
3
3
  type DocumentListProps = {
4
- handleChange: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
4
+ handleChange?: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
5
+ onAutosave?: (fieldId: string) => void | Promise<void>;
5
6
  id: string;
6
7
  canUpdateProperty: boolean;
7
8
  value: (File | SavedDocumentReference)[] | undefined;
@@ -24,7 +24,7 @@ const viewableFileTypes = [
24
24
  'text/plain',
25
25
  ];
26
26
  export const DocumentList = (props) => {
27
- const { handleChange, id, canUpdateProperty, value: documents, setSnackbarError } = props;
27
+ const { handleChange, onAutosave, id, canUpdateProperty, value: documents, setSnackbarError } = props;
28
28
  const apiServices = useApiServices();
29
29
  const { fetchedOptions, setFetchedOptions, object, instance } = useFormContext();
30
30
  const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
@@ -88,9 +88,22 @@ export const DocumentList = (props) => {
88
88
  };
89
89
  const isFile = (doc) => doc instanceof File;
90
90
  const fileExists = (doc) => savedDocuments?.find((d) => d.id === doc.id);
91
- const handleRemove = (index) => {
91
+ const handleRemove = async (index) => {
92
92
  const updatedDocuments = documents?.filter((_, i) => i !== index) ?? [];
93
- handleChange(id, updatedDocuments.length === 0 ? undefined : updatedDocuments);
93
+ const newValue = updatedDocuments.length === 0 ? undefined : updatedDocuments;
94
+ try {
95
+ handleChange && (await handleChange(id, newValue));
96
+ }
97
+ catch (error) {
98
+ console.error('Failed to update field:', error);
99
+ return;
100
+ }
101
+ try {
102
+ await onAutosave?.(id);
103
+ }
104
+ catch (error) {
105
+ console.error('Autosave failed:', error);
106
+ }
94
107
  };
95
108
  const openDocument = async (index) => {
96
109
  const doc = documents?.[index];
@@ -55,7 +55,7 @@ const styles = {
55
55
  };
56
56
  export const Image = (props) => {
57
57
  const { id, canUpdateProperty, error, value, hasDescription } = props;
58
- const { handleChange } = useFormContext();
58
+ const { handleChange, onAutosave: onAutosave } = useFormContext();
59
59
  const [image, setImage] = useState();
60
60
  useEffect(() => {
61
61
  if (typeof value === 'string') {
@@ -63,15 +63,41 @@ export const Image = (props) => {
63
63
  }
64
64
  }, [value]);
65
65
  const handleUpload = async (file) => {
66
+ // max file size 300KB
66
67
  if (file?.size && file.size <= 300000) {
67
- const dataUrl = file ? await blobToDataUrl(file) : null;
68
+ const dataUrl = await blobToDataUrl(file);
68
69
  setImage(dataUrl);
69
- handleChange(id, dataUrl);
70
+ try {
71
+ handleChange && (await handleChange(id, dataUrl));
72
+ }
73
+ catch (error) {
74
+ console.error('Failed to update field:', error);
75
+ return;
76
+ }
77
+ try {
78
+ await onAutosave?.(id);
79
+ }
80
+ catch (error) {
81
+ console.error('Autosave failed:', error);
82
+ }
70
83
  }
71
84
  };
72
- const handleRemove = (e) => {
85
+ const handleRemove = async (e) => {
73
86
  setImage(null);
74
- handleChange(id, '');
87
+ try {
88
+ handleChange && (await handleChange(id, ''));
89
+ }
90
+ catch (error) {
91
+ console.error('Failed to update field:', error);
92
+ e.stopPropagation();
93
+ return;
94
+ }
95
+ try {
96
+ await onAutosave?.(id);
97
+ }
98
+ catch (error) {
99
+ console.error('Autosave failed:', error);
100
+ }
75
101
  e.stopPropagation();
76
102
  };
77
103
  const { getRootProps, getInputProps, open } = useDropzone({
@@ -6,7 +6,7 @@ import { Autocomplete, IconButton, Paper, TextField, Typography } from '../../..
6
6
  import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
7
7
  const UserProperty = (props) => {
8
8
  const { id, error, value, readOnly, hasDescription } = props;
9
- const { fetchedOptions, setFetchedOptions, handleChange, fieldHeight } = useFormContext();
9
+ const { fetchedOptions, setFetchedOptions, handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
10
10
  const [loadingOptions, setLoadingOptions] = useState(false);
11
11
  const apiServices = useApiServices();
12
12
  const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
@@ -40,9 +40,21 @@ const UserProperty = (props) => {
40
40
  });
41
41
  }
42
42
  }, [id]);
43
- function handleChangeUserProperty(id, value) {
43
+ async function handleChangeUserProperty(id, value) {
44
44
  const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
45
- handleChange(id, updatedValue);
45
+ try {
46
+ handleChange && (await handleChange(id, updatedValue));
47
+ }
48
+ catch (error) {
49
+ console.error('Failed to update field:', error);
50
+ return; // Exit early if handleChange fails
51
+ }
52
+ try {
53
+ await onAutosave?.(id);
54
+ }
55
+ catch (error) {
56
+ console.error('Autosave failed:', error);
57
+ }
46
58
  }
47
59
  return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), clearIcon: !loadingOptions && userValue ? (React.createElement(IconButton, { size: "small", disableRipple: true, onKeyDown: (e) => {
48
60
  if (e.key === 'Enter') {