@evoke-platform/ui-components 1.10.0-dev.33 → 1.10.0-dev.35
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 +24 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +45 -0
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +22 -6
- package/dist/published/components/custom/FormField/FormField.d.ts +3 -1
- package/dist/published/components/custom/FormField/FormField.js +17 -5
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -4
- package/dist/published/components/custom/FormV2/FormRenderer.js +17 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +66 -83
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +13 -13
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +5 -4
- package/dist/published/components/custom/FormV2/components/PropertyProtection.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/PropertyProtection.js +113 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +3 -2
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/utils.js +72 -4
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +127 -1
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +13 -3
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/dist/published/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -400,9 +400,31 @@ const CriteriaBuilder = (props) => {
|
|
|
400
400
|
const fields = useMemo(() => {
|
|
401
401
|
return properties
|
|
402
402
|
.filter(({ type }) => type !== 'collection')
|
|
403
|
-
.
|
|
403
|
+
.flatMap((property) => {
|
|
404
|
+
if (property.type === 'object') {
|
|
405
|
+
const result = [
|
|
406
|
+
{
|
|
407
|
+
name: `${property.id}.id`,
|
|
408
|
+
label: `${property.name} ID`,
|
|
409
|
+
inputType: property.type,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: `${property.id}.name`,
|
|
413
|
+
label: `${property.name} Name`,
|
|
414
|
+
inputType: property.type,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
if (!property.objectId) {
|
|
418
|
+
result.push({
|
|
419
|
+
name: `${property.id}.objectId`,
|
|
420
|
+
label: `${property.name} Object ID`,
|
|
421
|
+
inputType: property.type,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
404
426
|
return {
|
|
405
|
-
name: property.
|
|
427
|
+
name: property.id,
|
|
406
428
|
label: property.name,
|
|
407
429
|
inputType: property.type,
|
|
408
430
|
...(property.enum && {
|
|
@@ -56,6 +56,17 @@ const mockProperties = [
|
|
|
56
56
|
name: 'Boolean',
|
|
57
57
|
type: 'boolean',
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
id: 'regularRelatedObject',
|
|
61
|
+
name: 'Regular Related Object',
|
|
62
|
+
type: 'object',
|
|
63
|
+
objectId: 'relatedObjectId',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'dynamicRelatedObject',
|
|
67
|
+
name: 'Dynamic Related Object',
|
|
68
|
+
type: 'object',
|
|
69
|
+
},
|
|
59
70
|
];
|
|
60
71
|
describe('CriteriaBuilder', () => {
|
|
61
72
|
// Mock function for setCriteria
|
|
@@ -64,6 +75,40 @@ describe('CriteriaBuilder', () => {
|
|
|
64
75
|
// Reset the mock before each test
|
|
65
76
|
setCriteriaMock.mockReset();
|
|
66
77
|
});
|
|
78
|
+
describe('when passed regular related object fields', () => {
|
|
79
|
+
it('should render the field ID', () => {
|
|
80
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
81
|
+
'regularRelatedObject.id': 'relatedInstanceId',
|
|
82
|
+
}, setCriteria: setCriteriaMock }));
|
|
83
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object ID');
|
|
84
|
+
});
|
|
85
|
+
it('should render the field Name', () => {
|
|
86
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
87
|
+
'regularRelatedObject.name': 'relatedInstanceName',
|
|
88
|
+
}, setCriteria: setCriteriaMock }));
|
|
89
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object Name');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('when passed dynamic related object fields', () => {
|
|
93
|
+
it('should render the field ID', () => {
|
|
94
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
95
|
+
'dynamicRelatedObject.id': 'relatedInstanceId',
|
|
96
|
+
}, setCriteria: setCriteriaMock }));
|
|
97
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object ID');
|
|
98
|
+
});
|
|
99
|
+
it('should render the field Name', () => {
|
|
100
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
101
|
+
'dynamicRelatedObject.name': 'relatedInstanceName',
|
|
102
|
+
}, setCriteria: setCriteriaMock }));
|
|
103
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Name');
|
|
104
|
+
});
|
|
105
|
+
it('should render the field Object ID', () => {
|
|
106
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
107
|
+
'dynamicRelatedObject.objectId': 'relatedInstanceObjectId',
|
|
108
|
+
}, setCriteria: setCriteriaMock }));
|
|
109
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Object ID');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
67
112
|
describe('when passed single-select fields', () => {
|
|
68
113
|
it('should render the field name', () => {
|
|
69
114
|
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DateTimeFormatter } from '@js-joda/core';
|
|
2
2
|
import { omit } from 'lodash';
|
|
3
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
4
|
import { useFormContext } from '../../../../theme/hooks';
|
|
5
5
|
import { InvalidDate, LocalDate, nativeJs } from '../../../../util';
|
|
6
6
|
import { DatePicker, LocalizationProvider, TextField } from '../../../core';
|
|
7
|
+
import { obfuscateValue } from '../../FormV2/components/utils';
|
|
7
8
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
8
9
|
function asCalendarDate(value) {
|
|
9
10
|
if (!value) {
|
|
@@ -28,12 +29,16 @@ const asMonthDayYearFormat = (date) => {
|
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
const DatePickerSelect = (props) => {
|
|
31
|
-
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, } = props;
|
|
32
|
-
const [value, setValue] = useState(asCalendarDate(defaultValue));
|
|
32
|
+
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, endAdornment, protection, } = props;
|
|
33
33
|
const { onAutosave } = useFormContext();
|
|
34
|
+
const processValue = useCallback((val) => {
|
|
35
|
+
const isProtected = protection?.maskChar && val === obfuscateValue(val, { protection });
|
|
36
|
+
return isProtected && typeof val === 'string' ? val : asCalendarDate(val);
|
|
37
|
+
}, [protection]);
|
|
38
|
+
const [value, setValue] = useState(() => processValue(defaultValue));
|
|
34
39
|
useEffect(() => {
|
|
35
|
-
setValue(
|
|
36
|
-
}, [defaultValue]);
|
|
40
|
+
setValue(processValue(defaultValue));
|
|
41
|
+
}, [defaultValue, processValue]);
|
|
37
42
|
const handleChange = (date) => {
|
|
38
43
|
setValue(date);
|
|
39
44
|
onChange && onChange(property.id, date, property);
|
|
@@ -49,12 +54,23 @@ const DatePickerSelect = (props) => {
|
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
};
|
|
52
|
-
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
57
|
+
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
58
|
+
...props,
|
|
59
|
+
defaultValue: protection?.maskChar && value === obfuscateValue(value, { protection })
|
|
60
|
+
? value
|
|
61
|
+
: asMonthDayYearFormat(value),
|
|
62
|
+
endAdornment,
|
|
63
|
+
} })) : (React.createElement(LocalizationProvider, null,
|
|
53
64
|
React.createElement(DatePicker, { value: value, onChange: handleChange, onAccept: handleAccept, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
54
65
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
55
66
|
inputProps: {
|
|
56
67
|
...params.inputProps,
|
|
57
68
|
...(additionalProps?.inputProps ?? {}),
|
|
69
|
+
}, InputProps: {
|
|
70
|
+
...params.InputProps,
|
|
71
|
+
endAdornment: (React.createElement(React.Fragment, null,
|
|
72
|
+
params.InputProps?.endAdornment,
|
|
73
|
+
endAdornment)),
|
|
58
74
|
}, ...omit(additionalProps, ['inputProps']) })) })));
|
|
59
75
|
};
|
|
60
76
|
export default DatePickerSelect;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelectOption } from '@evoke-platform/context';
|
|
1
|
+
import { PropertyProtection as PropertyProtectionType, SelectOption } from '@evoke-platform/context';
|
|
2
2
|
import React, { FocusEventHandler, ReactNode } from 'react';
|
|
3
3
|
import { ObjectProperty } from '../../../types';
|
|
4
4
|
import { AutocompleteOption } from '../../core';
|
|
@@ -36,6 +36,8 @@ export type FormFieldProps = {
|
|
|
36
36
|
description?: string;
|
|
37
37
|
tooltip?: string;
|
|
38
38
|
isCombobox?: boolean;
|
|
39
|
+
endAdornment?: ReactNode;
|
|
40
|
+
protection?: PropertyProtectionType;
|
|
39
41
|
};
|
|
40
42
|
declare const FormField: (props: FormFieldProps) => React.JSX.Element;
|
|
41
43
|
export default FormField;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import PropertyProtection from '../FormV2/components/PropertyProtection';
|
|
2
3
|
import AddressFieldComponent from './AddressFieldComponent/addressFieldComponent';
|
|
3
4
|
import BooleanSelect from './BooleanSelect/BooleanSelect';
|
|
4
5
|
import DatePickerSelect from './DatePickerSelect/DatePickerSelect';
|
|
@@ -8,8 +9,16 @@ import InputFieldComponent from './InputFieldComponent/InputFieldComponent';
|
|
|
8
9
|
import Select from './Select/Select';
|
|
9
10
|
import TimePickerSelect from './TimePickerSelect/TimePickerSelect';
|
|
10
11
|
const FormField = (props) => {
|
|
11
|
-
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, isCombobox, } = props;
|
|
12
|
-
|
|
12
|
+
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, isCombobox, protection, } = props;
|
|
13
|
+
const [currentDisplayValue, setCurrentDisplayValue] = useState(defaultValue);
|
|
14
|
+
const isProtectedProperty = !!protection?.maskChar;
|
|
15
|
+
const [protectionMode, setProtectionMode] = useState(isProtectedProperty ? (!currentDisplayValue ? 'edit' : 'mask') : 'full');
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isProtectedProperty && protectionMode === 'edit') {
|
|
18
|
+
setCurrentDisplayValue(defaultValue);
|
|
19
|
+
}
|
|
20
|
+
}, [defaultValue]);
|
|
21
|
+
const protectionComponent = isProtectedProperty && !!defaultValue ? (React.createElement(PropertyProtection, { parameter: property, protection: protection, mask: mask, canEdit: !readOnly, value: defaultValue, handleChange: (value) => onChange?.(property.id, value, property), setCurrentDisplayValue: setCurrentDisplayValue, mode: protectionMode, setMode: setProtectionMode })) : null;
|
|
13
22
|
const commonProps = {
|
|
14
23
|
id: id ?? property.id,
|
|
15
24
|
property,
|
|
@@ -17,8 +26,8 @@ const FormField = (props) => {
|
|
|
17
26
|
onBlur,
|
|
18
27
|
error,
|
|
19
28
|
errorMessage,
|
|
20
|
-
readOnly,
|
|
21
|
-
defaultValue,
|
|
29
|
+
readOnly: readOnly || (!!isProtectedProperty && protectionMode !== 'edit'),
|
|
30
|
+
defaultValue: isProtectedProperty ? currentDisplayValue : defaultValue,
|
|
22
31
|
selectOptions,
|
|
23
32
|
required,
|
|
24
33
|
strictlyTrue,
|
|
@@ -37,7 +46,10 @@ const FormField = (props) => {
|
|
|
37
46
|
description,
|
|
38
47
|
tooltip,
|
|
39
48
|
isCombobox,
|
|
49
|
+
endAdornment: protectionComponent,
|
|
50
|
+
protection,
|
|
40
51
|
};
|
|
52
|
+
let control;
|
|
41
53
|
if (queryAddresses) {
|
|
42
54
|
control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
|
|
43
55
|
return control;
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js
CHANGED
|
@@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
3
3
|
import InputMask from 'react-input-mask';
|
|
4
4
|
import NumberFormat from 'react-number-format';
|
|
5
5
|
import { Autocomplete, TextField } from '../../../core';
|
|
6
|
+
import { obfuscateValue } from '../../FormV2/components/utils';
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
8
|
export const NumericFormat = (props) => {
|
|
8
9
|
const { inputRef, onChange, defaultValue, ...other } = props;
|
|
@@ -15,7 +16,7 @@ export const NumericFormat = (props) => {
|
|
|
15
16
|
}, isNumericString: true, fixedDecimalScale: true, allowNegative: true }));
|
|
16
17
|
};
|
|
17
18
|
const InputFieldComponent = (props) => {
|
|
18
|
-
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, required, size, placeholder, mask, min, max, isMultiLineText, rows, inputMaskPlaceholderChar, additionalProps, } = props;
|
|
19
|
+
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, required, size, placeholder, mask, min, max, isMultiLineText, rows, inputMaskPlaceholderChar, additionalProps, endAdornment, protection, } = props;
|
|
19
20
|
const [value, setValue] = useState(defaultValue ?? '');
|
|
20
21
|
const [inputValue, setInputValue] = useState('');
|
|
21
22
|
useEffect(() => {
|
|
@@ -43,6 +44,7 @@ const InputFieldComponent = (props) => {
|
|
|
43
44
|
: property.type === 'integer'
|
|
44
45
|
? { inputProps: { min, max, ...(additionalProps?.inputProps ?? {}) } }
|
|
45
46
|
: null;
|
|
47
|
+
const isValueProtected = protection?.maskChar && defaultValue === obfuscateValue(defaultValue, { protection, mask });
|
|
46
48
|
return property.enum && !readOnly ? (React.createElement(Autocomplete, { id: id,
|
|
47
49
|
// note: this is different between widgets and builder
|
|
48
50
|
// builder had select options being {label, value}
|
|
@@ -53,7 +55,7 @@ const InputFieldComponent = (props) => {
|
|
|
53
55
|
? [...property.enum, defaultValue]
|
|
54
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) => {
|
|
55
57
|
return option.value === value;
|
|
56
|
-
}, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask ? (React.createElement(TextField, { id: id, sx: {
|
|
58
|
+
}, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask || isValueProtected ? (React.createElement(TextField, { id: id, sx: {
|
|
57
59
|
background: 'white',
|
|
58
60
|
borderRadius: '8px',
|
|
59
61
|
...(readOnly && {
|
|
@@ -65,7 +67,7 @@ const InputFieldComponent = (props) => {
|
|
|
65
67
|
backgroundColor: '#f4f6f8',
|
|
66
68
|
},
|
|
67
69
|
}),
|
|
68
|
-
}, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, 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
|
|
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
|
|
69
71
|
? {
|
|
70
72
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
71
73
|
border: 'none',
|
|
@@ -75,7 +77,7 @@ const InputFieldComponent = (props) => {
|
|
|
75
77
|
backgroundColor: '#f4f6f8',
|
|
76
78
|
},
|
|
77
79
|
}
|
|
78
|
-
: undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...InputProps, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })
|
|
80
|
+
: undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...InputProps, endAdornment, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })
|
|
79
81
|
// Casting to `React.ReactNode` is necessary to resolve TypeScript errors
|
|
80
82
|
// due to compatibility issues with the outdated `react-input-mask` version
|
|
81
83
|
// and the newer `@types/react` package.
|
|
@@ -122,6 +122,22 @@ const FormRendererInternal = (props) => {
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
};
|
|
125
|
+
const removeUneditedProtectedValues = () => {
|
|
126
|
+
const protectedProperties = object?.properties?.filter((prop) => prop.protection?.maskChar);
|
|
127
|
+
if (!protectedProperties || protectedProperties.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
protectedProperties.forEach((property) => {
|
|
131
|
+
const fieldId = property.id;
|
|
132
|
+
const originalValue = instance?.[fieldId];
|
|
133
|
+
const value = getValues(fieldId);
|
|
134
|
+
// When protected value hasn't been edited or viewed, unregister to
|
|
135
|
+
// avoid saving the obfuscated value.
|
|
136
|
+
if (value === originalValue) {
|
|
137
|
+
processFieldUnregister(fieldId);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
};
|
|
125
141
|
const processFieldUnregister = (fieldId) => {
|
|
126
142
|
if (isAddressProperty(fieldId)) {
|
|
127
143
|
// Unregister entire addressObject to clear hidden field errors, then restore existing values since unregistering address.line1 etc is not working
|
|
@@ -145,6 +161,7 @@ const FormRendererInternal = (props) => {
|
|
|
145
161
|
};
|
|
146
162
|
async function unregisterHiddenFieldsAndSubmit() {
|
|
147
163
|
unregisterHiddenFields(entries ?? []);
|
|
164
|
+
removeUneditedProtectedValues();
|
|
148
165
|
await handleSubmit((data) => onSubmit && onSubmit(action?.type === 'delete' ? {} : data), (errors) => onSubmitError(errors))();
|
|
149
166
|
}
|
|
150
167
|
const headerProps = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { cloneDeep, get, isArray, isEmpty, isEqual,
|
|
3
|
+
import { cloneDeep, get, isArray, isEmpty, isEqual, omit, pick, set, uniq } from 'lodash';
|
|
4
4
|
import React, { useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { Skeleton, Snackbar } from '../../core';
|
|
6
6
|
import { Box } from '../../layout';
|
|
@@ -278,100 +278,83 @@ function FormRendererContainer(props) {
|
|
|
278
278
|
};
|
|
279
279
|
const getDefaultValues = async (entries, instanceData) => {
|
|
280
280
|
const result = {};
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
const unnestedEntries = getUnnestedEntries(entries);
|
|
282
|
+
for (const entry of unnestedEntries) {
|
|
283
|
+
if ((entry.type === 'input' || entry.type === 'inputField') &&
|
|
284
|
+
isAddressProperty(entry.parameterId || entry.input?.id)) {
|
|
285
|
+
const fieldId = getEntryId(entry);
|
|
286
|
+
if (!fieldId)
|
|
287
|
+
continue;
|
|
288
|
+
const fieldValue = get(instanceData, fieldId);
|
|
289
|
+
if ((isEmpty(instanceData) || fieldValue === undefined || fieldValue === null || fieldValue === '') &&
|
|
290
|
+
entry?.display?.defaultValue &&
|
|
291
|
+
parameters) {
|
|
292
|
+
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
293
|
+
if (isArray(defaultValuesArray)) {
|
|
294
|
+
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
295
|
+
set(result, fieldId, fieldValue);
|
|
296
|
+
});
|
|
292
297
|
}
|
|
293
298
|
}
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
const fieldId = getEntryId(entry);
|
|
297
|
-
if (!fieldId)
|
|
298
|
-
return;
|
|
299
|
-
const fieldValue = get(instanceData, fieldId);
|
|
300
|
-
if ((isEmpty(instanceData) ||
|
|
301
|
-
fieldValue === undefined ||
|
|
302
|
-
fieldValue === null ||
|
|
303
|
-
fieldValue === '') &&
|
|
304
|
-
entry?.display?.defaultValue &&
|
|
305
|
-
parameters) {
|
|
306
|
-
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
307
|
-
if (isArray(defaultValuesArray)) {
|
|
308
|
-
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
309
|
-
set(result, fieldId, fieldValue);
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
314
|
-
set(result, fieldId, fieldValue);
|
|
315
|
-
}
|
|
299
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
300
|
+
set(result, fieldId, fieldValue);
|
|
316
301
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
catch (error) {
|
|
336
|
-
console.error(error);
|
|
337
|
-
}
|
|
302
|
+
}
|
|
303
|
+
else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
|
|
304
|
+
const fieldId = entry.type === 'input'
|
|
305
|
+
? entry.parameterId
|
|
306
|
+
: entry.type === 'inputField'
|
|
307
|
+
? entry.input?.id
|
|
308
|
+
: undefined;
|
|
309
|
+
if (fieldId) {
|
|
310
|
+
const fieldValue = instanceData?.[fieldId] ??
|
|
311
|
+
instanceData?.metadata?.[fieldId];
|
|
312
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
313
|
+
if (associatedObject?.propertyId === fieldId &&
|
|
314
|
+
associatedObject?.instanceId &&
|
|
315
|
+
parameter &&
|
|
316
|
+
action?.type === 'create') {
|
|
317
|
+
try {
|
|
318
|
+
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`));
|
|
319
|
+
result[associatedObject.propertyId] = instance;
|
|
338
320
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (entry.type !== 'readonlyField' && isEmptyWithDefault(fieldValue, entry, instanceData)) {
|
|
326
|
+
if (fieldId && parameters && parameters.length > 0) {
|
|
327
|
+
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
328
|
+
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
329
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
330
|
+
if (parameter?.type === 'object') {
|
|
331
|
+
const dependentFields = await processValueUpdate(unnestedEntries, parameters, fieldValue, apiServices, fieldId, formDataRef.current, userAccount);
|
|
332
|
+
for (const field of dependentFields) {
|
|
333
|
+
set(result, field.fieldId, field.fieldValue);
|
|
350
334
|
}
|
|
351
|
-
set(result, fieldId, fieldValue);
|
|
352
335
|
}
|
|
336
|
+
set(result, fieldId, fieldValue);
|
|
353
337
|
}
|
|
354
338
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
result[fieldId] = RTFFieldValue;
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
result[fieldId] = fieldValue;
|
|
339
|
+
}
|
|
340
|
+
else if (parameter?.type === 'boolean' && (fieldValue === undefined || fieldValue === null)) {
|
|
341
|
+
result[fieldId] = false;
|
|
342
|
+
}
|
|
343
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
344
|
+
if (parameter?.type === 'richText' && typeof fieldValue === 'string') {
|
|
345
|
+
let RTFFieldValue = fieldValue;
|
|
346
|
+
if (!fieldValue.trim().startsWith('{\\rtf')) {
|
|
347
|
+
RTFFieldValue = plainTextToRtf(fieldValue);
|
|
368
348
|
}
|
|
349
|
+
result[fieldId] = RTFFieldValue;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
result[fieldId] = fieldValue;
|
|
369
353
|
}
|
|
370
354
|
}
|
|
371
355
|
}
|
|
372
356
|
}
|
|
373
|
-
}
|
|
374
|
-
await processEntries(entries);
|
|
357
|
+
}
|
|
375
358
|
return result;
|
|
376
359
|
};
|
|
377
360
|
const handleAutosave = async (fieldId) => {
|
|
@@ -423,7 +406,7 @@ function FormRendererContainer(props) {
|
|
|
423
406
|
if (parameter) {
|
|
424
407
|
if (parameter.type === 'object' && parameters && parameters.length > 0) {
|
|
425
408
|
// On change of a related object, update default values dependent on that object
|
|
426
|
-
const dependentFields = await processValueUpdate(
|
|
409
|
+
const dependentFields = await processValueUpdate(entries, parameters, value, apiServices, id, formDataRef.current, userAccount);
|
|
427
410
|
for (const field of dependentFields) {
|
|
428
411
|
onChange(field.fieldId, field.fieldValue);
|
|
429
412
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ApiServices, FormEntry, InputField, InputParameter, InputParameterReference, ObjectInstance, Reference, UserAccount } from '@evoke-platform/context';
|
|
2
2
|
import { FieldValues } from 'react-hook-form';
|
|
3
|
-
export declare function evalDefaultVals(parameters: InputParameter[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
|
|
3
|
+
export declare function evalDefaultVals(parameters: InputParameter[], unnestedEntries: FormEntry[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
|
|
4
4
|
fieldId: string;
|
|
5
5
|
fieldValue: unknown;
|
|
6
6
|
}[]>;
|
|
7
|
-
export declare function processValueUpdate(
|
|
7
|
+
export declare function processValueUpdate(unnestedEntries: FormEntry[], parameters: InputParameter[], updatedRelatedObjectValue: ObjectInstance | null | Reference, apiServices: ApiServices, changedEntryId?: string, formValues?: FieldValues, userAccount?: UserAccount): Promise<{
|
|
8
8
|
fieldId: string;
|
|
9
9
|
fieldValue: unknown;
|
|
10
10
|
}[]>;
|