@evoke-platform/ui-components 1.9.1-testing.1 → 1.10.0-testing.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.
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +20 -6
- package/dist/published/components/custom/FormV2/FormRenderer.js +142 -128
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +21 -2
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +44 -46
- package/dist/published/components/custom/FormV2/components/Body.d.ts +18 -0
- package/dist/published/components/custom/FormV2/components/Body.js +27 -0
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +15 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +77 -0
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/FormContext.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +64 -22
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +18 -16
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +7 -7
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +10 -8
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +38 -49
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +95 -51
- package/dist/published/components/custom/FormV2/components/Header.d.ts +29 -0
- package/dist/published/components/custom/FormV2/components/Header.js +63 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +3 -3
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/{ValidationErrorDisplay.js → ValidationErrors.js} +11 -8
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -7
- package/dist/published/components/custom/FormV2/index.d.ts +3 -0
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +6 -5
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +4 -3
- package/dist/published/components/custom/index.d.ts +1 -1
- package/dist/published/components/custom/index.js +1 -1
- package/dist/published/index.d.ts +1 -1
- package/dist/published/stories/FormRenderer.stories.d.ts +24 -16
- package/dist/published/stories/FormRenderer.stories.js +2 -10
- package/dist/published/stories/FormRendererContainer.stories.d.ts +40 -10
- package/dist/published/theme/hooks.d.ts +12 -3
- package/package.json +1 -1
- package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +0 -17
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +0 -111
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +0 -11
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
|
|
2
2
|
import React, { ComponentType } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldValues, SubmitErrorHandler } from 'react-hook-form';
|
|
4
|
+
import { BodyProps } from './components/Body';
|
|
5
|
+
import { FooterProps } from './components/Footer';
|
|
6
|
+
import { HeaderProps } from './components/Header';
|
|
4
7
|
import { BaseProps, Document, SimpleEditorProps } from './components/types';
|
|
8
|
+
import ValidationErrors from './components/ValidationFiles/ValidationErrors';
|
|
5
9
|
export type FormRendererProps = BaseProps & {
|
|
6
10
|
richTextEditor?: ComponentType<SimpleEditorProps>;
|
|
7
|
-
hideButtons?: boolean;
|
|
8
11
|
value?: FieldValues;
|
|
9
12
|
onSubmit?: (data: FieldValues) => void;
|
|
13
|
+
onDiscardChanges?: () => void;
|
|
14
|
+
onSubmitError?: SubmitErrorHandler<FieldValues>;
|
|
10
15
|
fieldHeight?: 'small' | 'medium';
|
|
11
|
-
stickyFooter?: boolean;
|
|
12
|
-
onCancel?: () => void;
|
|
13
16
|
form: EvokeForm;
|
|
17
|
+
title?: string | React.ReactNode;
|
|
14
18
|
instance?: ObjectInstance | Document;
|
|
15
19
|
onChange: (id: string, value: unknown) => void;
|
|
16
|
-
onValidationChange?: (errors: FieldErrors) => void;
|
|
17
20
|
associatedObject?: {
|
|
18
21
|
instanceId?: string;
|
|
19
22
|
propertyId?: string;
|
|
20
23
|
};
|
|
24
|
+
renderHeader?: (props: HeaderProps) => React.ReactNode;
|
|
25
|
+
renderBody?: (props: BodyProps) => React.ReactNode;
|
|
26
|
+
renderFooter?: (props: FooterProps) => React.ReactNode;
|
|
27
|
+
};
|
|
28
|
+
export declare const FormRenderer: React.FC<FormRendererProps> & {
|
|
29
|
+
Header: React.FC<HeaderProps>;
|
|
30
|
+
Body: React.FC<BodyProps>;
|
|
31
|
+
Footer: React.FC<FooterProps>;
|
|
32
|
+
FooterActions: (props: import("./components/Footer").FooterActionsProps) => React.JSX.Element;
|
|
33
|
+
Title: React.FC<import("./components/Header").TitleProps>;
|
|
34
|
+
AccordionActions: React.FC<import("./components/Header").AccordionActionsProps>;
|
|
35
|
+
ValidationErrors: typeof ValidationErrors;
|
|
21
36
|
};
|
|
22
|
-
declare function FormRenderer(props: FormRendererProps): React.JSX.Element;
|
|
23
37
|
export default FormRenderer;
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { useObject } from '@evoke-platform/context';
|
|
2
|
-
import { isEmpty, isEqual } from 'lodash';
|
|
2
|
+
import { isEmpty, isEqual, omit } from 'lodash';
|
|
3
3
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { useForm } from 'react-hook-form';
|
|
5
5
|
import { useWidgetSize } from '../../../theme';
|
|
6
|
-
import { Button, Skeleton, Typography } from '../../core';
|
|
7
6
|
import { Box } from '../../layout';
|
|
8
|
-
import
|
|
7
|
+
import { Body } from './components/Body';
|
|
8
|
+
import { Footer, FooterActions } from './components/Footer';
|
|
9
9
|
import { FormContext } from './components/FormContext';
|
|
10
|
-
import {
|
|
11
|
-
import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams } from './components/utils';
|
|
10
|
+
import Header, { AccordionActions, Title } from './components/Header';
|
|
11
|
+
import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, } from './components/utils';
|
|
12
12
|
import { handleValidation } from './components/ValidationFiles/Validation';
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
const { onSubmit, value, fieldHeight, richTextEditor,
|
|
13
|
+
import ValidationErrors from './components/ValidationFiles/ValidationErrors';
|
|
14
|
+
const FormRendererInternal = (props) => {
|
|
15
|
+
const { onSubmit, onDiscardChanges, onSubmitError, value, fieldHeight, richTextEditor, form, instance, onChange, associatedObject, renderHeader, renderBody, renderFooter, } = props;
|
|
16
16
|
const { entries, name: title, objectId, actionId, display } = form;
|
|
17
17
|
const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
|
|
18
18
|
defaultValues: value,
|
|
19
19
|
});
|
|
20
20
|
const hasSections = entries.some((entry) => entry.type === 'sections');
|
|
21
|
-
const
|
|
22
|
-
const { ref: containerRef, breakpoints, isBelow, width, } = useWidgetSize({
|
|
21
|
+
const { ref: containerRef, isBelow, width, } = useWidgetSize({
|
|
23
22
|
scroll: false,
|
|
24
23
|
defaultWidth: 1200,
|
|
25
24
|
});
|
|
26
|
-
const { isXs, isSm } = breakpoints;
|
|
27
25
|
const isSmallerThanMd = isBelow('md');
|
|
28
|
-
const objectStore = useObject(objectId);
|
|
29
26
|
const [expandedSections, setExpandedSections] = useState([]);
|
|
30
27
|
const [fetchedOptions, setFetchedOptions] = useState({});
|
|
31
28
|
const [expandAll, setExpandAll] = useState();
|
|
32
29
|
const [action, setAction] = useState();
|
|
33
30
|
const [object, setObject] = useState();
|
|
34
31
|
const [triggerFieldReset, setTriggerFieldReset] = useState(false);
|
|
32
|
+
const [isInitializing, setIsInitializing] = useState(true);
|
|
33
|
+
const [parameters, setParameters] = useState();
|
|
34
|
+
const objectStore = useObject(objectId);
|
|
35
35
|
const updateFetchedOptions = (newData) => {
|
|
36
36
|
setFetchedOptions((prev) => ({
|
|
37
37
|
...prev,
|
|
@@ -44,18 +44,6 @@ function FormRenderer(props) {
|
|
|
44
44
|
function handleCollapseAll() {
|
|
45
45
|
setExpandAll(false);
|
|
46
46
|
}
|
|
47
|
-
const parameters = useMemo(() => {
|
|
48
|
-
if (form.id === 'documentForm') {
|
|
49
|
-
return convertDocToParameters(instance);
|
|
50
|
-
}
|
|
51
|
-
else if (action?.parameters) {
|
|
52
|
-
return action.parameters;
|
|
53
|
-
}
|
|
54
|
-
else if (object) {
|
|
55
|
-
// if forms actionId is synced with object properties
|
|
56
|
-
return convertPropertiesToParams(object);
|
|
57
|
-
}
|
|
58
|
-
}, [form.id, action?.parameters, object, instance]);
|
|
59
47
|
const updatedEntries = useMemo(() => {
|
|
60
48
|
return assignIdsToSectionsAndRichText(entries, object, parameters);
|
|
61
49
|
}, [entries, object, parameters]);
|
|
@@ -63,14 +51,25 @@ function FormRenderer(props) {
|
|
|
63
51
|
(async () => {
|
|
64
52
|
try {
|
|
65
53
|
const object = await objectStore.get({ sanitized: true });
|
|
54
|
+
const action = object?.actions?.find((a) => a.id === actionId);
|
|
66
55
|
setObject(object);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
setAction(action);
|
|
57
|
+
if (form.id === 'documentForm') {
|
|
58
|
+
setParameters(convertDocToParameters(instance));
|
|
59
|
+
}
|
|
60
|
+
else if (action?.parameters) {
|
|
61
|
+
setParameters(action.parameters);
|
|
62
|
+
}
|
|
63
|
+
else if (object) {
|
|
64
|
+
// if forms actionId is synced with object properties
|
|
65
|
+
setParameters(convertPropertiesToParams(object));
|
|
70
66
|
}
|
|
71
67
|
}
|
|
72
68
|
catch (error) {
|
|
73
|
-
console.error('Failed to fetch object or
|
|
69
|
+
console.error('Failed to fetch object, action or parameters:', error);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setIsInitializing(false);
|
|
74
73
|
}
|
|
75
74
|
})();
|
|
76
75
|
}, [objectStore, actionId]);
|
|
@@ -87,14 +86,9 @@ function FormRenderer(props) {
|
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
}, [value]);
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (onValidationChange) {
|
|
92
|
-
onValidationChange(errors);
|
|
93
|
-
}
|
|
94
|
-
}, [errors, onValidationChange]);
|
|
95
89
|
const handleReset = () => {
|
|
96
|
-
if (
|
|
97
|
-
|
|
90
|
+
if (onDiscardChanges) {
|
|
91
|
+
onDiscardChanges();
|
|
98
92
|
}
|
|
99
93
|
else {
|
|
100
94
|
reset(instance); // clears react-hook-form state back to default values
|
|
@@ -104,99 +98,119 @@ function FormRenderer(props) {
|
|
|
104
98
|
useEffect(() => {
|
|
105
99
|
handleValidation(entries, register, getValues(), action?.parameters, instance);
|
|
106
100
|
}, [action?.parameters, instance, entries, register, getValues]);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
101
|
+
const unregisterHiddenFields = (entriesToCheck) => {
|
|
102
|
+
entriesToCheck.forEach((entry) => {
|
|
103
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
104
|
+
const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
|
|
105
|
+
subEntries.forEach((subEntry) => {
|
|
106
|
+
if (subEntry.entries) {
|
|
107
|
+
unregisterHiddenFields(subEntry.entries);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
112
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
113
|
+
const fieldsToUnregister = getNestedParameterIds(entry);
|
|
114
|
+
fieldsToUnregister.forEach(processFieldUnregister);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const fieldId = getEntryId(entry);
|
|
118
|
+
if (fieldId)
|
|
119
|
+
processFieldUnregister(fieldId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const processFieldUnregister = (fieldId) => {
|
|
125
|
+
if (isAddressProperty(fieldId)) {
|
|
126
|
+
// Unregister entire addressObject to clear hidden field errors, then restore existing values since unregistering address.line1 etc is not working
|
|
127
|
+
const [addressObject, addressField] = fieldId.split('.');
|
|
128
|
+
let addressValues = getValues(addressObject);
|
|
129
|
+
addressValues = omit(addressValues, addressField);
|
|
130
|
+
unregister(addressObject);
|
|
131
|
+
setValue(addressObject, addressValues);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
unregister(fieldId);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
async function unregisterHiddenFieldsAndSubmit() {
|
|
138
|
+
unregisterHiddenFields(entries ?? []);
|
|
139
|
+
await handleSubmit((data) => onSubmit && onSubmit(action?.type === 'delete' ? {} : data), (errors) => onSubmitError?.(errors))();
|
|
140
|
+
}
|
|
141
|
+
const headerProps = {
|
|
142
|
+
title,
|
|
143
|
+
onExpandAll: handleExpandAll,
|
|
144
|
+
onCollapseAll: handleCollapseAll,
|
|
145
|
+
expandedSections,
|
|
146
|
+
errors,
|
|
147
|
+
hasAccordions: hasSections && isSmallerThanMd,
|
|
148
|
+
shouldShowValidationErrors: isSubmitted,
|
|
149
|
+
form,
|
|
150
|
+
action,
|
|
151
|
+
};
|
|
152
|
+
const footerProps = {
|
|
153
|
+
onSubmit: unregisterHiddenFieldsAndSubmit,
|
|
154
|
+
onDiscardChanges: handleReset,
|
|
155
|
+
action,
|
|
156
|
+
discardChangesButtonLabel: 'Discard Changes',
|
|
157
|
+
submitButtonLabel: display?.submitLabel ?? 'Submit',
|
|
158
|
+
};
|
|
159
|
+
return (React.createElement(Box, { ref: containerRef },
|
|
160
|
+
React.createElement(FormContext.Provider, { value: {
|
|
161
|
+
fetchedOptions,
|
|
162
|
+
setFetchedOptions: updateFetchedOptions,
|
|
163
|
+
getValues,
|
|
164
|
+
object,
|
|
165
|
+
errors,
|
|
166
|
+
instance,
|
|
167
|
+
richTextEditor,
|
|
168
|
+
expandedSections,
|
|
169
|
+
setExpandedSections,
|
|
170
|
+
expandAll,
|
|
171
|
+
setExpandAll,
|
|
172
|
+
parameters,
|
|
173
|
+
fieldHeight,
|
|
174
|
+
handleChange: onChange,
|
|
175
|
+
triggerFieldReset,
|
|
176
|
+
showSubmitError: isSubmitted,
|
|
177
|
+
associatedObject,
|
|
178
|
+
form,
|
|
179
|
+
width,
|
|
180
|
+
} },
|
|
181
|
+
React.createElement(React.Fragment, null,
|
|
182
|
+
((isSubmitted && !isEmpty(errors)) || (isSmallerThanMd && hasSections) || title || renderHeader) &&
|
|
183
|
+
(renderHeader ? renderHeader({ ...headerProps }) : React.createElement(Header, { ...headerProps })),
|
|
184
|
+
renderBody ? (renderBody({
|
|
185
|
+
isInitializing,
|
|
186
|
+
entries: updatedEntries,
|
|
157
187
|
errors,
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
shouldShowValidationErrors: isSubmitted,
|
|
189
|
+
onExpandAll: handleExpandAll,
|
|
190
|
+
onCollapseAll: handleCollapseAll,
|
|
160
191
|
expandedSections,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
display: 'flex',
|
|
186
|
-
justifyContent: isXs ? 'center' : 'flex-end',
|
|
187
|
-
alignItems: 'center',
|
|
188
|
-
marginX: isSmallerThanMd ? -2 : -3,
|
|
189
|
-
borderRadius: '0px 0px 6px 6px',
|
|
190
|
-
} },
|
|
191
|
-
React.createElement(ActionButtons, { onSubmit: onSubmit, handleSubmit: handleSubmit, isModal: isModal, actionType: action?.type, submitButtonLabel: display?.submitLabel, onReset: handleReset, unregister: unregister, entries: entries, setValue: setValue, formId: form.id })))))));
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
return (React.createElement(Box, { p: 2 },
|
|
195
|
-
React.createElement(Skeleton, null),
|
|
196
|
-
React.createElement(Skeleton, null),
|
|
197
|
-
React.createElement(Skeleton, null),
|
|
198
|
-
React.createElement(Skeleton, null),
|
|
199
|
-
React.createElement(Skeleton, null)));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
192
|
+
hasAccordions: hasSections && isSmallerThanMd,
|
|
193
|
+
})) : (React.createElement(Body, { ...{
|
|
194
|
+
isInitializing,
|
|
195
|
+
entries: updatedEntries,
|
|
196
|
+
errors,
|
|
197
|
+
shouldShowValidationErrors: isSubmitted,
|
|
198
|
+
onExpandAll: handleExpandAll,
|
|
199
|
+
onCollapseAll: handleCollapseAll,
|
|
200
|
+
expandedSections,
|
|
201
|
+
hasAccordions: hasSections && isSmallerThanMd,
|
|
202
|
+
} })),
|
|
203
|
+
(actionId || form.id === 'documentForm') &&
|
|
204
|
+
onSubmit &&
|
|
205
|
+
(renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
|
|
206
|
+
};
|
|
207
|
+
export const FormRenderer = Object.assign(FormRendererInternal, {
|
|
208
|
+
Header,
|
|
209
|
+
Body,
|
|
210
|
+
Footer,
|
|
211
|
+
FooterActions,
|
|
212
|
+
Title,
|
|
213
|
+
AccordionActions,
|
|
214
|
+
ValidationErrors,
|
|
215
|
+
});
|
|
202
216
|
export default FormRenderer;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
|
|
2
|
+
import { SxProps } from '@mui/material';
|
|
3
|
+
import { AxiosError } from 'axios';
|
|
1
4
|
import React, { ComponentType } from 'react';
|
|
2
5
|
import { BaseProps, SimpleEditorProps } from './components/types';
|
|
6
|
+
import { FormRendererProps } from './FormRenderer';
|
|
7
|
+
export type FORM_RENDERER_CONTAINER_ERRORS = 'Configured action ID does not match form action ID' | 'A default action form could not be found' | 'Action could not be found';
|
|
8
|
+
export type FormRendererContainerError = AxiosError | number | FORM_RENDERER_CONTAINER_ERRORS | unknown;
|
|
9
|
+
export type FormRendererState = {
|
|
10
|
+
status: 'loading' | 'error' | 'ready';
|
|
11
|
+
error?: FormRendererContainerError;
|
|
12
|
+
form?: EvokeForm;
|
|
13
|
+
instance?: ObjectInstance;
|
|
14
|
+
defaultContainer: React.ReactNode;
|
|
15
|
+
};
|
|
3
16
|
export type FormRendererContainerProps = BaseProps & {
|
|
4
17
|
formId?: string;
|
|
5
18
|
instanceId?: string;
|
|
@@ -15,12 +28,18 @@ export type FormRendererContainerProps = BaseProps & {
|
|
|
15
28
|
hideButtons?: boolean;
|
|
16
29
|
objectId: string;
|
|
17
30
|
richTextEditor?: ComponentType<SimpleEditorProps>;
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
onSubmit?: (submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>;
|
|
32
|
+
onDiscardChanges?: FormRendererProps['onDiscardChanges'];
|
|
33
|
+
onSubmitError?: FormRendererProps['onSubmitError'];
|
|
20
34
|
associatedObject?: {
|
|
21
35
|
instanceId?: string;
|
|
22
36
|
propertyId?: string;
|
|
23
37
|
};
|
|
38
|
+
renderContainer?: (state: FormRendererState) => React.ReactNode;
|
|
39
|
+
renderHeader?: FormRendererProps['renderHeader'];
|
|
40
|
+
renderBody?: FormRendererProps['renderBody'];
|
|
41
|
+
renderFooter?: FormRendererProps['renderFooter'];
|
|
42
|
+
sx?: SxProps;
|
|
24
43
|
};
|
|
25
44
|
declare function FormRendererContainer(props: FormRendererContainerProps): React.JSX.Element;
|
|
26
45
|
export default FormRendererContainer;
|
|
@@ -9,7 +9,7 @@ import { evalDefaultVals, processValueUpdate } from './components/DefaultValues'
|
|
|
9
9
|
import { convertDocToEntries, deleteDocuments, encodePageSlug, formatDataToDoc, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, plainTextToRtf, } from './components/utils';
|
|
10
10
|
import FormRenderer from './FormRenderer';
|
|
11
11
|
function FormRendererContainer(props) {
|
|
12
|
-
const { instanceId, pageNavigation, documentId, dataType, display, formId,
|
|
12
|
+
const { instanceId, pageNavigation, documentId, dataType, display, formId, objectId, actionId, richTextEditor, onSubmit, onDiscardChanges: onDiscardChangesOverride, associatedObject, renderContainer, onSubmitError, sx, renderHeader, renderBody, renderFooter, } = props;
|
|
13
13
|
const apiServices = useApiServices();
|
|
14
14
|
const navigateTo = useNavigate();
|
|
15
15
|
const { id: appId, defaultPages } = useApp();
|
|
@@ -33,7 +33,7 @@ function FormRendererContainer(props) {
|
|
|
33
33
|
const onError = (err) => {
|
|
34
34
|
const code = axios.isAxiosError(err) ? err.response?.status : undefined;
|
|
35
35
|
setSnackbarError({ ...snackbarError, isError: true });
|
|
36
|
-
setError(code ??
|
|
36
|
+
setError(code ?? err);
|
|
37
37
|
};
|
|
38
38
|
useEffect(() => {
|
|
39
39
|
(async () => {
|
|
@@ -61,7 +61,7 @@ function FormRendererContainer(props) {
|
|
|
61
61
|
setAction(action);
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
-
setError(
|
|
64
|
+
setError('Action could not be found');
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -100,11 +100,11 @@ function FormRendererContainer(props) {
|
|
|
100
100
|
.get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
|
|
101
101
|
.then((evokeForm) => {
|
|
102
102
|
if (evokeForm?.actionId === actionId) {
|
|
103
|
-
const form =
|
|
103
|
+
const form = evokeForm;
|
|
104
104
|
setForm(form);
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
|
-
setError(
|
|
107
|
+
setError('Configured action ID does not match form action ID');
|
|
108
108
|
}
|
|
109
109
|
})
|
|
110
110
|
.catch((error) => {
|
|
@@ -125,7 +125,7 @@ function FormRendererContainer(props) {
|
|
|
125
125
|
})
|
|
126
126
|
.then((matchingForms) => {
|
|
127
127
|
if (matchingForms.length === 1) {
|
|
128
|
-
const form =
|
|
128
|
+
const form = matchingForms[0];
|
|
129
129
|
setForm(form);
|
|
130
130
|
// use this default form if no delete form is found
|
|
131
131
|
}
|
|
@@ -147,7 +147,7 @@ function FormRendererContainer(props) {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
else if (instance || action.type === 'create') {
|
|
150
|
-
setError(
|
|
150
|
+
setError('Default action form could not be found');
|
|
151
151
|
}
|
|
152
152
|
})
|
|
153
153
|
.catch((error) => {
|
|
@@ -231,25 +231,16 @@ function FormRendererContainer(props) {
|
|
|
231
231
|
}
|
|
232
232
|
try {
|
|
233
233
|
if (dataType === 'documents' && !!document) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
setSnackbarError({
|
|
248
|
-
isError: true,
|
|
249
|
-
showAlert: true,
|
|
250
|
-
message: error.response?.data?.error?.message ?? 'An error occurred',
|
|
251
|
-
});
|
|
252
|
-
}
|
|
234
|
+
await apiServices.patch(getPrefixedUrl(`/objects/${form.objectId}/instances/${instanceId}/documents/${documentId}`), pick(submission, ['metadata']).metadata ?? submission);
|
|
235
|
+
setDocument((prev) => ({
|
|
236
|
+
...prev,
|
|
237
|
+
metadata: submission.metadata,
|
|
238
|
+
}));
|
|
239
|
+
setSnackbarError({
|
|
240
|
+
showAlert: true,
|
|
241
|
+
message: 'Your changes have been submitted',
|
|
242
|
+
isError: false,
|
|
243
|
+
});
|
|
253
244
|
}
|
|
254
245
|
else if (action?.type === 'create') {
|
|
255
246
|
const response = await apiServices.post(getPrefixedUrl(`/objects/${form.objectId}/instances/actions`), {
|
|
@@ -285,6 +276,7 @@ function FormRendererContainer(props) {
|
|
|
285
276
|
showAlert: true,
|
|
286
277
|
message: error.response?.data?.error?.message ?? 'An error occurred',
|
|
287
278
|
});
|
|
279
|
+
throw error; // Throw error so caller knows submission failed
|
|
288
280
|
}
|
|
289
281
|
};
|
|
290
282
|
const getDefaultValues = async (entries, instanceData) => {
|
|
@@ -414,8 +406,11 @@ function FormRendererContainer(props) {
|
|
|
414
406
|
});
|
|
415
407
|
}
|
|
416
408
|
}
|
|
417
|
-
|
|
418
|
-
|
|
409
|
+
const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
|
|
410
|
+
const status = error ? 'error' : isLoading ? 'loading' : 'ready';
|
|
411
|
+
const onDiscardChanges = onDiscardChangesOverride
|
|
412
|
+
? onDiscardChangesOverride
|
|
413
|
+
: async () => {
|
|
419
414
|
if (document) {
|
|
420
415
|
const defaultValues = await getDefaultValues(convertDocToEntries(document), document);
|
|
421
416
|
setFormData(defaultValues);
|
|
@@ -424,29 +419,32 @@ function FormRendererContainer(props) {
|
|
|
424
419
|
const defaultValues = await getDefaultValues(form.entries, instance || {});
|
|
425
420
|
setFormData(defaultValues);
|
|
426
421
|
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
|
|
430
|
-
return !error ? (React.createElement(Box, { sx: {
|
|
422
|
+
};
|
|
423
|
+
const defaultContainer = !error ? (React.createElement(Box, { sx: {
|
|
431
424
|
backgroundColor: '#ffffff',
|
|
432
425
|
borderRadius: '6px',
|
|
433
426
|
padding: '0px',
|
|
434
|
-
border: !isLoading
|
|
435
|
-
|
|
436
|
-
!isLoading ? (React.createElement(FormRenderer, { onSubmit: onSubmit
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
427
|
+
border: !isLoading ? '1px solid #dbe0e4' : undefined,
|
|
428
|
+
...sx,
|
|
429
|
+
} }, !isLoading ? (React.createElement(React.Fragment, null, form && sanitizedObject && (React.createElement(FormRenderer, { onSubmit: onSubmit ? (data) => onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, associatedObject: associatedObject, renderHeader: renderHeader, renderBody: renderBody, renderFooter: document && !hasDocumentUpdateAccess ? () => React.createElement(React.Fragment, null) : renderFooter })))) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
430
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
431
|
+
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
432
|
+
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
433
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
434
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
435
|
+
React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
436
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
437
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
438
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
439
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))))) : (React.createElement(ErrorComponent, { colspan: props.colspan, code: error === 403 ? 'AccessDenied' : error === 404 ? 'NotFound' : 'Misconfigured',
|
|
440
|
+
// box shadow looks wrong when rendering a form in a modal, and there's no need for it
|
|
441
|
+
// in a standalone form v2 widget.
|
|
442
|
+
styles: { boxShadow: 'none' } }));
|
|
443
|
+
return (React.createElement(React.Fragment, null,
|
|
444
|
+
renderContainer ? React.createElement(React.Fragment, null, renderContainer({ status, error, defaultContainer })) : defaultContainer,
|
|
447
445
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
448
446
|
isError: snackbarError.isError,
|
|
449
447
|
showAlert: false,
|
|
450
|
-
}), message: snackbarError.message, error: snackbarError.isError })))
|
|
448
|
+
}), message: snackbarError.message, error: snackbarError.isError })));
|
|
451
449
|
}
|
|
452
450
|
export default FormRendererContainer;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FormEntry } from '@evoke-platform/context';
|
|
2
|
+
import { SxProps } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { FieldErrors } from 'react-hook-form';
|
|
5
|
+
import { ExpandedSection } from './types';
|
|
6
|
+
export type BodyProps = {
|
|
7
|
+
entries: FormEntry[];
|
|
8
|
+
isInitializing: boolean;
|
|
9
|
+
errors?: FieldErrors;
|
|
10
|
+
shouldShowValidationErrors: boolean;
|
|
11
|
+
hasAccordions: boolean;
|
|
12
|
+
expandedSections?: ExpandedSection[];
|
|
13
|
+
onExpandAll?: () => void;
|
|
14
|
+
onCollapseAll?: () => void;
|
|
15
|
+
sx?: SxProps;
|
|
16
|
+
};
|
|
17
|
+
export declare const Body: React.FC<BodyProps>;
|
|
18
|
+
export default Body;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { FormContext } from '../..';
|
|
3
|
+
import useWidgetSize from '../../../../theme/hooks';
|
|
4
|
+
import { Skeleton } from '../../../core';
|
|
5
|
+
import Box from '../../../layout/Box/Box';
|
|
6
|
+
import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
|
|
7
|
+
export const Body = (props) => {
|
|
8
|
+
const { entries, isInitializing, sx } = props;
|
|
9
|
+
const { width } = useContext(FormContext);
|
|
10
|
+
const { breakpoints } = useWidgetSize({
|
|
11
|
+
scroll: false,
|
|
12
|
+
defaultWidth: width,
|
|
13
|
+
});
|
|
14
|
+
const { isXs, isSm } = breakpoints;
|
|
15
|
+
return (React.createElement(React.Fragment, null, isInitializing ? (React.createElement(Box, { sx: { padding: '20px', ...sx } },
|
|
16
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
17
|
+
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
18
|
+
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
19
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
20
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
21
|
+
React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
22
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
23
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
24
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
25
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))) : (React.createElement(Box, { sx: { paddingX: isSm || isXs ? 2 : 3, ...sx } }, entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
|
|
26
|
+
};
|
|
27
|
+
export default Body;
|