@evoke-platform/ui-components 1.10.0-testing.9 → 1.10.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 (78) hide show
  1. package/dist/published/components/core/Autocomplete/Autocomplete.js +4 -2
  2. package/dist/published/components/core/Autocomplete/Autocomplete.test.js +112 -3
  3. package/dist/published/components/core/TextField/TextField.js +1 -1
  4. package/dist/published/components/core/TextField/TextField.test.js +0 -2
  5. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +24 -2
  6. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +45 -2
  7. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +2 -1
  8. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  9. package/dist/published/components/custom/Form/tests/Form.test.js +0 -2
  10. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +36 -7
  11. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  12. package/dist/published/components/custom/FormField/FormField.d.ts +3 -1
  13. package/dist/published/components/custom/FormField/FormField.js +17 -5
  14. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -4
  15. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js +0 -2
  16. package/dist/published/components/custom/FormField/Select/Select.test.js +0 -2
  17. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  18. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  19. package/dist/published/components/custom/FormV2/FormRenderer.js +46 -8
  20. package/dist/published/components/custom/FormV2/FormRendererContainer.js +178 -153
  21. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  22. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  23. package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
  24. package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
  25. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  26. package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
  27. package/dist/published/components/custom/FormV2/components/Footer.js +8 -5
  28. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  30. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +2 -2
  32. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +6 -23
  33. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
  34. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +22 -4
  35. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
  36. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
  37. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
  38. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
  39. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +115 -87
  40. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
  41. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +43 -20
  42. package/dist/published/components/custom/FormV2/components/Header.d.ts +5 -3
  43. package/dist/published/components/custom/FormV2/components/Header.js +47 -9
  44. package/dist/published/components/custom/FormV2/components/PropertyProtection.d.ts +16 -0
  45. package/dist/published/components/custom/FormV2/components/PropertyProtection.js +113 -0
  46. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +47 -24
  47. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
  48. package/dist/published/components/custom/FormV2/components/types.d.ts +2 -0
  49. package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
  50. package/dist/published/components/custom/FormV2/components/utils.js +83 -13
  51. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +411 -44
  52. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +983 -16
  53. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
  54. package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
  55. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  56. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +165 -0
  57. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
  58. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +144 -0
  59. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
  60. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  61. package/dist/published/components/custom/index.d.ts +2 -0
  62. package/dist/published/components/custom/index.js +1 -0
  63. package/dist/published/index.d.ts +6 -6
  64. package/dist/published/index.js +1 -1
  65. package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
  66. package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
  67. package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
  68. package/dist/published/stories/FormRendererContainer.stories.js +5 -0
  69. package/dist/published/stories/FormRendererData.d.ts +12 -0
  70. package/dist/published/stories/FormRendererData.js +26 -1
  71. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  72. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  73. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  74. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  75. package/dist/published/stories/sharedMswHandlers.js +49 -10
  76. package/dist/published/theme/hooks.d.ts +4 -3
  77. package/dist/published/types.d.ts +3 -0
  78. package/package.json +10 -8
@@ -1,10 +1,10 @@
1
1
  import { ApiServices, FormEntry, InputField, InputParameter, InputParameterReference, ObjectInstance, Reference, UserAccount } from '@evoke-platform/context';
2
2
  import { FieldValues } from 'react-hook-form';
3
- export declare function evalDefaultVals(parameters: InputParameter[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
3
+ export declare function evalDefaultVals(parameters: InputParameter[], unnestedEntries: FormEntry[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
4
4
  fieldId: string;
5
5
  fieldValue: unknown;
6
6
  }[]>;
7
- export declare function processValueUpdate(entries: FormEntry[] | undefined, parameters: InputParameter[], updatedRelatedObjectValue: ObjectInstance | null | Reference, apiServices: ApiServices, changedEntryId?: string, formValues?: FieldValues, userAccount?: UserAccount): Promise<{
7
+ export declare function processValueUpdate(unnestedEntries: FormEntry[], parameters: InputParameter[], updatedRelatedObjectValue: ObjectInstance | null | Reference, apiServices: ApiServices, changedEntryId?: string, formValues?: FieldValues, userAccount?: UserAccount): Promise<{
8
8
  fieldId: string;
9
9
  fieldValue: unknown;
10
10
  }[]>;
@@ -1,7 +1,7 @@
1
1
  import { isArray, isEmpty, uniq } from 'lodash';
2
2
  import { DateTime } from 'luxon';
3
3
  import { getEntryId, getPrefixedUrl, isAddressProperty } from './utils';
4
- export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
4
+ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
5
5
  const updates = [];
6
6
  const parameter = parameters.find((param) => param.id === fieldId);
7
7
  const defaultValue = entry.display?.defaultValue;
@@ -15,9 +15,9 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
15
15
  const groups = regex.exec(item)?.groups;
16
16
  if (groups?.relatedObjectProperty && groups?.nestedProperty) {
17
17
  const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
18
- let relatedObject = updatedRelatedObjectValue;
19
- if (!relatedObject && !isEmpty(formValues)) {
20
- relatedObject = formValues[groups.relatedObjectProperty];
18
+ let relatedObjectInstance = updatedRelatedObjectValue;
19
+ if (!relatedObjectInstance && !isEmpty(formValues)) {
20
+ relatedObjectInstance = formValues[groups.relatedObjectProperty];
21
21
  }
22
22
  if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
23
23
  fieldValue = uniq([
@@ -28,9 +28,14 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
28
28
  ]);
29
29
  updates.push({ fieldId, fieldValue });
30
30
  }
31
- else if (relatedObject && relatedObject.id && relatedObjectParameter) {
31
+ else if (relatedObjectInstance?.id && relatedObjectParameter) {
32
+ let relatedObjectId = relatedObjectParameter.objectId;
33
+ if (!relatedObjectId) {
34
+ const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
35
+ relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
36
+ }
32
37
  const instance = await new Promise((resolve) => {
33
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
38
+ apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
34
39
  if (error) {
35
40
  console.error(error);
36
41
  return resolve(undefined);
@@ -64,17 +69,22 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
64
69
  const groups = regex.exec(defaultValue)?.groups;
65
70
  if (groups?.relatedObjectProperty && groups?.addressProperty && groups?.nestedAddressProperty) {
66
71
  const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
67
- let relatedObject = updatedRelatedObjectValue;
68
- if (!relatedObject && !isEmpty(formValues)) {
69
- relatedObject = formValues[groups.relatedObjectProperty];
72
+ let relatedObjectInstance = updatedRelatedObjectValue;
73
+ if (!relatedObjectInstance && !isEmpty(formValues)) {
74
+ relatedObjectInstance = formValues[groups.relatedObjectProperty];
70
75
  }
71
76
  if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
72
77
  fieldValue = updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty];
73
78
  updates.push({ fieldId, fieldValue });
74
79
  }
75
- else if (relatedObject && relatedObject.id && relatedObjectParameter) {
80
+ else if (relatedObjectInstance?.id && relatedObjectParameter) {
81
+ let relatedObjectId = relatedObjectParameter.objectId;
82
+ if (!relatedObjectId) {
83
+ const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
84
+ relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
85
+ }
76
86
  const instance = await new Promise((resolve) => {
77
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
87
+ apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
78
88
  if (error) {
79
89
  console.error(error);
80
90
  return resolve(undefined);
@@ -99,17 +109,22 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
99
109
  const groups = regex.exec(defaultValue)?.groups;
100
110
  if (groups?.relatedObjectProperty && groups?.nestedProperty) {
101
111
  const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
102
- let relatedObject = updatedRelatedObjectValue;
103
- if (!relatedObject && !isEmpty(formValues)) {
104
- relatedObject = formValues[groups.relatedObjectProperty];
112
+ let relatedObjectInstance = updatedRelatedObjectValue;
113
+ if (!relatedObjectInstance && !isEmpty(formValues)) {
114
+ relatedObjectInstance = formValues[groups.relatedObjectProperty];
105
115
  }
106
116
  if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
107
117
  fieldValue = updatedRelatedObjectValue[groups.nestedProperty];
108
118
  updates.push({ fieldId, fieldValue });
109
119
  }
110
- else if (relatedObject?.id && relatedObjectParameter) {
120
+ else if (relatedObjectInstance?.id && relatedObjectParameter) {
121
+ let relatedObjectId = relatedObjectParameter.objectId;
122
+ if (!relatedObjectId) {
123
+ const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
124
+ relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
125
+ }
111
126
  const instance = await new Promise((resolve) => {
112
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
127
+ apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
113
128
  if (error) {
114
129
  console.error(error);
115
130
  return resolve(undefined);
@@ -153,16 +168,9 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
153
168
  }
154
169
  return updates;
155
170
  }
156
- export async function processValueUpdate(entries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount) {
171
+ export async function processValueUpdate(unnestedEntries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount) {
157
172
  const updates = [];
158
- for (const entry of entries || []) {
159
- if (entry.type === 'sections' || entry.type === 'columns') {
160
- const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
161
- for (const subEntry of subEntries) {
162
- const subUpdates = await processValueUpdate(subEntry.entries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount);
163
- updates.push(...subUpdates);
164
- }
165
- }
173
+ for (const entry of unnestedEntries) {
166
174
  if ((entry.type === 'input' || entry.type === 'inputField') && entry?.display?.defaultValue) {
167
175
  const parameterId = getEntryId(entry);
168
176
  if (!parameterId)
@@ -175,7 +183,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
175
183
  groups?.addressProperty &&
176
184
  groups?.nestedAddressProperty &&
177
185
  changedEntryId === groups.relatedObjectProperty) {
178
- const result = await evalDefaultVals(parameters, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
186
+ const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
179
187
  updates.push(...result);
180
188
  }
181
189
  }
@@ -187,7 +195,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
187
195
  if (groups?.relatedObjectProperty &&
188
196
  groups?.nestedProperty &&
189
197
  changedEntryId === groups.relatedObjectProperty) {
190
- const result = await evalDefaultVals(parameters, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
198
+ const result = await evalDefaultVals(parameters, unnestedEntries, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
191
199
  updates.push(...result);
192
200
  }
193
201
  }
@@ -198,7 +206,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
198
206
  if (groups?.relatedObjectProperty &&
199
207
  groups?.nestedProperty &&
200
208
  changedEntryId === groups.relatedObjectProperty) {
201
- const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
209
+ const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
202
210
  updates.push(...result);
203
211
  }
204
212
  }
@@ -55,7 +55,7 @@ const FieldWrapper = (props) => {
55
55
  const remainingChars = maxLength ? maxLength - charCount : undefined;
56
56
  return (React.createElement(Box, null,
57
57
  React.createElement(Box, { sx: { padding: '10px 0' } },
58
- inputType !== 'boolean' && (React.createElement(InputLabel, { htmlFor: inputId, sx: {
58
+ (inputType !== 'boolean' || viewOnly) && (React.createElement(InputLabel, { htmlFor: inputId, sx: {
59
59
  display: 'flex',
60
60
  alignItems: 'center',
61
61
  color: viewOnly ? 'text.secondary' : 'text.primary',
@@ -8,6 +8,7 @@ export type FooterProps = {
8
8
  submitButtonLabel?: string;
9
9
  discardChangesButtonLabel?: string;
10
10
  sx?: SxProps;
11
+ disableDiscardChanges?: boolean;
11
12
  };
12
13
  export declare const Footer: React.FC<FooterProps>;
13
14
  export type FooterActionsProps = Omit<FooterProps, 'sx'>;
@@ -6,7 +6,7 @@ import { Box } from '../../../layout';
6
6
  import { FormContext } from './FormContext';
7
7
  /* Default FormRenderer Footer. Displays a submit button and cancel changes button. */
8
8
  export const Footer = (props) => {
9
- const { action, sx } = props;
9
+ const { sx } = props;
10
10
  const { width } = useContext(FormContext);
11
11
  const { isBelow, breakpoints } = useWidgetSize({
12
12
  scroll: false,
@@ -20,15 +20,18 @@ export const Footer = (props) => {
20
20
  padding: isSmallerThanMd ? '16px' : '20px',
21
21
  justifyContent: isXs ? 'center' : 'flex-end',
22
22
  alignItems: 'center',
23
- borderTop: action?.type !== 'delete' ? '1px solid #f4f6f8' : 'none',
23
+ borderTop: '1px solid #f4f6f8',
24
24
  borderRadius: '0px 0px 6px 6px',
25
25
  zIndex: 3,
26
+ width: '100%',
27
+ // this will ensure footer does not exceed form width when form has padding
28
+ maxWidth: width - (isSmallerThanMd ? 32 : 40),
26
29
  ...sx,
27
30
  } },
28
31
  React.createElement(FooterActions, { ...props })));
29
32
  };
30
33
  export const FooterActions = (props) => {
31
- const { action, onDiscardChanges, onSubmit, submitButtonLabel, discardChangesButtonLabel } = props;
34
+ const { action, onDiscardChanges, onSubmit, submitButtonLabel, discardChangesButtonLabel, disableDiscardChanges } = props;
32
35
  const { width } = useContext(FormContext);
33
36
  const [loading, setLoading] = React.useState(false);
34
37
  const handleSubmit = async () => {
@@ -46,7 +49,7 @@ export const FooterActions = (props) => {
46
49
  });
47
50
  const { isXs } = breakpoints;
48
51
  return (React.createElement(React.Fragment, null,
49
- React.createElement(Button, { onClick: onDiscardChanges, variant: "outlined", sx: {
52
+ !disableDiscardChanges && (React.createElement(Button, { onClick: onDiscardChanges, variant: "outlined", sx: {
50
53
  margin: '5px',
51
54
  marginX: isXs ? '0px' : undefined,
52
55
  color: 'black',
@@ -56,7 +59,7 @@ export const FooterActions = (props) => {
56
59
  backgroundColor: '#f2f4f7',
57
60
  border: '1px solid rgb(206, 212, 218)',
58
61
  },
59
- } }, discardChangesButtonLabel),
62
+ } }, discardChangesButtonLabel)),
60
63
  React.createElement(LoadingButton, { onClick: handleSubmit, variant: "contained", sx: {
61
64
  lineHeight: '2.75',
62
65
  margin: '5px 0 5px 5px',
@@ -5,7 +5,7 @@ import { ExpandedSection, SimpleEditorProps } from './types';
5
5
  type FormContextType = {
6
6
  fetchedOptions: FieldValues;
7
7
  setFetchedOptions: (newData: FieldValues) => void;
8
- getValues: UseFormGetValues<FieldValues>;
8
+ getValues?: UseFormGetValues<FieldValues>;
9
9
  object?: Obj;
10
10
  errors?: FieldValues;
11
11
  instance?: FieldValues;
@@ -15,7 +15,8 @@ type FormContextType = {
15
15
  setExpandedSections?: React.Dispatch<React.SetStateAction<ExpandedSection[]>>;
16
16
  setExpandAll?: React.Dispatch<React.SetStateAction<boolean | undefined | null>>;
17
17
  parameters?: InputParameter[];
18
- handleChange: (name: string, value: unknown) => void;
18
+ handleChange?: (name: string, value: unknown) => void | Promise<void>;
19
+ onAutosave?: (fieldId: string) => void | Promise<void>;
19
20
  fieldHeight?: 'small' | 'medium';
20
21
  triggerFieldReset?: boolean;
21
22
  showSubmitError?: boolean;
@@ -2,7 +2,16 @@ import { InputField, InputParameter, InputParameterReference, Property, Readonly
2
2
  import React from 'react';
3
3
  interface AddressProps {
4
4
  entry: InputParameterReference | ReadonlyField | InputField;
5
+ /**
6
+ * Indicates that the field is a readonlyField in an action form.
7
+ * Used for regular form read-only fields.
8
+ */
5
9
  readOnly?: boolean;
10
+ /**
11
+ * Indicates that the field should not have a gray background.
12
+ * Used for ViewDetails widgets.
13
+ */
14
+ viewOnly?: boolean;
6
15
  entryId: string;
7
16
  fieldDefinition: InputParameter | Property;
8
17
  }
@@ -6,12 +6,12 @@ import FormField from '../../../FormField';
6
6
  import FieldWrapper from '../FieldWrapper';
7
7
  import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
8
8
  function AddressFields(props) {
9
- const { entry, readOnly, entryId, fieldDefinition } = props;
10
- const { getValues, instance, errors, handleChange, fieldHeight, parameters } = useFormContext();
9
+ const { entry, readOnly, viewOnly, entryId, fieldDefinition } = props;
10
+ const { getValues, instance, errors, handleChange, onAutosave, fieldHeight, parameters } = useFormContext();
11
11
  const apiServices = useApiServices();
12
12
  const addressObject = entryId.split('.')[0];
13
13
  const addressField = entryId.split('.')[1];
14
- const addressValues = entry.type === 'readonlyField' ? instance?.[addressObject] : getValues(addressObject);
14
+ const addressValues = entry.type === 'readonlyField' ? instance?.[addressObject] : getValues ? getValues(addressObject) : undefined;
15
15
  const fieldValue = addressValues?.[addressField];
16
16
  const display = entry?.display;
17
17
  const validation = fieldDefinition?.validation
@@ -22,24 +22,41 @@ function AddressFields(props) {
22
22
  params: { query: query },
23
23
  });
24
24
  };
25
- const handleAddressChange = (name, value) => {
26
- if (addressField === 'line1' && typeof value === 'object' && value.line1) {
27
- const addressKeys = ['line1', 'city', 'county', 'state', 'zipCode'];
28
- addressKeys.forEach((key) => {
29
- const fullKey = `${addressObject}.${key}`;
30
- if (parameters?.some((p) => p.id === fullKey)) {
31
- const fieldValue = value[key];
32
- handleChange(fullKey, fieldValue);
25
+ const handleAddressChange = async (name, value) => {
26
+ try {
27
+ if (addressField === 'line1' && typeof value === 'object' && value.line1) {
28
+ const addressKeys = ['line1', 'city', 'county', 'state', 'zipCode'];
29
+ // Await each handleChange sequentially to ensure proper order
30
+ for (const key of addressKeys) {
31
+ const fullKey = `${addressObject}.${key}`;
32
+ if (parameters?.some((p) => p.id === fullKey)) {
33
+ const fieldValue = value[key];
34
+ handleChange && (await handleChange(fullKey, fieldValue));
35
+ }
33
36
  }
34
- });
37
+ // Autosave immediately after autocomplete fills all fields
38
+ try {
39
+ await onAutosave?.(entryId);
40
+ }
41
+ catch (error) {
42
+ console.error('Autosave failed:', error);
43
+ }
44
+ }
45
+ else {
46
+ handleChange && (await handleChange(name, value));
47
+ }
35
48
  }
36
- else {
37
- handleChange(name, value);
49
+ catch (error) {
50
+ console.error('Failed to update field:', error);
38
51
  }
39
52
  };
40
53
  const addressErrors = errors?.[addressObject];
41
54
  const addressFieldError = addressErrors?.[addressField];
42
- return (React.createElement(FieldWrapper, { inputId: entryId, inputType: "string", label: display?.label || 'default', description: !readOnly ? display?.description : undefined, tooltip: display?.tooltip, value: fieldValue, maxLength: 'maxLength' in validation ? validation?.maxLength : 0, required: entry.display?.required || false, showCharCount: !readOnly && display?.charCount, viewOnly: !!readOnly, prefix: display?.prefix, suffix: display?.suffix }, !readOnly ? (React.createElement(FormField, { property: fieldDefinition, defaultValue: fieldValue, onChange: handleAddressChange, isMultiLineText: !!display?.rowCount, readOnly: entry.type === 'readonlyField', ...(addressField === 'line1' && { queryAddresses }), mask: validation?.mask, placeholder: display?.placeholder, isOptionEqualToValue: isOptionEqualToValue, size: fieldHeight, error: !!addressFieldError, errorMessage: addressFieldError?.message, additionalProps: {
55
+ return (React.createElement(FieldWrapper, { inputId: entryId, inputType: "string", label: display?.label || 'default', description: !readOnly ? display?.description : undefined, tooltip: display?.tooltip, value: fieldValue, maxLength: 'maxLength' in validation ? validation?.maxLength : 0, required: entry.display?.required || false, showCharCount: !readOnly && display?.charCount, viewOnly: !!(viewOnly ?? readOnly), prefix: display?.prefix, suffix: display?.suffix }, !viewOnly ? (React.createElement(FormField, { property: fieldDefinition, defaultValue: fieldValue, onChange: handleAddressChange, onBlur: () => {
56
+ onAutosave?.(entryId)?.catch((error) => {
57
+ console.error('Autosave failed:', error);
58
+ });
59
+ }, isMultiLineText: !!display?.rowCount, readOnly: entry.type === 'readonlyField', ...(addressField === 'line1' && { queryAddresses }), mask: validation?.mask, placeholder: display?.placeholder, isOptionEqualToValue: isOptionEqualToValue, size: fieldHeight, error: !!addressFieldError, errorMessage: addressFieldError?.message, additionalProps: {
43
60
  ...(display?.description && {
44
61
  inputProps: {
45
62
  'aria-describedby': `${entryId}-description`,
@@ -84,7 +84,7 @@ export const ActionDialog = (props) => {
84
84
  objectId: relatedParameter.objectId, onSubmit: handleFormSave, onDiscardChanges: onClose, onSubmitError: handleSaveError, richTextEditor: richTextEditor, associatedObject: associatedObject, renderHeader: (formHeaderProps) => {
85
85
  return (React.createElement(DialogTitle, { sx: {
86
86
  ...styles.dialogTitle,
87
- borderBottom: action.type === 'delete' ? undefined : '1px solid #e9ecef',
87
+ borderBottom: '1px solid #e9ecef',
88
88
  } },
89
89
  action && hasAccess && !loading ? action?.name : '',
90
90
  React.createElement(IconButton, { sx: styles.closeIcon, onClick: onClose, "aria-label": "Close" },
@@ -99,7 +99,7 @@ export const ActionDialog = (props) => {
99
99
  React.createElement("div", { ref: validationErrorsRef }, !isEmpty(bodyProps.errors) && bodyProps.shouldShowValidationErrors ? (React.createElement(ValidationErrors, { errors: bodyProps.errors, sx: {
100
100
  my: isSm || isXs ? 2 : 3,
101
101
  } })) : null),
102
- React.createElement(Body, { ...bodyProps, sx: { padding: 0 } }))), renderFooter: (footerProps) => (React.createElement(DialogActions, { sx: { padding: 0 } },
102
+ React.createElement(Body, { ...bodyProps, sx: { padding: 0 } }))), renderFooter: (footerProps) => (React.createElement(DialogActions, { sx: { padding: 0, margin: 0 } },
103
103
  React.createElement(Footer, { ...footerProps, discardChangesButtonLabel: "Cancel" }))), renderContainer: ({ status, error, defaultContainer }) => (React.createElement(React.Fragment, null,
104
104
  status === 'ready' && defaultContainer,
105
105
  status === 'loading' && (React.createElement(DialogContent, null,
@@ -66,6 +66,8 @@ const RepeatableField = (props) => {
66
66
  const updateAction = relatedObject?.actions?.find((item) => item.id === entry.display?.updateActionId);
67
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;
@@ -564,7 +545,7 @@ const RepeatableField = (props) => {
564
545
  entry.display?.deleteActionId && (React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
565
546
  React.createElement(Tooltip, { title: "Delete" },
566
547
  React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))))),
567
- hasCreateAction && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow, "aria-label": 'Add' }, "Add"))),
548
+ hasCreateAction && entry.display?.createActionId && (React.createElement(Button, { variant: "contained", sx: styles.addButton, disabled: !createAction, onClick: addRow, "aria-label": 'Add' }, "Add"))),
568
549
  relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), onSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
569
550
  (dialogType === 'create'
570
551
  ? entry.display?.createActionId
@@ -575,7 +556,9 @@ const RepeatableField = (props) => {
575
556
  : dialogType === 'update'
576
557
  ? updateForm?.id
577
558
  : dialogType === 'delete'
578
- ? deleteForm?.id
559
+ ? entry.display?.deleteFormId === '_auto_'
560
+ ? '_auto_'
561
+ : deleteForm?.id
579
562
  : undefined, instanceId: selectedInstanceId, relatedParameter: fieldDefinition, associatedObject: instance?.id && fieldDefinition.relatedPropertyId
580
563
  ? { instanceId: instance.id, propertyId: fieldDefinition.relatedPropertyId }
581
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`]);
@@ -42,6 +42,12 @@ export const Document = (props) => {
42
42
  [`${id}UpdatePermission`]: accessCheck.result,
43
43
  });
44
44
  setHasUpdatePermission(accessCheck.result);
45
+ })
46
+ .catch(() => {
47
+ setFetchedOptions({
48
+ [`${id}UpdatePermission`]: false,
49
+ });
50
+ setHasUpdatePermission(false);
45
51
  });
46
52
  }
47
53
  }, [canUpdateProperty, fetchedOptions, instance, object]);
@@ -49,13 +55,25 @@ export const Document = (props) => {
49
55
  checkPermissions();
50
56
  }, [checkPermissions]);
51
57
  const handleUpload = async (files) => {
58
+ // Store File objects in form state - they will be uploaded during autosave via formatSubmission()
52
59
  const newDocuments = [...(documents ?? []), ...(files ?? [])];
53
60
  setDocuments(newDocuments);
54
- handleChange(id, newDocuments);
61
+ try {
62
+ handleChange && (await handleChange(id, newDocuments));
63
+ }
64
+ catch (error) {
65
+ console.error('Failed to update field:', error);
66
+ return;
67
+ }
68
+ try {
69
+ await onAutosave?.(id);
70
+ }
71
+ catch (error) {
72
+ console.error('Autosave failed:', error);
73
+ }
55
74
  };
56
75
  const uploadDisabled = !!validate?.maxDocuments && (documents?.length ?? 0) >= validate.maxDocuments;
57
76
  const { getRootProps, getInputProps, open, fileRejections } = useDropzone({
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
77
  onDrop: (files) => handleUpload(files),
60
78
  disabled: uploadDisabled,
61
79
  accept: validate?.allowedFileExtensions
@@ -109,7 +127,7 @@ export const Document = (props) => {
109
127
  } }, validate?.maxDocuments === 1
110
128
  ? `Maximum size is ${formattedMaxSize}.`
111
129
  : `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 })),
130
+ 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
131
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
114
132
  errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
115
133
  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') {