Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] fix : 필수가 아닌 객관식, 서술형에서 답변 작성이 들어갈 경우에도 유효성 검사를 진행 #339

Merged
merged 9 commits into from
Aug 13, 2024
Merged
13 changes: 9 additions & 4 deletions frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types';
interface UseMultipleChoiceProps {
question: ReviewWritingCardQuestion;
updateAnswerMap: (answer: ReviewWritingAnswer) => void;
updateAnswerValidationMap: (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => void;
}

const useMultipleChoice = ({ question, updateAnswerMap }: UseMultipleChoiceProps) => {
const useMultipleChoice = ({ question, updateAnswerMap, updateAnswerValidationMap }: UseMultipleChoiceProps) => {
const [selectedOptionList, setSelectedOptionList] = useState<number[]>([]);
const [isOpenLimitGuide, setIsOpenLimitGuide] = useState(false);

Expand All @@ -23,11 +24,15 @@ const useMultipleChoice = ({ question, updateAnswerMap }: UseMultipleChoiceProps
const newSelectedOptionList = makeNewSelectedOptionList(event);
setSelectedOptionList(newSelectedOptionList);
// 유효한 선택(=객관식 문항의 최소,최대 개수를 지켰을 경우)인지에 따라 answer 변경
updateAnswerMap({
const isValidatedAnswer = isValidatedChoice(newSelectedOptionList);
const isNotRequiredEmptyAnswer = !question.required && newSelectedOptionList.length === 0;
const newAnswer: ReviewWritingAnswer = {
questionId: question.questionId,
selectedOptionIds: isValidatedChoice(newSelectedOptionList) ? newSelectedOptionList : [],
selectedOptionIds: isValidatedAnswer ? newSelectedOptionList : [],
text: null,
});
};
updateAnswerMap(newAnswer);
updateAnswerValidationMap(newAnswer, isValidatedAnswer || isNotRequiredEmptyAnswer);
};

/**
Expand Down
28 changes: 14 additions & 14 deletions frontend/src/hooks/review/writingCardForm/useReviewerAnswer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface UseReviewerAnswerProps {

const useReviewerAnswer = ({ currentCardIndex, questionList, updatedSelectedCategory }: UseReviewerAnswerProps) => {
const [answerMap, setAnswerMap] = useState<Map<number, ReviewWritingAnswer>>();
const [answerValidationMap, SetAnswerValidationMap] = useState<Map<number, boolean>>();
const [isAbleNextStep, setIsAbleNextStep] = useState(false);

const isCategoryAnswer = (answer: ReviewWritingAnswer) =>
Expand All @@ -25,24 +26,22 @@ const useReviewerAnswer = ({ currentCardIndex, questionList, updatedSelectedCate
}
};

const updateAnswerValidationMap = (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => {
const newAnswerValidationMap = new Map(answerValidationMap);
newAnswerValidationMap.set(answer.questionId, isValidatedAnswer);
SetAnswerValidationMap(newAnswerValidationMap);
};

const isValidateAnswerList = () => {
if (!questionList) return false;

return questionList[currentCardIndex].questions.every((question) => {
const { questionId, optionGroup, required } = question;
// case1. 필수가 아닌 답변
if (!required) return true;
// case2. 필수이 답변
// 2-1 답변 없음
if (!answerMap) return false;
const answer = answerMap.get(questionId);
if (!answer) return false;
// 2-2 답변이 있음 (세부적인 것을 확인)
// 전제 조건: 유효한 답변인 경우에만 답변 값이 있고, 그렇지 않으면 답변값이 없음
// 2-2-1.객관식 인 경우 선택된 문항의 개수의 유효성 검사
if (optionGroup) return !!answer.selectedOptionIds?.length;
// 2-2-2. 서술형
return !!answer.text?.length;
const { questionId, required } = question;
const answerValidation = answerValidationMap?.get(questionId);
const answer = answerMap?.get(questionId);

if (!required && !answer) return true;
return !!answerValidation;
});
};

Expand All @@ -55,6 +54,7 @@ const useReviewerAnswer = ({ currentCardIndex, questionList, updatedSelectedCate
answerMap,
isAbleNextStep,
updateAnswerMap,
updateAnswerValidationMap,
};
};

Expand Down
18 changes: 11 additions & 7 deletions frontend/src/hooks/review/writingCardForm/useTextAnswer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const TEXT_ANSWER_LENGTH = {
interface UseTextAnswerProps {
question: ReviewWritingCardQuestion;
updateAnswerMap: (answer: ReviewWritingAnswer) => void;
updateAnswerValidationMap: (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => void;
}
const useTextAnswer = ({ question, updateAnswerMap }: UseTextAnswerProps) => {
const useTextAnswer = ({ question, updateAnswerMap, updateAnswerValidationMap }: UseTextAnswerProps) => {
const [textAnswer, setTextAnswer] = useState('');

// NOTE: change 시 마다 상태 변경되어서, 디바운스를 적용할 지 고민...
Expand All @@ -20,12 +21,15 @@ const useTextAnswer = ({ question, updateAnswerMap }: UseTextAnswerProps) => {
const { value } = event.target;
const { min, max } = TEXT_ANSWER_LENGTH;
const isValidatedText = value.length >= min && value.length <= max;
// TODO: XSS 방어 되는 지 확인해봐야함
if (isValidatedText) {
setTextAnswer(value);
}
// 유효한 답변인지 여부에 따라 답변 변경
updateAnswerMap({ questionId: question.questionId, selectedOptionIds: null, text: isValidatedText ? value : '' });
setTextAnswer(value);
const isNotRequiredEmptyAnswer = !question.required && value === '';
const newAnswer: ReviewWritingAnswer = {
questionId: question.questionId,
selectedOptionIds: null,
text: isValidatedText ? value : '',
};
updateAnswerMap(newAnswer);
updateAnswerValidationMap(newAnswer, isValidatedText || isNotRequiredEmptyAnswer);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const CardForm = () => {

const { questionList, updatedSelectedCategory } = useQuestionList({ questionListSectionsData: data.sections });

const { answerMap, isAbleNextStep, updateAnswerMap } = useReviewerAnswer({
const { answerMap, isAbleNextStep, updateAnswerMap, updateAnswerValidationMap } = useReviewerAnswer({
currentCardIndex,
questionList,
updatedSelectedCategory,
Expand Down Expand Up @@ -96,6 +96,7 @@ const CardForm = () => {
isLastCard={questionList.length - INDEX_OFFSET === currentCardIndex}
handleCurrentCardIndex={handleCurrentCardIndex}
updateAnswerMap={updateAnswerMap}
updateAnswerValidationMap={updateAnswerValidationMap}
handleRecheckButtonClick={handleRecheckButtonClick}
handleSubmitButtonClick={handleSubmitButtonClick}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ const NextButton = ({ isAbleNextStep, handleCurrentCardIndex }: NextButtonProps)
};

interface SubmitButtonProps {
isAbleNextStep: boolean;
handleSubmitButtonClick: () => void;
}
const SubmitButton = ({ handleSubmitButtonClick }: SubmitButtonProps) => {
const SubmitButton = ({ isAbleNextStep, handleSubmitButtonClick }: SubmitButtonProps) => {
const styledType: ButtonStyleType = isAbleNextStep ? 'primary' : 'disabled';
return (
<Button styleType="primary" type={'submit'} onClick={handleSubmitButtonClick}>
<Button disabled={!isAbleNextStep} styleType={styledType} type={'submit'} onClick={handleSubmitButtonClick}>
제출
</Button>
);
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/pages/ReviewWritingCardFromPage/QnABox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ import * as S from './style';
interface QnABoxProps {
question: ReviewWritingCardQuestion;
updateAnswerMap: (answer: ReviewWritingAnswer) => void;
updateAnswerValidationMap: (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => void;
}

const QnABox = ({ question, updateAnswerMap }: QnABoxProps) => {
const QnABox = ({ question, updateAnswerMap, updateAnswerValidationMap }: QnABoxProps) => {
const { isOpenLimitGuide, handleCheckboxChange, isSelectedCheckbox } = useMultipleChoice({
question,
updateAnswerMap,
updateAnswerValidationMap,
});

const { textAnswer, handleTextAnswerChange, TEXT_ANSWER_LENGTH } = useTextAnswer({ question, updateAnswerMap });
const { textAnswer, handleTextAnswerChange, TEXT_ANSWER_LENGTH } = useTextAnswer({
question,
updateAnswerMap,
updateAnswerValidationMap,
});

const multipleGuideline = (() => {
const { optionGroup } = question;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface ReviewWritingCardProps extends CardSliderControllerProps {
isLastCard: boolean;
cardSection: ReviewWritingCardSection;
updateAnswerMap: (answer: ReviewWritingAnswer) => void;
updateAnswerValidationMap: (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => void;
}

const ReviewWritingCard = ({
Expand All @@ -20,6 +21,7 @@ const ReviewWritingCard = ({
isAbleNextStep,
handleCurrentCardIndex,
updateAnswerMap,
updateAnswerValidationMap,
handleRecheckButtonClick,
handleSubmitButtonClick,
}: ReviewWritingCardProps) => {
Expand All @@ -28,7 +30,12 @@ const ReviewWritingCard = ({
<S.Header>{cardSection.header}</S.Header>
<S.Main>
{cardSection.questions.map((question) => (
<QnABox key={question.questionId} question={question} updateAnswerMap={updateAnswerMap} />
<QnABox
key={question.questionId}
question={question}
updateAnswerMap={updateAnswerMap}
updateAnswerValidationMap={updateAnswerValidationMap}
/>
))}

<S.ButtonContainer>
Expand All @@ -41,7 +48,10 @@ const ReviewWritingCard = ({
{isLastCard ? (
<>
<CardSliderController.RecheckButton handleRecheckButtonClick={handleRecheckButtonClick} />
<CardSliderController.SubmitButton handleSubmitButtonClick={handleSubmitButtonClick} />
<CardSliderController.SubmitButton
isAbleNextStep={isAbleNextStep}
handleSubmitButtonClick={handleSubmitButtonClick}
/>
</>
) : (
<CardSliderController.NextButton
Expand Down