Skip to content

Commit

Permalink
[FE] fix : 필수가 아닌 객관식, 서술형에서 답변 작성이 들어갈 경우에도 유효성 검사를 진행 (#339)
Browse files Browse the repository at this point in the history
* fix: 리뷰 작성 페이지에서 api 가 아닌 목 데이터를 받아오고 있는 것 수정

* fix: 리뷰 작성 페이지에서 제출 실패 시에도 navigate 작동하는 오류 수정

* Update index.tsx

* chore: ReviewWriting 폴더 삭제

* feat: 제출 버튼에 활성화 기능 추가

* feat: 답변들에 대한 유효성여부를 담는 상태, 이를 핸들링 하는 함수 추가

- answerValidationMap, updateAnswerValidationMap 생성
- answerValidationMap을 업데이트할떼, useMultiple, useTextAnswer에서 필수가 아닌 답변이면서 서술형의 경우 빈문자열, 객관식의 경우 빈배열일때 유효성을 true로 설정
- useReviewAnswer에서 isValidateAnswerList에서 answerValidation을 사용
  • Loading branch information
BadaHertz52 authored Aug 13, 2024
1 parent 81285af commit 6fa82d3
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 32 deletions.
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

0 comments on commit 6fa82d3

Please sign in to comment.