@evoke-platform/ui-components 1.10.0-testing.15 → 1.10.0-testing.16
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.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/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
|
@@ -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 = {
|
|
@@ -7,7 +7,7 @@ import { Typography } from '../../../core/Typography';
|
|
|
7
7
|
import Box from '../../../layout/Box/Box';
|
|
8
8
|
import ValidationErrors from './ValidationFiles/ValidationErrors';
|
|
9
9
|
const Header = (props) => {
|
|
10
|
-
const { title, errors, hasAccordions, shouldShowValidationErrors, validationErrorsRef,
|
|
10
|
+
const { title, errors, hasAccordions, shouldShowValidationErrors, validationErrorsRef, sx, isDeleteForm, autosaveEnabled, } = props;
|
|
11
11
|
const { width } = useFormContext();
|
|
12
12
|
const { breakpoints, isBelow } = useWidgetSize({
|
|
13
13
|
scroll: false,
|
|
@@ -24,7 +24,7 @@ const Header = (props) => {
|
|
|
24
24
|
flexWrap: 'wrap',
|
|
25
25
|
paddingY: isSm || isXs ? 2 : 3,
|
|
26
26
|
// when rendering the default delete action, we don't want a border
|
|
27
|
-
borderBottom:
|
|
27
|
+
borderBottom: isDeleteForm ? undefined : '1px solid #e9ecef',
|
|
28
28
|
gap: isSm || isXs ? 2 : 3,
|
|
29
29
|
'.evoke-form-renderer-header': {
|
|
30
30
|
flex: '1 1 100%',
|
|
@@ -37,14 +37,14 @@ const Header = (props) => {
|
|
|
37
37
|
hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
|
|
38
38
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
39
39
|
React.createElement(AccordionActions, { ...props })),
|
|
40
|
-
React.createElement(Box, { sx: {
|
|
40
|
+
autosaveEnabled && (React.createElement(Box, { sx: {
|
|
41
41
|
width: '96px',
|
|
42
42
|
minWidth: '72px',
|
|
43
43
|
display: 'flex',
|
|
44
44
|
justifyContent: 'flex-end',
|
|
45
45
|
alignItems: 'center',
|
|
46
46
|
marginLeft: 0.5,
|
|
47
|
-
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } })))),
|
|
47
|
+
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } }))))),
|
|
48
48
|
React.createElement("div", { ref: validationErrorsRef, className: 'evoke-form-renderer-header' }, shouldShowValidationErrors && !isEmpty(errors) ? React.createElement(ValidationErrors, { errors: errors }) : null)));
|
|
49
49
|
};
|
|
50
50
|
// Default slot components for convenience
|
|
@@ -38,11 +38,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
38
38
|
}
|
|
39
39
|
export function RecursiveEntryRenderer(props) {
|
|
40
40
|
const { entry } = props;
|
|
41
|
-
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
42
|
-
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
43
|
-
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
41
|
+
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
46
42
|
const { isBelow, breakpoints } = useWidgetSize({
|
|
47
43
|
scroll: false,
|
|
48
44
|
defaultWidth: width,
|
|
@@ -53,20 +49,24 @@ export function RecursiveEntryRenderer(props) {
|
|
|
53
49
|
const userAccount = useAuthenticationContext()?.account;
|
|
54
50
|
const entryId = getEntryId(entry) || 'defaultId';
|
|
55
51
|
const display = 'display' in entry ? entry.display : undefined;
|
|
56
|
-
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues(entryId);
|
|
52
|
+
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues ? getValues(entryId) : undefined;
|
|
57
53
|
const initialMiddleObjectInstances = fetchedOptions[`${entryId}InitialMiddleObjectInstances`];
|
|
58
54
|
const middleObject = fetchedOptions[`${entryId}MiddleObject`];
|
|
59
55
|
const fieldDefinition = useMemo(() => {
|
|
60
56
|
return getFieldDefinition(entry, object, parameters, form?.id === 'documentForm');
|
|
61
57
|
}, [entry, parameters, object]);
|
|
62
58
|
const validation = fieldDefinition?.validation || {};
|
|
63
|
-
if (associatedObject?.propertyId === entryId)
|
|
64
|
-
return null;
|
|
65
59
|
useEffect(() => {
|
|
66
60
|
if (fieldDefinition?.type === 'collection' && fieldDefinition?.manyToManyPropertyId && instance) {
|
|
67
61
|
fetchCollectionData(apiServices, fieldDefinition, setFetchedOptions, instance.id, fetchedOptions, initialMiddleObjectInstances);
|
|
68
62
|
}
|
|
69
63
|
}, [fieldDefinition, instance]);
|
|
64
|
+
if (associatedObject?.propertyId === entryId)
|
|
65
|
+
return null;
|
|
66
|
+
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
67
|
+
if (!getValues || !entryIsVisible(entry, instance, getValues())) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
70
|
if (entry.type === 'content') {
|
|
71
71
|
return (React.createElement(Box, { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(entry.html) }, sx: {
|
|
72
72
|
fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
|
|
@@ -75,7 +75,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
75
75
|
else if ((entry.type === 'input' || entry.type === 'readonlyField' || entry.type === 'inputField') &&
|
|
76
76
|
fieldDefinition) {
|
|
77
77
|
if (isAddressProperty(entryId)) {
|
|
78
|
-
return React.createElement(AddressFields, { entry: entry, entryId: entryId, fieldDefinition: fieldDefinition });
|
|
78
|
+
return (React.createElement(AddressFields, { entry: entry, entryId: entryId, fieldDefinition: fieldDefinition, readOnly: entry.type === 'readonlyField' }));
|
|
79
79
|
}
|
|
80
80
|
else if (fieldDefinition.type === 'image') {
|
|
81
81
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -118,16 +118,11 @@ export function RecursiveEntryRenderer(props) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
else if (fieldDefinition.type === 'richText') {
|
|
121
|
-
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
121
|
+
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, RichTextEditor && handleChange ? (React.createElement(RichTextEditor
|
|
122
|
+
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
123
|
+
, {
|
|
122
124
|
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
123
|
-
id: entry.uniqueId,
|
|
124
|
-
value: fieldValue,
|
|
125
|
-
handleUpdate: (value) => handleChange(entryId, value),
|
|
126
|
-
format: 'rtf',
|
|
127
|
-
disabled: entry.type === 'readonlyField',
|
|
128
|
-
rows: display?.rowCount,
|
|
129
|
-
hasError: !!errors?.[entryId],
|
|
130
|
-
})) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, onBlur: () => {
|
|
125
|
+
id: entry.uniqueId, value: fieldValue, handleUpdate: (value) => handleChange(entryId, value), format: "rtf", disabled: entry.type === 'readonlyField', rows: display?.rowCount, hasError: !!errors?.[entryId] })) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue, onChange: handleChange, onBlur: () => {
|
|
131
126
|
onAutosave?.(entryId)?.catch((error) => {
|
|
132
127
|
console.error('Autosave failed:', error);
|
|
133
128
|
});
|
|
@@ -174,7 +169,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
174
169
|
: `${entryId}-reset-false`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
|
|
175
170
|
React.createElement(FormField, { id: entryId,
|
|
176
171
|
// TODO: Ideally the FormField prop should be called parameter but can't change the name for backwards compatibility reasons
|
|
177
|
-
property: fieldDefinition, defaultValue: fieldValue
|
|
172
|
+
property: fieldDefinition, defaultValue: fieldValue, onChange: handleChange, onBlur: () => {
|
|
178
173
|
// Blur event - reads current value from formData
|
|
179
174
|
onAutosave?.(entryId)?.catch((error) => {
|
|
180
175
|
console.error('Autosave failed:', error);
|