From 90646b60946aa52bd9aea5befbc0ba5bc41a7d4f Mon Sep 17 00:00:00 2001 From: badahertz52 Date: Tue, 19 Nov 2024 16:17:39 +0900 Subject: [PATCH] =?UTF-8?q?[FE]=20fix=20:=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A8?= =?UTF-8?q?=EC=95=84=EB=B3=B4=EA=B8=B0=20=EC=BF=BC=EB=A6=AC=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20=ED=98=95=EA=B4=91=ED=8E=9C=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B0=98=EC=98=81=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 질문별 모아보기 목 데이터 주관식 답변 변경 * feat : 현재 sectionId sessionSotrage에 저장하고, 모아보기 페이지 언마운트 시 삭제하는 기능 추가 * feat : 형광펜 API 요청 성공 후, 해당 질문 쿼리 무효화하는 기능 추가 * feat : 형광펜 데이터 로컬 스토리지에 저장한 코드 삭제 * feat : ReviewCollectionPage 에서 clearEditorAnswerMapStorage 코드 삭제 --- .../HighlightEditor/hooks/useHighlight.ts | 19 ++--------------- .../hooks/useMutateHighlight/index.ts | 21 +++++++++++++++++-- frontend/src/constants/storageKey.ts | 2 +- .../src/mocks/mockData/reviewCollection.ts | 12 +++++------ .../ReviewCollectionPageContents/index.tsx | 11 ++++++++-- .../hooks/useGetGroupedReviews.ts | 3 ++- .../src/pages/ReviewCollectionPage/index.tsx | 21 ------------------- 7 files changed, 39 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/highlight/components/HighlightEditor/hooks/useHighlight.ts b/frontend/src/components/highlight/components/HighlightEditor/hooks/useHighlight.ts index 703f8acd7..2b91ac8e3 100644 --- a/frontend/src/components/highlight/components/HighlightEditor/hooks/useHighlight.ts +++ b/frontend/src/components/highlight/components/HighlightEditor/hooks/useHighlight.ts @@ -1,11 +1,6 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; -import { - EDITOR_ANSWER_CLASS_NAME, - HIGHLIGHT_EVENT_NAME, - HIGHLIGHT_SPAN_CLASS_NAME, - SESSION_STORAGE_KEY, -} from '@/constants'; +import { EDITOR_ANSWER_CLASS_NAME, HIGHLIGHT_EVENT_NAME, HIGHLIGHT_SPAN_CLASS_NAME } from '@/constants'; import { EditorAnswerMap, EditorLine, HighlightResponseData, ReviewAnswerResponseData } from '@/types'; import { getEndLineOffset, @@ -76,14 +71,6 @@ const useHighlight = ({ handleModalMessage, }: UseHighlightProps) => { const [editorAnswerMap, setEditorAnswerMap] = useState(makeInitialEditorAnswerMap(answerList)); - const storageKey = `${SESSION_STORAGE_KEY.editorAnswerMap}-${questionId}`; - - useEffect(() => { - const item = localStorage.getItem(storageKey); - if (item) { - setEditorAnswerMap(new Map(JSON.parse(item)) as EditorAnswerMap); - } - }, []); // span 클릭 시, 제공되는 형광펜 삭제 기능 타겟 const [longPressRemovalTarget, setLongPressRemovalTarget] = useState(null); @@ -92,8 +79,6 @@ const useHighlight = ({ const updateEditorAnswerMap = (newEditorAnswerMap: EditorAnswerMap) => { setEditorAnswerMap(newEditorAnswerMap); - // editorAnswerMap이 변경될 때 새로운 값을 로컬 스토리지에 저장 - localStorage.setItem(storageKey, JSON.stringify(Array.from(newEditorAnswerMap))); }; const resetHighlightMenu = () => { diff --git a/frontend/src/components/highlight/components/HighlightEditor/hooks/useMutateHighlight/index.ts b/frontend/src/components/highlight/components/HighlightEditor/hooks/useMutateHighlight/index.ts index 56681e41e..d607901cc 100644 --- a/frontend/src/components/highlight/components/HighlightEditor/hooks/useMutateHighlight/index.ts +++ b/frontend/src/components/highlight/components/HighlightEditor/hooks/useMutateHighlight/index.ts @@ -1,7 +1,7 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { postHighlight } from '@/apis/highlight'; -import { LOCAL_STORAGE_KEY } from '@/constants'; +import { LOCAL_STORAGE_KEY, REVIEW_QUERY_KEY, SESSION_STORAGE_KEY } from '@/constants'; import { EditorAnswerMap } from '@/types'; export interface UseMutateHighlightProps { @@ -17,6 +17,21 @@ const useMutateHighlight = ({ updateEditorAnswerMap, resetHighlightMenu, }: UseMutateHighlightProps) => { + const queryClient = useQueryClient(); + /** + * 형광펜 API 성공 후, 현재 질문에 대한 쿼리 캐시 무효화해서, 변경된 형광펜 데이터 불러오도록 함 + */ + const invalidateCurrentSectionQuery = () => { + const sectionId = sessionStorage.getItem(SESSION_STORAGE_KEY.currentReviewCollectionSectionId); + + if (sectionId) { + queryClient.invalidateQueries({ + predicate: (query) => + query.queryKey[0] === REVIEW_QUERY_KEY.groupedReviews && query.queryKey[1] === Number(sectionId), + }); + } + }; + const mutation = useMutation({ mutationFn: (newEditorAnswerMap: EditorAnswerMap) => postHighlight(newEditorAnswerMap, questionId), onMutate: () => { @@ -28,6 +43,8 @@ const useMutateHighlight = ({ // 토스트 모달 지우기 handleErrorModal(false); localStorage.removeItem(LOCAL_STORAGE_KEY.isHighlightError); + // 해당 질문 쿼리 캐시 무효화 + invalidateCurrentSectionQuery(); }, onError: (error) => { //토스트 모달 띄움 diff --git a/frontend/src/constants/storageKey.ts b/frontend/src/constants/storageKey.ts index f25f48312..5fb04d984 100644 --- a/frontend/src/constants/storageKey.ts +++ b/frontend/src/constants/storageKey.ts @@ -4,5 +4,5 @@ export const LOCAL_STORAGE_KEY = { }; export const SESSION_STORAGE_KEY = { - editorAnswerMap: 'editorAnswerMap-question', + currentReviewCollectionSectionId: 'currentReviewCollectionSectionId', }; diff --git a/frontend/src/mocks/mockData/reviewCollection.ts b/frontend/src/mocks/mockData/reviewCollection.ts index c2946f3bf..4b8b5df43 100644 --- a/frontend/src/mocks/mockData/reviewCollection.ts +++ b/frontend/src/mocks/mockData/reviewCollection.ts @@ -115,7 +115,7 @@ export const GROUPED_REVIEWS_MOCK_DATA: GroupedReviews[] = [ { id: 2, content: - 'http://localhost:3000/user/review-zone/5WkYQLqW1http://localhost:3000/user/review-zone/5WkYQLqW2http://localhost:3000/user/review-zone/5WkYQLqW3http://localhost:3000/user/review-zone/5WkYQLqW4http://localhost:3000/user/review-zone/5WkYQLqW5http://localhost:3000/user/review-zone/5WkYQLqW6http://localhost:3000/user/review-zone/5WkYQLqW7http://localhost:3000/user/review-zone/5WkYQLqW8http://localhost:3000/user/review-zone/5WkYQLqW9http://localhost:3000/user/review-zone/5WkYQLqW10', + ' 복잡한 문제를 체계적으로 분석하고, 창의적인 해결책을 제안하며 이를 실행하는 데 뛰어난 역량을 보여줍니다. 특히, 제한된 시간과 자원 속에서도 효과적으로 우선순위를 정하고 문제를 해결하는 모습을 통해 팀에 큰 신뢰를 주었습니다. 이러한 능력은 팀의 목표 달성과 성장에 큰 기여를 하며, 앞으로도 더 많은 성과를 낼 수 있을 것으로 기대됩니다.!!!!!', highlights: [ { lineIndex: 0, @@ -132,13 +132,13 @@ export const GROUPED_REVIEWS_MOCK_DATA: GroupedReviews[] = [ { id: 3, content: - '장의 시작부분은 짧고 직접적이며, 뒤따라 나올 복잡한 정보를 어떻게 해석해야 할 것인지 프레임을 짜주는 역할을 해야 한다. 그러면 아무리 긴 문장이라도 쉽게 읽힌다.', + '문제의 핵심 원인을 빠르게 파악하고, 이를 바탕으로 실행 가능한 솔루션을 제시하며 팀의 목표를 달성하는 데 큰 기여를 했습니다. 특히, 예상치 못한 상황에서도 냉철한 판단과 적극적인 태도로 해결책을 찾아가는 모습은 팀원들에게 좋은 자극이 되었습니다.', highlights: [], }, { id: 4, content: - '고액공제건강보험과 건강저축계좌를 만들어 노동자와 고용주가 세금공제를 받을 수 있도록 하면 결과적으로 노동자의 의료보험 부담이 커진다. 세금공제를 받을 수 있도록 하면------------------------------------------- 결과적으로 노동자의 의료보험 부담이 커진다.', + '문제를 다양한 관점에서 바라보며 가장 적합한 해결책을 찾아내는 능력이 뛰어납니다. 특히, 여러 이해관계자 간의 의견을 조율하며 모두가 만족할 수 있는 방안을 제안한 점이 돋보였습니다. 이 과정에서 보여준 적극적인 소통과 논리적인 접근법은 팀의 신뢰를 더욱 높였고, 어려운 과제를 성공적으로 마무리할 수 있는 원동력이 되었습니다.', highlights: [], }, ], @@ -181,7 +181,7 @@ export const GROUPED_REVIEWS_MOCK_DATA: GroupedReviews[] = [ { id: 2, content: - 'http://localhost:3000/user/review-zone/5WkYQLqW1http://localhost:3000/user/review-zone/5WkYQLqW2http://localhost:3000/user/review-zone/5WkYQLqW3http://localhost:3000/user/review-zone/5WkYQLqW4http://localhost:3000/user/review-zone/5WkYQLqW5http://localhost:3000/user/review-zone/5WkYQLqW6http://localhost:3000/user/review-zone/5WkYQLqW7http://localhost:3000/user/review-zone/5WkYQLqW8http://localhost:3000/user/review-zone/5WkYQLqW9http://localhost:3000/user/review-zone/5WkYQLqW10', + '효율적인 시간 관리 능력을 통해 중요한 작업을 기한 내에 완수하는 모습이 매우 인상적이었습니다. 특히, 작업의 우선순위를 명확히 구분하고 이를 기반으로 체계적으로 계획을 세워 진행하는 점이 돋보였습니다. 이러한 능력 덕분에 팀 전체의 생산성이 향상되었고, 예상치 못한 문제가 발생했을 때도 유연하게 대처하며 프로젝트를 성공적으로 이끌었습니다.', highlights: [ { lineIndex: 0, @@ -198,13 +198,13 @@ export const GROUPED_REVIEWS_MOCK_DATA: GroupedReviews[] = [ { id: 3, content: - '장의 시작부분은 짧고 직접적이며, 뒤따라 나올 복잡한 정보를 어떻게 해석해야 할 것인지 프레임을 짜주는 역할을 해야 한다. 그러면 아무리 긴 문장이라도 쉽게 읽힌다.', + '시간을 효율적으로 활용하는 뛰어난 능력을 보여주셨습니다. 작업 초기부터 명확한 계획을 수립하고 이를 끝까지 유지하는 모습이 인상적이었으며, 예상치 못한 변수에도 침착하게 대처하며 프로젝트의 일정과 품질을 모두 충족시켰습니다. 이러한 점은 팀에 큰 안정감을 주었고, 함께 일하는 사람들에게도 좋은 본보기가 되었습니다.', highlights: [], }, { id: 4, content: - '고액공제건강보험과 건강저축계좌를 만들어 노동자와 고용주가 세금공제를 받을 수 있도록 하면 결과적으로 노동자의 의료보험 부담이 커진다. 세금공제를 받을 수 있도록 하면------------------------------------------- 결과적으로 노동자의 의료보험 부담이 커진다.', + '타이트한 일정 속에서도 주어진 목표를 체계적으로 달성하며, 동시에 세부적인 디테일까지 놓치지 않는 모습을 보여주셨습니다. 특히, 작업 과정에서 우선순위를 명확히 설정하고, 불필요한 시간 낭비를 줄이는 효율적인 접근 방식은 팀의 전반적인 속도와 성과에 크게 기여했습니다. 앞으로도 이런 시간 관리 능력을 통해 더 많은 성과를 이루시리라 믿습니다.', highlights: [], }, ], diff --git a/frontend/src/pages/ReviewCollectionPage/components/ReviewCollectionPageContents/index.tsx b/frontend/src/pages/ReviewCollectionPage/components/ReviewCollectionPageContents/index.tsx index 5b8f77b54..44cd6c5e0 100644 --- a/frontend/src/pages/ReviewCollectionPage/components/ReviewCollectionPageContents/index.tsx +++ b/frontend/src/pages/ReviewCollectionPage/components/ReviewCollectionPageContents/index.tsx @@ -1,10 +1,10 @@ -import React, { useContext, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { Accordion, Dropdown, HighlightEditorContainer } from '@/components'; import { DropdownItem } from '@/components/common/Dropdown'; import ReviewEmptySection from '@/components/common/ReviewEmptySection'; import { ReviewInfoDataContext } from '@/components/layouts/ReviewDisplayLayout/ReviewInfoDataProvider'; -import { REVIEW_EMPTY } from '@/constants'; +import { REVIEW_EMPTY, SESSION_STORAGE_KEY } from '@/constants'; import { GroupedReview } from '@/types'; import { substituteString } from '@/utils'; @@ -18,6 +18,7 @@ const ReviewCollectionPageContents = () => { const { revieweeName, projectName, totalReviewCount } = useContext(ReviewInfoDataContext); const { data: reviewSectionList } = useGetSectionList(); + const dropdownSectionList = reviewSectionList.sections.map((section) => { return { text: section.name, value: section.id }; }); @@ -29,6 +30,12 @@ const ReviewCollectionPageContents = () => { review.votes?.sort((voteA, voteB) => voteB.count - voteA.count); }); + useEffect(() => { + return () => { + sessionStorage.removeItem(SESSION_STORAGE_KEY.currentReviewCollectionSectionId); + }; + }, []); + const renderContent = (review: GroupedReview) => { if (review.question.type === 'CHECKBOX') { const hasNoCheckboxAnswer = review.votes?.every((vote) => vote.count === 0); diff --git a/frontend/src/pages/ReviewCollectionPage/hooks/useGetGroupedReviews.ts b/frontend/src/pages/ReviewCollectionPage/hooks/useGetGroupedReviews.ts index be16a1427..27b2d687f 100644 --- a/frontend/src/pages/ReviewCollectionPage/hooks/useGetGroupedReviews.ts +++ b/frontend/src/pages/ReviewCollectionPage/hooks/useGetGroupedReviews.ts @@ -1,7 +1,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { getGroupedReviews } from '@/apis/review'; -import { REVIEW_QUERY_KEY } from '@/constants'; +import { REVIEW_QUERY_KEY, SESSION_STORAGE_KEY } from '@/constants'; import { GroupedReviews } from '@/types'; interface UseGetGroupedReviewsProps { @@ -11,6 +11,7 @@ interface UseGetGroupedReviewsProps { const useGetGroupedReviews = ({ sectionId }: UseGetGroupedReviewsProps) => { const fetchGroupedReviews = async () => { const result = await getGroupedReviews({ sectionId }); + sessionStorage.setItem(SESSION_STORAGE_KEY.currentReviewCollectionSectionId, sectionId.toString()); return result; }; diff --git a/frontend/src/pages/ReviewCollectionPage/index.tsx b/frontend/src/pages/ReviewCollectionPage/index.tsx index 9b838b0d0..c582d30e6 100644 --- a/frontend/src/pages/ReviewCollectionPage/index.tsx +++ b/frontend/src/pages/ReviewCollectionPage/index.tsx @@ -1,30 +1,9 @@ -import { useEffect } from 'react'; - import { AuthAndServerErrorFallback, ErrorSuspenseContainer, TopButton } from '@/components'; import ReviewDisplayLayout from '@/components/layouts/ReviewDisplayLayout'; -import { SESSION_STORAGE_KEY } from '@/constants'; import ReviewCollectionPageContents from './components/ReviewCollectionPageContents'; const ReviewCollectionPage = () => { - const clearEditorAnswerMapStorage = () => { - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - - // 키에 특정 문자열이 포함되어 있는지 확인 - if (key?.includes(SESSION_STORAGE_KEY.editorAnswerMap)) { - localStorage.removeItem(key); // 해당 키 삭제 - i--; // removeItem 후에 인덱스가 변경되므로 i를 감소시켜야 함 - } - } - }; - - useEffect(() => { - return () => { - clearEditorAnswerMapStorage(); - }; - }, []); - return (