@evoke-platform/ui-components 1.10.0-dev.11 → 1.10.0-dev.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  2. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +14 -1
  3. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  4. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  5. package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
  6. package/dist/published/components/custom/FormV2/FormRendererContainer.js +1 -15
  7. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  8. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  9. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  10. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +5 -5
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +1 -1
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -1
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -1
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +1 -1
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -2
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +57 -66
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +2 -2
  21. package/dist/published/components/custom/FormV2/components/Header.d.ts +11 -3
  22. package/dist/published/components/custom/FormV2/components/Header.js +4 -4
  23. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -19
  24. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
  25. package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
  26. package/dist/published/components/custom/FormV2/components/utils.js +7 -7
  27. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +9 -18
  28. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  29. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
  30. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.d.ts +13 -0
  31. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.js +140 -0
  32. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +2 -0
  33. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  34. package/dist/published/components/custom/index.d.ts +1 -0
  35. package/dist/published/components/custom/index.js +1 -0
  36. package/dist/published/index.d.ts +1 -1
  37. package/dist/published/index.js +1 -1
  38. package/dist/published/stories/FormRendererData.d.ts +12 -0
  39. package/dist/published/stories/FormRendererData.js +23 -0
  40. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  41. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  42. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  43. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  44. package/dist/published/stories/sharedMswHandlers.js +49 -10
  45. package/dist/published/theme/hooks.d.ts +3 -3
  46. package/package.json +2 -2
@@ -430,7 +430,7 @@ const RepeatableField = (props) => {
430
430
  hasCreateAction && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow }, "Add"))),
431
431
  relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, apiServices: apiServices, onClose: () => setOpenDialog(false), instanceInput: dialogType === 'update' ? (relatedInstances.find((i) => i.id === selectedRow) ?? {}) : {}, handleSubmit: save,
432
432
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
433
- objectInputCommonProps: { apiServices }, action: relatedObject?.actions?.find((a) => a.id ===
433
+ objectInputCommonProps: { apiServices, setSnackbarError }, action: relatedObject?.actions?.find((a) => a.id ===
434
434
  (dialogType === 'create' ? '_create' : dialogType === 'update' ? '_update' : '_delete')), instanceId: selectedRow, queryAddresses: queryAddresses, user: user, associatedObject: instance.id && property.relatedPropertyId
435
435
  ? { instanceId: instance.id, propertyId: property.relatedPropertyId }
436
436
  : undefined, richTextEditor: richTextEditor })),
@@ -1,6 +1,7 @@
1
1
  import { DateTimeFormatter } from '@js-joda/core';
2
2
  import { omit } from 'lodash';
3
3
  import React, { useEffect, useState } from 'react';
4
+ import { useFormContext } from '../../../../theme/hooks';
4
5
  import { InvalidDate, LocalDate, nativeJs } from '../../../../util';
5
6
  import { DatePicker, LocalizationProvider, TextField } from '../../../core';
6
7
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
@@ -29,6 +30,7 @@ const asMonthDayYearFormat = (date) => {
29
30
  const DatePickerSelect = (props) => {
30
31
  const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, } = props;
31
32
  const [value, setValue] = useState(asCalendarDate(defaultValue));
33
+ const { onAutosave } = useFormContext();
32
34
  useEffect(() => {
33
35
  setValue(asCalendarDate(defaultValue));
34
36
  }, [defaultValue]);
@@ -36,8 +38,19 @@ const DatePickerSelect = (props) => {
36
38
  setValue(date);
37
39
  onChange && onChange(property.id, date, property);
38
40
  };
41
+ const handleAccept = async () => {
42
+ // Trigger autosave when date is accepted (picker closes after selection)
43
+ if (onAutosave) {
44
+ try {
45
+ await onAutosave(id);
46
+ }
47
+ catch (error) {
48
+ console.error('Autosave failed:', error);
49
+ }
50
+ }
51
+ };
39
52
  return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: asMonthDayYearFormat(value) } })) : (React.createElement(LocalizationProvider, null,
40
- React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
53
+ React.createElement(DatePicker, { value: value, onChange: handleChange, onAccept: handleAccept, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
41
54
  // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
42
55
  inputProps: {
43
56
  ...params.inputProps,
@@ -1,6 +1,7 @@
1
1
  import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
2
2
  import { omit } from 'lodash';
3
3
  import React, { useEffect, useState } from 'react';
4
+ import { useFormContext } from '../../../../theme/hooks';
4
5
  import { InvalidDate } from '../../../../util';
5
6
  import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
6
7
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
@@ -30,6 +31,7 @@ const formatDateTime = (date) => {
30
31
  const DateTimePickerSelect = (props) => {
31
32
  const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, additionalProps } = props;
32
33
  const [value, setValue] = useState(asCalendarDate(defaultValue));
34
+ const { onAutosave } = useFormContext();
33
35
  useEffect(() => {
34
36
  setValue(asCalendarDate(defaultValue));
35
37
  }, [defaultValue]);
@@ -43,8 +45,19 @@ const DateTimePickerSelect = (props) => {
43
45
  setValue(date);
44
46
  props.onChange && props.onChange(property.id, date, property);
45
47
  };
48
+ const handleAccept = async () => {
49
+ // Trigger autosave when date/time is accepted (picker closes after selection)
50
+ if (onAutosave) {
51
+ try {
52
+ await onAutosave(id);
53
+ }
54
+ catch (error) {
55
+ console.error('Autosave failed:', error);
56
+ }
57
+ }
58
+ };
46
59
  return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: formatDateTime(value) } })) : (React.createElement(LocalizationProvider, null,
47
- React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
60
+ React.createElement(DateTimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
48
61
  // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
49
62
  inputProps: {
50
63
  ...params.inputProps,
@@ -3,11 +3,13 @@ import { TimePicker } from '@mui/x-date-pickers';
3
3
  import { isUndefined, omit, padStart } from 'lodash';
4
4
  import { DateTime } from 'luxon';
5
5
  import React, { useEffect, useState } from 'react';
6
+ import { useFormContext } from '../../../../theme/hooks';
6
7
  import { InvalidDate } from '../../../../util';
7
8
  import { LocalizationProvider, TextField } from '../../../core';
8
9
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
9
10
  const TimePickerSelect = (props) => {
10
11
  const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, placeholder, additionalProps, } = props;
12
+ const { onAutosave } = useFormContext();
11
13
  const values = defaultValue ? defaultValue.split(':') : undefined;
12
14
  const hour = values ? parseInt(values[0]) : undefined;
13
15
  const minute = values ? parseInt(values[1]) : undefined;
@@ -41,11 +43,22 @@ const TimePickerSelect = (props) => {
41
43
  props.onChange && props.onChange(property.id, date, property);
42
44
  }
43
45
  };
46
+ const handleAccept = async () => {
47
+ // Trigger autosave when time is accepted (picker closes after selection)
48
+ if (onAutosave) {
49
+ try {
50
+ await onAutosave(id);
51
+ }
52
+ catch (error) {
53
+ console.error('Autosave failed:', error);
54
+ }
55
+ }
56
+ };
44
57
  return readOnly ? (React.createElement(InputFieldComponent, { ...{
45
58
  ...props,
46
59
  defaultValue: value instanceof LocalDateTime ? DateTime.fromISO(value.toString()).toFormat('hh:mm a') : '',
47
60
  } })) : (React.createElement(LocalizationProvider, null,
48
- React.createElement(TimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
61
+ React.createElement(TimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
49
62
  // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
50
63
  inputProps: {
51
64
  ...params.inputProps,
@@ -155,9 +155,10 @@ const FormRendererInternal = (props) => {
155
155
  errors,
156
156
  hasAccordions: hasSections && isSmallerThanMd,
157
157
  shouldShowValidationErrors: isSubmitted,
158
- form,
158
+ isDeleteForm: form.id === '',
159
159
  action,
160
160
  validationErrorsRef: validationErrorsRef,
161
+ autosaveEnabled: !!form.autosaveActionId,
161
162
  };
162
163
  const footerProps = {
163
164
  onSubmit: unregisterHiddenFieldsAndSubmit,
@@ -13,9 +13,8 @@ function FormRendererContainer(props) {
13
13
  const { instanceId, pageNavigation, documentId, dataType, display, formId, objectId, actionId, richTextEditor, onSubmit, onDiscardChanges: onDiscardChangesOverride, associatedObject, renderContainer, onSubmitError, sx, renderHeader, renderBody, renderFooter, } = props;
14
14
  const apiServices = useApiServices();
15
15
  const navigateTo = useNavigate();
16
- const { id: appId, defaultPages } = useApp();
16
+ const { id: appId } = useApp();
17
17
  const [hasDocumentUpdateAccess, setHasDocumentUpdateAccess] = useState();
18
- const [defaultPagesWithSlugs, setDefaultPagesWithSlugs] = useState({});
19
18
  const [sanitizedObject, setSanitizedObject] = useState();
20
19
  const [navigationSlug, setNavigationSlug] = useState();
21
20
  const [parameters, setParameters] = useState();
@@ -103,19 +102,6 @@ function FormRendererContainer(props) {
103
102
  setNavigationSlug(page?.slug);
104
103
  });
105
104
  }
106
- if (defaultPages) {
107
- for (const [objectId, defaultPage] of Object.entries(defaultPages)) {
108
- const pageId = defaultPage.includes('/')
109
- ? encodePageSlug(defaultPage.split('/').slice(2).join('/'))
110
- : defaultPage;
111
- apiServices.get(getPrefixedUrl(`/apps/${appId}/pages/${pageId}`)).then((page) => {
112
- setDefaultPagesWithSlugs({
113
- ...defaultPagesWithSlugs,
114
- [objectId]: '/' + page.appId + '/' + page.slug,
115
- });
116
- });
117
- }
118
- }
119
105
  }, []);
120
106
  useEffect(() => {
121
107
  if (dataType === 'documents' || form)
@@ -4,10 +4,11 @@ import React, { useEffect } from 'react';
4
4
  import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
5
5
  import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../../core';
6
6
  import { Box } from '../../../layout';
7
+ import { ViewOnlyEntryRenderer } from '../../ViewDetailsV2';
7
8
  import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
8
9
  import { getErrorCountForSection } from './utils';
9
10
  function AccordionSections(props) {
10
- const { entry } = props;
11
+ const { entry, readOnly } = props;
11
12
  const { errors, expandedSections, setExpandedSections, expandAll, setExpandAll, showSubmitError, width } = useFormContext();
12
13
  const { isAbove } = useWidgetSize({
13
14
  scroll: false,
@@ -92,6 +93,8 @@ function AccordionSections(props) {
92
93
  '&:before': {
93
94
  display: 'none',
94
95
  },
96
+ ...(sectionIndex === lastSection && { marginBottom: '16px !important' }),
97
+ ...(sectionIndex === 0 && { marginTop: '16px !important' }),
95
98
  } },
96
99
  React.createElement(AccordionSummary, { sx: {
97
100
  '&.Mui-expanded': {
@@ -133,7 +136,9 @@ function AccordionSections(props) {
133
136
  margin: '0px',
134
137
  marginRight: '16px',
135
138
  } }, errorCount)))),
136
- React.createElement(AccordionDetails, null, section.entries?.map((sectionEntry, index) => (React.createElement(RecursiveEntryRenderer, { key: sectionEntry.type + index, entry: sectionEntry }))))));
139
+ React.createElement(AccordionDetails, null, readOnly
140
+ ? section.entries?.map((sectionEntry, index) => (React.createElement(ViewOnlyEntryRenderer, { key: sectionEntry.type + index, entry: sectionEntry })))
141
+ : section.entries?.map((sectionEntry, index) => (React.createElement(RecursiveEntryRenderer, { key: sectionEntry.type + index, entry: sectionEntry }))))));
137
142
  })));
138
143
  }
139
144
  export default AccordionSections;
@@ -7,7 +7,7 @@ export type BodyProps = {
7
7
  entries: FormEntry[];
8
8
  isInitializing: boolean;
9
9
  errors?: FieldErrors;
10
- shouldShowValidationErrors: boolean;
10
+ shouldShowValidationErrors?: boolean;
11
11
  hasAccordions: boolean;
12
12
  expandedSections?: ExpandedSection[];
13
13
  onExpandAll?: () => void;
@@ -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',
@@ -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,7 @@ 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 | Promise<void>;
18
+ handleChange?: (name: string, value: unknown) => void | Promise<void>;
19
19
  onAutosave?: (fieldId: string) => void | Promise<void>;
20
20
  fieldHeight?: 'small' | 'medium';
21
21
  triggerFieldReset?: 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;
9
+ const { entry, readOnly, viewOnly, entryId, fieldDefinition } = props;
10
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
@@ -31,7 +31,7 @@ function AddressFields(props) {
31
31
  const fullKey = `${addressObject}.${key}`;
32
32
  if (parameters?.some((p) => p.id === fullKey)) {
33
33
  const fieldValue = value[key];
34
- await handleChange(fullKey, fieldValue);
34
+ handleChange && (await handleChange(fullKey, fieldValue));
35
35
  }
36
36
  }
37
37
  // Autosave immediately after autocomplete fills all fields
@@ -43,7 +43,7 @@ function AddressFields(props) {
43
43
  }
44
44
  }
45
45
  else {
46
- await handleChange(name, value);
46
+ handleChange && (await handleChange(name, value));
47
47
  }
48
48
  }
49
49
  catch (error) {
@@ -52,7 +52,7 @@ function AddressFields(props) {
52
52
  };
53
53
  const addressErrors = errors?.[addressObject];
54
54
  const addressFieldError = addressErrors?.[addressField];
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: !!readOnly, prefix: display?.prefix, suffix: display?.suffix }, !readOnly ? (React.createElement(FormField, { property: fieldDefinition, defaultValue: fieldValue, onChange: handleAddressChange, onBlur: () => {
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
56
  onAutosave?.(entryId)?.catch((error) => {
57
57
  console.error('Autosave failed:', error);
58
58
  });
@@ -61,7 +61,7 @@ export default function Criteria(props) {
61
61
  if (criteria || value) {
62
62
  const newValue = criteria ?? null;
63
63
  try {
64
- await handleChange(fieldDefinition.id, newValue);
64
+ handleChange && (await handleChange(fieldDefinition.id, newValue));
65
65
  }
66
66
  catch (error) {
67
67
  console.error('Failed to update field:', error);
@@ -53,7 +53,7 @@ export const Document = (props) => {
53
53
  const newDocuments = [...(documents ?? []), ...(files ?? [])];
54
54
  setDocuments(newDocuments);
55
55
  try {
56
- await handleChange(id, newDocuments);
56
+ handleChange && (await handleChange(id, newDocuments));
57
57
  }
58
58
  catch (error) {
59
59
  console.error('Failed to update field:', error);
@@ -1,7 +1,7 @@
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
5
  onAutosave?: (fieldId: string) => void | Promise<void>;
6
6
  id: string;
7
7
  canUpdateProperty: boolean;
@@ -92,7 +92,7 @@ export const DocumentList = (props) => {
92
92
  const updatedDocuments = documents?.filter((_, i) => i !== index) ?? [];
93
93
  const newValue = updatedDocuments.length === 0 ? undefined : updatedDocuments;
94
94
  try {
95
- await handleChange(id, newValue);
95
+ handleChange && (await handleChange(id, newValue));
96
96
  }
97
97
  catch (error) {
98
98
  console.error('Failed to update field:', error);
@@ -68,7 +68,7 @@ export const Image = (props) => {
68
68
  const dataUrl = await blobToDataUrl(file);
69
69
  setImage(dataUrl);
70
70
  try {
71
- await handleChange(id, dataUrl);
71
+ handleChange && (await handleChange(id, dataUrl));
72
72
  }
73
73
  catch (error) {
74
74
  console.error('Failed to update field:', error);
@@ -85,7 +85,7 @@ export const Image = (props) => {
85
85
  const handleRemove = async (e) => {
86
86
  setImage(null);
87
87
  try {
88
- await handleChange(id, '');
88
+ handleChange && (await handleChange(id, ''));
89
89
  }
90
90
  catch (error) {
91
91
  console.error('Failed to update field:', error);
@@ -43,7 +43,7 @@ const UserProperty = (props) => {
43
43
  async function handleChangeUserProperty(id, value) {
44
44
  const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
45
45
  try {
46
- await handleChange(id, updatedValue);
46
+ handleChange && (await handleChange(id, updatedValue));
47
47
  }
48
48
  catch (error) {
49
49
  console.error('Failed to update field:', error);
@@ -7,7 +7,7 @@ import { Close } from '../../../../../../icons';
7
7
  import { useFormContext } from '../../../../../../theme/hooks';
8
8
  import { Autocomplete, Button, IconButton, Link, ListItem, Paper, Snackbar, TextField, Tooltip, Typography, } from '../../../../../core';
9
9
  import { Box } from '../../../../../layout';
10
- import { encodePageSlug, getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
10
+ import { getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
11
11
  import RelatedObjectInstance from './RelatedObjectInstance';
12
12
  const ObjectPropertyInput = (props) => {
13
13
  const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, } = props;
@@ -15,7 +15,6 @@ const ObjectPropertyInput = (props) => {
15
15
  const { defaultPages, findDefaultPageSlugFor } = useApp();
16
16
  const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
17
17
  const [openCreateDialog, setOpenCreateDialog] = useState(false);
18
- const [allDefaultPages, setAllDefaultPages] = useState(defaultPages ?? {});
19
18
  const [loadingOptions, setLoadingOptions] = useState(false);
20
19
  const [navigationSlug, setNavigationSlug] = useState(fetchedOptions[`${id}NavigationSlug`]);
21
20
  const [relatedObject, setRelatedObject] = useState(fetchedOptions[`${id}RelatedObject`]);
@@ -24,7 +23,6 @@ const ObjectPropertyInput = (props) => {
24
23
  const [hasFetched, setHasFetched] = useState(fetchedOptions[`${id}OptionsHaveFetched`] || false);
25
24
  const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
26
25
  const [layout, setLayout] = useState();
27
- const [appId, setAppId] = useState(fetchedOptions[`${id}AppId`]);
28
26
  const [form, setForm] = useState();
29
27
  const [snackbarError, setSnackbarError] = useState({
30
28
  showAlert: false,
@@ -98,7 +96,7 @@ const ObjectPropertyInput = (props) => {
98
96
  if (instances && instances.length > 0) {
99
97
  setSelectedInstance(instances[0]);
100
98
  try {
101
- await handleChangeObjectField(id, instances[0]);
99
+ handleChangeObjectField && (await handleChangeObjectField(id, instances[0]));
102
100
  }
103
101
  catch (error) {
104
102
  console.error('Failed to update field:', error);
@@ -164,38 +162,43 @@ const ObjectPropertyInput = (props) => {
164
162
  setSelectedInstance(initialValue);
165
163
  }, [initialValue]);
166
164
  useEffect(() => {
167
- if (formId || action?.defaultFormId) {
168
- apiServices
169
- .get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
170
- .then((evokeForm) => {
171
- setForm(evokeForm);
172
- })
173
- .catch((error) => {
174
- console.error(error);
175
- });
176
- }
177
- else if (action) {
178
- apiServices
179
- .get(getPrefixedUrl('/forms'), {
180
- params: {
181
- filter: {
182
- where: {
183
- actionId: action.id,
184
- objectId: fieldDefinition.objectId,
165
+ // Early return if already fetched
166
+ if (fetchedOptions[`${id}Form`])
167
+ return;
168
+ const fetchForm = async () => {
169
+ try {
170
+ let evokeForm;
171
+ if (formId || action?.defaultFormId) {
172
+ evokeForm = await apiServices.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`));
173
+ }
174
+ else if (action) {
175
+ const matchingForms = await apiServices.get(getPrefixedUrl('/forms'), {
176
+ params: {
177
+ filter: {
178
+ where: {
179
+ actionId: action.id,
180
+ objectId: fieldDefinition.objectId,
181
+ },
182
+ },
185
183
  },
186
- },
187
- },
188
- })
189
- .then((matchingForms) => {
190
- if (matchingForms.length === 1) {
191
- setForm(matchingForms[0]);
184
+ });
185
+ if (matchingForms.length === 1) {
186
+ evokeForm = matchingForms[0];
187
+ }
192
188
  }
193
- })
194
- .catch((error) => {
195
- console.error(error);
196
- });
197
- }
198
- }, [action, formId]);
189
+ if (evokeForm) {
190
+ setForm(evokeForm);
191
+ setFetchedOptions({
192
+ [`${id}Form`]: evokeForm,
193
+ });
194
+ }
195
+ }
196
+ catch (error) {
197
+ console.error('Error fetching form:', error);
198
+ }
199
+ };
200
+ fetchForm();
201
+ }, [action, formId, id, fieldDefinition.objectId, apiServices, fetchedOptions]);
199
202
  useEffect(() => {
200
203
  if (!fetchedOptions[`${id}RelatedObject`]) {
201
204
  apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective?sanitizedVersion=true`), (error, object) => {
@@ -209,31 +212,24 @@ const ObjectPropertyInput = (props) => {
209
212
  }
210
213
  }, [fieldDefinition.objectId, fetchedOptions, id]);
211
214
  useEffect(() => {
212
- const fetchDefaultPages = async () => {
213
- if (parameters && !fetchedOptions['allDefaultPages']) {
215
+ (async () => {
216
+ if (parameters && fetchedOptions[`${id}NavigationSlug`] === undefined) {
214
217
  const pages = await getDefaultPages(parameters, defaultPages, findDefaultPageSlugFor);
215
- setAllDefaultPages(pages);
216
- setFetchedOptions({ [`allDefaultPages`]: pages });
217
- }
218
- };
219
- fetchDefaultPages();
220
- }, [fetchedOptions, parameters, defaultPages, findDefaultPageSlugFor]);
221
- useEffect(() => {
222
- if (fieldDefinition.objectId &&
223
- allDefaultPages &&
224
- allDefaultPages[fieldDefinition.objectId] &&
225
- (!fetchedOptions?.[`${id}NavigationSlug`] || !fetchedOptions[`${id}AppId`])) {
226
- apiServices.get(getPrefixedUrl(`/apps/${allDefaultPages[fieldDefinition.objectId].split('/').slice(1, 2)}/pages/${encodePageSlug(allDefaultPages[fieldDefinition.objectId].split('/').slice(2).join('/'))}`), (error, page) => {
227
- if (error) {
228
- console.error(error);
218
+ if (fieldDefinition.objectId && pages[fieldDefinition.objectId]) {
219
+ setNavigationSlug(pages[fieldDefinition.objectId]);
220
+ setFetchedOptions({
221
+ [`${id}NavigationSlug`]: pages[fieldDefinition.objectId],
222
+ });
229
223
  }
230
224
  else {
231
- setAppId(page?.appId);
232
- setNavigationSlug(page?.slug);
225
+ // setting the nav slug to null if there is no default page for this object to avoid re-fetching
226
+ setFetchedOptions({
227
+ [`${id}NavigationSlug`]: null,
228
+ });
233
229
  }
234
- });
235
- }
236
- }, [fieldDefinition, allDefaultPages, fetchedOptions, id]);
230
+ }
231
+ })();
232
+ }, [parameters, defaultPages, findDefaultPageSlugFor, fieldDefinition, fetchedOptions]);
237
233
  const handleClose = () => {
238
234
  setOpenCreateDialog(false);
239
235
  };
@@ -261,12 +257,7 @@ const ObjectPropertyInput = (props) => {
261
257
  [`${id}NavigationSlug`]: navigationSlug,
262
258
  });
263
259
  }
264
- if (appId && !fetchedOptions[`${id}AppId`]) {
265
- setFetchedOptions({
266
- [`${id}AppId`]: appId,
267
- });
268
- }
269
- }, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, appId, id]);
260
+ }, [relatedObject, options, hasFetched, navigationSlug, fetchedOptions, id]);
270
261
  const dropdownOptions = [
271
262
  ...options.map((o) => ({ label: o.name, value: o.id })),
272
263
  ...(mode !== 'existingOnly' && relatedObject?.actions?.some((a) => a.id === createActionId)
@@ -409,7 +400,7 @@ const ObjectPropertyInput = (props) => {
409
400
  if (isNil(value)) {
410
401
  setDropdownInput(undefined);
411
402
  setSelectedInstance(undefined);
412
- await handleChangeObjectField(id, null);
403
+ handleChangeObjectField && (await handleChangeObjectField(id, null));
413
404
  // Trigger autosave immediately upon clearing
414
405
  try {
415
406
  await onAutosave?.(id);
@@ -424,7 +415,7 @@ const ObjectPropertyInput = (props) => {
424
415
  else {
425
416
  const selectedInstance = options.find((o) => o.id === value?.value);
426
417
  setSelectedInstance(selectedInstance);
427
- await handleChangeObjectField(id, selectedInstance);
418
+ handleChangeObjectField && (await handleChangeObjectField(id, selectedInstance));
428
419
  // Trigger autosave immediately upon selection
429
420
  try {
430
421
  await onAutosave?.(id);
@@ -474,7 +465,7 @@ const ObjectPropertyInput = (props) => {
474
465
  ...params.InputProps,
475
466
  startAdornment: selectedInstance?.id ? (React.createElement(Typography, { onClick: () => {
476
467
  if (navigationSlug && selectedInstance?.id) {
477
- navigateTo(`/${appId}/${navigationSlug.replace(':instanceId', selectedInstance.id)}`);
468
+ navigateTo(`/${navigationSlug.replace(':instanceId', selectedInstance.id)}`);
478
469
  }
479
470
  }, sx: {
480
471
  cursor: navigationSlug ? 'pointer' : 'default',
@@ -504,14 +495,14 @@ const ObjectPropertyInput = (props) => {
504
495
  ? '#999'
505
496
  : '#212B36',
506
497
  }, variant: "body2", href: navigationSlug && !isModal
507
- ? `${'/app'}/${appId}/${navigationSlug.replace(':instanceId', selectedInstance?.id ?? '')}`
498
+ ? `${'/app'}${navigationSlug.replace(':instanceId', selectedInstance?.id ?? '')}`
508
499
  : undefined, "aria-label": selectedInstance?.name }, selectedInstance?.name ? selectedInstance?.name : readOnly && 'None')),
509
500
  !readOnly && selectedInstance ? (React.createElement(Tooltip, { title: `Unlink` },
510
501
  React.createElement("span", null,
511
502
  React.createElement(IconButton, { onClick: async (event) => {
512
503
  event.stopPropagation();
513
504
  try {
514
- await handleChangeObjectField(id, null);
505
+ handleChangeObjectField && (await handleChangeObjectField(id, null));
515
506
  }
516
507
  catch (error) {
517
508
  console.error('Failed to update field:', error);
@@ -40,7 +40,7 @@ const RelatedObjectInstance = (props) => {
40
40
  });
41
41
  const { isXs, isSm } = breakpoints;
42
42
  const linkExistingInstance = async () => {
43
- if (selectedRow) {
43
+ if (selectedRow && handleChangeObjectField) {
44
44
  setSelectedInstance(selectedRow);
45
45
  try {
46
46
  await handleChangeObjectField(id, selectedRow);
@@ -74,7 +74,7 @@ const RelatedObjectInstance = (props) => {
74
74
  input: submission,
75
75
  });
76
76
  try {
77
- await handleChangeObjectField(id, response);
77
+ handleChangeObjectField && (await handleChangeObjectField(id, response));
78
78
  }
79
79
  catch (error) {
80
80
  console.error('Failed to update field:', error);
@@ -1,21 +1,29 @@
1
- import { Action, EvokeForm } from '@evoke-platform/context';
1
+ import { Action } from '@evoke-platform/context';
2
2
  import { SxProps } from '@mui/material';
3
3
  import React from 'react';
4
4
  import { FieldErrors } from 'react-hook-form';
5
5
  import { ExpandedSection } from './types';
6
6
  export type HeaderProps = {
7
7
  hasAccordions: boolean;
8
- shouldShowValidationErrors: boolean;
8
+ shouldShowValidationErrors?: boolean;
9
9
  validationErrorsRef?: React.Ref<HTMLDivElement>;
10
10
  title?: string;
11
11
  expandedSections?: ExpandedSection[];
12
12
  onExpandAll?: () => void;
13
13
  onCollapseAll?: () => void;
14
- form: EvokeForm;
14
+ /**
15
+ * Indicates whether this is a "delete form".
16
+ * This flag adjusts header styling specifically for delete forms.
17
+ *
18
+ * @warning This prop is temporary and will be removed
19
+ * when delete form styling is finalized.
20
+ */
21
+ isDeleteForm?: boolean;
15
22
  errors?: FieldErrors;
16
23
  action?: Action;
17
24
  autosaving?: boolean;
18
25
  sx?: SxProps;
26
+ autosaveEnabled?: boolean;
19
27
  };
20
28
  declare const Header: React.FC<HeaderProps>;
21
29
  export type TitleProps = {