@evoke-platform/ui-components 1.10.0-testing.8 → 1.10.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/core/Autocomplete/Autocomplete.js +4 -2
- package/dist/published/components/core/Autocomplete/Autocomplete.test.js +112 -3
- package/dist/published/components/core/TextField/TextField.js +1 -1
- package/dist/published/components/core/TextField/TextField.test.js +0 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +25 -3
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.d.ts +1 -0
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +473 -0
- package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +19 -6
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +2 -1
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/tests/Form.test.js +0 -2
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +36 -7
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
- 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/FormField/InputFieldComponent/InputFieldComponent.test.js +0 -2
- package/dist/published/components/custom/FormField/Select/Select.test.js +0 -2
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +46 -8
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +178 -153
- 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/DefaultValues.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +8 -5
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +6 -23
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +22 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +115 -87
- 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 +43 -20
- package/dist/published/components/custom/FormV2/components/Header.d.ts +5 -3
- package/dist/published/components/custom/FormV2/components/Header.js +47 -9
- 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 +47 -24
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
- package/dist/published/components/custom/FormV2/components/types.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
- package/dist/published/components/custom/FormV2/components/utils.js +83 -13
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +413 -46
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +983 -16
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +165 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +144 -0
- package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
- package/dist/published/components/custom/index.d.ts +2 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +6 -6
- package/dist/published/index.js +1 -1
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
- package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
- package/dist/published/stories/FormRendererContainer.stories.js +5 -0
- package/dist/published/stories/FormRendererData.d.ts +12 -0
- package/dist/published/stories/FormRendererData.js +26 -1
- 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 +4 -3
- package/dist/published/types.d.ts +3 -0
- package/package.json +12 -8
package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
|
|
2
2
|
import { omit } from 'lodash';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
4
5
|
import { InvalidDate } from '../../../../util';
|
|
5
6
|
import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
|
|
6
7
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
@@ -30,6 +31,7 @@ const formatDateTime = (date) => {
|
|
|
30
31
|
const DateTimePickerSelect = (props) => {
|
|
31
32
|
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, additionalProps } = props;
|
|
32
33
|
const [value, setValue] = useState(asCalendarDate(defaultValue));
|
|
34
|
+
const { onAutosave } = useFormContext();
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
setValue(asCalendarDate(defaultValue));
|
|
35
37
|
}, [defaultValue]);
|
|
@@ -43,8 +45,19 @@ const DateTimePickerSelect = (props) => {
|
|
|
43
45
|
setValue(date);
|
|
44
46
|
props.onChange && props.onChange(property.id, date, property);
|
|
45
47
|
};
|
|
48
|
+
const handleAccept = async () => {
|
|
49
|
+
// Trigger autosave when date/time is accepted (picker closes after selection)
|
|
50
|
+
if (onAutosave) {
|
|
51
|
+
try {
|
|
52
|
+
await onAutosave(id);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Autosave failed:', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
46
59
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: formatDateTime(value) } })) : (React.createElement(LocalizationProvider, null,
|
|
47
|
-
React.createElement(DateTimePicker, { value: value, onChange: handleChange, 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',
|
|
60
|
+
React.createElement(DateTimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, 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',
|
|
48
61
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
49
62
|
inputProps: {
|
|
50
63
|
...params.inputProps,
|
|
@@ -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.
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import { userEvent } from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
6
5
|
import InputField from './InputFieldComponent';
|
|
7
|
-
expect.extend(matchers);
|
|
8
6
|
describe('Free-text input', () => {
|
|
9
7
|
// Right now an object property is required for this to function, but eventually this should go
|
|
10
8
|
// away.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import { userEvent } from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
6
5
|
import Select from './Select';
|
|
7
|
-
expect.extend(matchers);
|
|
8
6
|
describe('Single select', () => {
|
|
9
7
|
// Right now an object property is required for this to function, but eventually this should go
|
|
10
8
|
// away.
|
|
@@ -3,11 +3,13 @@ import { TimePicker } from '@mui/x-date-pickers';
|
|
|
3
3
|
import { isUndefined, omit, padStart } from 'lodash';
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
5
|
import React, { useEffect, useState } from 'react';
|
|
6
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
6
7
|
import { InvalidDate } from '../../../../util';
|
|
7
8
|
import { LocalizationProvider, TextField } from '../../../core';
|
|
8
9
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
9
10
|
const TimePickerSelect = (props) => {
|
|
10
11
|
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, placeholder, additionalProps, } = props;
|
|
12
|
+
const { onAutosave } = useFormContext();
|
|
11
13
|
const values = defaultValue ? defaultValue.split(':') : undefined;
|
|
12
14
|
const hour = values ? parseInt(values[0]) : undefined;
|
|
13
15
|
const minute = values ? parseInt(values[1]) : undefined;
|
|
@@ -41,11 +43,22 @@ const TimePickerSelect = (props) => {
|
|
|
41
43
|
props.onChange && props.onChange(property.id, date, property);
|
|
42
44
|
}
|
|
43
45
|
};
|
|
46
|
+
const handleAccept = async () => {
|
|
47
|
+
// Trigger autosave when time is accepted (picker closes after selection)
|
|
48
|
+
if (onAutosave) {
|
|
49
|
+
try {
|
|
50
|
+
await onAutosave(id);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('Autosave failed:', error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
44
57
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
45
58
|
...props,
|
|
46
59
|
defaultValue: value instanceof LocalDateTime ? DateTime.fromISO(value.toString()).toFormat('hh:mm a') : '',
|
|
47
60
|
} })) : (React.createElement(LocalizationProvider, null,
|
|
48
|
-
React.createElement(TimePicker, { value: value, onChange: handleChange, 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', placeholder: placeholder,
|
|
61
|
+
React.createElement(TimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, 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', placeholder: placeholder,
|
|
49
62
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
50
63
|
inputProps: {
|
|
51
64
|
...params.inputProps,
|
|
@@ -16,7 +16,8 @@ export type FormRendererProps = BaseProps & {
|
|
|
16
16
|
form: EvokeForm;
|
|
17
17
|
title?: string | React.ReactNode;
|
|
18
18
|
instance?: ObjectInstance | Document;
|
|
19
|
-
onChange: (id: string, value: unknown) => void
|
|
19
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
20
|
+
onAutosave?: (fieldId: string) => void | Promise<void>;
|
|
20
21
|
associatedObject?: {
|
|
21
22
|
instanceId?: string;
|
|
22
23
|
propertyId?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useObject } from '@evoke-platform/context';
|
|
2
2
|
import { isEmpty, isEqual, omit } from 'lodash';
|
|
3
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { useForm } from 'react-hook-form';
|
|
5
5
|
import { useWidgetSize } from '../../../theme';
|
|
6
6
|
import { Box } from '../../layout';
|
|
@@ -8,11 +8,11 @@ import { Body } from './components/Body';
|
|
|
8
8
|
import { Footer, FooterActions } from './components/Footer';
|
|
9
9
|
import { FormContext } from './components/FormContext';
|
|
10
10
|
import Header, { AccordionActions, Title } from './components/Header';
|
|
11
|
-
import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, } from './components/utils';
|
|
11
|
+
import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, obfuscateValue, } from './components/utils';
|
|
12
12
|
import { handleValidation } from './components/ValidationFiles/Validation';
|
|
13
13
|
import ValidationErrors from './components/ValidationFiles/ValidationErrors';
|
|
14
14
|
const FormRendererInternal = (props) => {
|
|
15
|
-
const { onSubmit, onDiscardChanges, onSubmitError, value, fieldHeight, richTextEditor, form, instance, onChange, associatedObject, renderHeader, renderBody, renderFooter, } = props;
|
|
15
|
+
const { onSubmit, onDiscardChanges, onSubmitError: onSubmitErrorOverride, value, fieldHeight, richTextEditor, form, instance, onChange, onAutosave, associatedObject, renderHeader, renderBody, renderFooter, } = props;
|
|
16
16
|
const { entries, name: title, objectId, actionId, display } = form;
|
|
17
17
|
const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
|
|
18
18
|
defaultValues: value,
|
|
@@ -32,6 +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 validationContainerRef = useRef(null);
|
|
35
36
|
const updateFetchedOptions = (newData) => {
|
|
36
37
|
setFetchedOptions((prev) => ({
|
|
37
38
|
...prev,
|
|
@@ -45,7 +46,7 @@ const FormRendererInternal = (props) => {
|
|
|
45
46
|
setExpandAll(false);
|
|
46
47
|
}
|
|
47
48
|
const updatedEntries = useMemo(() => {
|
|
48
|
-
return assignIdsToSectionsAndRichText(entries, object, parameters);
|
|
49
|
+
return object ? assignIdsToSectionsAndRichText(entries, object, parameters) : [];
|
|
49
50
|
}, [entries, object, parameters]);
|
|
50
51
|
useEffect(() => {
|
|
51
52
|
(async () => {
|
|
@@ -78,6 +79,15 @@ const FormRendererInternal = (props) => {
|
|
|
78
79
|
if (value) {
|
|
79
80
|
for (const key of Object.keys(currentValues)) {
|
|
80
81
|
if (!isEqual(currentValues[key], value[key])) {
|
|
82
|
+
// For protected properties, don't validate initial obfuscated value
|
|
83
|
+
const property = object?.properties?.find((prop) => prop.id === key);
|
|
84
|
+
const isProtectedProperty = property?.protection?.maskChar;
|
|
85
|
+
if (isProtectedProperty) {
|
|
86
|
+
if (value[key] === obfuscateValue(value[key], property)) {
|
|
87
|
+
setValue(key, value[key], { shouldValidate: false });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
81
91
|
setValue(key, value[key], { shouldValidate: true });
|
|
82
92
|
}
|
|
83
93
|
}
|
|
@@ -108,7 +118,7 @@ const FormRendererInternal = (props) => {
|
|
|
108
118
|
}
|
|
109
119
|
});
|
|
110
120
|
}
|
|
111
|
-
if (!entryIsVisible(entry, getValues()
|
|
121
|
+
if (!entryIsVisible(entry, instance, getValues())) {
|
|
112
122
|
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
113
123
|
const fieldsToUnregister = getNestedParameterIds(entry);
|
|
114
124
|
fieldsToUnregister.forEach(processFieldUnregister);
|
|
@@ -121,6 +131,22 @@ const FormRendererInternal = (props) => {
|
|
|
121
131
|
}
|
|
122
132
|
});
|
|
123
133
|
};
|
|
134
|
+
const removeUneditedProtectedValues = () => {
|
|
135
|
+
const protectedProperties = object?.properties?.filter((prop) => prop.protection?.maskChar);
|
|
136
|
+
if (!protectedProperties || protectedProperties.length === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
protectedProperties.forEach((property) => {
|
|
140
|
+
const fieldId = property.id;
|
|
141
|
+
const originalValue = instance?.[fieldId];
|
|
142
|
+
const value = getValues(fieldId);
|
|
143
|
+
// When protected value hasn't been edited or viewed, unregister to
|
|
144
|
+
// avoid saving the obfuscated value.
|
|
145
|
+
if (value === originalValue) {
|
|
146
|
+
processFieldUnregister(fieldId);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
};
|
|
124
150
|
const processFieldUnregister = (fieldId) => {
|
|
125
151
|
if (isAddressProperty(fieldId)) {
|
|
126
152
|
// Unregister entire addressObject to clear hidden field errors, then restore existing values since unregistering address.line1 etc is not working
|
|
@@ -134,9 +160,18 @@ const FormRendererInternal = (props) => {
|
|
|
134
160
|
unregister(fieldId);
|
|
135
161
|
}
|
|
136
162
|
};
|
|
163
|
+
const onSubmitError = (errors) => {
|
|
164
|
+
if (onSubmitErrorOverride) {
|
|
165
|
+
onSubmitErrorOverride(errors);
|
|
166
|
+
}
|
|
167
|
+
else if (validationContainerRef.current) {
|
|
168
|
+
validationContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
137
171
|
async function unregisterHiddenFieldsAndSubmit() {
|
|
138
172
|
unregisterHiddenFields(entries ?? []);
|
|
139
|
-
|
|
173
|
+
removeUneditedProtectedValues();
|
|
174
|
+
await handleSubmit((data) => onSubmit && onSubmit(action?.type === 'delete' ? {} : data), (errors) => onSubmitError(errors))();
|
|
140
175
|
}
|
|
141
176
|
const headerProps = {
|
|
142
177
|
title,
|
|
@@ -146,8 +181,9 @@ const FormRendererInternal = (props) => {
|
|
|
146
181
|
errors,
|
|
147
182
|
hasAccordions: hasSections && isSmallerThanMd,
|
|
148
183
|
shouldShowValidationErrors: isSubmitted,
|
|
149
|
-
form,
|
|
150
184
|
action,
|
|
185
|
+
validationContainerRef: validationContainerRef,
|
|
186
|
+
autosaveEnabled: !!form.autosaveActionId,
|
|
151
187
|
};
|
|
152
188
|
const footerProps = {
|
|
153
189
|
onSubmit: unregisterHiddenFieldsAndSubmit,
|
|
@@ -155,6 +191,7 @@ const FormRendererInternal = (props) => {
|
|
|
155
191
|
action,
|
|
156
192
|
discardChangesButtonLabel: 'Discard Changes',
|
|
157
193
|
submitButtonLabel: display?.submitLabel ?? 'Submit',
|
|
194
|
+
disableDiscardChanges: !!form?.autosaveActionId,
|
|
158
195
|
};
|
|
159
196
|
return (React.createElement(Box, { ref: containerRef },
|
|
160
197
|
React.createElement(FormContext.Provider, { value: {
|
|
@@ -172,6 +209,7 @@ const FormRendererInternal = (props) => {
|
|
|
172
209
|
parameters,
|
|
173
210
|
fieldHeight,
|
|
174
211
|
handleChange: onChange,
|
|
212
|
+
onAutosave,
|
|
175
213
|
triggerFieldReset,
|
|
176
214
|
showSubmitError: isSubmitted,
|
|
177
215
|
associatedObject,
|
|
@@ -200,7 +238,7 @@ const FormRendererInternal = (props) => {
|
|
|
200
238
|
expandedSections,
|
|
201
239
|
hasAccordions: hasSections && isSmallerThanMd,
|
|
202
240
|
} })),
|
|
203
|
-
(
|
|
241
|
+
(action || form.id === 'documentForm') &&
|
|
204
242
|
onSubmit &&
|
|
205
243
|
(renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
|
|
206
244
|
};
|