@evoke-platform/ui-components 1.10.0-testing.15 → 1.10.0-testing.17
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 +6 -5
- 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 +12 -4
- package/dist/published/components/custom/FormV2/components/Header.js +7 -9
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -19
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
- 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
|
@@ -32,7 +32,7 @@ const FormRendererInternal = (props) => {
|
|
|
32
32
|
const [isInitializing, setIsInitializing] = useState(true);
|
|
33
33
|
const [parameters, setParameters] = useState();
|
|
34
34
|
const objectStore = useObject(objectId);
|
|
35
|
-
const
|
|
35
|
+
const validationContainerRef = useRef(null);
|
|
36
36
|
const updateFetchedOptions = (newData) => {
|
|
37
37
|
setFetchedOptions((prev) => ({
|
|
38
38
|
...prev,
|
|
@@ -139,8 +139,8 @@ const FormRendererInternal = (props) => {
|
|
|
139
139
|
if (onSubmitErrorOverride) {
|
|
140
140
|
onSubmitErrorOverride(errors);
|
|
141
141
|
}
|
|
142
|
-
else if (
|
|
143
|
-
|
|
142
|
+
else if (validationContainerRef.current) {
|
|
143
|
+
validationContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
144
144
|
}
|
|
145
145
|
};
|
|
146
146
|
async function unregisterHiddenFieldsAndSubmit() {
|
|
@@ -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
|
+
validationContainerRef: validationContainerRef,
|
|
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
|
|
9
|
-
|
|
8
|
+
shouldShowValidationErrors?: boolean;
|
|
9
|
+
validationContainerRef?: 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,
|
|
10
|
+
const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, isDeleteForm, autosaveEnabled, } = props;
|
|
11
11
|
const { width } = useFormContext();
|
|
12
12
|
const { breakpoints, isBelow } = useWidgetSize({
|
|
13
13
|
scroll: false,
|
|
@@ -16,6 +16,7 @@ const Header = (props) => {
|
|
|
16
16
|
const isSmallerThanMd = isBelow('md');
|
|
17
17
|
const { isXs, isSm } = breakpoints;
|
|
18
18
|
const isSmall = isSm || isXs;
|
|
19
|
+
const displayValidationErrors = shouldShowValidationErrors && !isEmpty(errors);
|
|
19
20
|
return (React.createElement(Box, { sx: {
|
|
20
21
|
paddingX: isSmallerThanMd ? 2 : 3,
|
|
21
22
|
paddingTop: '0px',
|
|
@@ -24,28 +25,25 @@ const Header = (props) => {
|
|
|
24
25
|
flexWrap: 'wrap',
|
|
25
26
|
paddingY: isSm || isXs ? 2 : 3,
|
|
26
27
|
// when rendering the default delete action, we don't want a border
|
|
27
|
-
borderBottom:
|
|
28
|
+
borderBottom: isDeleteForm ? undefined : '1px solid #e9ecef',
|
|
28
29
|
gap: isSm || isXs ? 2 : 3,
|
|
29
|
-
'.evoke-form-renderer-header': {
|
|
30
|
-
flex: '1 1 100%',
|
|
31
|
-
},
|
|
32
30
|
...sx,
|
|
33
|
-
} },
|
|
31
|
+
}, ref: validationContainerRef },
|
|
34
32
|
title && (React.createElement(Box, { sx: { flex: '1 1 auto', minWidth: 0, display: 'flex', alignItems: 'center', gap: 1 } },
|
|
35
33
|
React.createElement(Title, { ...props }),
|
|
36
34
|
props.autosaving && !isSmall && React.createElement(SavingIndicator, null))),
|
|
37
35
|
hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
|
|
38
36
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
39
37
|
React.createElement(AccordionActions, { ...props })),
|
|
40
|
-
React.createElement(Box, { sx: {
|
|
38
|
+
autosaveEnabled && (React.createElement(Box, { sx: {
|
|
41
39
|
width: '96px',
|
|
42
40
|
minWidth: '72px',
|
|
43
41
|
display: 'flex',
|
|
44
42
|
justifyContent: 'flex-end',
|
|
45
43
|
alignItems: 'center',
|
|
46
44
|
marginLeft: 0.5,
|
|
47
|
-
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } })))),
|
|
48
|
-
|
|
45
|
+
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } }))))),
|
|
46
|
+
displayValidationErrors ? React.createElement(ValidationErrors, { errors: errors }) : null));
|
|
49
47
|
};
|
|
50
48
|
// Default slot components for convenience
|
|
51
49
|
export const Title = ({ title }) => (React.createElement(Typography, { sx: {
|