From 10c293b378d531da0451f00ac06ed11cd66078c6 Mon Sep 17 00:00:00 2001 From: Kim0426 <706shin1728@naver.com> Date: Tue, 28 Nov 2023 17:05:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=A4=EB=AC=B8=20=EB=A7=88=EA=B0=90?= =?UTF-8?q?,=20=EC=A0=84=EC=86=A1=20api=20=EC=97=B0=EA=B2=B0=20(#71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 모든 수신자가 결과를 받았는지 검증하는 훅 useQuery로 수정 * refactor: 아이디 비밀번호 틀렸을 때 예외처리 * refactor: 취합된 설문 갱신하는 api 요청 타입 재설정 * refactor: useQueryClient훅으로 낙관적 업데이트 구현 * refactor: 수신자별 응답 결과 확인했는지 여부 낙관적 업데이트 적용 * feat: 전송된 설문일 때 전송 버튼 disabled처리 * refactor: 최종리뷰 결과가 저장되지 않은 수신자에 대해서만 리뷰 저장 호출 * feat: 리뷰 전송 시 성공,에러 토스트 처리 * feat: 필수 답변이 아닐 때 예외 처리 * refactor: default 값으로 넘어오는 값 필터링 처리 * refactor: api 주소 수정 * refactor: 수정된 api로 연결 * refactor: api타입 수정 * refactor: 빈문자열의 응답 최종리뷰결과에 반영되는 문제 예외 처리 * refactor: 설문 마감시 모두 응답 안한다면 에러 처리 * refactor: 가져오기 수정 * refactor: 토스트 띄우는 부분 컴포넌트에서 띄우게 분기 * refactor: 불필요 코드 제거 * refactor: 불필요 코드 제거 * refactor: strictmode 시 post 두번 가는 요청 ref로 처리 * refactor: 아코디언 높이 수정 * refactor: tab 스타일 수정 --- src/apis/hooks/index.ts | 2 +- src/apis/hooks/useCheckAllReceiverReceived.ts | 4 +- .../hooks/useCheckAllRecipientsReceived.ts | 23 ---- src/apis/hooks/useCloseReview.ts | 2 +- src/apis/hooks/useGetInvitedReview.ts | 2 +- src/apis/hooks/useGetResponseByReceiver.ts | 2 +- src/apis/hooks/useGetResponseByResponser.ts | 27 ----- src/apis/hooks/useLogin.ts | 5 +- src/apis/hooks/useSaveFinalResult.ts | 23 +++- src/apis/hooks/useSendReview.ts | 6 +- src/apis/hooks/useUpdateFinalReviewAnswer.ts | 8 -- .../ReceiverTabReviewDatail/index.tsx | 114 ++++++++++++------ .../ResponserTabReviewDetail/index.tsx | 13 +- .../components/Tabs/index.tsx | 4 +- src/pages/CreatedReviewManagePage/index.tsx | 74 +++++++++--- src/pages/CreatedReviewManagePage/utils.ts | 11 +- 16 files changed, 183 insertions(+), 137 deletions(-) delete mode 100644 src/apis/hooks/useCheckAllRecipientsReceived.ts delete mode 100644 src/apis/hooks/useGetResponseByResponser.ts diff --git a/src/apis/hooks/index.ts b/src/apis/hooks/index.ts index 4098a5af..ed2e82ce 100644 --- a/src/apis/hooks/index.ts +++ b/src/apis/hooks/index.ts @@ -26,7 +26,7 @@ export { default as useSaveFinalResult } from './useSaveFinalResult' export { default as useCheckDuplicatedName } from './useCheckDuplicatedName' export { default as useCheckDuplicatedEmail } from './useCheckDuplicatedEmail' export { default as useCheckAllReceiverReceived } from './useCheckAllReceiverReceived' -export { default as useCheckAllRecipientReceived } from './useCheckAllRecipientsReceived' +export { default as useCheckAllRecipientReceived } from './useCheckAllReceiverReceived' export { default as useEditResponse } from './useEditResponse' export { default as useEditName } from './useEditName' export { default as useEditPassword } from './useEditPassword' diff --git a/src/apis/hooks/useCheckAllReceiverReceived.ts b/src/apis/hooks/useCheckAllReceiverReceived.ts index 3da6e4a4..b408bb38 100644 --- a/src/apis/hooks/useCheckAllReceiverReceived.ts +++ b/src/apis/hooks/useCheckAllReceiverReceived.ts @@ -1,5 +1,5 @@ //NOTE - 모든 수신자가 결과를 받았는지 검증하는 훅 -import { useSuspenseQuery } from '@tanstack/react-query' +import { useQuery } from '@tanstack/react-query' import apiClient from '@/apis/apiClient' interface Response { @@ -16,7 +16,7 @@ const useCheckAllReceiverReceived = ({ id }: { id: string }) => { return response.data } - return useSuspenseQuery({ + return useQuery({ queryKey: [`/final-results/${id}/status`], queryFn: getCheckAllReceiverReceived, }) diff --git a/src/apis/hooks/useCheckAllRecipientsReceived.ts b/src/apis/hooks/useCheckAllRecipientsReceived.ts deleted file mode 100644 index c8fd4ac0..00000000 --- a/src/apis/hooks/useCheckAllRecipientsReceived.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useSuspenseQuery } from '@tanstack/react-query' -import apiClient from '@/apis/apiClient' - -interface Response { - success: boolean - data: number[] -} -const useCheckAllRecipientReceived = ({ reviewId }: { reviewId: string }) => { - const getCheckAllRecipientReceived = async () => { - const response = await apiClient.get( - `/final-results/${reviewId}/status`, - ) - - return response.data - } - - return useSuspenseQuery({ - queryKey: [`/final-results/${reviewId}/status`], - queryFn: getCheckAllRecipientReceived, - }) -} - -export default useCheckAllRecipientReceived diff --git a/src/apis/hooks/useCloseReview.ts b/src/apis/hooks/useCloseReview.ts index 7df70d3b..2d8b70cd 100644 --- a/src/apis/hooks/useCloseReview.ts +++ b/src/apis/hooks/useCloseReview.ts @@ -14,7 +14,7 @@ const useCloseSurvey = ({ id }: { id: string }) => { mutationFn: closeSurvey, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: [`/reviews/${id}`], + queryKey: [`/reviews/${id}/creator`], }) }, }) diff --git a/src/apis/hooks/useGetInvitedReview.ts b/src/apis/hooks/useGetInvitedReview.ts index 668f10ce..607d574b 100644 --- a/src/apis/hooks/useGetInvitedReview.ts +++ b/src/apis/hooks/useGetInvitedReview.ts @@ -31,7 +31,7 @@ const useGetInvitedReview = (reviewId: number) => { } return useQuery({ - queryKey: ['/invited-surveys/${reviewId}'], + queryKey: [`/invited-surveys/${reviewId}`], queryFn: getInvitedReview, }) } diff --git a/src/apis/hooks/useGetResponseByReceiver.ts b/src/apis/hooks/useGetResponseByReceiver.ts index 63d90abb..2f185a28 100644 --- a/src/apis/hooks/useGetResponseByReceiver.ts +++ b/src/apis/hooks/useGetResponseByReceiver.ts @@ -26,7 +26,7 @@ interface Receiver { interface Reply { id: string - questionId: string + questionId: number //TODO - 몇명의 피어가 답변했는지를 이 responser로 판별해야함 //TODO - 모든 replies의 responser를 뽑아와야 함 responser: Receiver diff --git a/src/apis/hooks/useGetResponseByResponser.ts b/src/apis/hooks/useGetResponseByResponser.ts deleted file mode 100644 index ccc9e9d0..00000000 --- a/src/apis/hooks/useGetResponseByResponser.ts +++ /dev/null @@ -1,27 +0,0 @@ -//NOTE - 작성자별 응답 결과 단일 조회 -import { useSuspenseQuery } from '@tanstack/react-query' -import apiClient from '@/apis/apiClient' -import { Response } from './useGetResponseByReceiver' - -//NOTE - 참여 ID -const useGetResponseByResponser = ({ - responserId, - reviewId, -}: { - responserId: string - reviewId: string -}) => { - const getSingleAuthorResponse = async () => { - const singleAuthorResponse = await apiClient.get( - `/reviews/${reviewId}/responser/${responserId}`, - ) - - return singleAuthorResponse.data - } - - return useSuspenseQuery({ - queryKey: [`/reviews/${reviewId}/responser/${responserId}`], - queryFn: getSingleAuthorResponse, - }) -} -export default useGetResponseByResponser diff --git a/src/apis/hooks/useLogin.ts b/src/apis/hooks/useLogin.ts index 4e108b98..26aaf477 100644 --- a/src/apis/hooks/useLogin.ts +++ b/src/apis/hooks/useLogin.ts @@ -21,9 +21,8 @@ const login = async (user: loginProps) => { const useLogin = () => { return useMutation({ mutationFn: login, - onSuccess: ({ data }) => { - localStorage.setItem(TOKEN_KEY, data.data.accessToken) - }, + onSuccess: ({ data }) => + localStorage.setItem(TOKEN_KEY, data.data.accessToken), }) } diff --git a/src/apis/hooks/useSaveFinalResult.ts b/src/apis/hooks/useSaveFinalResult.ts index 6581bae2..98a05d38 100644 --- a/src/apis/hooks/useSaveFinalResult.ts +++ b/src/apis/hooks/useSaveFinalResult.ts @@ -1,12 +1,13 @@ -import { QueryClient, useMutation } from '@tanstack/react-query' +import { useQueryClient, useMutation } from '@tanstack/react-query' import apiClient from '@/apis/apiClient' interface ReviewId { reviewId: string + userId: string } const useSaveFinalResult = (finalResult: T) => { - const queryClient = new QueryClient() + const queryClient = useQueryClient() const saveFinalResult = async () => { const response = await apiClient.post('/final-results', finalResult) @@ -15,7 +16,23 @@ const useSaveFinalResult = (finalResult: T) => { return useMutation({ mutationFn: saveFinalResult, - onSuccess: () => { + onMutate: async () => { + const reviewId = finalResult.reviewId + const userId = finalResult.userId + + const prevSnapShot = queryClient.getQueryData<{ + success: boolean + data: number[] + }>([`/final-results/${reviewId}/status`]) + + if (prevSnapShot?.success && prevSnapShot?.data) { + queryClient.setQueryData([`/final-results/${reviewId}/status`], { + success: prevSnapShot?.success, + data: [...new Set([...prevSnapShot.data, Number(userId)])], + }) + } + }, + onSuccess: async () => { const reviewId = finalResult.reviewId queryClient.invalidateQueries({ queryKey: [`/final-results/${reviewId}/status`], diff --git a/src/apis/hooks/useSendReview.ts b/src/apis/hooks/useSendReview.ts index ae8b270c..72936643 100644 --- a/src/apis/hooks/useSendReview.ts +++ b/src/apis/hooks/useSendReview.ts @@ -3,6 +3,8 @@ import apiClient from '@/apis/apiClient' interface Response { success: boolean + errorCode?: string + message?: string } //NOTE - 대상자별 조합된 리뷰 결과를 저장 @@ -11,6 +13,8 @@ const useSendReview = () => { return await apiClient.post(`/final-results/${reviewId}`) } - return useMutation({ mutationFn: sendReview }) + return useMutation({ + mutationFn: sendReview, + }) } export default useSendReview diff --git a/src/apis/hooks/useUpdateFinalReviewAnswer.ts b/src/apis/hooks/useUpdateFinalReviewAnswer.ts index da2e1066..5b943dfc 100644 --- a/src/apis/hooks/useUpdateFinalReviewAnswer.ts +++ b/src/apis/hooks/useUpdateFinalReviewAnswer.ts @@ -1,5 +1,4 @@ import { useMutation } from '@tanstack/react-query' -import { useToast } from '@/hooks' import apiClient from '@/apis/apiClient' interface updatedReviewAnswer { @@ -29,15 +28,8 @@ const updateFinalReviewResult = async ({ } const useUpdateFinalReviewAnswer = () => { - const { addToast } = useToast() - return useMutation({ mutationFn: updateFinalReviewResult, - onSuccess: ({ success }) => { - if (success) { - addToast({ message: '성공적으로 저장되었습니다!', type: 'success' }) - } - }, }) } diff --git a/src/pages/CreatedReviewManagePage/components/ReceiverTabReviewDatail/index.tsx b/src/pages/CreatedReviewManagePage/components/ReceiverTabReviewDatail/index.tsx index c7ffbe0a..a25d0112 100644 --- a/src/pages/CreatedReviewManagePage/components/ReceiverTabReviewDatail/index.tsx +++ b/src/pages/CreatedReviewManagePage/components/ReceiverTabReviewDatail/index.tsx @@ -1,6 +1,7 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' +import { useToast } from '@/hooks' import { - useGetReviewQuestion, + useGetReviewForCreator, useGetResponseByReceiver, useSaveFinalResult, useUpdateFinalReviewAnswer, @@ -13,57 +14,87 @@ interface ReviewDetailAccordionProps { receiverId: string receiverName: string reviewId: string + ResponserList?: number[] } const ReceiverReviewDetail = ({ receiverId, reviewId, receiverName, + ResponserList, }: ReviewDetailAccordionProps) => { //NOTE - 하나라도 응답 실패했을 떄 처리 - const { data: getReviewQuestion } = useGetReviewQuestion({ - id: reviewId, - }).data + + const { addToast } = useToast() + const hasAnswered = useRef(false) + const { data: getReviewQuestion } = useGetReviewForCreator({ + id: Number(reviewId), + }) const { data: responseByReceiver } = useGetResponseByReceiver({ receiverId, reviewId, }).data + const formatAnswers = ( + questionType: Parameters[0], + questionId: Parameters[1], + ) => { + const answer = getAnswer(questionType, questionId, responseByReceiver) + switch (questionType) { + case 'HEXASTAT': + return answer?.map((value) => { + if ('name' in value) + return { + statName: value?.name, + statScore: value.value, + } + }) + + case 'SUBJECTIVE': { + const result = answer?.map((value) => value.value)?.join('') + + return result === '' ? [] : new Array(result) + } + + default: + return answer?.map((value) => value.value) + } + } + const saveFinalReviewResult = { userId: receiverId, userName: receiverName, reviewId, reviewTitle: getReviewQuestion?.title, reviewDescription: getReviewQuestion?.description, - replies: getReviewQuestion?.questions?.map((question) => { - return { - questionId: question.id, - questionTitle: question.title, - questionType: question.type, - answers: [ - question.type !== 'HEXASTAT' - ? getAnswer(question?.type, question?.id, responseByReceiver)?.map( - (value) => value.value, - ) - : getAnswer(question?.type, question?.id, responseByReceiver)?.map( - (value) => { - if ('name' in value) - return { - statName: value?.name, - statScore: value.value, - } - }, - ), - ].flat(), - } - }), + replies: getReviewQuestion?.questions + ?.map((question) => { + const combinedAnswer = formatAnswers(question.type, Number(question.id)) + + if (combinedAnswer.length > 0) { + return { + questionId: question.id, + questionTitle: question.title, + questionType: question.type, + answers: combinedAnswer, + } + } + }) + ?.filter((result) => typeof result !== 'undefined'), } + const { mutate: saveFinalResult } = useSaveFinalResult(saveFinalReviewResult) const { mutate: updateFinalReviewAnswer } = useUpdateFinalReviewAnswer() useEffect(() => { - saveFinalResult() + if ( + !ResponserList?.includes(Number(receiverId)) && + hasAnswered.current.valueOf() === false + ) { + saveFinalResult() + hasAnswered.current = true + } }, [receiverId]) //NOTE - 전체 몇 명이 응답했는지 여부 @@ -75,20 +106,29 @@ const ReceiverReviewDetail = ({ updatedAnswer: string, questionId: string, ) => { - updateFinalReviewAnswer({ - userId: receiverId, - answer: updatedAnswer, - reviewId, - questionId, - }) + updateFinalReviewAnswer( + { + userId: receiverId, + answer: updatedAnswer, + reviewId, + questionId, + }, + { + onSuccess: ({ success }) => { + if (success) { + addToast({ message: '성공적으로 저장되었습니다!', type: 'success' }) + } + }, + }, + ) } return ( <> -
+
-
@@ -109,7 +149,7 @@ const ReceiverReviewDetail = ({ responseByReceiver, )} onClickCleanButton={(newAnswer: string) => { - handleUpdateFinalReviewAnswer(newAnswer, question.id) + handleUpdateFinalReviewAnswer(newAnswer, question.id + '') }} /> ))} diff --git a/src/pages/CreatedReviewManagePage/components/ResponserTabReviewDetail/index.tsx b/src/pages/CreatedReviewManagePage/components/ResponserTabReviewDetail/index.tsx index ed98a5a7..5faee63f 100644 --- a/src/pages/CreatedReviewManagePage/components/ResponserTabReviewDetail/index.tsx +++ b/src/pages/CreatedReviewManagePage/components/ResponserTabReviewDetail/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { - useGetReviewQuestion, + useGetReviewForCreator, useGetResponseByResponserForCreator, } from '@/apis/hooks' import { CloseDropDownIcon } from '@/assets/icons' @@ -19,9 +19,9 @@ const ReceiverReviewDetail = ({ responserId, }: ReviewDetailAccordionProps) => { //NOTE - 하나라도 응답 실패했을 떄 처리 - const { data: getReviewQuestion } = useGetReviewQuestion({ - id: reviewId, - }).data + const { data: getReviewQuestion } = useGetReviewForCreator({ + id: Number(reviewId), + }) const { data: responseByReceiver } = useGetResponseByResponserForCreator({ responserId, @@ -44,7 +44,10 @@ const ReceiverReviewDetail = ({
-
diff --git a/src/pages/CreatedReviewManagePage/components/Tabs/index.tsx b/src/pages/CreatedReviewManagePage/components/Tabs/index.tsx index 9d2b2ea6..23e04734 100644 --- a/src/pages/CreatedReviewManagePage/components/Tabs/index.tsx +++ b/src/pages/CreatedReviewManagePage/components/Tabs/index.tsx @@ -9,11 +9,11 @@ interface TabsProps { } const Tabs = ({ activeTab, setActiveTab }: TabsProps) => { - const tabBorder = `after:absolute after:bottom-0 after:h-1 after:w-full after:bg-white after-duration-[400ms] after:content-[''] after:transition-transform after:scale-x-[0.25] after:rounded-full ${REVIEW_MANAGE_TAB_MENU_STYLE[activeTab]}` + const tabBorder = `after:absolute after:bottom-0 after:h-1 after:w-full after:bg-white after-duration-[400ms] after:content-[''] after:transition-transform after:scale-x-[0.25] after:rounded-full ${REVIEW_MANAGE_TAB_MENU_STYLE[activeTab]} w-full` return (