Skip to content

Commit

Permalink
Merge pull request #47 from indec-it/feat/subQuestionsValidations
Browse files Browse the repository at this point in the history
feat: add subQuestions validations
  • Loading branch information
maximilianoforlenza authored Feb 18, 2023
2 parents b9210e6 + b719d57 commit 650322d
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 42 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": "1.6.0",
"version": "1.7.0",
"description": "Form builder",
"main": "index.js",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function Checkbox({
{getSelectedOptions(options, field.value)}
</Typography>
) : (
<FormGroup row>
<FormGroup>
{options.map((option, index) => (
<FormControlLabel
key={option.value}
Expand Down
2 changes: 1 addition & 1 deletion src/components/FieldMessage/FieldMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Alert from '@mui/material/Alert';
import {formikField, formikForm} from '@/utils/propTypes';
import hasFormikErrorsAndWarnings from '@/utils/hasFormikErrorsAndWarnings';

const alertStyles = {width: '100%', justifyContent: 'center', mt: 2};
const alertStyles = {justifyContent: 'center', mt: 2};

function FieldMessage({
form, field, readOnly, warnings
Expand Down
1 change: 1 addition & 0 deletions src/components/FormBuilder/FormBuilder.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const sections = [
label: 'Sección 2',
questions: [
{
id: 1,
label: 'Select the correct option',
name: 'S2P1',
number: '1',
Expand Down
8 changes: 4 additions & 4 deletions src/components/QuestionBuilder/QuestionBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import Wrapper from './Wrapper';

const mapSubQuestions = ({
sectionName, sectionIndex, questionName, subQuestions
}) => subQuestions.map((subQuestion, index) => ({
}) => subQuestions.map(subQuestion => ({
...subQuestion,
name: `${sectionName}.${sectionIndex}.${questionName}.specifications.${index}.answer.value`
name: `${sectionName}.${sectionIndex}.${questionName}.answer.specifications.${subQuestion.name}.answer.value`
}));

const getComponent = (section, sectionIndex, questionIndex, readOnlyMode, warnings, values) => {
Expand All @@ -26,11 +26,11 @@ const getComponent = (section, sectionIndex, questionIndex, readOnlyMode, warnin
return null;
}
let QuestionComponent;
const questionName = `${section.name}.${sectionIndex}.${question.name}.answer.value`;
const questionName = `${section.name}.${sectionIndex}.${question.name}.answer`;
const isRequired = question.validations.some(validation => validation.type === 'required');
const label = `${question.number} - ${question.label}`;
const isMultiple = question.multiple;
const subQuestions = question.subQuestions.length > 0
const subQuestions = question.subQuestions && question.subQuestions.length > 0
? mapSubQuestions({
sectionName: section.name, sectionIndex, questionName: question.name, subQuestions: question.subQuestions
})
Expand Down
15 changes: 12 additions & 3 deletions src/components/QuestionBuilder/Wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,18 @@ function Wrapper({
/>
);
} else {
Component = <FastField {...props} options={options} name={name} readOnlyMode={readOnlyMode} />;
Component = <FastField {...props} options={options} name={`${name}.value`} readOnlyMode={readOnlyMode} />;
}
if (subQuestions.length > 0 && options.length > 0) {
Component = (
<>
{Component}
<SubQuestions values={values} subQuestions={subQuestions} readOnlyMode={readOnlyMode} Component={TextField} />
<SubQuestions
values={values}
subQuestions={subQuestions}
readOnlyMode={readOnlyMode}
Component={TextField}
/>
</>
);
}
Expand All @@ -57,7 +62,11 @@ Wrapper.propTypes = {
isMultiple: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
values: valuesPropTypes.isRequired,
subQuestions: PropTypes.arrayOf(subQuestionPropTypes).isRequired
subQuestions: PropTypes.arrayOf(subQuestionPropTypes)
};

Wrapper.defaultProps = {
subQuestions: []
};

export default Wrapper;
2 changes: 1 addition & 1 deletion src/components/Radio/Radio.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function Radio({
{readOnlyMode ? (
<Typography>{getSelectedOptionLabel(options, field.value)}</Typography>
) : (
<RadioGroup row {...field}>
<RadioGroup {...field}>
{options.map(option => (
<FormControlLabel key={option.value} value={option.value} control={<MuiRadio />} label={option.label} />
))}
Expand Down
29 changes: 14 additions & 15 deletions src/components/SubQuestions/SubQuestions.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
import PropTypes from 'prop-types';
import {FastField} from 'formik';
import Box from '@mui/material/Box';

import castArray from '@/utils/castArray';
import subQuestionPropTypes from '@/utils/propTypes/subQuestion';
import valuesPropTypes from '@/utils/propTypes/values';

function SubQuestions({values, subQuestions, readOnlyMode, Component}) {
const specifications = values.specifications.filter(
specification => castArray(values.answer.value).includes(specification.optionId.toString())
const selectedQuestions = subQuestions.filter(
subQuestion => castArray(values.answer.value).includes(subQuestion.optionId.toString())
);
return (
<>
{specifications.map(specification => {
const subQuestion = subQuestions.find(
currentSubQuestion => currentSubQuestion.optionId === specification.optionId
);
{selectedQuestions.map(subQuestion => {
const isRequired = subQuestion.validations.some(validation => validation.type === 'required');
return (
<FastField
key={specification.id}
component={Component}
name={subQuestion.name}
label={subQuestion.label}
placeholder={subQuestion.placeholder}
required={isRequired}
readOnlyMode={readOnlyMode}
/>
<Box key={subQuestion.id} mb={2}>
<FastField
component={Component}
name={subQuestion.name}
label={subQuestion.label}
placeholder={subQuestion.placeholder}
required={isRequired}
readOnlyMode={readOnlyMode}
/>
</Box>
);
})}
</>
Expand Down
20 changes: 14 additions & 6 deletions src/utils/buildQuestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ const getAnswerValueType = question => {
return answer;
};

const getSubQuestions = subQuestions => subQuestions.reduce((accumulator, currentValue, currentIndex) => {
accumulator[currentValue.name] = {
id: currentIndex + 1,
optionId: currentValue.optionId,
answer: {value: ''}
};
return accumulator;
}, {});

const buildQuestions = section => {
const values = {[section.name]: {id: 1}};
if (section.interruption.interruptible) {
Expand All @@ -32,14 +41,13 @@ const buildQuestions = section => {
if (question.multiple) {
values[section.name][question.name] = {id, answer: [{id: 1, value: getAnswerValueType(question)}]};
}
if (question.subQuestions.length > 0) {
if (question.subQuestions && question.subQuestions.length > 0) {
values[section.name][question.name] = {
...values[section.name][question.name],
specifications: question.subQuestions.map(
(subQuestion, index) => ({
id: index + 1, optionId: subQuestion.optionId, answer: {value: ''}
})
)
answer: {
...values[section.name][question.name].answer,
specifications: getSubQuestions(question.subQuestions)
}
};
}
});
Expand Down
40 changes: 36 additions & 4 deletions src/utils/buildYupSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Yup from 'yup';

import dateTypes from '@/constants/dateTypes';
import questionTypes from '@/constants/questionTypes';
import castArray from '@/utils/castArray';

const getValidatorType = (type, options, {isRequired, message, metadata}) => {
let validator;
Expand Down Expand Up @@ -69,10 +70,43 @@ const handleValidations = ({validator, validations, type, opts}) => {
return newValidator;
};

const buildSubQuestionsValidations = (subQuestions, opts) => subQuestions.reduce((acc, currentValue) => {
let subQuestionValidator = Yup.string();
const subQuestionValidations = currentValue.validations;
subQuestionValidator = handleValidations({
validator: subQuestionValidator,
validations: subQuestionValidations,
type: currentValue.type,
opts
});
acc[currentValue.name] = Yup.object({answer: Yup.object({value: subQuestionValidator})});
return acc;
}, {});

const buildAnswerObj = ({values, subQuestions, validator, multiple, opts}) => {
const defaultSchema = multiple ? Yup.array().of(
Yup.object({
id: Yup.number().required(),
value: validator
})
) : Yup.object({value: validator});
if (subQuestions.length > 0) {
const selectedQuestions = buildSubQuestionsValidations(subQuestions.filter(
subQuestion => castArray(values.value).includes(subQuestion.optionId.toString())
), opts);
return defaultSchema.concat(
Yup.object({
specifications: Yup.object(selectedQuestions)
})
);
}
return defaultSchema;
};

export default function buildYupSchema(schema, config, opts = {}) {
const schemaWithValidations = schema;
const {
name, type, validations, options, metadata, multiple
name, type, validations, options, metadata, multiple, subQuestions = []
} = config;
const requiredField = validations.find(validation => validation.type === 'required');
let validator = getValidatorType(
Expand All @@ -90,9 +124,7 @@ export default function buildYupSchema(schema, config, opts = {}) {
validator = handleValidations({validator, validations, type, opts});
schemaWithValidations[name] = Yup.object({
id: Yup.number().required(),
answer: Yup.object({
value: multiple ? Yup.array().of(Yup.object({id: Yup.number(), value: validator})) : validator
})
answer: Yup.lazy(values => buildAnswerObj({values, type, subQuestions, validator, multiple, opts}))
});
return schemaWithValidations;
}
11 changes: 5 additions & 6 deletions src/utils/propTypes/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ export default PropTypes.shape({
})
)
]),
specifications: PropTypes.arrayOf(
PropTypes.shape({
optionId: PropTypes.number,
name: PropTypes.string
})
)
specifications: PropTypes.shape({
optionId: PropTypes.number,
name: PropTypes.string,
answer: PropTypes.shape({value: PropTypes.string})
})
});

0 comments on commit 650322d

Please sign in to comment.