Skip to content

Commit

Permalink
Merge pull request #83 from indec-it/feat/dateValidations
Browse files Browse the repository at this point in the history
feat: add date without range validations
  • Loading branch information
maximilianoforlenza authored Sep 8, 2023
2 parents c9dc3d8 + f0cd082 commit 28b29aa
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 95 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@indec/form-builder",
"version": "2.4.3",
"version": "2.4.4",
"description": "Form builder",
"main": "index.js",
"private": false,
Expand Down
6 changes: 3 additions & 3 deletions src/components/DatePicker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ import PropTypes from 'prop-types';
import {es} from 'date-fns/locale';
import {AdapterDateFns} from '@mui/x-date-pickers/AdapterDateFns';
import {LocalizationProvider} from '@mui/x-date-pickers/LocalizationProvider';
import MuiInputLabel from '@mui/material/InputLabel';
import Stack from '@mui/material/Stack';

import dateTypes from '@/constants/dateTypes';
import formikField from '@/utils/propTypes/formikField';
import formikForm from '@/utils/propTypes/formikForm';

import FieldMessage from '../FieldMessage';
import InputLabel from '../InputLabel';
import TextField from '../TextField';
import DateTimePickerSelector from './DatePickerSelector';
import DateTimePickerSelector from './DateTimePickerSelector';

function DatePicker({
metadata: {dateType}, field, label, form, warnings, disabled, ...props
}) {
const isRange = [dateTypes.RANGE_WITHOUT_HOUR, dateTypes.RANGE_WITH_HOUR].includes(dateType);
return (
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={es}>
<MuiInputLabel>{label}</MuiInputLabel>
<InputLabel label={label} form={form} field={field} warnings={warnings} disabled={disabled} />
<Stack direction={{xs: 'column', sm: 'row'}} spacing={{xs: 1, sm: 2, md: 4}}>
<DateTimePickerSelector
type={dateType}
Expand Down
20 changes: 10 additions & 10 deletions src/components/DatePicker/DatePicker.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ const section = dateType => ({

export const Basic = Template.bind({});
Basic.args = {
readOnlyMode: false,
label: 'Select dates',
disabled: false,
label: {text: 'Select dates'},
name: 'S1.0.S1P1.answer.value',
warnings: {},
metadata: {
Expand All @@ -100,8 +100,8 @@ Basic.args = {

export const DateWithHour = Template.bind({});
DateWithHour.args = {
readOnlyMode: false,
label: 'Select dates',
disabled: false,
label: {text: 'Select dates'},
name: 'S1.0.S1P1.answer.value',
warnings: {},
metadata: {
Expand All @@ -113,8 +113,8 @@ DateWithHour.args = {

export const RangeWithoutHour = Template.bind({});
RangeWithoutHour.args = {
readOnlyMode: false,
label: 'Select dates',
disabled: false,
label: {text: 'Select dates'},
name: 'S1.0.S1P1.answer.value',
warnings: {},
metadata: {
Expand All @@ -126,8 +126,8 @@ RangeWithoutHour.args = {

export const RangeWithHour = Template.bind({});
RangeWithHour.args = {
readOnlyMode: false,
label: 'Select dates',
disabled: false,
label: {text: 'Select dates'},
name: 'S1.0.S1P1.answer.value',
warnings: {},
metadata: {
Expand All @@ -139,8 +139,8 @@ RangeWithHour.args = {

export const WithErrors = Template.bind({});
WithErrors.args = {
readOnlyMode: false,
label: 'Select dates',
disabled: false,
label: {text: 'Select dates'},
name: 'S1.0.S1P1.answer.value',
warnings: {},
metadata: {
Expand Down
22 changes: 0 additions & 22 deletions src/components/DatePicker/DatePickerSelector.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import PropTypes from 'prop-types';
import {DatePicker as MuiDatePicker} from '@mui/x-date-pickers/DatePicker';
import {DateTimePicker as MuiDateTimePicker} from '@mui/x-date-pickers/DateTimePicker';

import dateTypes from '@/constants/dateTypes';

function DateTimePickerSelector({type, onChange, value, ...props}) {
const handleChange = date => {
onChange(date ? date.toISOString() : date);
};
if ([dateTypes.DATE_WITH_HOUR, dateTypes.RANGE_WITH_HOUR].includes(type)) {
return (
<MuiDateTimePicker
{...props}
ampm={false}
inputFormat="dd/MM/yyyy HH:mm"
onChange={handleChange}
value={value ? new Date(value) : value}
/>
);
}
return (
<MuiDatePicker
{...props}
inputFormat="dd/MM/yyyy"
onChange={handleChange}
value={value ? new Date(value) : value}
/>
);
}

DateTimePickerSelector.propTypes = {
onChange: PropTypes.func.isRequired,
type: PropTypes.oneOf(Object.values(dateTypes)).isRequired,
value: PropTypes.string
};

DateTimePickerSelector.defaultProps = {
value: undefined
};

export default DateTimePickerSelector;
3 changes: 3 additions & 0 deletions src/components/DatePicker/DateTimePickerSelector/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DateTimePickerSelector from './DatePickerSelector';

export default DateTimePickerSelector;
52 changes: 25 additions & 27 deletions src/components/FormBuilder/FormBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,33 +109,31 @@ function FormBuilder({
}
/>
{
components.NavigationButtons
? (
<components.NavigationButtons
schema={validateSchema}
values={values ? values[section.name] : {}}
onAddNew={section.multiple ? () => addNewSection(setValues, values) : undefined}
onInterrupt={
section.interruption.interruptible
? () => handleOpenModal(modals.INTERRUPTION_MODAL, section.id)
: undefined
}
/>
)
: (
<NavigationButtons
onPrevious={onPrevious}
disablePreviousButton={page === 0}
isLastSection={isLastSection}
onAddNew={section.multiple ? () => addNewSection(setValues, values) : undefined}
onInterrupt={
section.interruption.interruptible
? () => handleOpenModal(modals.INTERRUPTION_MODAL, section.id)
: undefined
}
readOnlyMode={isReadOnly}
/>
)
components.NavigationButtons ? (
<components.NavigationButtons
schema={validateSchema}
values={values ? values[section.name] : {}}
onAddNew={section.multiple ? () => addNewSection(setValues, values) : undefined}
onInterrupt={
section.interruption.interruptible
? () => handleOpenModal(modals.INTERRUPTION_MODAL, section.id)
: undefined
}
/>
) : (
<NavigationButtons
onPrevious={onPrevious}
disablePreviousButton={page === 0}
isLastSection={isLastSection}
onAddNew={section.multiple ? () => addNewSection(setValues, values) : undefined}
onInterrupt={
section.interruption.interruptible
? () => handleOpenModal(modals.INTERRUPTION_MODAL, section.id)
: undefined
}
readOnlyMode={isReadOnly}
/>
)
}
</Form>
);
Expand Down
21 changes: 3 additions & 18 deletions src/components/InputLabel/InputLabel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import {blue} from '@mui/material/colors';

import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
Expand All @@ -17,26 +15,13 @@ function InputLabel({
label, form, field, disabled, warnings
}) {
const {hasWarning, hasError} = hasFormikErrorsAndWarnings({form, field, warnings});
const labelNumber = label.number ? `${label.number} - ` : '';

return (
<Stack direction="row" spacing={2} data-testid="input-label">
<Stack direction="row" mb={0.5} alignItems="center" sx={{opacity: !disabled ? 1 : 0.3}}>
{label.number && (
<Avatar
sx={{
bgcolor: blue[600],
mr: 1,
minWidth: 15,
minHeight: 15,
maxWidth: 25,
maxHeight: 25,
fontSize: '13px'
}}
>
{label.number}
</Avatar>
)}
<Typography sx={{fontWeight: 'bold', fontSize: '17px'}}>
{label.text}
{`${labelNumber}${label.text}`}
{' '}
{hasError && '*'}
</Typography>
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Checkbox from './Checkbox';
import Currency from './Currency';
import FieldMessage from './FieldMessage';
import FormBuilder from './FormBuilder';
import InputLabel from './InputLabel';
Expand All @@ -7,6 +8,7 @@ import Select from './Select';
import TextField from './TextField';

export {Checkbox};
export {Currency};
export {FieldMessage};
export {FormBuilder};
export {InputLabel};
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useSubQuestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const useSubQuestions = ({subQuestions, value, name}) => {
useEffect(() => {
const subQuestionsFiltered = subQuestions.filter(
subQuestion => {
const condition = getNavigation({navigation: subQuestion.navigation, answers: value});
const condition = getNavigation({
navigation: subQuestion.navigation, answers: value, questionType: subQuestion.type
});
return !condition;
}
);
Expand Down
44 changes: 44 additions & 0 deletions src/utils/__tests__/operations.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import questionTypes from '@/constants/questionTypes';

import operations from '../operations';

describe('operations', () => {
Expand All @@ -21,6 +23,20 @@ describe('operations', () => {
expect(operations.eq({a: 'test', b: 'test'}, '')).toBe(false);
});
});

describe('when `questionType` is date', () => {
it('should return `true` if `a` is equal to `b`', () => {
expect(
operations.eq('2023-09-01T16:54:00.000Z', '2023-09-01T16:54:00.000Z', questionTypes.DATE)
).toBe(true);
});

it('should return `false` if `a` is not equal to `b`', () => {
expect(
operations.eq('2023-09-01T16:53:00.000Z', '2023-09-01T16:54:00.000Z', questionTypes.DATE)
).toBe(false);
});
});
});

describe('not equals', () => {
Expand All @@ -31,6 +47,20 @@ describe('operations', () => {
it('should return `true` if `a` is not equals to `b`', () => {
expect(operations.ne(1, 2)).toBe(true);
});

describe('when `questionType` is date', () => {
it('should return `false` if `a` is equal to `b`', () => {
expect(
operations.ne('2023-09-01T16:54:00.000Z', '2023-09-01T16:54:00.000Z', questionTypes.DATE)
).toBe(false);
});

it('should return `true` if `a` is not equal to `b`', () => {
expect(
operations.ne('2023-09-01T16:53:00.000Z', '2023-09-01T16:54:00.000Z', questionTypes.DATE)
).toBe(true);
});
});
});

describe('greater than', () => {
Expand Down Expand Up @@ -61,6 +91,20 @@ describe('operations', () => {
it('should return `false` if `a` is not less than `b`', () => {
expect(operations.lt('test', 3)).toBe(false);
});

describe('when `questionType` is date', () => {
it('should return `true` if `a` is before than `b`', () => {
expect(
operations.lt('2023-09-01T16:54:00.000Z', '2023-09-01T16:55:00.000Z', questionTypes.DATE)
).toBe(true);
});

it('should return `false` if `a` is not before than `b`', () => {
expect(
operations.lt('2023-09-01T16:55:00.000Z', '2023-09-01T16:54:00.000Z', questionTypes.DATE)
).toBe(false);
});
});
});

describe('less than or equal to', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/utils/buildYupSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const getValidatorType = (type, options, metadata) => {
}
};

const handleValidations = ({validator, validations, opts, answers, questionName, multiple = false}) => {
const handleValidations = ({validator, validations, opts, answers, questionName, multiple = false, questionType}) => {
let newValidator = validator;
validations.forEach((validation) => {
const {type: messageType} = validation.message;
Expand All @@ -54,7 +54,7 @@ const handleValidations = ({validator, validations, opts, answers, questionName,
function (currentValue) {
let formatAnswer = answers;
formatAnswer = multiple ? {...formatAnswer, [questionName]: {answer: {value: currentValue}}} : formatAnswer;
const rules = getValidationRules({validation, answers: formatAnswer});
const rules = getValidationRules({validation, answers: formatAnswer, questionType});
if (rules.some(value => value === true)) {
return this.createError({path: this.path, message: validation.message.text});
}
Expand Down Expand Up @@ -136,7 +136,7 @@ export default function buildYupSchema(schema, config, values, opts = {}) {
return schemaWithValidations;
}
validator = handleValidations({
validator, validations, opts, answers: values, questionName: name, multiple
validator, validations, opts, answers: values, questionName: name, multiple, questionType: type
});
schemaWithValidations[name] = Yup.object({
id: Yup.number().required(),
Expand Down
4 changes: 2 additions & 2 deletions src/utils/getNavigation.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import getValidationRules from './getValidationRules';

const getNavigation = ({navigation = [], answers}) => {
const getNavigation = ({navigation = [], answers, questionType}) => {
if (navigation.length === 0) {
return true;
}
const navigationRules = navigation.map(nav => {
const rules = getValidationRules({validation: nav, answers});
const rules = getValidationRules({validation: nav, answers, questionType});
// eslint-disable-next-line consistent-return
return {
action: nav.action,
Expand Down
Loading

0 comments on commit 28b29aa

Please sign in to comment.