diff --git a/libs/ui/src/components/RecordEdition/EditRecordContent/antdUtils.tsx b/libs/ui/src/components/RecordEdition/EditRecordContent/antdUtils.tsx index 3195ce813..c0b0c1519 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordContent/antdUtils.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordContent/antdUtils.tsx @@ -50,35 +50,42 @@ export const getAntdFormInitialValues = (recordForm: IRecordForm) => } const standardValue = value as RecordFormElementsValueStandardValue; - if (attribute.format === AttributeFormat.text) { - acc[attribute.id] = standardValue?.raw_value ?? ''; - } - - if (attribute.format === AttributeFormat.numeric) { - acc[attribute.id] = Number(standardValue?.raw_value) ?? ''; - } - - if (attribute.format === AttributeFormat.date) { - acc[attribute.id] = standardValue?.raw_value ? dayjs.unix(Number(standardValue?.raw_value)) : ''; - } - if (attribute.format === AttributeFormat.date_range) { - if (!standardValue?.raw_value) { + if (!standardValue?.raw_value) { + if (attribute.format === AttributeFormat.date_range) { return acc; } - if (hasDateRangeValues(standardValue.raw_value)) { - acc[attribute.id] = [ - dayjs.unix(Number(standardValue.raw_value.from)), - dayjs.unix(Number(standardValue.raw_value.to)) - ]; - } else if (typeof standardValue.raw_value === 'string') { - const convertedFieldValue = JSON.parse(standardValue.raw_value); - acc[attribute.id] = [ - dayjs.unix(Number(convertedFieldValue.from)), - dayjs.unix(Number(convertedFieldValue.to)) - ]; - } + acc[attribute.id] = ''; + return acc; + } + + switch (attribute.format) { + case AttributeFormat.text: + case AttributeFormat.boolean: + acc[attribute.id] = standardValue.raw_value; + break; + case AttributeFormat.numeric: + acc[attribute.id] = Number(standardValue.raw_value); + break; + case AttributeFormat.date: + acc[attribute.id] = dayjs.unix(Number(standardValue.raw_value)); + break; + case AttributeFormat.date_range: + if (hasDateRangeValues(standardValue.raw_value)) { + acc[attribute.id] = [ + dayjs.unix(Number(standardValue.raw_value.from)), + dayjs.unix(Number(standardValue.raw_value.to)) + ]; + break; + } else if (typeof standardValue.raw_value === 'string') { + const convertedFieldValue = JSON.parse(standardValue.raw_value); + acc[attribute.id] = [ + dayjs.unix(Number(convertedFieldValue.from)), + dayjs.unix(Number(convertedFieldValue.to)) + ]; + break; + } } return acc; diff --git a/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.test.tsx b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.test.tsx new file mode 100644 index 000000000..2cf1493de --- /dev/null +++ b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.test.tsx @@ -0,0 +1,300 @@ +// Copyright LEAV Solutions 2017 +// This file is released under LGPL V3 +// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt +import {render, screen} from '_ui/_tests/testUtils'; +import {AntForm} from 'aristid-ds'; +import {DSBooleanWrapper} from './DSBooleanWrapper'; +import { + InheritedFlags, + IStandardFieldReducerState, + StandardFieldValueState +} from '../../../reducers/standardFieldReducer/standardFieldReducer'; +import {mockRecord} from '_ui/__mocks__/common/record'; +import {mockFormElementInput} from '_ui/__mocks__/common/form'; +import {mockAttributeLink} from '_ui/__mocks__/common/attribute'; +import {FieldScope} from '../../../_types'; +import userEvent from '@testing-library/user-event'; + +const en_label = 'label'; +const fr_label = 'libellé'; +const idValue = '123'; +const mockValueFalse = { + index: 0, + displayValue: null, + editingValue: null, + originRawValue: null, + idValue: null, + isEditing: false, + isErrorDisplayed: false, + value: { + id_value: null, + value: null, + raw_value: null, + modified_at: null, + created_at: null, + created_by: null, + modified_by: null + }, + version: null, + error: '', + state: StandardFieldValueState.PRISTINE +}; + +const inheritedValues = [ + { + isInherited: null, + value: false, + raw_value: false + }, + { + isInherited: true, + value: true, + raw_value: true + } +]; + +const inheritedNotOverrideValue: InheritedFlags = { + isInheritedValue: true, + isInheritedOverrideValue: false, + isInheritedNotOverrideValue: true, + inheritedValue: {raw_value: inheritedValues[1].raw_value} +}; + +const inheritedOverrideValue: InheritedFlags = { + isInheritedValue: true, + isInheritedOverrideValue: true, + isInheritedNotOverrideValue: false, + inheritedValue: {raw_value: inheritedValues[1].raw_value} +}; + +const getInitialState = ({ + required, + fallbackLang +}: { + required: boolean; + fallbackLang: boolean; +}): IStandardFieldReducerState => ({ + record: mockRecord, + formElement: { + ...mockFormElementInput, + settings: { + label: fallbackLang ? {en: en_label} : {fr: fr_label, en: en_label}, + required + } + }, + attribute: mockAttributeLink, + isReadOnly: false, + activeScope: FieldScope.CURRENT, + values: { + [FieldScope.CURRENT]: { + version: null, + values: {[idValue]: mockValueFalse} + }, + [FieldScope.INHERITED]: null + }, + metadataEdit: false, + inheritedValue: null, + isInheritedNotOverrideValue: false, + isInheritedOverrideValue: false, + isInheritedValue: false +}); + +describe('DSBooleanWrapper', () => { + let user!: ReturnType; + const mockHandleSubmit = jest.fn(); + const mockOnChange = jest.fn(); + + beforeEach(() => { + user = userEvent.setup({}); + mockHandleSubmit.mockReset(); + mockOnChange.mockReset(); + }); + + test('Should display boolean with fr label', async () => { + const state = getInitialState({required: false, fallbackLang: false}); + render( + + + + + + ); + + expect(screen.getByText(fr_label)).toBeVisible(); + }); + + test('Should display boolean with fallback label', async () => { + const state = getInitialState({required: false, fallbackLang: true}); + render( + + + + + + ); + + expect(screen.getByText(en_label)).toBeVisible(); + }); + + test('Should display boolean value as yes', async () => { + const state = getInitialState({required: false, fallbackLang: false}); + + render( + + + + + + ); + + expect(screen.getByText(/yes/)).toBeVisible(); + }); + + test('Should display boolean value as no', async () => { + const state = getInitialState({required: false, fallbackLang: false}); + + render( + + + + + + ); + + expect(screen.getByText(/no/)).toBeVisible(); + }); + + test('Should submit true if the switch was set to false', async () => { + const state = getInitialState({required: false, fallbackLang: false}); + + render( + + + + + + ); + + const switchInput = screen.getByRole('switch'); + await user.click(switchInput); + + expect(mockHandleSubmit).toHaveBeenCalledWith('true', state.attribute.id); + }); + + test('Should submit false if the switch was set to true', async () => { + const state = getInitialState({required: false, fallbackLang: false}); + + render( + + + + + + ); + + const switchInput = screen.getByRole('switch'); + await user.click(switchInput); + + expect(mockHandleSubmit).toHaveBeenCalledWith('false', state.attribute.id); + }); + + describe('With inheritance', () => { + test('Should display inherited value', async () => { + let state = getInitialState({required: false, fallbackLang: false}); + state = { + ...state, + ...inheritedNotOverrideValue, + formElement: {...state.formElement, values: inheritedValues} + }; + + render( + + + + + + ); + + expect(screen.getByText(/yes/)).toBeVisible(); + }); + + test('Should display override value, clear icon and inherited value under it', async () => { + let state = getInitialState({required: false, fallbackLang: false}); + state = { + ...state, + ...inheritedOverrideValue, + formElement: {...state.formElement, values: inheritedValues} + }; + + render( + + + + + + ); + + expect(screen.getByText(/no/)).toBeVisible(); + + const helperText = screen.getByText(/inherited_input_helper/); + expect(helperText).toBeInTheDocument(); + }); + + test('Should allow to clear override value', async () => { + let state = getInitialState({required: false, fallbackLang: false}); + state = { + ...state, + ...inheritedOverrideValue, + formElement: {...state.formElement, values: inheritedValues} + }; + + render( + + + + + + ); + + const clearButton = screen.getByRole('button'); + await user.click(clearButton); + + expect(mockHandleSubmit).toHaveBeenCalledWith('', 'my_attribute'); + }); + }); +}); diff --git a/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.tsx b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.tsx new file mode 100644 index 000000000..b754b4aee --- /dev/null +++ b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/DSBooleanWrapper.tsx @@ -0,0 +1,84 @@ +// Copyright LEAV Solutions 2017 +// This file is released under LGPL V3 +// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt +import {KitInputWrapper, KitSwitch, KitTypography} from 'aristid-ds'; +import {FunctionComponent, MouseEvent} from 'react'; +import {IStandardFieldReducerState} from '../../../reducers/standardFieldReducer/standardFieldReducer'; +import {Form} from 'antd'; +import {IProvidedByAntFormItem} from '_ui/components/RecordEdition/EditRecordContent/_types'; +import styled from 'styled-components'; +import {useSharedTranslation} from '_ui/hooks/useSharedTranslation'; +import {useLang} from '_ui/hooks'; +import {localizedTranslation} from '@leav/utils'; +import {IKitSwitch} from 'aristid-ds/dist/Kit/DataEntry/Switch/types'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCircleXmark} from '@fortawesome/free-solid-svg-icons'; + +interface IDSBooleanWrapperProps extends IProvidedByAntFormItem { + state: IStandardFieldReducerState; + handleSubmit: (value: string, id?: string) => void; +} + +const KitTypographyTextStyled = styled(KitTypography.Text)<{$shouldHighlightColor: boolean}>` + vertical-align: middle; + margin-left: calc(var(--general-spacing-xs) * 1px); + color: ${({$shouldHighlightColor}) => ($shouldHighlightColor ? 'var(--general-colors-primary-400)' : 'initial')}; +`; + +const FontAwesomeIconStyled = styled(FontAwesomeIcon)` + vertical-align: middle; + margin-left: calc(var(--general-spacing-xs) * 1px); + color: var(--general-utilities-text-primary); + cursor: pointer; +`; + +const _getBooleanValueAsStringForTranslation = (value: boolean): string => (value ? 'global.yes' : 'global.no'); + +export const DSBooleanWrapper: FunctionComponent = ({value, onChange, state, handleSubmit}) => { + const {t} = useSharedTranslation(); + const {errors} = Form.Item.useStatus(); + const {lang: availableLang} = useLang(); + + const _resetOverrideValue = () => { + onChange(state.inheritedValue.raw_value, undefined); + handleSubmit('', state.attribute.id); + }; + + const _handleOnChange: (checked: boolean, event: MouseEvent) => void = (checked, event) => { + handleSubmit(String(checked), state.attribute.id); + onChange(checked, event); + }; + + const label = localizedTranslation(state.formElement.settings.label, availableLang); + + return ( + 0 ? 'error' : undefined} + disabled={state.isReadOnly} + > + + {state.isInheritedOverrideValue && ( + + + + )} + + ); +}; diff --git a/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/StandardFieldValue.tsx b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/StandardFieldValue.tsx index fa3846d7b..61b837101 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/StandardFieldValue.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordContent/uiElements/StandardField/StandardFieldValue/StandardFieldValue.tsx @@ -43,7 +43,6 @@ import { StandardFieldReducerActionsTypes, StandardFieldValueState } from '../../../reducers/standardFieldReducer/standardFieldReducer'; -import CheckboxInput from './Inputs/CheckboxInput'; import ColorInput from './Inputs/ColorInput'; import TextInput from './Inputs/TextInput'; import ValuesList from './ValuesList'; @@ -52,6 +51,7 @@ import {DSInputNumberWrapper} from './DSInputNumberWrapper'; import {useLang} from '_ui/hooks'; import {DSInputPasswordWrapper} from './DSInputPasswordWrapper'; import {DSDatePickerWrapper} from './DSDatePickerWrapper'; +import {DSBooleanWrapper} from './DSBooleanWrapper'; const ErrorMessage = styled.div` color: ${themeVars.errorColor}; @@ -180,7 +180,7 @@ const inputComponentByFormat: {[format in AttributeFormat]: (props: IStandardInp [AttributeFormat.text]: null, [AttributeFormat.date]: null, [AttributeFormat.date_range]: null, - [AttributeFormat.boolean]: CheckboxInput, + [AttributeFormat.boolean]: null, [AttributeFormat.numeric]: null, [AttributeFormat.encrypted]: null, [AttributeFormat.extended]: TextInput, @@ -558,15 +558,11 @@ function StandardFieldValue({ AttributeFormat.date_range, AttributeFormat.numeric, AttributeFormat.encrypted, - AttributeFormat.date + AttributeFormat.date, + AttributeFormat.boolean ]; - const attributeFormatsWithoutDS = [ - AttributeFormat.boolean, - AttributeFormat.color, - AttributeFormat.extended, - AttributeFormat.rich_text - ]; + const attributeFormatsWithoutDS = [AttributeFormat.color, AttributeFormat.extended, AttributeFormat.rich_text]; return ( <> @@ -625,6 +621,9 @@ function StandardFieldValue({ shouldShowValueDetailsButton={editRecordState.withInfoButton} /> )} + {attribute.format === AttributeFormat.boolean && ( + + )} )} diff --git a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx index df4c917ac..a6d3f22a4 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx @@ -145,10 +145,10 @@ export const EditRecordModal: FunctionComponent = ({ } /> - } /> + } /> = ({ } /> )}