From 39ab5fe27cb7bc5e3306733e5bb7ec9c651f1910 Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Mon, 12 Aug 2024 18:11:38 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20api?= =?UTF-8?q?=20=EA=B0=80=20=EC=95=84=EB=8B=8C=20=EB=AA=A9=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EA=B3=A0=20=EC=9E=88=EB=8A=94=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/writingCardForm/useQuestionList.ts | 27 ++++++++++--------- .../CardForm/index.tsx | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/hooks/review/writingCardForm/useQuestionList.ts b/frontend/src/hooks/review/writingCardForm/useQuestionList.ts index 858e859fe..23debddfe 100644 --- a/frontend/src/hooks/review/writingCardForm/useQuestionList.ts +++ b/frontend/src/hooks/review/writingCardForm/useQuestionList.ts @@ -1,29 +1,30 @@ import { useEffect, useState } from 'react'; -import { REVIEW_WRITING_FORM_CARD_DATA } from '@/mocks/mockData'; import { ReviewWritingCardSection } from '@/types'; -const useQuestionList = () => { - const questionListSectionsData = REVIEW_WRITING_FORM_CARD_DATA.sections; +interface UseQuestionListProps { + questionListSectionsData: ReviewWritingCardSection[]; +} + +const useQuestionList = ({ questionListSectionsData }: UseQuestionListProps) => { const [questionList, setQuestionList] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); - // TODO: react-query로 서버에서 질문 목록 다 받아오기 const updatedSelectedCategory = (newSelectedCategory: number[]) => { setSelectedCategory(newSelectedCategory); }; const updateQuestionList = () => { - setQuestionList( - questionListSectionsData.filter((data) => { - // 공통 질문 추출 - if (data.visible === 'ALWAYS') return true; - // 선택된 카테고리 답변과 data.onSelectedOptionId를 비교 - if (!data.onSelectedOptionId) return false; - return !!selectedCategory?.includes(data.onSelectedOptionId); - }), - ); + const newQuestionList = questionListSectionsData.filter((data) => { + // 공통 질문 추출 + if (data.visible === 'ALWAYS') return true; + // 선택된 카테고리 답변과 data.onSelectedOptionId를 비교 + if (!data.onSelectedOptionId) return false; + return !!selectedCategory?.includes(data.onSelectedOptionId); + }); + setQuestionList(newQuestionList); }; + useEffect(() => { updateQuestionList(); }, [selectedCategory]); diff --git a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx index 93ce59783..f98fd7d2a 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx @@ -38,7 +38,7 @@ const CardForm = () => { const { wrapperRef, slideWidth, slideHeight, makeId } = useSlideWidthAndHeight({ currentCardIndex }); - const { questionList, updatedSelectedCategory } = useQuestionList(); + const { questionList, updatedSelectedCategory } = useQuestionList({ questionListSectionsData: data.sections }); const { answerMap, isAbleNextStep, updateAnswerMap } = useReviewerAnswer({ currentCardIndex, From bf5ce48a8aa1196d3365bc4eb590df89a30d8414 Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Mon, 12 Aug 2024 18:24:13 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=EC=97=90?= =?UTF-8?q?=EB=8F=84=20navigate=20=EC=9E=91=EB=8F=99=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writingCardForm/useMutateReview/index.ts | 11 ++------ .../writingCardForm/useMutateReview/test.tsx | 6 ++-- .../CardForm/index.tsx | 28 +++++++------------ 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/frontend/src/hooks/review/writingCardForm/useMutateReview/index.ts b/frontend/src/hooks/review/writingCardForm/useMutateReview/index.ts index 32fecd892..64cb038c6 100644 --- a/frontend/src/hooks/review/writingCardForm/useMutateReview/index.ts +++ b/frontend/src/hooks/review/writingCardForm/useMutateReview/index.ts @@ -4,11 +4,7 @@ import { postReviewApi } from '@/apis/review'; import { REVIEW_QUERY_KEYS } from '@/constants'; import { ReviewWritingFormResult } from '@/types'; -interface UseMutateReviewProps { - openErrorModal: (message: string) => void; -} - -const useMutateReview = ({ openErrorModal }: UseMutateReviewProps) => { +const useMutateReview = () => { const queryClient = useQueryClient(); const reviewMutation = useMutation({ @@ -16,16 +12,13 @@ const useMutateReview = ({ openErrorModal }: UseMutateReviewProps) => { onSuccess: () => { queryClient.invalidateQueries({ queryKey: [REVIEW_QUERY_KEYS.postReview] }); }, - onError: () => { - openErrorModal('리뷰 제출에 실패했어요'); - }, }); const postReview = (formResult: ReviewWritingFormResult) => { reviewMutation.mutate(formResult); }; - return { reviewMutation, postReview }; + return { ...reviewMutation, postReview }; }; export default useMutateReview; diff --git a/frontend/src/hooks/review/writingCardForm/useMutateReview/test.tsx b/frontend/src/hooks/review/writingCardForm/useMutateReview/test.tsx index 4cb709f43..189c068a3 100644 --- a/frontend/src/hooks/review/writingCardForm/useMutateReview/test.tsx +++ b/frontend/src/hooks/review/writingCardForm/useMutateReview/test.tsx @@ -8,14 +8,14 @@ import useMutateReview from '.'; describe('리뷰 생성 요청 테스트', () => { test('성공적으로 리뷰를 생성한다.', async () => { - const { result } = renderHook(() => useMutateReview({ openErrorModal: () => {} }), { wrapper: QueryClientWrapper }); + const { result } = renderHook(() => useMutateReview(), { wrapper: QueryClientWrapper }); act(() => { result.current.postReview(REVIEW_FORM_RESULT_DATA); }); - await waitFor(() => result.current.reviewMutation.isSuccess); + await waitFor(() => result.current.isSuccess); - expect(result.current.reviewMutation.isSuccess).toBe(true); + expect(result.current.isSuccess).toBe(true); }); }); diff --git a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx index f98fd7d2a..f6c6c4a94 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; import { ConfirmModal, ErrorAlertModal, ProjectImg, AnswerListRecheckModal } from '@/components'; @@ -23,7 +23,6 @@ const INDEX_OFFSET = 1; const MODAL_KEYS = { confirm: 'CONFIRM', recheck: 'RECHECK', - error: 'ERROR', }; const CardForm = () => { @@ -46,28 +45,28 @@ const CardForm = () => { updatedSelectedCategory, }); const { isOpen, openModal, closeModal } = useModals(); - const [errorMessage, setErrorMessage] = useState(''); const navigate = useNavigate(); - const openErrorModal = (message: string) => { - setErrorMessage(message); - openModal(MODAL_KEYS.error); - }; - const { postReview } = useMutateReview({ openErrorModal }); + const { postReview, isSuccess } = useMutateReview(); const handleSubmitButtonClick = () => { openModal(MODAL_KEYS.confirm); }; - const submitAnswer = () => { + useEffect(() => { + if (isSuccess) { + navigate('/user/review-writing-complete'); + closeModal(MODAL_KEYS.confirm); + } + }, [isSuccess]); + + const submitAnswer = async () => { if (!answerMap || !reviewRequestCode) return; const result: ReviewWritingFormResult = { reviewRequestCode: reviewRequestCode, answers: Array.from(answerMap.values()), }; postReview(result); - navigate('/user/review-writing-complete'); - closeModal(MODAL_KEYS.confirm); }; const handleRecheckButtonClick = () => { @@ -117,13 +116,6 @@ const CardForm = () => { )} - {isOpen(MODAL_KEYS.error) && ( - closeModal(MODAL_KEYS.error) }} - handleClose={() => closeModal(MODAL_KEYS.error)} - /> - )} {isOpen(MODAL_KEYS.recheck) && questionList && answerMap && ( Date: Mon, 12 Aug 2024 18:24:52 +0900 Subject: [PATCH 3/6] Update index.tsx --- .../src/pages/ReviewWritingCardFromPage/CardForm/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx index f6c6c4a94..36ceb0ddb 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useNavigate } from 'react-router'; -import { ConfirmModal, ErrorAlertModal, ProjectImg, AnswerListRecheckModal } from '@/components'; +import { ConfirmModal, ProjectImg, AnswerListRecheckModal } from '@/components'; import { useCurrentCardIndex, useGetDataToWrite, From 8bc8c3298308687698ba4671f374544d8c9a244e Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Mon, 12 Aug 2024 18:26:41 +0900 Subject: [PATCH 4/6] =?UTF-8?q?chore:=20ReviewWriting=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeywordButton/index.tsx | 17 -- .../components/KeywordButton/styles.ts | 21 --- .../ReviewWritingContents/index.tsx | 155 ------------------ .../ReviewWritingContents/styles.ts | 117 ------------- .../components/RevieweeComment/index.tsx | 13 -- .../components/RevieweeComment/styles.ts | 16 -- frontend/src/pages/ReviewWriting/index.tsx | 13 -- 7 files changed, 352 deletions(-) delete mode 100644 frontend/src/pages/ReviewWriting/components/KeywordButton/index.tsx delete mode 100644 frontend/src/pages/ReviewWriting/components/KeywordButton/styles.ts delete mode 100644 frontend/src/pages/ReviewWriting/components/ReviewWritingContents/index.tsx delete mode 100644 frontend/src/pages/ReviewWriting/components/ReviewWritingContents/styles.ts delete mode 100644 frontend/src/pages/ReviewWriting/components/RevieweeComment/index.tsx delete mode 100644 frontend/src/pages/ReviewWriting/components/RevieweeComment/styles.ts delete mode 100644 frontend/src/pages/ReviewWriting/index.tsx diff --git a/frontend/src/pages/ReviewWriting/components/KeywordButton/index.tsx b/frontend/src/pages/ReviewWriting/components/KeywordButton/index.tsx deleted file mode 100644 index ea40dace7..000000000 --- a/frontend/src/pages/ReviewWriting/components/KeywordButton/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ButtonHTMLAttributes } from 'react'; - -import * as S from './styles'; - -interface KeywordButtonProps extends ButtonHTMLAttributes { - isSelected: boolean; -} - -const KeywordButton = ({ isSelected, children, ...props }: React.PropsWithChildren) => { - return ( - - {children} - - ); -}; - -export default KeywordButton; diff --git a/frontend/src/pages/ReviewWriting/components/KeywordButton/styles.ts b/frontend/src/pages/ReviewWriting/components/KeywordButton/styles.ts deleted file mode 100644 index 1d61b9e83..000000000 --- a/frontend/src/pages/ReviewWriting/components/KeywordButton/styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import styled from '@emotion/styled'; - -export const KeywordButton = styled.button<{ isSelected: boolean }>` - display: flex; - align-items: center; - justify-content: flex-start; - - width: fit-content; - height: 5rem; - padding: 2rem; - - color: black; - - background-color: ${({ theme, isSelected }) => (isSelected ? '#E6E3F9' : theme.colors.white)}; - border: 0.2rem solid ${({ theme, isSelected }) => (isSelected ? theme.colors.primary : theme.colors.lightGray)}; - border-radius: 2rem; - - &:hover { - background-color: ${({ theme, isSelected }) => (isSelected ? theme.colors.primaryHover : theme.colors.lightGray)}; - } -`; diff --git a/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/index.tsx b/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/index.tsx deleted file mode 100644 index 17e5520c0..000000000 --- a/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/index.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useNavigate } from 'react-router'; - -// import ClockLogo from '@/assets/clock.svg'; -import GithubLogoIcon from '@/assets/githubLogo.svg'; -import { ConfirmModal, ErrorAlertModal } from '@/components'; -import Button from '@/components/common/Button'; -import LongReviewItem from '@/components/common/LongReviewItem'; -import useGetDataToWrite from '@/hooks/review/useGetDataToWrite'; -import useMutateReview from '@/hooks/review/useMutateReview'; -import useModals from '@/hooks/useModals'; -import useReviewForm from '@/hooks/useReviewForm'; -import useReviewRequestCode from '@/hooks/useReviewRequestCode'; -import { ReviewData } from '@/types'; - -import LoadingPage from '../../../LoadingPage'; -import KeywordButton from '../KeywordButton'; -// import RevieweeComment from './components/RevieweeComment'; - -import * as S from './styles'; - -const SUBMIT_CONFIRM_MESSAGE = `리뷰를 제출할까요? -제출한 뒤에는 수정할 수 없어요.`; - -const MODAL_KEYS = { - confirm: 'CONFIRM', - error: 'ERROR', -}; - -const ReviewWritingContents = () => { - const navigate = useNavigate(); - - const { reviewRequestCode } = useReviewRequestCode(); - const { isOpen, openModal, closeModal } = useModals(); - const { postReview } = useMutateReview(); - const { dataToWrite } = useGetDataToWrite({ reviewRequestCode }); - - const { answers, errorMessage, selectedKeywords, isValidForm, handleAnswerChange, handleKeywordButtonClick } = - useReviewForm({ - dataToWrite, - }); - - const handleClickSubmitButton = async (event: React.FormEvent) => { - event.preventDefault(); - openModal(MODAL_KEYS.confirm); - }; - - const handleSubmitReview = async () => { - const reviewData: ReviewData = { - reviewRequestCode, - reviewContents: answers, - keywords: selectedKeywords, - }; - - try { - postReview({ reviewData }); - closeModal(MODAL_KEYS.confirm); - navigate('/user/review-writing-complete', { replace: true }); - } catch (error) { - closeModal(MODAL_KEYS.confirm); - openModal(MODAL_KEYS.error); - } - }; - - if (!dataToWrite) return ; - - return ( - - - - - - {dataToWrite.projectName} - - - {dataToWrite.revieweeName}님을 리뷰해주세요! - - {/* - - 리뷰 마감일: {dataToWrite.reviewerGroup.deadline.toString()} - */} - - - - {/* */} - - - - {dataToWrite.questions.map((question, index) => { - return ( - <> -

- {index + 1}. {question.content} -

- handleAnswerChange(question.id, e.target.value)} - /> - - - // answer.questionId === question.id)?.answer || ''} - // handleWrite={(value) => handleAnswerChange(question.id, value)} - // /> - ); - })} -
- - {dataToWrite.questions.length + 1}. 키워드 - - {dataToWrite.keywords.map((keyword) => { - return ( - handleKeywordButtonClick(keyword)} - > - {keyword.content} - - ); - })} - - - - {/* - -
- {isOpen(MODAL_KEYS.confirm) && ( - closeModal(MODAL_KEYS.confirm) }} - handleClose={() => closeModal(MODAL_KEYS.confirm)} - isClosableOnBackground={true} - > - {SUBMIT_CONFIRM_MESSAGE} - - )} - {isOpen(MODAL_KEYS.error) && ( - closeModal(MODAL_KEYS.error) }} - handleClose={() => closeModal(MODAL_KEYS.error)} - /> - )} -
- ); -}; - -export default ReviewWritingContents; diff --git a/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/styles.ts b/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/styles.ts deleted file mode 100644 index c6a43104e..000000000 --- a/frontend/src/pages/ReviewWriting/components/ReviewWritingContents/styles.ts +++ /dev/null @@ -1,117 +0,0 @@ -import styled from '@emotion/styled'; - -export const ReviewWritingContents = styled.form` - display: flex; - flex-direction: column; - - width: ${({ theme }) => theme.formWidth}; - height: fit-content; - margin-bottom: 5rem; -`; - -export const ReviewFormHeader = styled.header` - display: flex; - flex-direction: column; - gap: 1rem; - margin-bottom: 3rem; -`; - -export const ReviewInfoContainer = styled.div` - display: flex; - gap: 1.5rem; -`; - -export const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - width: 100%; -`; - -export const LogoImage = styled.img` - width: 6rem; - height: 6rem; -`; - -export const ProjectName = styled.div` - font-size: 2rem; - font-weight: 700; -`; - -export const ReviewInfo = styled.div` - display: flex; - justify-content: space-between; -`; - -export const Reviewee = styled.div` - & > span { - color: ${({ theme }) => theme.colors.primary}; - } -`; - -export const ReviewExpirationDate = styled.div` - display: flex; - gap: 0.5rem; -`; - -export const ClockImage = styled.img` - width: 1.5rem; - height: 2.4rem; -`; - -export const ReviewFormMain = styled.main` - display: flex; - flex-direction: column; -`; - -export const ReviewContainer = styled.section` - display: flex; - flex-direction: column; - gap: 2rem; - margin-bottom: 2.4rem; -`; - -export const ButtonContainer = styled.div` - display: flex; - gap: 1rem; - justify-content: flex-end; -`; - -export const Button = styled.button` - width: 3.5rem; - height: 2rem; - border: 0.0625rem solid black; -`; - -export const KeywordContainer = styled.div` - display: flex; - flex-direction: column; -`; - -export const KeywordTitle = styled.div` - font-size: 1.6rem; - font-weight: 600; - - &::after { - content: ' (최소 1개 ~ 최대 5개)'; - } -`; - -export const KeywordList = styled.ul` - display: flex; - flex-wrap: wrap; - gap: 1rem; - margin: 1rem 0; -`; - -export const KeywordItem = styled.li` - display: flex; - gap: 0.5rem; - align-items: center; - - & > input { - top: 0.09375rem; - width: 1rem; - height: 1rem; - } -`; diff --git a/frontend/src/pages/ReviewWriting/components/RevieweeComment/index.tsx b/frontend/src/pages/ReviewWriting/components/RevieweeComment/index.tsx deleted file mode 100644 index 4ac5b5f18..000000000 --- a/frontend/src/pages/ReviewWriting/components/RevieweeComment/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as S from './styles'; - -const defaultText = '안녕하세요! 리뷰 잘 부탁드립니다.'; - -interface RevieweeCommentProps { - text: string; -} - -const RevieweeComment = ({ text }: RevieweeCommentProps) => { - return {text || defaultText}; -}; - -export default RevieweeComment; diff --git a/frontend/src/pages/ReviewWriting/components/RevieweeComment/styles.ts b/frontend/src/pages/ReviewWriting/components/RevieweeComment/styles.ts deleted file mode 100644 index 0e6b1b36f..000000000 --- a/frontend/src/pages/ReviewWriting/components/RevieweeComment/styles.ts +++ /dev/null @@ -1,16 +0,0 @@ -import styled from '@emotion/styled'; - -export const RevieweeComment = styled.section` - display: flex; - align-items: center; - justify-content: flex-start; - - height: 3.5rem; - margin: 1rem 0; - padding: 1rem; - - color: ${({ theme }) => theme.colors.black}; - - background-color: ${({ theme }) => theme.colors.white}; - border-left: 0.4rem solid ${({ theme }) => theme.colors.black}; -`; diff --git a/frontend/src/pages/ReviewWriting/index.tsx b/frontend/src/pages/ReviewWriting/index.tsx deleted file mode 100644 index 204657387..000000000 --- a/frontend/src/pages/ReviewWriting/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { ErrorSuspenseContainer } from '@/components'; - -import ReviewWritingContents from './components/ReviewWritingContents'; - -const ReviewWritingPage = () => { - return ( - - - - ); -}; - -export default ReviewWritingPage; From df7a602f6f010d438f12316cc342bed77d8cffbd Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Tue, 13 Aug 2024 11:25:07 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EC=A0=9C=EC=B6=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=97=90=20=ED=99=9C=EC=84=B1=ED=99=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CardSliderController/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingCardFromPage/CardSliderController/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/CardSliderController/index.tsx index ec2cd5a9e..ccf22f413 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/CardSliderController/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/CardSliderController/index.tsx @@ -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 ( - ); From 1dda6e6fea140f177c6f92c777f3c98b655ab92a Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Tue, 13 Aug 2024 11:27:36 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=EB=8B=B5=EB=B3=80=EB=93=A4?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EB=A5=BC=20=EB=8B=B4=EB=8A=94=20=EC=83=81?= =?UTF-8?q?=ED=83=9C,=20=EC=9D=B4=EB=A5=BC=20=ED=95=B8=EB=93=A4=EB=A7=81?= =?UTF-8?q?=20=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - answerValidationMap, updateAnswerValidationMap 생성 - answerValidationMap을 업데이트할떼, useMultiple, useTextAnswer에서 필수가 아닌 답변이면서 서술형의 경우 빈문자열, 객관식의 경우 빈배열일때 유효성을 true로 설정 - useReviewAnswer에서 isValidateAnswerList에서 answerValidation을 사용 --- .../writingCardForm/useMultipleChoice.ts | 13 ++++++--- .../writingCardForm/useReviewerAnswer.ts | 28 +++++++++---------- .../review/writingCardForm/useTextAnswer.ts | 18 +++++++----- .../CardForm/index.tsx | 3 +- .../QnABox/index.tsx | 10 +++++-- .../ReviewWritingCard/index.tsx | 14 ++++++++-- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts b/frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts index d5d0e6352..472b326f9 100644 --- a/frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts +++ b/frontend/src/hooks/review/writingCardForm/useMultipleChoice.ts @@ -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([]); const [isOpenLimitGuide, setIsOpenLimitGuide] = useState(false); @@ -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 : null, + selectedOptionIds: isValidatedAnswer ? newSelectedOptionList : [], text: null, - }); + }; + updateAnswerMap(newAnswer); + updateAnswerValidationMap(newAnswer, isValidatedAnswer || isNotRequiredEmptyAnswer); }; /** diff --git a/frontend/src/hooks/review/writingCardForm/useReviewerAnswer.ts b/frontend/src/hooks/review/writingCardForm/useReviewerAnswer.ts index f36d367f9..8f760af6e 100644 --- a/frontend/src/hooks/review/writingCardForm/useReviewerAnswer.ts +++ b/frontend/src/hooks/review/writingCardForm/useReviewerAnswer.ts @@ -10,6 +10,7 @@ interface UseReviewerAnswerProps { const useReviewerAnswer = ({ currentCardIndex, questionList, updatedSelectedCategory }: UseReviewerAnswerProps) => { const [answerMap, setAnswerMap] = useState>(); + const [answerValidationMap, SetAnswerValidationMap] = useState>(); const [isAbleNextStep, setIsAbleNextStep] = useState(false); const isCategoryAnswer = (answer: ReviewWritingAnswer) => @@ -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; }); }; @@ -55,6 +54,7 @@ const useReviewerAnswer = ({ currentCardIndex, questionList, updatedSelectedCate answerMap, isAbleNextStep, updateAnswerMap, + updateAnswerValidationMap, }; }; diff --git a/frontend/src/hooks/review/writingCardForm/useTextAnswer.ts b/frontend/src/hooks/review/writingCardForm/useTextAnswer.ts index 44d44fc8b..6143f3d6b 100644 --- a/frontend/src/hooks/review/writingCardForm/useTextAnswer.ts +++ b/frontend/src/hooks/review/writingCardForm/useTextAnswer.ts @@ -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 시 마다 상태 변경되어서, 디바운스를 적용할 지 고민... @@ -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 : null }); + 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 { diff --git a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx index 36ceb0ddb..80c14b951 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/CardForm/index.tsx @@ -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, @@ -96,6 +96,7 @@ const CardForm = () => { isLastCard={questionList.length - INDEX_OFFSET === currentCardIndex} handleCurrentCardIndex={handleCurrentCardIndex} updateAnswerMap={updateAnswerMap} + updateAnswerValidationMap={updateAnswerValidationMap} handleRecheckButtonClick={handleRecheckButtonClick} handleSubmitButtonClick={handleSubmitButtonClick} /> diff --git a/frontend/src/pages/ReviewWritingCardFromPage/QnABox/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/QnABox/index.tsx index 13d6028cf..8016f78b3 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/QnABox/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/QnABox/index.tsx @@ -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; diff --git a/frontend/src/pages/ReviewWritingCardFromPage/ReviewWritingCard/index.tsx b/frontend/src/pages/ReviewWritingCardFromPage/ReviewWritingCard/index.tsx index d63b3835d..37bd7bc71 100644 --- a/frontend/src/pages/ReviewWritingCardFromPage/ReviewWritingCard/index.tsx +++ b/frontend/src/pages/ReviewWritingCardFromPage/ReviewWritingCard/index.tsx @@ -10,6 +10,7 @@ interface ReviewWritingCardProps extends CardSliderControllerProps { isLastCard: boolean; cardSection: ReviewWritingCardSection; updateAnswerMap: (answer: ReviewWritingAnswer) => void; + updateAnswerValidationMap: (answer: ReviewWritingAnswer, isValidatedAnswer: boolean) => void; } const ReviewWritingCard = ({ @@ -20,6 +21,7 @@ const ReviewWritingCard = ({ isAbleNextStep, handleCurrentCardIndex, updateAnswerMap, + updateAnswerValidationMap, handleRecheckButtonClick, handleSubmitButtonClick, }: ReviewWritingCardProps) => { @@ -28,7 +30,12 @@ const ReviewWritingCard = ({ {cardSection.header} {cardSection.questions.map((question) => ( - + ))} @@ -41,7 +48,10 @@ const ReviewWritingCard = ({ {isLastCard ? ( <> - + ) : (