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] refactor,fix : 리뷰 작성 페이지 디자인 버그 수정 및 상태 관리 리팩토링 #369

Merged
merged 39 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
033925e
chore: 깃허브 로고 주석 처리
BadaHertz52 Aug 15, 2024
792b59e
fix: Checkbox 의 onChange 오류 수정
BadaHertz52 Aug 15, 2024
6dd162b
chore: 서술형 질문에 글자 수 안내 문구 삭제, 선택 문항에 '(선택)' 문구 추
BadaHertz52 Aug 15, 2024
2a65937
refactor: multipleGuidline을 useMultipleChoice로 이동
BadaHertz52 Aug 15, 2024
3db5766
refactor: useMutateReview의 onSuccess에서 리뷰 생성 후 기능 실행하도록 수정
BadaHertz52 Aug 15, 2024
5d1308a
refactor: useMutateReview의 onSuccess에서 리뷰 생성 후 기능 실행하도록 수정
BadaHertz52 Aug 15, 2024
0bc5551
fix: Checkbox props명 오류 수정
BadaHertz52 Aug 15, 2024
a09cd0c
refactor : 리뷰 작성 질문들을 recoil로 관리하도록 리팩토링
BadaHertz52 Aug 15, 2024
1dd2a35
refactor: answerMap, answerValidationMap을 atom으로 변경
BadaHertz52 Aug 15, 2024
e5615ea
refactor: useReviewAnswer를 3개의 훅으로 분리
BadaHertz52 Aug 15, 2024
7f393e0
feat: 리뷰 작성 폼 관련 recoil 상태 초기화하는 훅 생성 및 적용
BadaHertz52 Aug 15, 2024
59f7257
fix: 리뷰 작성 페이지 목 데이터에서 optionId 중복 수정
BadaHertz52 Aug 15, 2024
ddf61cf
chore: 코드 설명에 대한 주석 추가
BadaHertz52 Aug 15, 2024
ccea08e
refactor: useUpdateDefaultAnswers 에서 객관식/서술형 기본값 상수 처리
BadaHertz52 Aug 15, 2024
0405874
fix: 서술형에서 길에 한문장으로 작성 시 맥에서 가로 스크롤이 생기는 오류 수정
BadaHertz52 Aug 15, 2024
5fe1c6f
fix : ReviewWritingFrom의 formId 타입 변경 (string-> number)
BadaHertz52 Aug 15, 2024
aee57bc
chore: REVIEW_WRITING_FORM_CARD_DATA 변경
BadaHertz52 Aug 15, 2024
088bb3f
refactor: 훅 , selector 네이밍 변경
BadaHertz52 Aug 15, 2024
a9016a1
refactor: useMultipleChoice 리팩토링
BadaHertz52 Aug 15, 2024
ce1b1b2
feat :기존에 답변한 카테고리를 카테고리 질문에서 해제할 때에 대한 대응 추가
BadaHertz52 Aug 15, 2024
48ac6a0
refactor: ReviewWritingCardForm하위 컴포넌트 파일들을 components 폴더로 이동
BadaHertz52 Aug 15, 2024
06654a7
feat: QnABox에 답변한 카테고리 해제 시, 관련 기능 적용
BadaHertz52 Aug 15, 2024
16c8ca6
refactor: QnABox에서 MultipleChoiceQuestion 분리
BadaHertz52 Aug 15, 2024
c915924
fix: 개수 제한 가이드 라인 표시 이전에 선택 오류 수정
BadaHertz52 Aug 15, 2024
fdd9c4c
chore: 필요없는 코드 삭제
BadaHertz52 Aug 15, 2024
9ab540c
fix: 답변이 없는 카테고리 선택 취소 시, 모달 띄워지는 오류 수정
BadaHertz52 Aug 15, 2024
27d2103
fix: handleModalOpen 파라미터 오류 수정
BadaHertz52 Aug 15, 2024
d2bcbc7
fix: 답변이 없는 카테고리 선택 취소 시 답변 기본값에 의한 오류 수정
BadaHertz52 Aug 15, 2024
69021fb
refactor: useTextAnswer에서 useUpdateReviewerAnswer사용
BadaHertz52 Aug 15, 2024
dbdd8f1
design: 프로젝트 이름, 리뷰이의 이름이 길어지는 경우를 대비한 디자인 수정
BadaHertz52 Aug 15, 2024
4c39249
feat: 작성 내용 확인 버튼에 비활성화 기능 추가
BadaHertz52 Aug 15, 2024
f125284
fix: Checkbox 의 onChange 오류 수정
BadaHertz52 Aug 15, 2024
43f70a8
chore: 서술형 질문에 글자 수 안내 문구 삭제, 선택 문항에 '(선택)' 문구 추
BadaHertz52 Aug 15, 2024
9e53fee
fix: Checkbox 의 onChange 오류 수정
BadaHertz52 Aug 15, 2024
1bc8ceb
fix: 서술형에서 길에 한문장으로 작성 시 맥에서 가로 스크롤이 생기는 오류 수정
BadaHertz52 Aug 15, 2024
8ba8913
design: 프로젝트 이름, 리뷰이의 이름이 길어지는 경우를 대비한 디자인 수정
BadaHertz52 Aug 15, 2024
c3fa124
Merge branch 'develop' of https://github.com/woowacourse-teams/2024-r…
BadaHertz52 Aug 15, 2024
5f2798e
chore: Checkbox 변경으로 인한 코드 수정
BadaHertz52 Aug 15, 2024
9133104
chore: Checkbox 변경으로 인한 코드 수정
BadaHertz52 Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/components/common/LongReviewItem/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const Textarea = styled.textarea<TextareaProps>`
padding: 1.6rem;

font-weight: ${({ theme }) => theme.fontWeight.medium};
overflow-wrap: break-word;

border: 0.1rem solid ${({ $isError, theme }) => ($isError ? theme.colors.red : theme.colors.black)};
border-radius: 0.8rem;
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/hooks/review/writingCardForm/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export { default as useReviewerAnswer } from './useReviewerAnswer';
export { default as useSlideWidthAndHeight } from './useSlideWidthAndHeight';
export { default as useCheckNextStepAvailability } from './useCheckNextStepAvailability';
export { default as useCurrentCardIndex } from './useCurrentCardIndex';
export { default as useMultipleChoice } from './useMultipleChoice';
export { default as useTextAnswer } from './useTextAnswer';
export { default as useQuestionList } from './useQuestionList';
export { default as useGetDataToWrite } from './useGetDataToWrite';
export { default as useMutateReview } from './useMutateReview';
export { default as useMultipleChoice } from './multiplceChoice/useMultipleChoice';
export { default as useCardSectionList } from './useCardSectionList';
export { default as useResetFormRecoil } from './useResetFormRecoil';
export { default as useUpdateReviewerAnswer } from './useUpdateReviewerAnswer';
export { default as useSlideWidthAndHeight } from './useSlideWidthAndHeight';
export { default as useTextAnswer } from './useTextAnswer';
export { default as useUpdateDefaultAnswers } from './useUpdateDefaultAnswers';
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState } from 'react';

import { ReviewWritingCardQuestion } from '@/types';

interface UseAboveSelectionLimit {
question: ReviewWritingCardQuestion;
selectedOptionList: number[];
}
const useAboveSelectionLimit = ({ question, selectedOptionList }: UseAboveSelectionLimit) => {
const [isOpenLimitGuide, setIsOpenLimitGuide] = useState(false);

const isMaxCheckedNumber = () => {
if (!question.optionGroup) return false;
return selectedOptionList.length >= question.optionGroup.maxCount;
};

const isSelectedCheckbox = (optionId: number) => {
return selectedOptionList.includes(optionId);
};

/**
* 선택 가능한 문항 수를 넘어서 문항을 선택하려 하는지 여부
*/
const isAboveSelectionLimit = (optionId: number) => !!(isMaxCheckedNumber() && !isSelectedCheckbox(optionId));

/**
* 최대 문항 수를 넘어서 선택하려는지, 그럴 경우에 대한 핸들링
* @param id : 객관식 문항의 optionId
*/
const handleLimitGuideOpen = (isOpen: boolean) => {
setIsOpenLimitGuide(isOpen);
};

return {
isOpenLimitGuide,
isSelectedCheckbox,
isAboveSelectionLimit,
handleLimitGuideOpen,
};
};

export default useAboveSelectionLimit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useRecoilValue } from 'recoil';

import { answerMapAtom, cardSectionListSelector } from '@/recoil';
import { ReviewWritingCardQuestion } from '@/types';

interface UseCancelAnsweredCategoryProps {
question: ReviewWritingCardQuestion;
}
const useCancelAnsweredCategory = ({ question }: UseCancelAnsweredCategoryProps) => {
const cardSectionList = useRecoilValue(cardSectionListSelector);
const answerMap = useRecoilValue(answerMapAtom);
const isCategoryQuestion = () => {
return question.questionId === cardSectionList[0].questions[0].questionId;
};
// 이미 답변을 작성한 카테고리를 해제하는 경우
/**
* 카테고리 항목 선택일때, optionId에 해당하는 카테고리 찾기
*/
const getCategoryByOptionId = (categoryOptionId: number) => {
return cardSectionList.filter((section) => section.onSelectedOptionId === categoryOptionId)[0];
};

/**
* categoryOptionId로 찾은 카테고리에 답변이 달려있는 지 여부
* 답이 있기만 하다면 true
*/
const isSelectedCategoryAnswer = (categoryOptionId: number) => {
if (!answerMap) return false;
// 선택한 객관식에 해당하는 카테고리의 질문들 가져오기
const targetCategoryQuestionList = getCategoryByOptionId(categoryOptionId);

if (!targetCategoryQuestionList) return false;
//카테고리에 유효한 답변이 있는 지 판단
const questionIdList = targetCategoryQuestionList.questions.map((question) => question.questionId);
const questionId = questionIdList.find((id) => answerMap.has(id));

if (!questionId) return;

const answer = answerMap.get(questionId);

return !!answer?.selectedOptionIds?.length || !!answer?.text?.length;
};

/**
* 해제하기 위해 카테고리 문항을 선택한 경우, 이미 이에 대해 답변을 했는 지 여부
* @param optionId : 문항의 optionId
*/
const isAnsweredCategoryChanged = (optionId: number) => {
if (!isCategoryQuestion) return false;
return isSelectedCategoryAnswer(optionId);
};

return {
isAnsweredCategoryChanged,
};
};

export default useCancelAnsweredCategory;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from 'react';

import { ReviewWritingCardQuestion } from '@/types';

import useAboveSelectionLimit from './useAboveSelectionLimit';
import useCancelAnsweredCategory from './useCancelAnsweredCategory';
import useUpdateMultipleChoiceAnswer from './useUpdateMultipleChoiceAnswer';

interface UseMultipleChoiceProps {
question: ReviewWritingCardQuestion;
handleModalOpen: (isOpen: boolean) => void;
}
/**
* 하나의 객관식 질문에서 선택된 문항, 문항 선택 관리(최대를 넘는 문항 선택 시, 안내 문구 표시)등을 하는 훅
*/
const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps) => {
const [unCheckTargetOptionId, setUnCheckTargetOptionId] = useState<number | null>(null);

const { isAnsweredCategoryChanged } = useCancelAnsweredCategory({ question });

const { selectedOptionList, updateAnswerState } = useUpdateMultipleChoiceAnswer({ question });

const { isOpenLimitGuide, isSelectedCheckbox, isAboveSelectionLimit, handleLimitGuideOpen } = useAboveSelectionLimit({
question,
selectedOptionList,
});

const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { id, checked } = event.currentTarget;
const optionId = Number(id);
if (isAboveSelectionLimit(optionId)) {
return handleLimitGuideOpen(true);
}
handleLimitGuideOpen(false);
// 답변이 달린 카테고리를 해제하려는 경우
const isUnCheckCategory = isAnsweredCategoryChanged(optionId);
setUnCheckTargetOptionId(isUnCheckCategory ? optionId : null);
handleModalOpen(!!isUnCheckCategory);

if (!isUnCheckCategory) {
updateAnswerState({ optionId, checked });
}
};

const unCheckTargetOption = () => {
if (unCheckTargetOptionId) {
updateAnswerState({ optionId: unCheckTargetOptionId, checked: false });
}
};
return {
isOpenLimitGuide,
handleCheckboxChange,
isSelectedCheckbox,
unCheckTargetOption,
};
};
export default useMultipleChoice;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useState } from 'react';

import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types';

import useUpdateReviewerAnswer from '../useUpdateReviewerAnswer';

interface UseUpdateMultipleChoiceAnswerProps {
question: ReviewWritingCardQuestion;
}

const useUpdateMultipleChoiceAnswer = ({ question }: UseUpdateMultipleChoiceAnswerProps) => {
const [selectedOptionList, setSelectedOptionList] = useState<number[]>([]);

const { updateAnswerMap, updateAnswerValidationMap } = useUpdateReviewerAnswer();

interface MakeNewSelectedOptionList {
optionId: number;
checked: boolean;
}
/**
* checkbox의 change 이벤트에 따라 새로운 selectedOptionList를 반환하는 함수
*/
const makeNewSelectedOptionList = ({ optionId, checked }: MakeNewSelectedOptionList) => {
if (checked) {
return selectedOptionList.concat(optionId);
}

return selectedOptionList.filter((option) => option !== optionId);
};

const isValidatedChoice = (newSelectedOptionList: number[]) => {
if (!question.optionGroup) return false;

const { minCount, maxCount } = question.optionGroup;
const { length } = newSelectedOptionList;

return length >= minCount && length <= maxCount;
};

const updateAnswerState = ({ optionId, checked }: MakeNewSelectedOptionList) => {
const newSelectedOptionList = makeNewSelectedOptionList({ optionId, checked });
setSelectedOptionList(newSelectedOptionList);

// 유효한 선택(=객관식 문항의 최소,최대 개수를 지켰을 경우)인지에 따라 answer 변경
const isValidatedAnswer = isValidatedChoice(newSelectedOptionList);
const isNotRequiredEmptyAnswer = !question.required && newSelectedOptionList.length === 0;

const newAnswer: ReviewWritingAnswer = {
questionId: question.questionId,
selectedOptionIds: isValidatedAnswer ? newSelectedOptionList : [],
text: null,
};

updateAnswerMap(newAnswer);
updateAnswerValidationMap(newAnswer, isValidatedAnswer || isNotRequiredEmptyAnswer);
};

return {
selectedOptionList,
updateAnswerState,
};
};

export default useUpdateMultipleChoiceAnswer;
29 changes: 29 additions & 0 deletions frontend/src/hooks/review/writingCardForm/useCardSectionList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';

import { cardSectionListSelector, reviewWritingFormSectionListAtom } from '@/recoil';
import { ReviewWritingCardSection } from '@/types';

interface UseCardSectionListProps {
cardSectionListData: ReviewWritingCardSection[];
}
/**
* 서버에서 받아온 데이터를 바탕으로 리뷰 작성 폼에서 사용할 질문지(상태)를 변경하는 훅
* @param {ReviewWritingCardSection[]} cardSectionListData 서버에서 받아온 질문 데이터
* @returns
*/
const useCardSectionList = ({ cardSectionListData }: UseCardSectionListProps) => {
const setReviewWritingFormSectionList = useSetRecoilState(reviewWritingFormSectionListAtom);

const cardSectionList = useRecoilValue(cardSectionListSelector);

useEffect(() => {
setReviewWritingFormSectionList(cardSectionListData);
}, [cardSectionListData]);

return {
cardSectionList,
};
};

export default useCardSectionList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';

import { cardSectionListSelector, answerValidationMapAtom, answerMapAtom } from '@/recoil';

interface UseCheckNextStepAvailability {
currentCardIndex: number;
}
const useCheckNextStepAvailability = ({ currentCardIndex }: UseCheckNextStepAvailability) => {
const cardSectionList = useRecoilValue(cardSectionListSelector);
const answerValidationMap = useRecoilValue(answerValidationMapAtom);
const answerMap = useRecoilValue(answerMapAtom);

const [isAbleNextStep, setIsAbleNextStep] = useState(false);

const isValidateAnswerList = () => {
if (!cardSectionList.length) return false;

return cardSectionList[currentCardIndex].questions.every((question) => {
const { questionId, required } = question;
const answerValidation = answerValidationMap?.get(questionId);

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

useEffect(() => {
const answerListValidation = isValidateAnswerList();
setIsAbleNextStep(answerListValidation);
}, [answerMap, currentCardIndex]);

return {
isAbleNextStep,
};
};

export default useCheckNextStepAvailability;
78 changes: 0 additions & 78 deletions frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts

This file was deleted.

Loading