From 564fd8af977b184d32aa77aafcb213902eaa2113 Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:14:19 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=EC=82=AC=EC=A0=84=EC=9D=98?= =?UTF-8?q?=EA=B2=AC=20=EA=B4=80=EB=A0=A8=20=ED=8F=B4=EB=8D=94=20=EB=B3=91?= =?UTF-8?q?=ED=95=A9=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PreOpinionDetail.tsx | 6 +- .../components/PreOpinionMemberList.tsx | 4 +- .../components/PreOpinionWriteHeader.tsx | 36 ++----- src/features/pre-opinion/components/index.ts | 5 + src/features/pre-opinion/hooks/index.ts | 2 + .../pre-opinion/hooks/preOpinionQueryKeys.ts | 17 ++- .../hooks/useDeleteMyPreOpinionAnswer.ts | 4 +- .../hooks/usePreOpinionAnswers.ts | 8 +- src/features/pre-opinion/index.ts | 16 +++ src/features/pre-opinion/preOpinion.api.ts | 46 +++++++- .../preOpinion.constants.ts} | 2 +- .../pre-opinion/preOpinion.endpoints.ts | 7 ++ src/features/pre-opinion/preOpinion.mock.ts | 81 +++++++++++++- src/features/pre-opinion/preOpinion.types.ts | 71 ++++++++++++ src/features/preOpinions/components/index.ts | 2 - src/features/preOpinions/hooks/index.ts | 3 - .../preOpinions/hooks/preOpinionQueryKeys.ts | 21 ---- src/features/preOpinions/index.ts | 13 --- src/features/preOpinions/preOpinions.api.ts | 59 ---------- .../preOpinions/preOpinions.endpoints.ts | 11 -- src/features/preOpinions/preOpinions.mock.ts | 102 ------------------ src/features/preOpinions/preOpinions.types.ts | 73 ------------- .../PersonalRetrospectiveContent.tsx | 88 +++++++++++++++ src/features/retrospectives/hooks/index.ts | 2 + .../hooks/personalRetrospectiveQueryKeys.ts | 14 +++ .../hooks/usePersonalRetrospective.ts | 44 ++++++++ .../personalRetrospective.api.ts | 41 +++++++ .../personalRetrospective.endpoints.ts | 7 ++ .../personalRetrospective.mock.ts | 47 ++++++++ .../personalRetrospective.types.ts | 70 ++++++++++++ src/pages/PreOpinions/PreOpinionListPage.tsx | 2 +- src/pages/PreOpinions/PreOpinionWritePage.tsx | 6 +- .../PersonalRetrospectivePage.tsx | 45 ++++++++ 33 files changed, 623 insertions(+), 332 deletions(-) rename src/features/{preOpinions => pre-opinion}/components/PreOpinionDetail.tsx (96%) rename src/features/{preOpinions => pre-opinion}/components/PreOpinionMemberList.tsx (91%) create mode 100644 src/features/pre-opinion/components/index.ts rename src/features/{preOpinions => pre-opinion}/hooks/useDeleteMyPreOpinionAnswer.ts (92%) rename src/features/{preOpinions => pre-opinion}/hooks/usePreOpinionAnswers.ts (82%) create mode 100644 src/features/pre-opinion/index.ts rename src/features/{preOpinions/preOpinions.constants.ts => pre-opinion/preOpinion.constants.ts} (80%) delete mode 100644 src/features/preOpinions/components/index.ts delete mode 100644 src/features/preOpinions/hooks/index.ts delete mode 100644 src/features/preOpinions/hooks/preOpinionQueryKeys.ts delete mode 100644 src/features/preOpinions/index.ts delete mode 100644 src/features/preOpinions/preOpinions.api.ts delete mode 100644 src/features/preOpinions/preOpinions.endpoints.ts delete mode 100644 src/features/preOpinions/preOpinions.mock.ts delete mode 100644 src/features/preOpinions/preOpinions.types.ts create mode 100644 src/features/retrospectives/components/PersonalRetrospectiveContent.tsx create mode 100644 src/features/retrospectives/hooks/index.ts create mode 100644 src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts create mode 100644 src/features/retrospectives/hooks/usePersonalRetrospective.ts create mode 100644 src/features/retrospectives/personalRetrospective.api.ts create mode 100644 src/features/retrospectives/personalRetrospective.endpoints.ts create mode 100644 src/features/retrospectives/personalRetrospective.mock.ts create mode 100644 src/features/retrospectives/personalRetrospective.types.ts create mode 100644 src/pages/Retrospectives/PersonalRetrospectivePage.tsx diff --git a/src/features/preOpinions/components/PreOpinionDetail.tsx b/src/features/pre-opinion/components/PreOpinionDetail.tsx similarity index 96% rename from src/features/preOpinions/components/PreOpinionDetail.tsx rename to src/features/pre-opinion/components/PreOpinionDetail.tsx index 2339bab..613434e 100644 --- a/src/features/preOpinions/components/PreOpinionDetail.tsx +++ b/src/features/pre-opinion/components/PreOpinionDetail.tsx @@ -5,12 +5,12 @@ import { Chip } from '@/shared/ui/Chip' import { useGlobalModalStore } from '@/store' import { useDeleteMyPreOpinionAnswer } from '../hooks/useDeleteMyPreOpinionAnswer' -import { ROLE_TO_AVATAR_VARIANT } from '../preOpinions.constants' -import type { PreOpinionMember, PreOpinionTopic } from '../preOpinions.types' +import { ROLE_TO_AVATAR_VARIANT } from '../preOpinion.constants' +import type { PreOpinionAnswerTopic, PreOpinionMember } from '../preOpinion.types' type PreOpinionDetailProps = { member: PreOpinionMember - topics: PreOpinionTopic[] + topics: PreOpinionAnswerTopic[] gatheringId: number meetingId: number } diff --git a/src/features/preOpinions/components/PreOpinionMemberList.tsx b/src/features/pre-opinion/components/PreOpinionMemberList.tsx similarity index 91% rename from src/features/preOpinions/components/PreOpinionMemberList.tsx rename to src/features/pre-opinion/components/PreOpinionMemberList.tsx index 26e843d..a95990a 100644 --- a/src/features/preOpinions/components/PreOpinionMemberList.tsx +++ b/src/features/pre-opinion/components/PreOpinionMemberList.tsx @@ -1,7 +1,7 @@ import { UserChip } from '@/shared/ui/UserChip' -import { ROLE_TO_AVATAR_VARIANT } from '../preOpinions.constants' -import type { PreOpinionMember } from '../preOpinions.types' +import { ROLE_TO_AVATAR_VARIANT } from '../preOpinion.constants' +import type { PreOpinionMember } from '../preOpinion.types' type PreOpinionMemberListProps = { members: PreOpinionMember[] diff --git a/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx b/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx index 9975621..2a83159 100644 --- a/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx +++ b/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx @@ -1,6 +1,5 @@ -import { useEffect, useRef, useState } from 'react' - import type { PreOpinionBook } from '@/features/pre-opinion/preOpinion.types' +import { useScrollShadow } from '@/shared/hooks' import { cn } from '@/shared/lib/utils' import { Button } from '@/shared/ui' @@ -42,33 +41,15 @@ const PreOpinionWriteHeader = ({ isSubmitting, isReviewValid = false, }: PreOpinionWriteHeaderProps) => { - const sentinelRef = useRef(null) - const [isStuck, setIsStuck] = useState(false) - - useEffect(() => { - const sentinel = sentinelRef.current - if (!sentinel) return - - const observer = new IntersectionObserver( - ([entry]) => { - setIsStuck(!entry.isIntersecting) - }, - { threshold: 0 } - ) - - observer.observe(sentinel) - return () => observer.disconnect() - }, []) + const isScrolled = useScrollShadow() return ( - <> -
-
+
@@ -97,7 +78,6 @@ const PreOpinionWriteHeader = ({
- ) } diff --git a/src/features/pre-opinion/components/index.ts b/src/features/pre-opinion/components/index.ts new file mode 100644 index 0000000..6d58f29 --- /dev/null +++ b/src/features/pre-opinion/components/index.ts @@ -0,0 +1,5 @@ +export * from './BookReviewSection' +export * from './PreOpinionDetail' +export * from './PreOpinionMemberList' +export * from './PreOpinionWriteHeader' +export * from './TopicItem' diff --git a/src/features/pre-opinion/hooks/index.ts b/src/features/pre-opinion/hooks/index.ts index 46f7dec..6aaa404 100644 --- a/src/features/pre-opinion/hooks/index.ts +++ b/src/features/pre-opinion/hooks/index.ts @@ -1,4 +1,6 @@ export * from './preOpinionQueryKeys' +export * from './useDeleteMyPreOpinionAnswer' export * from './usePreOpinion' +export * from './usePreOpinionAnswers' export * from './useSavePreOpinion' export * from './useSubmitPreOpinion' diff --git a/src/features/pre-opinion/hooks/preOpinionQueryKeys.ts b/src/features/pre-opinion/hooks/preOpinionQueryKeys.ts index 5d5057f..f52a3d6 100644 --- a/src/features/pre-opinion/hooks/preOpinionQueryKeys.ts +++ b/src/features/pre-opinion/hooks/preOpinionQueryKeys.ts @@ -3,11 +3,26 @@ * @description 사전 의견 관련 Query Key Factory */ -import type { GetPreOpinionParams } from '@/features/pre-opinion/preOpinion.types' +import type { + GetPreOpinionAnswersParams, + GetPreOpinionParams, +} from '@/features/pre-opinion/preOpinion.types' +/** + * Query Key Factory + * + * @description + * 사전 의견 관련 Query Key를 일관되게 관리하기 위한 팩토리 함수 + */ export const preOpinionQueryKeys = { all: ['preOpinions'] as const, + // 내 사전 의견 작성/조회 관련 details: () => [...preOpinionQueryKeys.all, 'detail'] as const, detail: (params: GetPreOpinionParams) => [...preOpinionQueryKeys.details(), params] as const, + + // 사전 의견 목록 관련 + answers: () => [...preOpinionQueryKeys.all, 'answers'] as const, + answerList: (params: GetPreOpinionAnswersParams) => + [...preOpinionQueryKeys.answers(), params] as const, } diff --git a/src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts b/src/features/pre-opinion/hooks/useDeleteMyPreOpinionAnswer.ts similarity index 92% rename from src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts rename to src/features/pre-opinion/hooks/useDeleteMyPreOpinionAnswer.ts index 555376d..bc89faa 100644 --- a/src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts +++ b/src/features/pre-opinion/hooks/useDeleteMyPreOpinionAnswer.ts @@ -7,8 +7,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import type { ApiError } from '@/api' -import { deleteMyPreOpinionAnswer } from '../preOpinions.api' -import type { DeleteMyPreOpinionAnswerParams } from '../preOpinions.types' +import { deleteMyPreOpinionAnswer } from '../preOpinion.api' +import type { DeleteMyPreOpinionAnswerParams } from '../preOpinion.types' import { preOpinionQueryKeys } from './preOpinionQueryKeys' /** diff --git a/src/features/preOpinions/hooks/usePreOpinionAnswers.ts b/src/features/pre-opinion/hooks/usePreOpinionAnswers.ts similarity index 82% rename from src/features/preOpinions/hooks/usePreOpinionAnswers.ts rename to src/features/pre-opinion/hooks/usePreOpinionAnswers.ts index 2c0860b..28a5542 100644 --- a/src/features/preOpinions/hooks/usePreOpinionAnswers.ts +++ b/src/features/pre-opinion/hooks/usePreOpinionAnswers.ts @@ -7,8 +7,8 @@ import { useQuery } from '@tanstack/react-query' import type { ApiError } from '@/api' -import { getPreOpinionAnswers } from '../preOpinions.api' -import type { GetPreOpinionAnswersParams, PreOpinionAnswersData } from '../preOpinions.types' +import { getPreOpinionAnswers } from '../preOpinion.api' +import type { GetPreOpinionAnswersParams, PreOpinionAnswersData } from '../preOpinion.types' import { preOpinionQueryKeys } from './preOpinionQueryKeys' /** @@ -18,9 +18,7 @@ import { preOpinionQueryKeys } from './preOpinionQueryKeys' * TanStack Query를 사용하여 약속의 사전 의견 목록을 조회합니다. * 멤버별 책 평가, 주제별 의견 등을 포함합니다. * - * @param params - 조회 파라미터 - * @param params.gatheringId - 모임 식별자 - * @param params.meetingId - 약속 식별자 + * @param params - 모임 ID와 약속 ID * * @returns TanStack Query 결과 객체 */ diff --git a/src/features/pre-opinion/index.ts b/src/features/pre-opinion/index.ts new file mode 100644 index 0000000..1486a3d --- /dev/null +++ b/src/features/pre-opinion/index.ts @@ -0,0 +1,16 @@ +// Components +export * from './components' + +// Hooks +export * from './hooks' + +// API +export * from './preOpinion.api' +export * from './preOpinion.endpoints' +export * from './preOpinion.mock' + +// Constants +export * from './preOpinion.constants' + +// Types +export * from './preOpinion.types' diff --git a/src/features/pre-opinion/preOpinion.api.ts b/src/features/pre-opinion/preOpinion.api.ts index f141310..d673216 100644 --- a/src/features/pre-opinion/preOpinion.api.ts +++ b/src/features/pre-opinion/preOpinion.api.ts @@ -5,10 +5,16 @@ import { api } from '@/api/client' import { PRE_OPINION_ENDPOINTS } from '@/features/pre-opinion/preOpinion.endpoints' -import { getMockPreOpinionDetail } from '@/features/pre-opinion/preOpinion.mock' +import { + getMockPreOpinionAnswers, + getMockPreOpinionDetail, +} from '@/features/pre-opinion/preOpinion.mock' import type { + DeleteMyPreOpinionAnswerParams, + GetPreOpinionAnswersParams, GetPreOpinionParams, GetPreOpinionResponse, + PreOpinionAnswersData, SavePreOpinionBody, SavePreOpinionParams, SubmitPreOpinionBody, @@ -76,3 +82,41 @@ export const submitPreOpinion = async ( ): Promise => { return api.patch(PRE_OPINION_ENDPOINTS.SUBMIT(gatheringId, meetingId), body) } + +/** + * 사전 의견 목록 조회 + * + * @description + * 약속의 사전 의견 목록(멤버별 책 평가 + 주제 의견)을 조회합니다. + * + * @param params - 모임 ID와 약속 ID + * + * @returns 사전 의견 목록 데이터 (topics + members) + */ +export const getPreOpinionAnswers = async ( + params: GetPreOpinionAnswersParams +): Promise => { + const { gatheringId, meetingId } = params + + if (USE_MOCK) { + await new Promise((resolve) => setTimeout(resolve, 500)) + return getMockPreOpinionAnswers() + } + + return api.get(PRE_OPINION_ENDPOINTS.ANSWERS(gatheringId, meetingId)) +} + +/** + * 내 사전 의견 삭제 + * + * @description + * 현재 로그인한 사용자의 사전 의견을 삭제합니다. + * + * @param params - 모임 ID와 약속 ID + */ +export const deleteMyPreOpinionAnswer = async ( + params: DeleteMyPreOpinionAnswerParams +): Promise => { + const { gatheringId, meetingId } = params + return api.delete(PRE_OPINION_ENDPOINTS.DELETE_MY_ANSWER(gatheringId, meetingId)) +} diff --git a/src/features/preOpinions/preOpinions.constants.ts b/src/features/pre-opinion/preOpinion.constants.ts similarity index 80% rename from src/features/preOpinions/preOpinions.constants.ts rename to src/features/pre-opinion/preOpinion.constants.ts index 1671b60..f7f89a6 100644 --- a/src/features/preOpinions/preOpinions.constants.ts +++ b/src/features/pre-opinion/preOpinion.constants.ts @@ -1,4 +1,4 @@ -import type { MemberRole } from './preOpinions.types' +import type { MemberRole } from './preOpinion.types' /** API MemberRole → Avatar variant 매핑 */ export const ROLE_TO_AVATAR_VARIANT: Record = { diff --git a/src/features/pre-opinion/preOpinion.endpoints.ts b/src/features/pre-opinion/preOpinion.endpoints.ts index 7f6b5af..3e41e02 100644 --- a/src/features/pre-opinion/preOpinion.endpoints.ts +++ b/src/features/pre-opinion/preOpinion.endpoints.ts @@ -1,6 +1,13 @@ import { API_PATHS } from '@/api' export const PRE_OPINION_ENDPOINTS = { + // 사전 의견 목록 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/answers) + ANSWERS: (gatheringId: number, meetingId: number) => + `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/answers`, + + // 내 사전 의견 삭제 (DELETE /api/gatherings/{gatheringId}/meetings/{meetingId}/topics/answers/me) + DELETE_MY_ANSWER: (gatheringId: number, meetingId: number) => + `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/topics/answers/me`, // 사전 의견 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/answers/me) DETAIL: (gatheringId: number, meetingId: number) => `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/answers/me`, diff --git a/src/features/pre-opinion/preOpinion.mock.ts b/src/features/pre-opinion/preOpinion.mock.ts index 7be34e6..784ceda 100644 --- a/src/features/pre-opinion/preOpinion.mock.ts +++ b/src/features/pre-opinion/preOpinion.mock.ts @@ -3,7 +3,86 @@ * @description 사전 의견 API 목데이터 */ -import type { GetPreOpinionResponse } from '@/features/pre-opinion/preOpinion.types' +import type { GetPreOpinionResponse, PreOpinionAnswersData } from '@/features/pre-opinion/preOpinion.types' + +/** + * 사전 의견 목록 목데이터 + */ +const mockPreOpinionAnswers: PreOpinionAnswersData = { + topics: [ + { + topicId: 1, + title: '책의 주요 메시지', + description: '이 책에서 전달하고자 하는 핵심 메시지는 무엇인가요?', + topicType: 'DISCUSSION', + topicTypeLabel: '토론형', + confirmOrder: 1, + }, + { + topicId: 2, + title: '가장 인상 깊었던 장면', + description: '책을 읽으며 가장 기억에 남았던 장면은 무엇인가요?', + topicType: 'DISCUSSION', + topicTypeLabel: '토론형', + confirmOrder: 2, + }, + ], + members: [ + { + memberInfo: { + userId: 1, + nickname: '독서왕', + profileImage: 'https://picsum.photos/seed/user1/100/100', + role: 'GATHERING_LEADER', + }, + isSubmitted: true, + bookReview: { + rating: 4.5, + keywordInfo: [ + { id: 3, name: '성장', type: 'BOOK' }, + { id: 7, name: '여운이 남는', type: 'IMPRESSION' }, + ], + }, + topicOpinions: [ + { topicId: 1, content: '이 책의 핵심 메시지는 자기 성찰이라고 생각합니다.' }, + { topicId: 2, content: '주인공이 선택의 기로에 서는 장면이 가장 인상 깊었습니다.' }, + ], + }, + { + memberInfo: { + userId: 10, + nickname: '밤독서', + profileImage: 'https://picsum.photos/seed/user3/100/100', + role: 'MEETING_LEADER', + }, + isSubmitted: true, + bookReview: { + rating: 3.0, + keywordInfo: [ + { id: 5, name: '관계', type: 'BOOK' }, + { id: 7, name: '여운이 남는', type: 'IMPRESSION' }, + ], + }, + topicOpinions: [ + { topicId: 1, content: null }, + { topicId: 2, content: '잔잔하지만 오래 남는 장면들이 많았습니다.' }, + ], + }, + { + memberInfo: { + userId: 2, + nickname: '페이지러버', + profileImage: 'https://picsum.photos/seed/user2/100/100', + role: 'MEMBER', + }, + isSubmitted: false, + bookReview: null, + topicOpinions: [], + }, + ], +} + +export const getMockPreOpinionAnswers = (): PreOpinionAnswersData => mockPreOpinionAnswers /** * 사전 의견 조회 목데이터 diff --git a/src/features/pre-opinion/preOpinion.types.ts b/src/features/pre-opinion/preOpinion.types.ts index 598737c..b12d621 100644 --- a/src/features/pre-opinion/preOpinion.types.ts +++ b/src/features/pre-opinion/preOpinion.types.ts @@ -4,9 +4,80 @@ */ import type { ReviewKeyword } from '@/features/book/book.types' +import type { KeywordType } from '@/features/keywords/keywords.types' import type { TopicType } from '../topics' +// ─── 사전 의견 목록 조회 관련 타입 ────────────────────────────────────────────── + +/** 멤버 역할 타입 */ +export type MemberRole = 'GATHERING_LEADER' | 'MEETING_LEADER' | 'MEMBER' + +/** 사전 의견 키워드 */ +export type PreOpinionKeyword = { + id: number + name: string + type: KeywordType +} + +/** 책 평가 정보 */ +export type BookReviewSummary = { + rating: number + keywordInfo: PreOpinionKeyword[] +} + +/** 멤버 정보 */ +export type PreOpinionMemberInfo = { + userId: number + nickname: string + profileImage: string + role: MemberRole +} + +/** 주제별 의견 */ +export type TopicOpinion = { + topicId: number + content: string | null +} + +/** 확정된 주제 항목 (목록 조회용) */ +export type PreOpinionAnswerTopic = { + topicId: number + title: string + description: string + topicType: TopicType + topicTypeLabel: string + confirmOrder: number +} + +/** 멤버별 사전 의견 */ +export type PreOpinionMember = { + memberInfo: PreOpinionMemberInfo + isSubmitted: boolean + bookReview: BookReviewSummary | null + topicOpinions: TopicOpinion[] +} + +/** 사전 의견 목록 조회 응답 데이터 */ +export type PreOpinionAnswersData = { + topics: PreOpinionAnswerTopic[] + members: PreOpinionMember[] +} + +/** 사전 의견 목록 조회 파라미터 */ +export type GetPreOpinionAnswersParams = { + gatheringId: number + meetingId: number +} + +/** 내 사전 의견 삭제 파라미터 */ +export type DeleteMyPreOpinionAnswerParams = { + gatheringId: number + meetingId: number +} + +// ─── 사전 의견 작성/조회 관련 타입 ────────────────────────────────────────────── + /** * 사전 의견 주제 항목 */ diff --git a/src/features/preOpinions/components/index.ts b/src/features/preOpinions/components/index.ts deleted file mode 100644 index 317611c..0000000 --- a/src/features/preOpinions/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './PreOpinionDetail' -export * from './PreOpinionMemberList' diff --git a/src/features/preOpinions/hooks/index.ts b/src/features/preOpinions/hooks/index.ts deleted file mode 100644 index 305c6e7..0000000 --- a/src/features/preOpinions/hooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './preOpinionQueryKeys' -export * from './useDeleteMyPreOpinionAnswer' -export * from './usePreOpinionAnswers' diff --git a/src/features/preOpinions/hooks/preOpinionQueryKeys.ts b/src/features/preOpinions/hooks/preOpinionQueryKeys.ts deleted file mode 100644 index d4d25d6..0000000 --- a/src/features/preOpinions/hooks/preOpinionQueryKeys.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file preOpinionQueryKeys.ts - * @description 사전 의견 관련 Query Key Factory - */ - -import type { GetPreOpinionAnswersParams } from '../preOpinions.types' - -/** - * Query Key Factory - * - * @description - * 사전 의견 관련 Query Key를 일관되게 관리하기 위한 팩토리 함수 - */ -export const preOpinionQueryKeys = { - all: ['preOpinions'] as const, - - // 사전 의견 목록 관련 - answers: () => [...preOpinionQueryKeys.all, 'answers'] as const, - answerList: (params: GetPreOpinionAnswersParams) => - [...preOpinionQueryKeys.answers(), params] as const, -} diff --git a/src/features/preOpinions/index.ts b/src/features/preOpinions/index.ts deleted file mode 100644 index e804ad9..0000000 --- a/src/features/preOpinions/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Components -export * from './components' - -// Hooks -export * from './hooks' - -// API -export * from './preOpinions.api' -export * from './preOpinions.endpoints' -export * from './preOpinions.mock' - -// Types -export * from './preOpinions.types' diff --git a/src/features/preOpinions/preOpinions.api.ts b/src/features/preOpinions/preOpinions.api.ts deleted file mode 100644 index 158a104..0000000 --- a/src/features/preOpinions/preOpinions.api.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @file preOpinions.api.ts - * @description 사전 의견 API 요청 함수 - */ - -import { api } from '@/api/client' - -import { PRE_OPINIONS_ENDPOINTS } from './preOpinions.endpoints' -import { getMockPreOpinionAnswers } from './preOpinions.mock' -import type { - DeleteMyPreOpinionAnswerParams, - GetPreOpinionAnswersParams, - PreOpinionAnswersData, -} from './preOpinions.types' - -/** 목데이터 사용 여부 플래그 */ -const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true' - -/** - * 사전 의견 목록 조회 - * - * @description - * 약속의 사전 의견 목록(멤버별 책 평가 + 주제 의견)을 조회합니다. - * - * @param params - 조회 파라미터 - * @param params.gatheringId - 모임 식별자 - * @param params.meetingId - 약속 식별자 - * - * @returns 사전 의견 목록 데이터 (topics + members) - */ -export const getPreOpinionAnswers = async ( - params: GetPreOpinionAnswersParams -): Promise => { - const { gatheringId, meetingId } = params - - if (USE_MOCK) { - await new Promise((resolve) => setTimeout(resolve, 500)) - return getMockPreOpinionAnswers() - } - - return api.get(PRE_OPINIONS_ENDPOINTS.ANSWERS(gatheringId, meetingId)) -} - -/** - * 내 사전 의견 삭제 - * - * @description - * 현재 로그인한 사용자의 사전 의견을 삭제합니다. - * - * @param params - 삭제 파라미터 - * @param params.gatheringId - 모임 식별자 - * @param params.meetingId - 약속 식별자 - */ -export const deleteMyPreOpinionAnswer = async ( - params: DeleteMyPreOpinionAnswerParams -): Promise => { - const { gatheringId, meetingId } = params - return api.delete(PRE_OPINIONS_ENDPOINTS.DELETE_MY_ANSWER(gatheringId, meetingId)) -} diff --git a/src/features/preOpinions/preOpinions.endpoints.ts b/src/features/preOpinions/preOpinions.endpoints.ts deleted file mode 100644 index d4a748b..0000000 --- a/src/features/preOpinions/preOpinions.endpoints.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { API_PATHS } from '@/api' - -export const PRE_OPINIONS_ENDPOINTS = { - // 사전 의견 목록 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/answers) - ANSWERS: (gatheringId: number, meetingId: number) => - `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/answers`, - - // 내 사전 의견 삭제 (DELETE /api/gatherings/{gatheringId}/meetings/{meetingId}/topics/answers/me) - DELETE_MY_ANSWER: (gatheringId: number, meetingId: number) => - `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/topics/answers/me`, -} as const diff --git a/src/features/preOpinions/preOpinions.mock.ts b/src/features/preOpinions/preOpinions.mock.ts deleted file mode 100644 index 3b7eef8..0000000 --- a/src/features/preOpinions/preOpinions.mock.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @file preOpinions.mock.ts - * @description 사전 의견 API 목데이터 - */ - -import type { PreOpinionAnswersData } from './preOpinions.types' - -/** - * 사전 의견 목록 목데이터 - */ -const mockPreOpinionAnswers: PreOpinionAnswersData = { - topics: [ - { - topicId: 1, - title: '책의 주요 메시지', - description: '이 책에서 전달하고자 하는 핵심 메시지는 무엇인가요?', - topicType: 'DISCUSSION', - topicTypeLabel: '토론형', - confirmOrder: 1, - }, - { - topicId: 2, - title: '가장 인상 깊었던 장면', - description: '책을 읽으며 가장 기억에 남았던 장면은 무엇인가요?', - topicType: 'DISCUSSION', - topicTypeLabel: '토론형', - confirmOrder: 2, - }, - ], - members: [ - { - memberInfo: { - userId: 1, - nickname: '독서왕', - profileImage: 'https://picsum.photos/seed/user1/100/100', - role: 'GATHERING_LEADER', - }, - isSubmitted: true, - bookReview: { - rating: 4.5, - keywordInfo: [ - { id: 3, name: '성장', type: 'BOOK' }, - { id: 7, name: '여운이 남는', type: 'IMPRESSION' }, - ], - }, - topicOpinions: [ - { - topicId: 1, - content: '이 책의 핵심 메시지는 자기 성찰이라고 생각합니다.', - }, - { - topicId: 2, - content: '주인공이 선택의 기로에 서는 장면이 가장 인상 깊었습니다.', - }, - ], - }, - { - memberInfo: { - userId: 10, - nickname: '밤독서', - profileImage: 'https://picsum.photos/seed/user3/100/100', - role: 'MEETING_LEADER', - }, - isSubmitted: true, - bookReview: { - rating: 3.0, - keywordInfo: [ - { id: 5, name: '관계', type: 'BOOK' }, - { id: 7, name: '여운이 남는', type: 'IMPRESSION' }, - ], - }, - topicOpinions: [ - { - topicId: 1, - content: null, - }, - { - topicId: 2, - content: '잔잔하지만 오래 남는 장면들이 많았습니다.', - }, - ], - }, - { - memberInfo: { - userId: 2, - nickname: '페이지러버', - profileImage: 'https://picsum.photos/seed/user2/100/100', - role: 'MEMBER', - }, - isSubmitted: false, - bookReview: null, - topicOpinions: [], - }, - ], -} - -/** - * 사전 의견 목록 목데이터 반환 함수 - */ -export const getMockPreOpinionAnswers = (): PreOpinionAnswersData => { - return mockPreOpinionAnswers -} diff --git a/src/features/preOpinions/preOpinions.types.ts b/src/features/preOpinions/preOpinions.types.ts deleted file mode 100644 index 8e5fbd2..0000000 --- a/src/features/preOpinions/preOpinions.types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @file preOpinions.types.ts - * @description 사전 의견 API 관련 타입 정의 - */ - -import type { KeywordType } from '@/features/keywords/keywords.types' -import type { TopicType } from '@/features/topics/topics.types' - -/** 멤버 역할 타입 */ -export type MemberRole = 'GATHERING_LEADER' | 'MEETING_LEADER' | 'MEMBER' - -/** 사전 의견 키워드 */ -export type PreOpinionKeyword = { - id: number - name: string - type: KeywordType -} - -/** 책 평가 정보 */ -export type BookReviewSummary = { - rating: number - keywordInfo: PreOpinionKeyword[] -} - -/** 멤버 정보 */ -export type PreOpinionMemberInfo = { - userId: number - nickname: string - profileImage: string - role: MemberRole -} - -/** 주제별 의견 */ -export type TopicOpinion = { - topicId: number - content: string | null -} - -/** 확정된 주제 항목 */ -export type PreOpinionTopic = { - topicId: number - title: string - description: string - topicType: TopicType - topicTypeLabel: string - confirmOrder: number -} - -/** 멤버별 사전 의견 */ -export type PreOpinionMember = { - memberInfo: PreOpinionMemberInfo - isSubmitted: boolean - bookReview: BookReviewSummary | null - topicOpinions: TopicOpinion[] -} - -/** 사전 의견 목록 조회 응답 데이터 */ -export type PreOpinionAnswersData = { - topics: PreOpinionTopic[] - members: PreOpinionMember[] -} - -/** 사전 의견 목록 조회 파라미터 */ -export type GetPreOpinionAnswersParams = { - gatheringId: number - meetingId: number -} - -/** 내 사전 의견 삭제 파라미터 */ -export type DeleteMyPreOpinionAnswerParams = { - gatheringId: number - meetingId: number -} diff --git a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx new file mode 100644 index 0000000..f6c89fe --- /dev/null +++ b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx @@ -0,0 +1,88 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, + Card, +} from '@/shared/ui' + +import type { GetPersonalRetrospectiveResponse } from '../personalRetrospective.types' + +export interface PersonalRetrospectiveContentProps { + data: GetPersonalRetrospectiveResponse +} + +/** + * 개인 회고 콘텐츠 + * + * @description + * 개인 회고 페이지의 전체 콘텐츠를 렌더링합니다. + * 책 정보, 참여 멤버, 내 사전 의견, 토론 주제 섹션으로 구성됩니다. + * + * @example + * ```tsx + * + * ``` + */ +export default function PersonalRetrospectiveContent({ + data, +}: PersonalRetrospectiveContentProps) { + const { gatheringName, bookTitle, bookAuthor, preOpinions, topics, meetingMembers } = data + + return ( +
+ {/* 책 정보 */} + +

{gatheringName}

+

{bookTitle}

+

{bookAuthor}

+
+ + {/* 함께한 멤버 */} +
+

함께한 멤버

+
+ {meetingMembers.map((member) => ( +
+ + + {member.nickname.slice(0, 1)} + + {member.nickname} +
+ ))} +
+
+ + {/* 토론 주제 */} +
+

토론 주제

+
+ {topics.map((topic) => ( + + + {topic.confirmOrder} + + {topic.topicName} + + ))} +
+
+ + {/* 내 사전 의견 */} +
+

내 사전 의견

+
+ {preOpinions.map((opinion) => ( + +

{opinion.topicName}

+

{opinion.content}

+
+ ))} + {preOpinions.length === 0 && ( +

작성한 사전 의견이 없습니다.

+ )} +
+
+
+ ) +} diff --git a/src/features/retrospectives/hooks/index.ts b/src/features/retrospectives/hooks/index.ts new file mode 100644 index 0000000..bfcdcf6 --- /dev/null +++ b/src/features/retrospectives/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './personalRetrospectiveQueryKeys' +export * from './usePersonalRetrospective' diff --git a/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts b/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts new file mode 100644 index 0000000..36f8987 --- /dev/null +++ b/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts @@ -0,0 +1,14 @@ +/** + * @file personalRetrospectiveQueryKeys.ts + * @description 개인 회고 관련 Query Key Factory + */ + +import type { GetPersonalRetrospectiveParams } from '../personalRetrospective.types' + +export const personalRetrospectiveQueryKeys = { + all: ['personalRetrospectives'] as const, + + details: () => [...personalRetrospectiveQueryKeys.all, 'detail'] as const, + detail: (params: GetPersonalRetrospectiveParams) => + [...personalRetrospectiveQueryKeys.details(), params] as const, +} diff --git a/src/features/retrospectives/hooks/usePersonalRetrospective.ts b/src/features/retrospectives/hooks/usePersonalRetrospective.ts new file mode 100644 index 0000000..9881945 --- /dev/null +++ b/src/features/retrospectives/hooks/usePersonalRetrospective.ts @@ -0,0 +1,44 @@ +/** + * @file usePersonalRetrospective.ts + * @description 개인 회고 조회 훅 + */ + +import { useQuery } from '@tanstack/react-query' + +import type { ApiError } from '@/api' + +import { getPersonalRetrospective } from '../personalRetrospective.api' +import type { + GetPersonalRetrospectiveParams, + GetPersonalRetrospectiveResponse, +} from '../personalRetrospective.types' +import { personalRetrospectiveQueryKeys } from './personalRetrospectiveQueryKeys' + +/** + * 개인 회고 조회 훅 + * + * @description + * TanStack Query를 사용하여 개인 회고 정보를 조회합니다. + * 모임명, 책 정보, 내 사전 의견, 확정된 토픽, 참여 멤버 목록을 포함합니다. + * + * @param params - 모임 ID와 약속 ID + * + * @returns TanStack Query 결과 객체 + * + * @example + * ```tsx + * const { data, isLoading } = usePersonalRetrospective({ gatheringId: 1, meetingId: 2 }) + * ``` + */ +export const usePersonalRetrospective = (params: GetPersonalRetrospectiveParams) => { + const { gatheringId, meetingId } = params + const isValidParams = + !Number.isNaN(gatheringId) && gatheringId > 0 && !Number.isNaN(meetingId) && meetingId > 0 + + return useQuery({ + queryKey: personalRetrospectiveQueryKeys.detail(params), + queryFn: () => getPersonalRetrospective(params), + enabled: isValidParams, + gcTime: 10 * 60 * 1000, + }) +} diff --git a/src/features/retrospectives/personalRetrospective.api.ts b/src/features/retrospectives/personalRetrospective.api.ts new file mode 100644 index 0000000..52c82e7 --- /dev/null +++ b/src/features/retrospectives/personalRetrospective.api.ts @@ -0,0 +1,41 @@ +/** + * @file personalRetrospective.api.ts + * @description 개인 회고 API 요청 함수 + */ + +import { api } from '@/api/client' + +import { PERSONAL_RETROSPECTIVE_ENDPOINTS } from './personalRetrospective.endpoints' +import { getMockPersonalRetrospectiveDetail } from './personalRetrospective.mock' +import type { + GetPersonalRetrospectiveParams, + GetPersonalRetrospectiveResponse, +} from './personalRetrospective.types' + +/** 목데이터 사용 여부 플래그 */ +const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true' + +/** + * 개인 회고 조회 + * + * @description + * 약속에 대한 개인 회고 정보를 조회합니다. + * 모임명, 책 정보, 내 사전 의견, 확정된 토픽, 참여 멤버 목록을 포함합니다. + * + * @param params - 모임 ID와 약속 ID + * + * @returns 개인 회고 응답 데이터 + */ +export const getPersonalRetrospective = async ({ + gatheringId, + meetingId, +}: GetPersonalRetrospectiveParams): Promise => { + if (USE_MOCK) { + await new Promise((resolve) => setTimeout(resolve, 500)) + return getMockPersonalRetrospectiveDetail() + } + + return api.get( + PERSONAL_RETROSPECTIVE_ENDPOINTS.DETAIL(gatheringId, meetingId) + ) +} diff --git a/src/features/retrospectives/personalRetrospective.endpoints.ts b/src/features/retrospectives/personalRetrospective.endpoints.ts new file mode 100644 index 0000000..3501e3a --- /dev/null +++ b/src/features/retrospectives/personalRetrospective.endpoints.ts @@ -0,0 +1,7 @@ +import { API_PATHS } from '@/api' + +export const PERSONAL_RETROSPECTIVE_ENDPOINTS = { + // 개인 회고 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/retrospective/personal) + DETAIL: (gatheringId: number, meetingId: number) => + `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/retrospective/personal`, +} as const diff --git a/src/features/retrospectives/personalRetrospective.mock.ts b/src/features/retrospectives/personalRetrospective.mock.ts new file mode 100644 index 0000000..c86d466 --- /dev/null +++ b/src/features/retrospectives/personalRetrospective.mock.ts @@ -0,0 +1,47 @@ +/** + * @file personalRetrospective.mock.ts + * @description 개인 회고 API 목데이터 + */ + +import type { GetPersonalRetrospectiveResponse } from './personalRetrospective.types' + +/** + * 개인 회고 조회 목데이터 + */ +const mockPersonalRetrospectiveDetail: GetPersonalRetrospectiveResponse = { + gatheringName: '책을 읽자', + bookTitle: '데미안', + bookAuthor: '헤르만 헤세', + meetingId: 1, + preOpinions: [ + { + topicId: 1, + topicName: '깨끗한 코드', + content: '사전 의견 내용을 작성합니다.', + }, + ], + topics: [ + { + topicId: 1, + topicName: '깨끗한 코드', + confirmOrder: 1, + }, + ], + meetingMembers: [ + { + meetingMemberId: 10, + nickname: '독서왕', + profileImage: 'https://example.com/profile.jpg', + }, + ], +} + +/** + * 개인 회고 조회 목데이터 반환 함수 + * + * @description + * 실제 API 호출을 시뮬레이션하여 개인 회고 목데이터를 반환합니다. + */ +export const getMockPersonalRetrospectiveDetail = (): GetPersonalRetrospectiveResponse => { + return mockPersonalRetrospectiveDetail +} diff --git a/src/features/retrospectives/personalRetrospective.types.ts b/src/features/retrospectives/personalRetrospective.types.ts new file mode 100644 index 0000000..cd428ab --- /dev/null +++ b/src/features/retrospectives/personalRetrospective.types.ts @@ -0,0 +1,70 @@ +/** + * @file personalRetrospective.types.ts + * @description 개인 회고 API 관련 타입 정의 + */ + +/** + * 개인 회고 - 사전 의견 항목 + */ +export type PersonalRetrospectivePreOpinion = { + /** 주제 ID */ + topicId: number + /** 주제 이름 */ + topicName: string + /** 작성한 사전 의견 내용 */ + content: string +} + +/** + * 개인 회고 - 확정된 토픽 항목 + */ +export type PersonalRetrospectiveTopic = { + /** 주제 ID */ + topicId: number + /** 주제 이름 */ + topicName: string + /** 확정 순서 */ + confirmOrder: number +} + +/** + * 개인 회고 - 모임 멤버 항목 + */ +export type PersonalRetrospectiveMember = { + /** 모임 멤버 ID */ + meetingMemberId: number + /** 닉네임 */ + nickname: string + /** 프로필 이미지 URL */ + profileImage: string +} + +/** + * 개인 회고 조회 요청 파라미터 + */ +export type GetPersonalRetrospectiveParams = { + /** 모임 ID */ + gatheringId: number + /** 약속 ID */ + meetingId: number +} + +/** + * 개인 회고 조회 응답 타입 + */ +export type GetPersonalRetrospectiveResponse = { + /** 모임 이름 */ + gatheringName: string + /** 책 제목 */ + bookTitle: string + /** 저자 */ + bookAuthor: string + /** 약속 ID */ + meetingId: number + /** 내 사전 의견 목록 */ + preOpinions: PersonalRetrospectivePreOpinion[] + /** 확정된 토픽 목록 */ + topics: PersonalRetrospectiveTopic[] + /** 약속 참여 멤버 목록 */ + meetingMembers: PersonalRetrospectiveMember[] +} diff --git a/src/pages/PreOpinions/PreOpinionListPage.tsx b/src/pages/PreOpinions/PreOpinionListPage.tsx index 2e54739..4d18948 100644 --- a/src/pages/PreOpinions/PreOpinionListPage.tsx +++ b/src/pages/PreOpinions/PreOpinionListPage.tsx @@ -5,7 +5,7 @@ import { PreOpinionDetail, PreOpinionMemberList, usePreOpinionAnswers, -} from '@/features/preOpinions' +} from '@/features/pre-opinion' import SubPageHeader from '@/shared/components/SubPageHeader' import { Spinner } from '@/shared/ui' import { useGlobalModalStore } from '@/store' diff --git a/src/pages/PreOpinions/PreOpinionWritePage.tsx b/src/pages/PreOpinions/PreOpinionWritePage.tsx index d59d000..834be7b 100644 --- a/src/pages/PreOpinions/PreOpinionWritePage.tsx +++ b/src/pages/PreOpinions/PreOpinionWritePage.tsx @@ -145,7 +145,7 @@ export default function PreOpinionWritePage() { if (isLoading || !preOpinion) { return ( <> - +
@@ -155,7 +155,7 @@ export default function PreOpinionWritePage() { return ( <> - + -
+

diff --git a/src/pages/Retrospectives/PersonalRetrospectivePage.tsx b/src/pages/Retrospectives/PersonalRetrospectivePage.tsx new file mode 100644 index 0000000..4e89818 --- /dev/null +++ b/src/pages/Retrospectives/PersonalRetrospectivePage.tsx @@ -0,0 +1,45 @@ +import { useParams } from 'react-router-dom' + +import { PersonalRetrospectiveContent, usePersonalRetrospective } from '@/features/retrospectives' +import SubPageHeader from '@/shared/components/SubPageHeader' +import { ROUTES } from '@/shared/constants' +import { Spinner } from '@/shared/ui' + +export default function PersonalRetrospectivePage() { + const { gatheringId: gatheringIdParam, meetingId: meetingIdParam } = useParams<{ + gatheringId: string + meetingId: string + }>() + + const gatheringId = Number(gatheringIdParam) + const meetingId = Number(meetingIdParam) + + const { data, isLoading, isError } = usePersonalRetrospective({ gatheringId, meetingId }) + + if (!gatheringIdParam || !meetingIdParam) return null + + return ( + <> + + +

+

개인 회고

+ + {isLoading && ( +
+ +
+ )} + + {isError && ( +

개인 회고 정보를 불러오지 못했습니다.

+ )} + + {data && } +
+ + ) +} From 864dd5e33ba1e28af3f96efce0be04e085c30b19 Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:14:42 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20SubPageHeader=20=EA=B7=B8=EB=A6=BC?= =?UTF-8?q?=EC=9E=90=20prop=20=EC=B6=94=EA=B0=80=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/components/SubPageHeader.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared/components/SubPageHeader.tsx b/src/shared/components/SubPageHeader.tsx index f850130..fceb4f3 100644 --- a/src/shared/components/SubPageHeader.tsx +++ b/src/shared/components/SubPageHeader.tsx @@ -12,6 +12,8 @@ export interface SubPageHeaderProps { to?: string /** 외부에서 전달하는 추가 클래스 */ className?: string + /** 스크롤 시 하단 shadow 비활성화 */ + disableShadow?: boolean } /** @@ -31,7 +33,7 @@ export interface SubPageHeaderProps { * * ``` */ -export default function SubPageHeader({ label = '뒤로가기', to, className }: SubPageHeaderProps) { +export default function SubPageHeader({ label = '뒤로가기', to, className, disableShadow = false }: SubPageHeaderProps) { const navigate = useNavigate() const isScrolled = useScrollShadow() @@ -48,11 +50,11 @@ export default function SubPageHeader({ label = '뒤로가기', to, className }: aria-label={`${label} 페이지로 이동`} className={cn( 'sticky top-gnb-height z-40 bg-white transition-shadow', - isScrolled && 'shadow-drop-bottom', + isScrolled && !disableShadow && 'shadow-drop-bottom', className )} > -
+
{label} From 82f42b62d1aedc8e49c8ea78b9e556827a43e006 Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:17:06 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=A4=ED=8C=85=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PreOpinionWriteHeader.tsx | 50 +++++++++---------- src/features/pre-opinion/preOpinion.mock.ts | 5 +- .../PersonalRetrospectiveContent.tsx | 11 +--- src/shared/components/SubPageHeader.tsx | 7 ++- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx b/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx index 2a83159..e9d163c 100644 --- a/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx +++ b/src/features/pre-opinion/components/PreOpinionWriteHeader.tsx @@ -50,34 +50,34 @@ const PreOpinionWriteHeader = ({ isScrolled && 'shadow-drop-bottom' )} > -
-
-
-

사전 의견 작성하기

-

- {book.title} · {book.author} -

-
-
- {updatedAt && ( -

{formatUpdatedAt(updatedAt)}

- )} - - -
+
+
+
+

사전 의견 작성하기

+

+ {book.title} · {book.author} +

+
+
+ {updatedAt && ( +

{formatUpdatedAt(updatedAt)}

+ )} + +
+
) } diff --git a/src/features/pre-opinion/preOpinion.mock.ts b/src/features/pre-opinion/preOpinion.mock.ts index 784ceda..3ac9347 100644 --- a/src/features/pre-opinion/preOpinion.mock.ts +++ b/src/features/pre-opinion/preOpinion.mock.ts @@ -3,7 +3,10 @@ * @description 사전 의견 API 목데이터 */ -import type { GetPreOpinionResponse, PreOpinionAnswersData } from '@/features/pre-opinion/preOpinion.types' +import type { + GetPreOpinionResponse, + PreOpinionAnswersData, +} from '@/features/pre-opinion/preOpinion.types' /** * 사전 의견 목록 목데이터 diff --git a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx index f6c89fe..cbd52ec 100644 --- a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx +++ b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx @@ -1,9 +1,4 @@ -import { - Avatar, - AvatarFallback, - AvatarImage, - Card, -} from '@/shared/ui' +import { Avatar, AvatarFallback, AvatarImage, Card } from '@/shared/ui' import type { GetPersonalRetrospectiveResponse } from '../personalRetrospective.types' @@ -23,9 +18,7 @@ export interface PersonalRetrospectiveContentProps { * * ``` */ -export default function PersonalRetrospectiveContent({ - data, -}: PersonalRetrospectiveContentProps) { +export default function PersonalRetrospectiveContent({ data }: PersonalRetrospectiveContentProps) { const { gatheringName, bookTitle, bookAuthor, preOpinions, topics, meetingMembers } = data return ( diff --git a/src/shared/components/SubPageHeader.tsx b/src/shared/components/SubPageHeader.tsx index fceb4f3..0db0a4a 100644 --- a/src/shared/components/SubPageHeader.tsx +++ b/src/shared/components/SubPageHeader.tsx @@ -33,7 +33,12 @@ export interface SubPageHeaderProps { * * ``` */ -export default function SubPageHeader({ label = '뒤로가기', to, className, disableShadow = false }: SubPageHeaderProps) { +export default function SubPageHeader({ + label = '뒤로가기', + to, + className, + disableShadow = false, +}: SubPageHeaderProps) { const navigate = useNavigate() const isScrolled = useScrollShadow() From 8d23827991aaa0fba8a785bd70cb8b1f663a1d24 Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:23:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/retrospectives/components/index.ts | 1 + src/features/retrospectives/index.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/features/retrospectives/components/index.ts b/src/features/retrospectives/components/index.ts index 91b716a..52e3ed8 100644 --- a/src/features/retrospectives/components/index.ts +++ b/src/features/retrospectives/components/index.ts @@ -1,4 +1,5 @@ export { default as AiLoadingOverlay } from './AiLoadingOverlay' export { default as AiSummaryToast } from './AiSummaryToast' +export { default as PersonalRetrospectiveContent } from './PersonalRetrospectiveContent' export { default as RetrospectiveCardButtons } from './RetrospectiveCardButtons' export { default as RetrospectiveSummarySkeleton } from './RetrospectiveSummarySkeleton' diff --git a/src/features/retrospectives/index.ts b/src/features/retrospectives/index.ts index 4ffb8fb..f0f5100 100644 --- a/src/features/retrospectives/index.ts +++ b/src/features/retrospectives/index.ts @@ -1,2 +1,4 @@ // Components export * from './components' +// Hooks +export * from './hooks' From 9b5981e45ac39f679dba6561f28d5f2f30a64954 Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:41:38 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EA=B0=9C=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EA=B3=A0=20nullable=20=ED=95=84=EB=93=9C=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EB=AA=A9=20=EB=AA=A8=EB=93=9C=20=EC=A1=B0?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=98=ED=99=98=20=EC=B6=94=EA=B0=80=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/pre-opinion/preOpinion.api.ts | 6 ++++++ .../components/PersonalRetrospectiveContent.tsx | 4 ++-- src/features/retrospectives/personalRetrospective.api.ts | 9 ++++++++- .../retrospectives/personalRetrospective.types.ts | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/features/pre-opinion/preOpinion.api.ts b/src/features/pre-opinion/preOpinion.api.ts index d673216..0280580 100644 --- a/src/features/pre-opinion/preOpinion.api.ts +++ b/src/features/pre-opinion/preOpinion.api.ts @@ -59,6 +59,8 @@ export const savePreOpinion = async ( { gatheringId, meetingId, isFirstSave }: SavePreOpinionParams, body: SavePreOpinionBody ): Promise => { + if (USE_MOCK) return + if (isFirstSave) { return api.post(PRE_OPINION_ENDPOINTS.CREATE(gatheringId, meetingId), body) } @@ -80,6 +82,8 @@ export const submitPreOpinion = async ( meetingId: number, body: SubmitPreOpinionBody ): Promise => { + if (USE_MOCK) return + return api.patch(PRE_OPINION_ENDPOINTS.SUBMIT(gatheringId, meetingId), body) } @@ -117,6 +121,8 @@ export const getPreOpinionAnswers = async ( export const deleteMyPreOpinionAnswer = async ( params: DeleteMyPreOpinionAnswerParams ): Promise => { + if (USE_MOCK) return + const { gatheringId, meetingId } = params return api.delete(PRE_OPINION_ENDPOINTS.DELETE_MY_ANSWER(gatheringId, meetingId)) } diff --git a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx index cbd52ec..d87a12a 100644 --- a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx +++ b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx @@ -11,7 +11,7 @@ export interface PersonalRetrospectiveContentProps { * * @description * 개인 회고 페이지의 전체 콘텐츠를 렌더링합니다. - * 책 정보, 참여 멤버, 내 사전 의견, 토론 주제 섹션으로 구성됩니다. + * 책 정보, 참여 멤버, 토론 주제, 내 사전 의견 섹션으로 구성됩니다. * * @example * ```tsx @@ -37,7 +37,7 @@ export default function PersonalRetrospectiveContent({ data }: PersonalRetrospec {meetingMembers.map((member) => (
- + {member.nickname.slice(0, 1)} {member.nickname} diff --git a/src/features/retrospectives/personalRetrospective.api.ts b/src/features/retrospectives/personalRetrospective.api.ts index 52c82e7..00e8645 100644 --- a/src/features/retrospectives/personalRetrospective.api.ts +++ b/src/features/retrospectives/personalRetrospective.api.ts @@ -35,7 +35,14 @@ export const getPersonalRetrospective = async ({ return getMockPersonalRetrospectiveDetail() } - return api.get( + const data = await api.get( PERSONAL_RETROSPECTIVE_ENDPOINTS.DETAIL(gatheringId, meetingId) ) + + return { + ...data, + preOpinions: data.preOpinions ?? [], + topics: data.topics ?? [], + meetingMembers: data.meetingMembers ?? [], + } } diff --git a/src/features/retrospectives/personalRetrospective.types.ts b/src/features/retrospectives/personalRetrospective.types.ts index cd428ab..7ba79bc 100644 --- a/src/features/retrospectives/personalRetrospective.types.ts +++ b/src/features/retrospectives/personalRetrospective.types.ts @@ -36,7 +36,7 @@ export type PersonalRetrospectiveMember = { /** 닉네임 */ nickname: string /** 프로필 이미지 URL */ - profileImage: string + profileImage: string | null } /** From 8090a1c47ce1a82f7306abc12c97ee2b6dbc7b1e Mon Sep 17 00:00:00 2001 From: Choi Youngae Date: Sun, 22 Feb 2026 22:58:55 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=EA=B0=9C=EC=9D=B8=ED=9A=8C?= =?UTF-8?q?=EA=B3=A0=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersonalRetrospectiveContent.tsx | 81 ------------------- .../retrospectives/components/index.ts | 1 - src/features/retrospectives/hooks/index.ts | 2 - .../hooks/personalRetrospectiveQueryKeys.ts | 14 ---- .../hooks/usePersonalRetrospective.ts | 44 ---------- src/features/retrospectives/index.ts | 2 - .../personalRetrospective.api.ts | 48 ----------- .../personalRetrospective.endpoints.ts | 7 -- .../personalRetrospective.mock.ts | 47 ----------- .../personalRetrospective.types.ts | 70 ---------------- .../PersonalRetrospectivePage.tsx | 45 ----------- 11 files changed, 361 deletions(-) delete mode 100644 src/features/retrospectives/components/PersonalRetrospectiveContent.tsx delete mode 100644 src/features/retrospectives/hooks/index.ts delete mode 100644 src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts delete mode 100644 src/features/retrospectives/hooks/usePersonalRetrospective.ts delete mode 100644 src/features/retrospectives/personalRetrospective.api.ts delete mode 100644 src/features/retrospectives/personalRetrospective.endpoints.ts delete mode 100644 src/features/retrospectives/personalRetrospective.mock.ts delete mode 100644 src/features/retrospectives/personalRetrospective.types.ts delete mode 100644 src/pages/Retrospectives/PersonalRetrospectivePage.tsx diff --git a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx b/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx deleted file mode 100644 index d87a12a..0000000 --- a/src/features/retrospectives/components/PersonalRetrospectiveContent.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage, Card } from '@/shared/ui' - -import type { GetPersonalRetrospectiveResponse } from '../personalRetrospective.types' - -export interface PersonalRetrospectiveContentProps { - data: GetPersonalRetrospectiveResponse -} - -/** - * 개인 회고 콘텐츠 - * - * @description - * 개인 회고 페이지의 전체 콘텐츠를 렌더링합니다. - * 책 정보, 참여 멤버, 토론 주제, 내 사전 의견 섹션으로 구성됩니다. - * - * @example - * ```tsx - * - * ``` - */ -export default function PersonalRetrospectiveContent({ data }: PersonalRetrospectiveContentProps) { - const { gatheringName, bookTitle, bookAuthor, preOpinions, topics, meetingMembers } = data - - return ( -
- {/* 책 정보 */} - -

{gatheringName}

-

{bookTitle}

-

{bookAuthor}

-
- - {/* 함께한 멤버 */} -
-

함께한 멤버

-
- {meetingMembers.map((member) => ( -
- - - {member.nickname.slice(0, 1)} - - {member.nickname} -
- ))} -
-
- - {/* 토론 주제 */} -
-

토론 주제

-
- {topics.map((topic) => ( - - - {topic.confirmOrder} - - {topic.topicName} - - ))} -
-
- - {/* 내 사전 의견 */} -
-

내 사전 의견

-
- {preOpinions.map((opinion) => ( - -

{opinion.topicName}

-

{opinion.content}

-
- ))} - {preOpinions.length === 0 && ( -

작성한 사전 의견이 없습니다.

- )} -
-
-
- ) -} diff --git a/src/features/retrospectives/components/index.ts b/src/features/retrospectives/components/index.ts index 52e3ed8..91b716a 100644 --- a/src/features/retrospectives/components/index.ts +++ b/src/features/retrospectives/components/index.ts @@ -1,5 +1,4 @@ export { default as AiLoadingOverlay } from './AiLoadingOverlay' export { default as AiSummaryToast } from './AiSummaryToast' -export { default as PersonalRetrospectiveContent } from './PersonalRetrospectiveContent' export { default as RetrospectiveCardButtons } from './RetrospectiveCardButtons' export { default as RetrospectiveSummarySkeleton } from './RetrospectiveSummarySkeleton' diff --git a/src/features/retrospectives/hooks/index.ts b/src/features/retrospectives/hooks/index.ts deleted file mode 100644 index bfcdcf6..0000000 --- a/src/features/retrospectives/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './personalRetrospectiveQueryKeys' -export * from './usePersonalRetrospective' diff --git a/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts b/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts deleted file mode 100644 index 36f8987..0000000 --- a/src/features/retrospectives/hooks/personalRetrospectiveQueryKeys.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file personalRetrospectiveQueryKeys.ts - * @description 개인 회고 관련 Query Key Factory - */ - -import type { GetPersonalRetrospectiveParams } from '../personalRetrospective.types' - -export const personalRetrospectiveQueryKeys = { - all: ['personalRetrospectives'] as const, - - details: () => [...personalRetrospectiveQueryKeys.all, 'detail'] as const, - detail: (params: GetPersonalRetrospectiveParams) => - [...personalRetrospectiveQueryKeys.details(), params] as const, -} diff --git a/src/features/retrospectives/hooks/usePersonalRetrospective.ts b/src/features/retrospectives/hooks/usePersonalRetrospective.ts deleted file mode 100644 index 9881945..0000000 --- a/src/features/retrospectives/hooks/usePersonalRetrospective.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file usePersonalRetrospective.ts - * @description 개인 회고 조회 훅 - */ - -import { useQuery } from '@tanstack/react-query' - -import type { ApiError } from '@/api' - -import { getPersonalRetrospective } from '../personalRetrospective.api' -import type { - GetPersonalRetrospectiveParams, - GetPersonalRetrospectiveResponse, -} from '../personalRetrospective.types' -import { personalRetrospectiveQueryKeys } from './personalRetrospectiveQueryKeys' - -/** - * 개인 회고 조회 훅 - * - * @description - * TanStack Query를 사용하여 개인 회고 정보를 조회합니다. - * 모임명, 책 정보, 내 사전 의견, 확정된 토픽, 참여 멤버 목록을 포함합니다. - * - * @param params - 모임 ID와 약속 ID - * - * @returns TanStack Query 결과 객체 - * - * @example - * ```tsx - * const { data, isLoading } = usePersonalRetrospective({ gatheringId: 1, meetingId: 2 }) - * ``` - */ -export const usePersonalRetrospective = (params: GetPersonalRetrospectiveParams) => { - const { gatheringId, meetingId } = params - const isValidParams = - !Number.isNaN(gatheringId) && gatheringId > 0 && !Number.isNaN(meetingId) && meetingId > 0 - - return useQuery({ - queryKey: personalRetrospectiveQueryKeys.detail(params), - queryFn: () => getPersonalRetrospective(params), - enabled: isValidParams, - gcTime: 10 * 60 * 1000, - }) -} diff --git a/src/features/retrospectives/index.ts b/src/features/retrospectives/index.ts index f0f5100..4ffb8fb 100644 --- a/src/features/retrospectives/index.ts +++ b/src/features/retrospectives/index.ts @@ -1,4 +1,2 @@ // Components export * from './components' -// Hooks -export * from './hooks' diff --git a/src/features/retrospectives/personalRetrospective.api.ts b/src/features/retrospectives/personalRetrospective.api.ts deleted file mode 100644 index 00e8645..0000000 --- a/src/features/retrospectives/personalRetrospective.api.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @file personalRetrospective.api.ts - * @description 개인 회고 API 요청 함수 - */ - -import { api } from '@/api/client' - -import { PERSONAL_RETROSPECTIVE_ENDPOINTS } from './personalRetrospective.endpoints' -import { getMockPersonalRetrospectiveDetail } from './personalRetrospective.mock' -import type { - GetPersonalRetrospectiveParams, - GetPersonalRetrospectiveResponse, -} from './personalRetrospective.types' - -/** 목데이터 사용 여부 플래그 */ -const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true' - -/** - * 개인 회고 조회 - * - * @description - * 약속에 대한 개인 회고 정보를 조회합니다. - * 모임명, 책 정보, 내 사전 의견, 확정된 토픽, 참여 멤버 목록을 포함합니다. - * - * @param params - 모임 ID와 약속 ID - * - * @returns 개인 회고 응답 데이터 - */ -export const getPersonalRetrospective = async ({ - gatheringId, - meetingId, -}: GetPersonalRetrospectiveParams): Promise => { - if (USE_MOCK) { - await new Promise((resolve) => setTimeout(resolve, 500)) - return getMockPersonalRetrospectiveDetail() - } - - const data = await api.get( - PERSONAL_RETROSPECTIVE_ENDPOINTS.DETAIL(gatheringId, meetingId) - ) - - return { - ...data, - preOpinions: data.preOpinions ?? [], - topics: data.topics ?? [], - meetingMembers: data.meetingMembers ?? [], - } -} diff --git a/src/features/retrospectives/personalRetrospective.endpoints.ts b/src/features/retrospectives/personalRetrospective.endpoints.ts deleted file mode 100644 index 3501e3a..0000000 --- a/src/features/retrospectives/personalRetrospective.endpoints.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { API_PATHS } from '@/api' - -export const PERSONAL_RETROSPECTIVE_ENDPOINTS = { - // 개인 회고 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/retrospective/personal) - DETAIL: (gatheringId: number, meetingId: number) => - `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/retrospective/personal`, -} as const diff --git a/src/features/retrospectives/personalRetrospective.mock.ts b/src/features/retrospectives/personalRetrospective.mock.ts deleted file mode 100644 index c86d466..0000000 --- a/src/features/retrospectives/personalRetrospective.mock.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file personalRetrospective.mock.ts - * @description 개인 회고 API 목데이터 - */ - -import type { GetPersonalRetrospectiveResponse } from './personalRetrospective.types' - -/** - * 개인 회고 조회 목데이터 - */ -const mockPersonalRetrospectiveDetail: GetPersonalRetrospectiveResponse = { - gatheringName: '책을 읽자', - bookTitle: '데미안', - bookAuthor: '헤르만 헤세', - meetingId: 1, - preOpinions: [ - { - topicId: 1, - topicName: '깨끗한 코드', - content: '사전 의견 내용을 작성합니다.', - }, - ], - topics: [ - { - topicId: 1, - topicName: '깨끗한 코드', - confirmOrder: 1, - }, - ], - meetingMembers: [ - { - meetingMemberId: 10, - nickname: '독서왕', - profileImage: 'https://example.com/profile.jpg', - }, - ], -} - -/** - * 개인 회고 조회 목데이터 반환 함수 - * - * @description - * 실제 API 호출을 시뮬레이션하여 개인 회고 목데이터를 반환합니다. - */ -export const getMockPersonalRetrospectiveDetail = (): GetPersonalRetrospectiveResponse => { - return mockPersonalRetrospectiveDetail -} diff --git a/src/features/retrospectives/personalRetrospective.types.ts b/src/features/retrospectives/personalRetrospective.types.ts deleted file mode 100644 index 7ba79bc..0000000 --- a/src/features/retrospectives/personalRetrospective.types.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file personalRetrospective.types.ts - * @description 개인 회고 API 관련 타입 정의 - */ - -/** - * 개인 회고 - 사전 의견 항목 - */ -export type PersonalRetrospectivePreOpinion = { - /** 주제 ID */ - topicId: number - /** 주제 이름 */ - topicName: string - /** 작성한 사전 의견 내용 */ - content: string -} - -/** - * 개인 회고 - 확정된 토픽 항목 - */ -export type PersonalRetrospectiveTopic = { - /** 주제 ID */ - topicId: number - /** 주제 이름 */ - topicName: string - /** 확정 순서 */ - confirmOrder: number -} - -/** - * 개인 회고 - 모임 멤버 항목 - */ -export type PersonalRetrospectiveMember = { - /** 모임 멤버 ID */ - meetingMemberId: number - /** 닉네임 */ - nickname: string - /** 프로필 이미지 URL */ - profileImage: string | null -} - -/** - * 개인 회고 조회 요청 파라미터 - */ -export type GetPersonalRetrospectiveParams = { - /** 모임 ID */ - gatheringId: number - /** 약속 ID */ - meetingId: number -} - -/** - * 개인 회고 조회 응답 타입 - */ -export type GetPersonalRetrospectiveResponse = { - /** 모임 이름 */ - gatheringName: string - /** 책 제목 */ - bookTitle: string - /** 저자 */ - bookAuthor: string - /** 약속 ID */ - meetingId: number - /** 내 사전 의견 목록 */ - preOpinions: PersonalRetrospectivePreOpinion[] - /** 확정된 토픽 목록 */ - topics: PersonalRetrospectiveTopic[] - /** 약속 참여 멤버 목록 */ - meetingMembers: PersonalRetrospectiveMember[] -} diff --git a/src/pages/Retrospectives/PersonalRetrospectivePage.tsx b/src/pages/Retrospectives/PersonalRetrospectivePage.tsx deleted file mode 100644 index 4e89818..0000000 --- a/src/pages/Retrospectives/PersonalRetrospectivePage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useParams } from 'react-router-dom' - -import { PersonalRetrospectiveContent, usePersonalRetrospective } from '@/features/retrospectives' -import SubPageHeader from '@/shared/components/SubPageHeader' -import { ROUTES } from '@/shared/constants' -import { Spinner } from '@/shared/ui' - -export default function PersonalRetrospectivePage() { - const { gatheringId: gatheringIdParam, meetingId: meetingIdParam } = useParams<{ - gatheringId: string - meetingId: string - }>() - - const gatheringId = Number(gatheringIdParam) - const meetingId = Number(meetingIdParam) - - const { data, isLoading, isError } = usePersonalRetrospective({ gatheringId, meetingId }) - - if (!gatheringIdParam || !meetingIdParam) return null - - return ( - <> - - -
-

개인 회고

- - {isLoading && ( -
- -
- )} - - {isError && ( -

개인 회고 정보를 불러오지 못했습니다.

- )} - - {data && } -
- - ) -}