@evoke-platform/ui-components 1.16.0 → 1.18.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/CriteriaBuilder/CriteriaBuilder.js +3 -0
- package/dist/published/components/custom/Form/utils.d.ts +2 -2
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +15 -7
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/Select/Select.js +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRenderer.js +12 -7
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +52 -31
- package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Body.js +4 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +3 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +8 -6
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +2 -0
- package/dist/published/components/custom/FormV2/components/FormletRenderer.d.ts +7 -0
- package/dist/published/components/custom/FormV2/components/FormletRenderer.js +22 -0
- package/dist/published/components/custom/FormV2/components/HtmlView.js +16 -9
- package/dist/published/components/custom/FormV2/components/MisconfiguredErrorMessage.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/MisconfiguredErrorMessage.js +15 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +17 -24
- package/dist/published/components/custom/FormV2/components/types.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -2
- package/dist/published/components/custom/FormV2/components/utils.js +84 -4
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +228 -7
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +491 -35
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +20 -9
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +1 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -0
- package/dist/published/stories/FormRenderer.stories.js +1 -0
- package/dist/published/stories/FormRendererContainer.stories.d.ts +5 -0
- package/dist/published/stories/FormRendererData.d.ts +15 -0
- package/dist/published/stories/FormRendererData.js +63 -0
- package/dist/published/stories/sharedMswHandlers.js +4 -2
- package/package.json +1 -1
|
@@ -562,6 +562,9 @@ const CriteriaBuilder = (props) => {
|
|
|
562
562
|
borderStyle: 'hidden',
|
|
563
563
|
background: '#fff',
|
|
564
564
|
},
|
|
565
|
+
'.ruleGroup:not(:has(.rule, .ruleGroup .ruleGroup))': {
|
|
566
|
+
backgroundColor: 'transparent',
|
|
567
|
+
},
|
|
565
568
|
'.ruleGroup-header': {
|
|
566
569
|
display: 'block',
|
|
567
570
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionInput, ActionInputType, ApiServices, AxiosError, FormEntry, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, PropertyType, UserAccount } from '@evoke-platform/context';
|
|
1
|
+
import { ActionInput, ActionInputType, ApiServices, AxiosError, FormEntry, FormletReference, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, PropertyType, UserAccount } from '@evoke-platform/context';
|
|
2
2
|
import { ReactComponent } from '@formio/react';
|
|
3
3
|
import { LocalDateTime } from '@js-joda/core';
|
|
4
4
|
import { AutocompleteOption } from '../../core';
|
|
@@ -7,7 +7,7 @@ export declare function determineComponentType(properties: Property[], parameter
|
|
|
7
7
|
export declare function determineParameterType(componentType: string): PropertyType;
|
|
8
8
|
export declare function getFlattenEntries(entries: FormEntry[]): InputParameterReference[];
|
|
9
9
|
export declare function convertFormToComponents(entries: FormEntry[], parameters: InputParameter[], object: Obj): ActionInput[];
|
|
10
|
-
export declare function convertComponentsToForm(components: ActionInput[]): FormEntry[];
|
|
10
|
+
export declare function convertComponentsToForm(components: ActionInput[]): Exclude<FormEntry, FormletReference>[];
|
|
11
11
|
export declare function getMiddleObjectFilter(property: Property, instance: ObjectInstance): {
|
|
12
12
|
where: {
|
|
13
13
|
[x: string]: string;
|
package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js
CHANGED
|
@@ -48,7 +48,7 @@ const AddressFieldComponent = (props) => {
|
|
|
48
48
|
!mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: readOnly ? undefined : placeholder, InputProps: {
|
|
49
49
|
type: 'search',
|
|
50
50
|
autoComplete: 'off',
|
|
51
|
-
}, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
|
|
51
|
+
}, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}), sx: { backgroundColor: 'white' } })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
|
|
52
52
|
? {
|
|
53
53
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
54
54
|
border: 'none',
|
|
@@ -4,11 +4,17 @@ import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank, Help, ToggleOff, Toggle
|
|
|
4
4
|
import { defaultTheme } from '../../../../theme/defaultTheme';
|
|
5
5
|
import { Autocomplete, Checkbox, FormControl, FormControlLabel, FormHelperText, IconButton, Switch, TextField, Tooltip, Typography, } from '../../../core';
|
|
6
6
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
whiteSpace: 'normal',
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import { Box } from '../../../layout';
|
|
8
|
+
const styles = {
|
|
9
|
+
descriptionStyles: { color: '#999 !important', whiteSpace: 'normal', paddingBottom: '4px', marginX: 0 },
|
|
10
|
+
checkboxIconBox: {
|
|
11
|
+
backgroundColor: 'white',
|
|
12
|
+
width: '18px',
|
|
13
|
+
height: '18px',
|
|
14
|
+
display: 'flex',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
},
|
|
12
18
|
};
|
|
13
19
|
const BooleanSelect = (props) => {
|
|
14
20
|
const { id, property, defaultValue, error, errorMessage, readOnly, size, displayOption, label, strictlyTrue, tooltip, description, placeholder, onBlur, additionalProps, } = props;
|
|
@@ -31,7 +37,7 @@ const BooleanSelect = (props) => {
|
|
|
31
37
|
},
|
|
32
38
|
];
|
|
33
39
|
const descriptionComponent = () => {
|
|
34
|
-
return (description && (React.createElement(FormHelperText, { sx: descriptionStyles, component: Typography }, parse(description))));
|
|
40
|
+
return (description && (React.createElement(FormHelperText, { sx: styles.descriptionStyles, component: Typography }, parse(description))));
|
|
35
41
|
};
|
|
36
42
|
const labelComponent = () => {
|
|
37
43
|
return (React.createElement(Typography, { component: "span", variant: "body2", sx: { wordWrap: 'break-word', ...defaultTheme.typography.body2 } },
|
|
@@ -58,7 +64,9 @@ const BooleanSelect = (props) => {
|
|
|
58
64
|
return displayOption === 'dropdown' ? (React.createElement(Autocomplete, { renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, placeholder: placeholder, size: size ?? 'medium' })), value: booleanOptions.find((opt) => opt.value === value) ?? '', onChange: (e, selectedValue) => handleChange(selectedValue.value), isOptionEqualToValue: (option, val) => option?.value === val?.value, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}), sortBy: "NONE", required: strictlyTrue })) : (React.createElement(FormControl, { required: strictlyTrue, error: error, fullWidth: true },
|
|
59
65
|
React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), sx: { marginLeft: '-8px' }, control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
|
|
60
66
|
alignSelf: 'start',
|
|
61
|
-
}, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx:
|
|
67
|
+
}, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), icon: React.createElement(Box, { sx: styles.checkboxIconBox },
|
|
68
|
+
React.createElement(CheckBoxOutlineBlank, { fontSize: size ?? 'medium' })), checkedIcon: React.createElement(Box, { sx: styles.checkboxIconBox },
|
|
69
|
+
React.createElement(CheckBoxIcon, { fontSize: size ?? 'medium' })), sx: {
|
|
62
70
|
alignSelf: 'start',
|
|
63
71
|
padding: '4px 9px 9px 9px',
|
|
64
72
|
}, ...(additionalProps ?? {}) })) }),
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js
CHANGED
|
@@ -56,7 +56,6 @@ const InputFieldComponent = (props) => {
|
|
|
56
56
|
: property.enum, onChange: handleSelectChange, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, error: error, errorMessage: errorMessage, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: placeholder })), disableClearable: true, value: value, isOptionEqualToValue: (option, value) => {
|
|
57
57
|
return option.value === value;
|
|
58
58
|
}, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask || isValueProtected ? (React.createElement(TextField, { id: id, sx: {
|
|
59
|
-
background: 'white',
|
|
60
59
|
borderRadius: '8px',
|
|
61
60
|
...(readOnly && {
|
|
62
61
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
@@ -67,6 +66,7 @@ const InputFieldComponent = (props) => {
|
|
|
67
66
|
backgroundColor: '#f4f6f8',
|
|
68
67
|
},
|
|
69
68
|
}),
|
|
69
|
+
backgroundColor: 'white',
|
|
70
70
|
}, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, endAdornment, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: readOnly ? undefined : placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
|
|
71
71
|
? {
|
|
72
72
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
@@ -161,7 +161,7 @@ const Select = (props) => {
|
|
|
161
161
|
React.createElement(Typography, { variant: "caption" }, "Clear Selection"))))) : (React.createElement(Autocomplete, { multiple: property?.type === 'array', id: id, sortBy: sortBy, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, fullWidth: true, onBlur: onBlur, inputProps: {
|
|
162
162
|
...params.inputProps,
|
|
163
163
|
'aria-describedby': isCombobox ? `${id}-instructions` : undefined,
|
|
164
|
-
} })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
|
|
164
|
+
}, sx: { backgroundColor: 'white', borderRadius: '8px' } })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
|
|
165
165
|
const filtered = filter(options, params);
|
|
166
166
|
const { inputValue } = params;
|
|
167
167
|
// Suggest to the user to add a new value.
|
|
@@ -26,6 +26,7 @@ export type FormRendererProps = BaseProps & {
|
|
|
26
26
|
};
|
|
27
27
|
renderHeader?: (props: HeaderProps) => React.ReactNode;
|
|
28
28
|
renderBody?: (props: BodyProps) => React.ReactNode;
|
|
29
|
+
readOnly?: boolean;
|
|
29
30
|
renderFooter?: (props: FooterProps) => React.ReactNode;
|
|
30
31
|
};
|
|
31
32
|
export declare const FormRenderer: ((props: FormRendererProps) => React.JSX.Element) & {
|
|
@@ -14,7 +14,7 @@ import { assignIdsToSectionsAndRichText, convertPropertiesToParams, entryIsVisib
|
|
|
14
14
|
import { handleValidation } from './components/ValidationFiles/Validation';
|
|
15
15
|
import ValidationErrors from './components/ValidationFiles/ValidationErrors';
|
|
16
16
|
const FormRendererInternal = (props) => {
|
|
17
|
-
const { onSubmit, onDiscardChanges, onSubmitError: onSubmitErrorOverride, value, hideTitle = false, fieldHeight, richTextEditor, form, instance, onChange, onAutosave, associatedObject, renderHeader, renderBody, renderFooter, } = props;
|
|
17
|
+
const { onSubmit, onDiscardChanges, onSubmitError: onSubmitErrorOverride, value, hideTitle = false, fieldHeight, richTextEditor, form, instance, onChange, onAutosave, associatedObject, renderHeader, renderBody, renderFooter, readOnly, } = props;
|
|
18
18
|
const { entries, name: title, objectId, actionId, display } = form;
|
|
19
19
|
const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
|
|
20
20
|
defaultValues: value,
|
|
@@ -54,17 +54,17 @@ const FormRendererInternal = (props) => {
|
|
|
54
54
|
enabled: !!objectId,
|
|
55
55
|
});
|
|
56
56
|
const updatedEntries = useMemo(() => {
|
|
57
|
-
return
|
|
57
|
+
return assignIdsToSectionsAndRichText(entries, object, parameters);
|
|
58
58
|
}, [entries, object, parameters]);
|
|
59
59
|
useEffect(() => {
|
|
60
|
-
if (!object)
|
|
60
|
+
if (objectId && !object)
|
|
61
61
|
return;
|
|
62
|
-
const action = object
|
|
62
|
+
const action = object?.actions?.find((a) => a.id === actionId);
|
|
63
63
|
setAction(action);
|
|
64
64
|
// if forms action is synced with object properties then convertPropertiesToParams
|
|
65
|
-
setParameters(action?.parameters ?? convertPropertiesToParams(object));
|
|
65
|
+
setParameters(action?.parameters ?? (object ? convertPropertiesToParams(object) : []));
|
|
66
66
|
setIsInitializing(false);
|
|
67
|
-
}, [object, actionId]);
|
|
67
|
+
}, [object, actionId, objectId]);
|
|
68
68
|
useEffect(() => {
|
|
69
69
|
const currentValues = getValues();
|
|
70
70
|
if (value) {
|
|
@@ -221,6 +221,7 @@ const FormRendererInternal = (props) => {
|
|
|
221
221
|
onCollapseAll: handleCollapseAll,
|
|
222
222
|
expandedSections,
|
|
223
223
|
hasAccordions: hasSections && isSmallerThanMd,
|
|
224
|
+
readOnly,
|
|
224
225
|
})) : (React.createElement(Body, { ...{
|
|
225
226
|
isInitializing,
|
|
226
227
|
entries: updatedEntries,
|
|
@@ -230,8 +231,12 @@ const FormRendererInternal = (props) => {
|
|
|
230
231
|
onCollapseAll: handleCollapseAll,
|
|
231
232
|
expandedSections,
|
|
232
233
|
hasAccordions: hasSections && isSmallerThanMd,
|
|
234
|
+
readOnly,
|
|
233
235
|
} })),
|
|
234
|
-
|
|
236
|
+
readOnly !== true &&
|
|
237
|
+
action &&
|
|
238
|
+
onSubmit &&
|
|
239
|
+
(renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
|
|
235
240
|
};
|
|
236
241
|
export const FormRenderer = Object.assign(function FormRenderer(props) {
|
|
237
242
|
return (React.createElement(ConditionalQueryClientProvider, null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
2
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
+
import { DepGraph } from 'dependency-graph';
|
|
4
5
|
import { cloneDeep, get, isArray, isEmpty, isEqual, isObject, pick, set } from 'lodash';
|
|
5
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
7
|
import { Skeleton, Snackbar } from '../../core';
|
|
@@ -9,9 +10,8 @@ import ErrorComponent from '../ErrorComponent';
|
|
|
9
10
|
import ConditionalQueryClientProvider from './components/ConditionalQueryClientProvider';
|
|
10
11
|
import { evalDefaultVals, processValueUpdate } from './components/DefaultValues';
|
|
11
12
|
import Header from './components/Header';
|
|
12
|
-
import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, handleFileUpload, isAddressProperty, isEmptyWithDefault, plainTextToRtf, useFormById, } from './components/utils';
|
|
13
|
+
import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, handleFileUpload, getVisibleEditableFieldIds, isAddressProperty, isEmptyWithDefault, plainTextToRtf, useFormById, } from './components/utils';
|
|
13
14
|
import FormRenderer from './FormRenderer';
|
|
14
|
-
import { DepGraph } from 'dependency-graph';
|
|
15
15
|
// Wrapper to provide QueryClient context for FormRendererContainer if this is not a nested form
|
|
16
16
|
function FormRendererContainer(props) {
|
|
17
17
|
return (React.createElement(ConditionalQueryClientProvider, null,
|
|
@@ -110,10 +110,16 @@ function FormRendererContainerInner(props) {
|
|
|
110
110
|
setError(code ?? err);
|
|
111
111
|
};
|
|
112
112
|
const { data: sanitizedObject, error: sanitizedObjectError } = useQuery({
|
|
113
|
-
queryKey: [
|
|
113
|
+
queryKey: [
|
|
114
|
+
form?.objectId ?? objectId,
|
|
115
|
+
...(instanceId ? [instanceId] : []),
|
|
116
|
+
display?.readOnly ? 'unsanitized' : 'sanitized',
|
|
117
|
+
],
|
|
114
118
|
queryFn: () =>
|
|
115
119
|
// form?.objectId is needed for subtype forms to get the correct object
|
|
116
|
-
apiServices.get(getPrefixedUrl(`/objects/${form?.objectId ?? objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`),
|
|
120
|
+
apiServices.get(getPrefixedUrl(`/objects/${form?.objectId ?? objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`),
|
|
121
|
+
// when the form is readonly the action is still needed, so we need to get the unsanitized object
|
|
122
|
+
{ params: { sanitizedVersion: display?.readOnly ? false : true } }),
|
|
117
123
|
staleTime: Infinity,
|
|
118
124
|
enabled: !!(form?.objectId || objectId),
|
|
119
125
|
});
|
|
@@ -164,7 +170,11 @@ function FormRendererContainerInner(props) {
|
|
|
164
170
|
set(result, fieldId, fieldValue);
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
|
-
else if (entry.type !== 'sections' &&
|
|
173
|
+
else if (entry.type !== 'sections' &&
|
|
174
|
+
entry.type !== 'columns' &&
|
|
175
|
+
entry.type !== 'content' &&
|
|
176
|
+
// there cannot be formlets here since the `form` is the effective form
|
|
177
|
+
entry.type !== 'formlet') {
|
|
168
178
|
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
169
179
|
if (associatedObject &&
|
|
170
180
|
'propertyId' in associatedObject &&
|
|
@@ -185,9 +195,6 @@ function FormRendererContainerInner(props) {
|
|
|
185
195
|
console.error(error);
|
|
186
196
|
}
|
|
187
197
|
}
|
|
188
|
-
else if (entry.type === 'formlet') {
|
|
189
|
-
// TODO: this should eventually fetch the formletId then get the fields and default values of those fields
|
|
190
|
-
}
|
|
191
198
|
else if (entry.type !== 'readonlyField') {
|
|
192
199
|
if (isEmptyWithDefault(fieldValue, entry, result)) {
|
|
193
200
|
if (fieldId && parameters && parameters.length > 0) {
|
|
@@ -227,28 +234,39 @@ function FormRendererContainerInner(props) {
|
|
|
227
234
|
}
|
|
228
235
|
}
|
|
229
236
|
return result;
|
|
230
|
-
}, [action,
|
|
237
|
+
}, [action, associatedObject, uniquePresetValues, formDataRef, apiServices, userAccount]);
|
|
231
238
|
useEffect(() => {
|
|
232
|
-
if (!sanitizedObject)
|
|
239
|
+
if (!sanitizedObject) {
|
|
233
240
|
return;
|
|
234
|
-
const allCriterias = extractAllCriteria(flattenFormEntries, parameters || []);
|
|
235
|
-
const uniquePresetValues = new Set();
|
|
236
|
-
for (const criteria of allCriterias) {
|
|
237
|
-
const presetValues = extractPresetValuesFromCriteria(criteria);
|
|
238
|
-
presetValues.forEach((value) => uniquePresetValues.add(value));
|
|
239
241
|
}
|
|
240
|
-
extractPresetValuesFromDynamicDefaultValues(flattenFormEntries).map((value) => uniquePresetValues.add(value));
|
|
241
|
-
setUniquePresetValues(Array.from(uniquePresetValues));
|
|
242
242
|
const action = sanitizedObject.actions?.find((a) => a.id === (form?.actionId || actionId));
|
|
243
243
|
if (action && (instanceId || action.type === 'create')) {
|
|
244
244
|
setAction(action);
|
|
245
|
-
// Clear error if action is found after being missing
|
|
246
245
|
setError((prevError) => (prevError === 'Action could not be found' ? undefined : prevError));
|
|
246
|
+
const getParamsFromObject = !action.parameters;
|
|
247
|
+
const parameters = (getParamsFromObject ? convertPropertiesToParams(sanitizedObject) : action.parameters) ?? [];
|
|
248
|
+
setParameters(parameters.filter((param) => param.type !== 'collection' && !param.formula));
|
|
247
249
|
}
|
|
248
250
|
else {
|
|
249
251
|
setError('Action could not be found');
|
|
252
|
+
setAction(undefined);
|
|
253
|
+
setParameters([]);
|
|
254
|
+
}
|
|
255
|
+
}, [sanitizedObject, actionId, form?.actionId, instanceId]);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (!flattenFormEntries.length || !parameters.length) {
|
|
258
|
+
setUniquePresetValues([]);
|
|
259
|
+
return;
|
|
250
260
|
}
|
|
251
|
-
|
|
261
|
+
const allCriterias = extractAllCriteria(flattenFormEntries, parameters);
|
|
262
|
+
const uniquePresetValues = new Set();
|
|
263
|
+
for (const criteria of allCriterias) {
|
|
264
|
+
const presetValues = extractPresetValuesFromCriteria(criteria);
|
|
265
|
+
presetValues.forEach((value) => uniquePresetValues.add(value));
|
|
266
|
+
}
|
|
267
|
+
extractPresetValuesFromDynamicDefaultValues(flattenFormEntries).forEach((value) => uniquePresetValues.add(value));
|
|
268
|
+
setUniquePresetValues(Array.from(uniquePresetValues));
|
|
269
|
+
}, [flattenFormEntries, parameters]);
|
|
252
270
|
const { data: navigationSlug } = useQuery({
|
|
253
271
|
queryKey: [appId, 'navigationSlug'],
|
|
254
272
|
queryFn: () => apiServices.get(getPrefixedUrl(`/apps/${appId}/pages/${encodePageSlug(pageNavigation)}`)),
|
|
@@ -293,14 +311,6 @@ function FormRendererContainerInner(props) {
|
|
|
293
311
|
if (error)
|
|
294
312
|
onError(error);
|
|
295
313
|
}, [sanitizedObjectError, fetchedFormError, instanceError]);
|
|
296
|
-
useEffect(() => {
|
|
297
|
-
if (!form || !action)
|
|
298
|
-
return;
|
|
299
|
-
// If no parameters are defined, then the action is synced with object properties
|
|
300
|
-
const getParamsFromObject = sanitizedObject && !action.parameters;
|
|
301
|
-
const parameters = (getParamsFromObject ? convertPropertiesToParams(sanitizedObject) : action.parameters) ?? [];
|
|
302
|
-
setParameters(parameters.filter((param) => param.type !== 'collection' && !param.formula));
|
|
303
|
-
}, [form, action?.parameters, sanitizedObject]);
|
|
304
314
|
useEffect(() => {
|
|
305
315
|
const getInitialValues = async () => {
|
|
306
316
|
if (flattenFormEntries.length && (instance || !instanceId)) {
|
|
@@ -309,6 +319,10 @@ function FormRendererContainerInner(props) {
|
|
|
309
319
|
// Deep clone to avoid reference issues
|
|
310
320
|
setLastSavedData(cloneDeep(defaultValues));
|
|
311
321
|
}
|
|
322
|
+
else {
|
|
323
|
+
// if there is a form with no entries
|
|
324
|
+
setFormData({});
|
|
325
|
+
}
|
|
312
326
|
};
|
|
313
327
|
getInitialValues();
|
|
314
328
|
}, [instanceId, instance, flattenFormEntries, getDefaultValues]);
|
|
@@ -394,7 +408,7 @@ function FormRendererContainerInner(props) {
|
|
|
394
408
|
}
|
|
395
409
|
else if (instanceId && action) {
|
|
396
410
|
let response = undefined;
|
|
397
|
-
if ((await objectStore.get()).rootObjectId === 'sys__file') {
|
|
411
|
+
if ((await objectStore.get()).rootObjectId === 'sys__file' && action.type !== 'delete') {
|
|
398
412
|
response = await handleFileUpload(apiServices, submission, action.id, objectId, instanceId);
|
|
399
413
|
}
|
|
400
414
|
else {
|
|
@@ -447,6 +461,12 @@ function FormRendererContainerInner(props) {
|
|
|
447
461
|
if (!form?.autosaveActionId || !formDataRef.current) {
|
|
448
462
|
return;
|
|
449
463
|
}
|
|
464
|
+
const visibleEditableFieldIds = getVisibleEditableFieldIds(form.entries ?? [], instance, formDataRef.current);
|
|
465
|
+
const allowedParameterIds = parameters?.filter((parameter) => parameter.type !== 'collection').map((parameter) => parameter.id) ?? [];
|
|
466
|
+
const autosaveFieldIds = visibleEditableFieldIds.filter((id) => allowedParameterIds.includes(id));
|
|
467
|
+
if (!autosaveFieldIds.includes(fieldId)) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
450
470
|
const currentValue = get(formDataRef.current, fieldId);
|
|
451
471
|
const lastValue = get(lastSavedData, fieldId);
|
|
452
472
|
if (isEqual(currentValue, lastValue)) {
|
|
@@ -455,7 +475,8 @@ function FormRendererContainerInner(props) {
|
|
|
455
475
|
try {
|
|
456
476
|
setIsSaving(true);
|
|
457
477
|
const cleanedData = removeUneditedProtectedValues(formDataRef.current);
|
|
458
|
-
const
|
|
478
|
+
const scopedData = pick(cleanedData, autosaveFieldIds);
|
|
479
|
+
const submission = await formatSubmission(scopedData, apiServices, objectId, instanceId, form, setSnackbarError, undefined, parameters);
|
|
459
480
|
// Handle object instance autosave
|
|
460
481
|
if (instanceId && action?.type === 'update') {
|
|
461
482
|
const pickedSubmission = pick(submission, sanitizedObject?.properties
|
|
@@ -508,7 +529,7 @@ function FormRendererContainerInner(props) {
|
|
|
508
529
|
setFormData(newData);
|
|
509
530
|
}
|
|
510
531
|
}
|
|
511
|
-
const isLoading = (instanceId &&
|
|
532
|
+
const isLoading = !form || !sanitizedObject || (instanceId && formDataRef.current === undefined);
|
|
512
533
|
const status = error ? 'error' : isLoading ? 'loading' : 'ready';
|
|
513
534
|
// Compose a header renderer that injects the saving indicator into the rendered header
|
|
514
535
|
const composedRenderHeader = (props) => {
|
|
@@ -532,7 +553,7 @@ function FormRendererContainerInner(props) {
|
|
|
532
553
|
border: !isLoading ? '1px solid #dbe0e4' : undefined,
|
|
533
554
|
...sx,
|
|
534
555
|
} }, !isLoading ? (React.createElement(React.Fragment, null,
|
|
535
|
-
React.createElement(FormRenderer, { onSubmit: onSubmit ? async (data) => await onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, hideTitle: title?.hidden, fieldHeight: display?.fieldHeight ?? 'medium', value: formDataRef.current, form: form, instance: instance, onChange: onChange, onAutosave: onAutosave, associatedObject: associatedObject && 'propertyId' in associatedObject ? associatedObject : undefined, renderHeader: composedRenderHeader, renderBody: renderBody, renderFooter: renderFooter }))) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
556
|
+
React.createElement(FormRenderer, { onSubmit: onSubmit ? async (data) => await onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, hideTitle: title?.hidden, fieldHeight: display?.fieldHeight ?? 'medium', value: formDataRef.current, form: form, instance: instance, onChange: onChange, onAutosave: onAutosave, associatedObject: associatedObject && 'propertyId' in associatedObject ? associatedObject : undefined, renderHeader: composedRenderHeader, renderBody: renderBody, renderFooter: renderFooter, readOnly: display?.readOnly ?? false }))) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
536
557
|
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
537
558
|
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
538
559
|
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
@@ -3,9 +3,11 @@ import { FormContext } from '../..';
|
|
|
3
3
|
import useWidgetSize from '../../../../theme/hooks';
|
|
4
4
|
import { Skeleton } from '../../../core';
|
|
5
5
|
import Box from '../../../layout/Box/Box';
|
|
6
|
+
import ViewDetailsEntryRenderer from '../../ViewDetailsV2/InstanceEntryRenderer';
|
|
6
7
|
import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
|
|
8
|
+
import { convertToReadOnly } from './utils';
|
|
7
9
|
export const Body = (props) => {
|
|
8
|
-
const { entries, isInitializing, sx } = props;
|
|
10
|
+
const { entries, isInitializing, sx, readOnly } = props;
|
|
9
11
|
const { width } = useContext(FormContext);
|
|
10
12
|
const { breakpoints } = useWidgetSize({
|
|
11
13
|
scroll: false,
|
|
@@ -22,6 +24,6 @@ export const Body = (props) => {
|
|
|
22
24
|
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
23
25
|
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
24
26
|
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, paddingY: isSm || isXs ? '6px' : '14px', ...sx } }, entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
|
|
27
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))) : (React.createElement(Box, { sx: { paddingX: isSm || isXs ? 2 : 3, paddingY: isSm || isXs ? '6px' : '14px', ...sx } }, entries.map((entry, index) => readOnly === true ? (React.createElement(ViewDetailsEntryRenderer, { key: index, entry: convertToReadOnly(entry) })) : (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
|
|
26
28
|
};
|
|
27
29
|
export default Body;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useApiServices, useNotification, } from '@evoke-platform/context';
|
|
2
2
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { get,
|
|
3
|
+
import { get, pick, startCase } from 'lodash';
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import sift from 'sift';
|
|
@@ -308,10 +308,12 @@ const RepeatableField = (props) => {
|
|
|
308
308
|
const relatedObjectId = relatedObject?.id;
|
|
309
309
|
try {
|
|
310
310
|
let response = undefined;
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
.
|
|
314
|
-
|
|
311
|
+
const actionParameters = action?.parameters ?? (relatedObject ? convertPropertiesToParams(relatedObject) : undefined) ?? [];
|
|
312
|
+
const allowedFieldIds = actionParameters
|
|
313
|
+
.filter((param) => param.type !== 'collection')
|
|
314
|
+
.map((param) => param.id);
|
|
315
|
+
const submission = pick(input, allowedFieldIds);
|
|
316
|
+
if (relatedObject?.rootObjectId === 'sys__file' && action?.id && action?.type !== 'delete') {
|
|
315
317
|
response = await handleFileUpload(apiServices, submission, action.id, relatedObjectId, selectedInstanceId);
|
|
316
318
|
}
|
|
317
319
|
else {
|
|
@@ -472,7 +474,7 @@ const RepeatableField = (props) => {
|
|
|
472
474
|
React.createElement(TableRow, null,
|
|
473
475
|
columns?.map((prop) => (React.createElement(TableCell, { sx: styles.tableCell }, prop.name))),
|
|
474
476
|
canUpdateProperty && React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
|
|
475
|
-
React.createElement(TableBody,
|
|
477
|
+
React.createElement(TableBody, { sx: { backgroundColor: 'white' } }, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
|
|
476
478
|
columns?.map((prop) => {
|
|
477
479
|
return (React.createElement(TableCell, { sx: { fontSize: '16px' } }, prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
|
|
478
480
|
? {
|
package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js
CHANGED
|
@@ -126,6 +126,7 @@ export const Document = (props) => {
|
|
|
126
126
|
border: `1px dashed ${error ? 'red' : uploadDisabled ? '#DFE3E8' : '#858585'}`,
|
|
127
127
|
position: 'relative',
|
|
128
128
|
cursor: uploadDisabled ? 'cursor' : 'pointer',
|
|
129
|
+
backgroundColor: 'white',
|
|
129
130
|
}, ...getRootProps(), onClick: open },
|
|
130
131
|
React.createElement("input", { ...getInputProps({ id }), disabled: uploadDisabled, ...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined) }),
|
|
131
132
|
React.createElement(Grid, { container: true, sx: { width: '100%' } },
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useApiServices } from '@evoke-platform/context';
|
|
3
|
+
import { convertToReadOnly, getPrefixedUrl } from './utils';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
5
|
+
import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
|
|
6
|
+
import { Skeleton } from '../../../core';
|
|
7
|
+
import ViewDetailsEntryRenderer from '../../ViewDetailsV2/InstanceEntryRenderer';
|
|
8
|
+
import MisconfiguredErrorMessage from './MisconfiguredErrorMessage';
|
|
9
|
+
function FormletRenderer(props) {
|
|
10
|
+
const { entry, readOnly } = props;
|
|
11
|
+
const apiServices = useApiServices();
|
|
12
|
+
const { data: formlet, isLoading } = useQuery({
|
|
13
|
+
queryKey: [entry.formletId, 'formlet'],
|
|
14
|
+
queryFn: () => apiServices.get(getPrefixedUrl(`/formlets/${entry.formletId}`)),
|
|
15
|
+
staleTime: Infinity,
|
|
16
|
+
enabled: !!entry.formletId,
|
|
17
|
+
});
|
|
18
|
+
if (isLoading)
|
|
19
|
+
return React.createElement(Skeleton, null);
|
|
20
|
+
return formlet ? (React.createElement(React.Fragment, null, formlet.entries?.map((formletEntry, index) => readOnly === true ? (React.createElement(ViewDetailsEntryRenderer, { key: index, entry: convertToReadOnly(formletEntry) })) : (React.createElement(RecursiveEntryRenderer, { key: index, entry: formletEntry }))))) : (React.createElement(MisconfiguredErrorMessage, null));
|
|
21
|
+
}
|
|
22
|
+
export default FormletRenderer;
|
|
@@ -33,14 +33,21 @@ const HtmlView = ({ value }) => {
|
|
|
33
33
|
quillRef.current.setContents([]);
|
|
34
34
|
quillRef.current.clipboard.dangerouslyPasteHTML(DOMPurify.sanitize(value));
|
|
35
35
|
}, [value]);
|
|
36
|
-
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
return (
|
|
37
|
+
// Needs to be wrapped in a Box to prevent quill from setting height: 100% on the container, which causes it to overflow its parent and ignore the maxHeight we set on the containerRef (only happens when in a grid item)
|
|
38
|
+
React.createElement(Box, null,
|
|
39
|
+
React.createElement(Box, { sx: {
|
|
40
|
+
width: '100%',
|
|
41
|
+
height: '100%',
|
|
42
|
+
'.ql-container.ql-snow': {
|
|
43
|
+
border: 'none',
|
|
44
|
+
minHeight: 20,
|
|
45
|
+
},
|
|
46
|
+
// Override Quill list markers for correct display
|
|
47
|
+
'.ql-editor ol li:before': {
|
|
48
|
+
content: '""',
|
|
49
|
+
},
|
|
50
|
+
} },
|
|
51
|
+
React.createElement("div", { ref: containerRef }))));
|
|
45
52
|
};
|
|
46
53
|
export default HtmlView;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WarningRounded } from '../../../../icons';
|
|
2
|
+
import { Typography } from '../../../core';
|
|
3
|
+
import { Box } from '../../../layout';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export default function MisconfiguredErrorMessage() {
|
|
6
|
+
return (React.createElement(Box, { sx: {
|
|
7
|
+
display: 'flex',
|
|
8
|
+
backgroundColor: '#ffc1073b',
|
|
9
|
+
borderRadius: '8px',
|
|
10
|
+
padding: '16.5px 14px',
|
|
11
|
+
marginTop: '6px',
|
|
12
|
+
} },
|
|
13
|
+
React.createElement(WarningRounded, { sx: { paddingRight: '8px' }, color: "warning" }),
|
|
14
|
+
React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "This field was not configured correctly")));
|
|
15
|
+
}
|