@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.
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +14 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +1 -15
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
- package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +5 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +57 -66
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +2 -2
- package/dist/published/components/custom/FormV2/components/Header.d.ts +11 -3
- package/dist/published/components/custom/FormV2/components/Header.js +4 -4
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -19
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/utils.js +7 -7
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +9 -18
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.d.ts +13 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.js +140 -0
- package/dist/published/components/custom/ViewDetailsV2/index.d.ts +2 -0
- package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +1 -1
- package/dist/published/index.js +1 -1
- package/dist/published/stories/FormRendererData.d.ts +12 -0
- package/dist/published/stories/FormRendererData.js +23 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
- package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
- package/dist/published/stories/ViewDetailsV2Data.js +203 -0
- package/dist/published/stories/sharedMswHandlers.js +49 -10
- package/dist/published/theme/hooks.d.ts +3 -3
- 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,
|
package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts
CHANGED
|
@@ -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 }, !
|
|
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);
|
package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
190
|
-
if (matchingForms.length === 1) {
|
|
191
|
-
setForm(matchingForms[0]);
|
|
184
|
+
});
|
|
185
|
+
if (matchingForms.length === 1) {
|
|
186
|
+
evokeForm = matchingForms[0];
|
|
187
|
+
}
|
|
192
188
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
213
|
-
if (parameters &&
|
|
215
|
+
(async () => {
|
|
216
|
+
if (parameters && fetchedOptions[`${id}NavigationSlug`] === undefined) {
|
|
214
217
|
const pages = await getDefaultPages(parameters, defaultPages, findDefaultPageSlugFor);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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(`/${
|
|
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'}
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = {
|