@evoke-platform/ui-components 1.10.0-testing.2 → 1.10.0-testing.20

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 +112 -73
  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 +31 -48
  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 +432 -4
  40. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +642 -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,9 +62,9 @@ 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
69
  if (formId || action?.defaultFormId) {
70
70
  apiServices
@@ -76,27 +76,6 @@ const RepeatableField = (props) => {
76
76
  console.error(error);
77
77
  });
78
78
  }
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
79
  }
101
80
  const fetchRelatedInstances = useCallback(async (refetch = false) => {
102
81
  let relatedObject;
@@ -209,11 +188,11 @@ const RepeatableField = (props) => {
209
188
  }, [fetchCriteriaObjects, relatedObject]);
210
189
  useEffect(() => {
211
190
  if (createAction && !createForm)
212
- getForm(setCreateForm, createAction); // TODO: pass entry.display?.createForm as a third argument
191
+ getForm(setCreateForm, createAction, entry.display?.createFormId);
213
192
  if (updateAction && !updateForm)
214
- getForm(setUpdateForm, updateAction); // TODO: pass entry.display?.updateForm as a third argument
193
+ getForm(setUpdateForm, updateAction, entry.display?.updateFormId);
215
194
  if (deleteAction && !deleteForm)
216
- getForm(setDeleteForm, deleteAction); // TODO: pass entry.display?.deleteForm as a third argument
195
+ getForm(setDeleteForm, deleteAction, entry.display?.deleteFormId);
217
196
  }, [entry.display, createAction, updateAction, deleteAction]);
218
197
  useEffect(() => {
219
198
  if (relatedObject?.rootObjectId) {
@@ -269,10 +248,10 @@ const RepeatableField = (props) => {
269
248
  if (fieldDefinition.objectId && canUpdateProperty && !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
270
249
  apiServices
271
250
  .get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/checkAccess`), {
272
- params: { action: 'execute', field: '_create', scope: 'data' },
251
+ params: { action: 'execute', field: entry.display?.createActionId, scope: 'data' },
273
252
  })
274
253
  .then((checkAccess) => {
275
- const action = relatedObject.actions?.find((item) => item.id === '_create');
254
+ const action = relatedObject.actions?.find((item) => item.id === entry.display?.createActionId);
276
255
  if (action && fieldDefinition.relatedPropertyId) {
277
256
  const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
278
257
  if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
@@ -346,17 +325,21 @@ const RepeatableField = (props) => {
346
325
  }, variant: "text", onClick: () => setReloadOnErrorTrigger((prevState) => !prevState) }, "Retry")));
347
326
  const save = async (input) => {
348
327
  const action = relatedObject?.actions?.find((a) => a.id ===
349
- (dialogType === 'create' ? createActionId : dialogType === 'update' ? updateActionId : deleteActionId));
328
+ (dialogType === 'create'
329
+ ? entry.display?.createActionId
330
+ : dialogType === 'update'
331
+ ? entry.display?.updateActionId
332
+ : entry.display?.deleteActionId));
350
333
  // when save is called we know that fieldDefinition is a parameter and fieldDefinition.objectId is defined
351
334
  input = await formatSubmission(input, apiServices, fieldDefinition.objectId, selectedInstanceId, action?.type === 'update' ? updateForm : undefined);
352
- if (action?.type === 'create' && createActionId) {
335
+ if (action?.type === 'create' && entry.display?.createActionId) {
353
336
  const updatedInput = {
354
337
  ...input,
355
338
  [fieldDefinition?.relatedPropertyId]: { id: instance?.id },
356
339
  };
357
340
  try {
358
341
  const instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
359
- actionId: createActionId,
342
+ actionId: entry.display?.createActionId,
360
343
  input: updatedInput,
361
344
  });
362
345
  const hasAccess = fieldDefinition?.relatedPropertyId && fieldDefinition.relatedPropertyId in instance;
@@ -379,8 +362,8 @@ const RepeatableField = (props) => {
379
362
  try {
380
363
  const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${selectedInstanceId}/actions`), {
381
364
  actionId: `_${action?.type}`,
382
- input: pick(input, relatedObject?.properties
383
- ?.filter((property) => !property.formula && property.type !== 'collection')
365
+ input: omit(input, relatedObject?.properties
366
+ ?.filter((property) => property.formula || property.type === 'collection')
384
367
  .map((property) => property.id) ?? []),
385
368
  });
386
369
  if (response && relatedObject && instance) {
@@ -519,12 +502,12 @@ const RepeatableField = (props) => {
519
502
  users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' &&
520
503
  ' (Inactive)')))))),
521
504
  canUpdateProperty && (React.createElement(Box, { sx: { mt: 2, display: 'flex', gap: 1 } },
522
- React.createElement(IconButton, { onClick: () => editRow(relatedInstance.id) },
505
+ entry.display?.updateActionId && (React.createElement(IconButton, { onClick: () => editRow(relatedInstance.id) },
523
506
  React.createElement(Tooltip, { title: "Edit" },
524
- React.createElement(Edit, null))),
525
- React.createElement(IconButton, { onClick: () => deleteRow(relatedInstance.id) },
507
+ React.createElement(Edit, null)))),
508
+ entry.display?.deleteActionId && (React.createElement(IconButton, { onClick: () => deleteRow(relatedInstance.id) },
526
509
  React.createElement(Tooltip, { title: "Delete" },
527
- React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))) : (React.createElement(TableContainer, { sx: {
510
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))) : (React.createElement(TableContainer, { sx: {
528
511
  borderRadius: '6px',
529
512
  border: '1px solid #919EAB3D',
530
513
  boxShadow: 'none',
@@ -544,7 +527,7 @@ const RepeatableField = (props) => {
544
527
  cursor: 'pointer',
545
528
  },
546
529
  }
547
- : {}, onClick: updateActionId &&
530
+ : {}, onClick: entry.display?.updateActionId &&
548
531
  canUpdateProperty &&
549
532
  prop.id === 'name'
550
533
  ? () => editRow(relatedInstance.id)
@@ -554,19 +537,19 @@ const RepeatableField = (props) => {
554
537
  users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
555
538
  }),
556
539
  canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
557
- updateActionId && (React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
540
+ entry.display?.updateActionId && (React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
558
541
  React.createElement(Tooltip, { title: "Edit" },
559
542
  React.createElement(Edit, null)))),
560
- React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
543
+ entry.display?.deleteActionId && (React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
561
544
  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"))),
545
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))))),
546
+ hasCreateAction && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, disabled: !createAction, onClick: addRow, "aria-label": 'Add' }, "Add"))),
564
547
  relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), onSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
565
548
  (dialogType === 'create'
566
- ? createActionId
549
+ ? entry.display?.createActionId
567
550
  : dialogType === 'update'
568
- ? updateActionId
569
- : deleteActionId)), relatedFormId: dialogType === 'create'
551
+ ? entry.display?.updateActionId
552
+ : entry.display?.deleteActionId)), relatedFormId: dialogType === 'create'
570
553
  ? createForm?.id
571
554
  : dialogType === 'update'
572
555
  ? updateForm?.id
@@ -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') {