Skip to content

[feat] 약속회고 생성 전 플로우 UI 및 기능 개발#100

Merged
haruyam15 merged 23 commits intodevelopfrom
feat/meeting-retro-87
Feb 25, 2026
Merged

[feat] 약속회고 생성 전 플로우 UI 및 기능 개발#100
haruyam15 merged 23 commits intodevelopfrom
feat/meeting-retro-87

Conversation

@haruyam15
Copy link
Contributor

@haruyam15 haruyam15 commented Feb 24, 2026

🚀 풀 리퀘스트 제안

📋 작업 내용

  • 약속 회고 생성 페이지 개발
  • 수집된 사전의견 조회 API 연동
  • 약속 완료 시 약속 상세 페이지에 회고 버튼 추가

공통 컴포넌트

  • Dropzone: 드래그 앤 드롭 파일 업로드 컴포넌트
  • Accordion: 접히는 콘텐츠 컴포넌트
  • AlertIcon: 경고/안내 아이콘

📸 스크린샷 (선택 사항)

image image

📄 기타

약속상세 데이터에 회고 버튼 분기처리를 위한 데이터 요청을 해두었습니다. 반영되면 추가 이슈 생성후 수정하겠습니다

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 회고 작성 화면에 사전 의견 수집 목록 추가 및 무한 스크롤 지원
    • 음성 파일 업로드 기능 추가 (최대 50MB 지원)
    • 회고 상세 조회 페이지 신규 추가
    • 아코디언 UI 컴포넌트 추가
  • 개선 사항

    • 약속 상세 화면 내비게이션 개선 및 에러 처리 강화
    • 약속 상태별 UI 자동 전환 기능 추가

@haruyam15 haruyam15 self-assigned this Feb 24, 2026
@haruyam15 haruyam15 added the feat 새로운 기능 추가 label Feb 24, 2026
@haruyam15 haruyam15 linked an issue Feb 24, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

회고 기능 중 사전 의견 수집, 조회, 처리를 위한 API 계층, 커스텀 훅, UI 컴포넌트를 추가하고 관련 페이지를 확장했습니다. Radix UI 기반 Accordion, AlertIcon, Dropzone 컴포넌트를 신규 제공하며 MeetingDetailPage의 탭 동작을 progressStatus 기반으로 개선했습니다.

Changes

Cohort / File(s) Summary
회고 API 및 훅 계층
src/features/retrospectives/retrospectives.api.ts, src/features/retrospectives/retrospectives.endpoints.ts, src/features/retrospectives/retrospectives.types.ts, src/features/retrospectives/retrospectives.mock.ts
사전 의견 수집 조회 API(getCollectedAnswers), 엔드포인트(COLLECTED_ANSWERS), 타입(GetCollectedAnswersParams, GetCollectedAnswersResponse 등), 모의 데이터 생성 함수 추가. 커서 기반 페이지네이션 지원.
회고 훅 및 내보내기
src/features/retrospectives/hooks/useCollectedAnswers.ts, src/features/retrospectives/hooks/retrospectiveQueryKeys.ts, src/features/retrospectives/hooks/index.ts, src/features/retrospectives/index.ts
useCollectedAnswers 훅(TanStack Query 기반 무한 스크롤), collectedAnswersList 쿼리 키, 타입 내보내기 추가.
회고 UI 컴포넌트
src/features/retrospectives/components/RetrospectiveCardButtons.tsx, src/features/retrospectives/components/index.ts
회고 선택 버튼에 아이콘(meetingRetroIcon, personalRetroIcon) 및 텍스트 개선, 레이아웃 조정.
공유 UI 컴포넌트
src/shared/ui/Accordion.tsx, src/shared/ui/AlertIcon.tsx, src/shared/ui/Dropzone.tsx, src/shared/ui/index.ts
Radix UI 기반 Accordion(Item, Trigger, Content 포함), 경고 아이콘, 파일 업로드 Dropzone 컴포넌트 신규 추가. 드래그-드롭 및 파일 검증 지원.
회고 페이지
src/pages/Retrospectives/MeetingRetrospectiveCreatePage.tsx, src/pages/Retrospectives/MeetingRetrospectiveDetailPage.tsx, src/pages/Retrospectives/index.ts
사전 의견 수집 조회, 무한 스크롤, 음성 파일 업로드 UI 추가(CreatePage); 회고 상세 조회 페이지 신규(DetailPage).
미팅 페이지 및 헤더
src/pages/Meetings/MeetingDetailPage.tsx, src/features/topics/components/TopicHeader.tsx
progressStatus 기반 탭 자동 선택 로직 추가; 에러 처리 중앙화 및 리다이렉트 개선; TopicHeader에 progressStatus prop 추가로 POST 상태 표시.
라우팅 및 상수
src/routes/index.tsx, src/shared/constants/routes.ts, src/shared/constants/pagination.ts
회고 상세 조회 라우트 추가(MEETING_RETROSPECTIVE_DETAIL); 페이지 크기 상수(COLLECTED_ANSWERS: 10) 추가.
스타일 및 의존성
package.json, src/shared/styles/theme.css
@radix-ui/react-accordion 의존성 추가; custom-scroll-grey 스크롤 스타일 유틸리티 추가.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분

이유: 다중 영역 변경(API 계층, 훅, 페이지, 라우팅), 상태 관리 로직 개선(MeetingDetailPage의 progressStatus 기반 탭 선택), 신규 UI 컴포넌트 3개(Accordion, AlertIcon, Dropzone) 통합, 무한 스크롤 구현. 각 영역별 일관성 검증과 에러 처리 흐름 검수 필요.

Possibly related PRs

Suggested reviewers

  • mgYang53
  • choiyoungae
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확히 반영합니다. 약속회고 생성 전 플로우의 UI 및 기능 개발이라는 핵심 목표가 잘 드러나 있으며, 변경사항(컴포넌트 추가, API 연동, UI 개선)과 정확히 일치합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/meeting-retro-87

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

Dropzone 파일 타입 검증 기능 추가 및 radix-ui 의존성 정리로 코드 품질 개선.

- Dropzone 파일 타입 검증 로직 추가
- 파일 타입 불일치 시 onTypeRejected 콜백 호출
- alert를 toast로 변경하여 일관된 에러 처리
- radix-ui 제거 후 @radix-ui/react-accordion 추가
- Accordion, AlertIcon 컴포넌트 코드 정리
- Import/export 경로 정리 및 불필요한 mock export 제거
coderabbitai[bot]

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/features/retrospectives/retrospectives.api.ts (1)

46-59: Mock 지연이 취소되지 않아 폐기된 요청도 끝까지 수행됩니다.

문제: USE_MOCK 분기에서 setTimeout 지연이 AbortSignal을 처리하지 않습니다.
영향: 빠른 화면 전환/커서 변경 시 이전 요청이 늦게 완료되어 상태 전이가 흔들릴 수 있습니다.
대안: getCollectedAnswers에도 signal을 받아 mock/real 요청 모두 취소 가능하게 맞춰주세요.

수정 예시
+const sleep = (ms: number, signal?: AbortSignal) =>
+  new Promise<void>((resolve, reject) => {
+    const timer = setTimeout(resolve, ms)
+    if (!signal) return
+
+    const onAbort = () => {
+      clearTimeout(timer)
+      reject(new DOMException('Aborted', 'AbortError'))
+    }
+
+    if (signal.aborted) {
+      onAbort()
+      return
+    }
+
+    signal.addEventListener('abort', onAbort, { once: true })
+  })
+
 export const getCollectedAnswers = async (
-  params: GetCollectedAnswersParams
+  params: GetCollectedAnswersParams,
+  signal?: AbortSignal
 ): Promise<GetCollectedAnswersResponse> => {
   const { meetingId, pageSize = PAGE_SIZES.COLLECTED_ANSWERS, cursorUserId } = params

   if (USE_MOCK) {
-    await new Promise((resolve) => setTimeout(resolve, 500))
+    await sleep(500, signal)
     return getMockCollectedAnswers(pageSize, cursorUserId)
   }

   return api.get<GetCollectedAnswersResponse>(
     RETROSPECTIVES_ENDPOINTS.COLLECTED_ANSWERS(meetingId),
     {
       params: {
         pageSize,
         cursorUserId,
       },
+      signal,
     }
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/retrospectives/retrospectives.api.ts` around lines 46 - 59,
Update getCollectedAnswers to accept an optional AbortSignal and wire it through
both branches: add a signal parameter to the getCollectedAnswers signature, use
the signal to cancel the mock delay (store the timeout id from setTimeout,
listen for signal.aborted to clearTimeout and reject/throw) and call
getMockCollectedAnswers only if not aborted, and pass the signal into the real
request by adding it to the axios config for api.get (alongside params).
Reference symbols to change: getCollectedAnswers (function signature), USE_MOCK
branch, getMockCollectedAnswers, and the api.get call that uses
RETROSPECTIVES_ENDPOINTS.COLLECTED_ANSWERS(meetingId).
src/pages/Meetings/MeetingDetailPage.tsx (1)

198-204: progressStatus 폴백 'PRE' — 안전하지만 참고 사항 있습니다.

meetingconfirmedTopics는 독립 쿼리이므로, meeting이 아직 로딩 중일 때 confirmedTopicsInfiniteData가 먼저 도착하면 progressStatus가 실제 값과 다른 'PRE'로 잠시 렌더될 수 있습니다. 기능상 문제는 없으나, 'PRE' 상태에서 CONFIRMED 헤더의 UI가 크게 달라진다면 깜빡임이 발생할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Meetings/MeetingDetailPage.tsx` around lines 198 - 204,
TopicHeader's progressStatus currently falls back to 'PRE' causing a potential
UI flicker when confirmedTopicsInfiniteData arrives before meeting; change the
props so progressStatus is not forcibly defaulted and/or defer rendering until
meeting is available: in MeetingDetailPage, replace
progressStatus={meeting?.progressStatus ?? 'PRE'} with either
progressStatus={meeting?.progressStatus} (allowing TopicHeader to handle
undefined/loading) or wrap the TopicHeader render in a short conditional like
meeting && <TopicHeader ... progressStatus={meeting.progressStatus} ... />;
ensure you reference TopicHeader, MeetingDetailPage,
confirmedTopicsInfiniteData.pages[0].actions and meeting?.confirmedTopicDate
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/retrospectives/retrospectives.api.ts`:
- Around line 41-45: getCollectedAnswers currently accepts params with meetingId
and pageSize without validation; add early guards in getCollectedAnswers to
validate that meetingId and pageSize are integers and > 0 (use Number.isInteger
and >0), normalize pageSize to PAGE_SIZES.COLLECTED_ANSWERS default only after
validation, and return or throw a clear 4xx error (e.g., throw new Error or a
ValidationError) when validation fails; reference the function
getCollectedAnswers and its params (meetingId, pageSize, cursorUserId,
GetCollectedAnswersParams) when applying the checks so invalid inputs are
rejected before any downstream calls.

In `@src/pages/Meetings/MeetingDetailPage.tsx`:
- Around line 80-91: The effect using useEffect that reads
meetingError/proposedError/confirmedError can fire multiple times and show
duplicate toasts and redirects; modify the effect (the useEffect block that
defines primaryError and calls showErrorToast and navigate) to guard against
repeated execution by tracking a ref flag (e.g., errorHandledRef) initialized
false and set true after the first handling, and only call
showErrorToast/navigate when primaryError exists and errorHandledRef.current is
false; ensure the ref is referenced inside the effect and not reset on
re-renders so subsequent error state changes won't re-trigger the
toast/redirect.

---

Nitpick comments:
In `@src/features/retrospectives/retrospectives.api.ts`:
- Around line 46-59: Update getCollectedAnswers to accept an optional
AbortSignal and wire it through both branches: add a signal parameter to the
getCollectedAnswers signature, use the signal to cancel the mock delay (store
the timeout id from setTimeout, listen for signal.aborted to clearTimeout and
reject/throw) and call getMockCollectedAnswers only if not aborted, and pass the
signal into the real request by adding it to the axios config for api.get
(alongside params). Reference symbols to change: getCollectedAnswers (function
signature), USE_MOCK branch, getMockCollectedAnswers, and the api.get call that
uses RETROSPECTIVES_ENDPOINTS.COLLECTED_ANSWERS(meetingId).

In `@src/pages/Meetings/MeetingDetailPage.tsx`:
- Around line 198-204: TopicHeader's progressStatus currently falls back to
'PRE' causing a potential UI flicker when confirmedTopicsInfiniteData arrives
before meeting; change the props so progressStatus is not forcibly defaulted
and/or defer rendering until meeting is available: in MeetingDetailPage, replace
progressStatus={meeting?.progressStatus ?? 'PRE'} with either
progressStatus={meeting?.progressStatus} (allowing TopicHeader to handle
undefined/loading) or wrap the TopicHeader render in a short conditional like
meeting && <TopicHeader ... progressStatus={meeting.progressStatus} ... />;
ensure you reference TopicHeader, MeetingDetailPage,
confirmedTopicsInfiniteData.pages[0].actions and meeting?.confirmedTopicDate
when making the change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42678b0 and 8326316.

📒 Files selected for processing (4)
  • src/features/retrospectives/index.ts
  • src/features/retrospectives/retrospectives.api.ts
  • src/pages/Meetings/MeetingDetailPage.tsx
  • src/shared/ui/Accordion.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/shared/ui/Accordion.tsx
  • src/features/retrospectives/index.ts

- MeetingRetroIcon, PersonalRetroIcon 컴포넌트를 SVG 파일로 대체
- useCollectedAnswers 훅의 유효성 검사 강화 (정수 체크, pageSize 검증)
- MeetingDetailPage 에러 처리 중복 실행 방지
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/features/retrospectives/hooks/useCollectedAnswers.ts (1)

66-66: gcTime만 설정하고 staleTime을 생략하면 무한 스크롤 데이터를 매번 리페치합니다.

staleTime 기본값은 0이므로, 10분간 캐시를 유지하더라도 컴포넌트 마운트나 윈도우 포커스 때마다 백그라운드 리페치가 발생합니다. 무한 쿼리는 쌓인 페이지를 모두 순차 리페치하므로 비용이 큽니다.

gcTime과 같은 값 또는 적절한 값으로 staleTime을 함께 설정하세요.

💡 제안
     // 캐시 데이터 10분간 유지
+    staleTime: 10 * 60 * 1000,
     gcTime: 10 * 60 * 1000,

As per coding guidelines: "queryKey 안정성, enabled 조건, select 비용, invalidate/refetch 타이밍을 중점적으로 봐줘."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/retrospectives/hooks/useCollectedAnswers.ts` at line 66, The
infinite-query options in useCollectedAnswers only set gcTime (gcTime: 10 * 60 *
1000) but omit staleTime, causing refetch on mount/window focus; update the
useInfiniteQuery options for useCollectedAnswers to include a suitable staleTime
(e.g., staleTime: 10 * 60 * 1000 or match gcTime) so pages remain fresh for that
period and avoid costly sequential refetches on each mount/focus; locate the
options object passed into the useInfiniteQuery call in useCollectedAnswers and
add the staleTime property with the chosen duration.
src/shared/ui/AlertIcon.tsx (1)

4-5: className 문자열 파싱으로 색상 추출 — 취약한 API 설계

className에서 regex로 색상 토큰을 추출하는 방식은 다음 문제가 있습니다:

  • 첫 번째 매칭만 반환"text-gray-500 text-red-400" 전달 시 gray-500 선택, 의도한 색이 무시됨.
  • 커버리지 한계 — 임의값(text-[#ff0000]), 단일어 토큰(text-white, text-inherit), v4 시맨틱 토큰(text-accent)은 매칭 불가 → 항상 purple-200 fallback.
  • className에 side effect 부여 — 스타일 적용과 동시에 동작 결정에 사용하는 것은 예측 불가한 버그를 유발합니다.

명시적 color prop으로 분리하는 것이 더 견고합니다:

♻️ 권장 리팩터
-export default function AlertIcon({ className = '', ...props }: SVGProps<SVGSVGElement>) {
-  const colorMatch = className.match(/(?:text-|bg-)([a-z]+-\d+)(?:\s|$)/)
-  const fillColor = colorMatch ? `var(--color-${colorMatch[1]})` : 'var(--color-purple-200)'
+interface AlertIconProps extends SVGProps<SVGSVGElement> {
+  color?: string
+}
+
+export default function AlertIcon({ color = 'var(--color-purple-200)', className, ...props }: AlertIconProps) {
+  const fillColor = color

호출부: <AlertIcon color="var(--color-red-400)" /> 또는 <AlertIcon color="var(--color-purple-200)" />

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/AlertIcon.tsx` around lines 4 - 5, The AlertIcon component
currently infers fillColor by parsing className with a regex (const colorMatch
... → fillColor), which is fragile and misses many token forms; change AlertIcon
to accept an explicit color prop (e.g., color?: string) and compute fillColor
from that prop with a default fallback (e.g., 'var(--color-purple-200)'); remove
or de-prioritize the regex className parsing inside the AlertIcon function,
update the JSX to use the new color prop when setting the SVG fill, and adjust
call sites to pass color (or leave none to use the fallback) so styling is not
inferred from className.
src/pages/Meetings/MeetingDetailPage.tsx (1)

45-45: ID 숫자 변환은 1회 계산으로 정리하는 편이 안전합니다.

문제: Line 45, 64-66, 77-79, 124-137에서 Number(...)를 반복 호출합니다.
영향: NaN 전파 지점이 늘고, 파라미터 처리 변경 시 수정 누락 가능성이 커집니다.
대안: 상단에서 numericGatheringId, numericMeetingId를 1회 계산해 재사용하세요.

♻️ 제안 코드
  const { gatheringId, meetingId } = useParams<{
    gatheringId: string
    meetingId: string
  }>()
+ const numericGatheringId = Number(gatheringId)
+ const numericMeetingId = Number(meetingId)

  const {
    data: meeting,
    isLoading: meetingLoading,
    error: meetingError,
- } = useMeetingDetail(Number(meetingId))
+ } = useMeetingDetail(numericMeetingId)

  const {
    data: proposedTopicsInfiniteData,
    isLoading: isProposedLoading,
    error: proposedError,
    fetchNextPage: fetchNextProposedPage,
    hasNextPage: hasNextProposedPage,
    isFetchingNextPage: isFetchingNextProposedPage,
  } = useProposedTopics({
-   gatheringId: Number(gatheringId),
-   meetingId: Number(meetingId),
+   gatheringId: numericGatheringId,
+   meetingId: numericMeetingId,
  })

  const {
    data: confirmedTopicsInfiniteData,
    isLoading: isConfirmedLoading,
    error: confirmedError,
    fetchNextPage: fetchNextConfirmedPage,
    hasNextPage: hasNextConfirmedPage,
    isFetchingNextPage: isFetchingNextConfirmedPage,
  } = useConfirmedTopics({
-   gatheringId: Number(gatheringId),
-   meetingId: Number(meetingId),
+   gatheringId: numericGatheringId,
+   meetingId: numericMeetingId,
  })

Also applies to: 64-66, 77-79, 124-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Meetings/MeetingDetailPage.tsx` at line 45, Compute
numericMeetingId = Number(meetingId) and numericGatheringId =
Number(gatheringId) once near the top of MeetingDetailPage and reuse them
everywhere instead of calling Number(...) repeatedly; update the call to
useMeetingDetail(Number(meetingId)) to useMeetingDetail(numericMeetingId) and
replace repeated Number(...) uses in useParticipants/fetch calls and delete/edit
handlers (the places around lines 64-66, 77-79, 124-137) to reference
numericMeetingId or numericGatheringId accordingly so parsing happens in a
single, central spot.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/Meetings/MeetingDetailPage.tsx`:
- Around line 83-91: The current logic computes primaryError = meetingError ||
proposedError || confirmedError and treats any of these as a trigger to set
hasHandledErrorRef, showErrorToast and navigate away; change this so only
meetingError causes the navigate/replace flow: if meetingError is set (and
!hasHandledErrorRef.current) set hasHandledErrorRef.current,
showErrorToast(meetingError.userMessage) and
navigate(ROUTES.GATHERING_DETAIL(...)); for proposedError and confirmedError do
not call navigate or flip hasHandledErrorRef — instead
showErrorToast(proposedError.userMessage / confirmedError.userMessage) locally
and let the tab components handle retries (do not mark hasHandledErrorRef as
handled so future errors can be processed).

In `@src/shared/ui/AlertIcon.tsx`:
- Line 1: The file uses the React namespace in the AlertIcon signature
(AlertIcon({ className = '', ...props }: React.SVGProps<SVGSVGElement>)) but
React is not imported, causing a TypeScript "Cannot find name 'React'" error;
fix it by adding an explicit import for React (e.g., import React from 'react'
or import * as React from 'react') at the top of the file so the React.SVGProps
type resolves, or alternatively change the prop type to a locally available type
(like SVGProps<SVGSVGElement> with an import from 'react' for that symbol) while
keeping the AlertIcon function name and signature semantics intact.

---

Nitpick comments:
In `@src/features/retrospectives/hooks/useCollectedAnswers.ts`:
- Line 66: The infinite-query options in useCollectedAnswers only set gcTime
(gcTime: 10 * 60 * 1000) but omit staleTime, causing refetch on mount/window
focus; update the useInfiniteQuery options for useCollectedAnswers to include a
suitable staleTime (e.g., staleTime: 10 * 60 * 1000 or match gcTime) so pages
remain fresh for that period and avoid costly sequential refetches on each
mount/focus; locate the options object passed into the useInfiniteQuery call in
useCollectedAnswers and add the staleTime property with the chosen duration.

In `@src/pages/Meetings/MeetingDetailPage.tsx`:
- Line 45: Compute numericMeetingId = Number(meetingId) and numericGatheringId =
Number(gatheringId) once near the top of MeetingDetailPage and reuse them
everywhere instead of calling Number(...) repeatedly; update the call to
useMeetingDetail(Number(meetingId)) to useMeetingDetail(numericMeetingId) and
replace repeated Number(...) uses in useParticipants/fetch calls and delete/edit
handlers (the places around lines 64-66, 77-79, 124-137) to reference
numericMeetingId or numericGatheringId accordingly so parsing happens in a
single, central spot.

In `@src/shared/ui/AlertIcon.tsx`:
- Around line 4-5: The AlertIcon component currently infers fillColor by parsing
className with a regex (const colorMatch ... → fillColor), which is fragile and
misses many token forms; change AlertIcon to accept an explicit color prop
(e.g., color?: string) and compute fillColor from that prop with a default
fallback (e.g., 'var(--color-purple-200)'); remove or de-prioritize the regex
className parsing inside the AlertIcon function, update the JSX to use the new
color prop when setting the SVG fill, and adjust call sites to pass color (or
leave none to use the fallback) so styling is not inferred from className.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8326316 and ffa1dad.

⛔ Files ignored due to path filters (2)
  • src/shared/assets/icon/meeting-retro.svg is excluded by !**/*.svg, !**/*.svg
  • src/shared/assets/icon/personal-retro.svg is excluded by !**/*.svg, !**/*.svg
📒 Files selected for processing (4)
  • src/features/retrospectives/components/RetrospectiveCardButtons.tsx
  • src/features/retrospectives/hooks/useCollectedAnswers.ts
  • src/pages/Meetings/MeetingDetailPage.tsx
  • src/shared/ui/AlertIcon.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/retrospectives/components/RetrospectiveCardButtons.tsx

Copy link
Contributor

@mgYang53 mgYang53 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

@haruyam15 haruyam15 merged commit 3048221 into develop Feb 25, 2026
2 checks passed
@haruyam15 haruyam15 deleted the feat/meeting-retro-87 branch February 25, 2026 15:25
mgYang53 added a commit that referenced this pull request Mar 7, 2026
* [refactor] 사전의견 폴더 병합 (#92)

* refactor: 사전의견 관련 폴더 병합 (#91)

* fix: SubPageHeader 그림자 prop 추가 (#91)

* refactor: 코드 포매팅 (#91)

* fix: 빌드 에러 수정 (#91)

* fix: 개인 회고 nullable 필드 처리 및 목 모드 조기 반환 추가 (#91)

* chore: 개인회고 관련 파일 삭제 (#91)

* feat: 약속 회고 API 레이어 구축 (#94) (#95)

* feat: 약속 회고 API 레이어 구축 (#94)

STT Job 생성, 요약 조회/수정/발행 API 함수 및 React Query 훅 구현.
AbortController 기반 STT 요청 취소 지원, setQueryData 캐시 전략 적용.

* style: prettier 포맷 수정 (#94)

* refactor: 코드 리뷰 반영 (#94)

- api 함수 api.* 헬퍼로 통일
- useCreateSttJob race condition 수정
- useCreateSttJob 언마운트 시 자동 취소
- KeyPointUpdateRequest 타입 alias로 변경

* feat: 에러 코드 추가 및 사전 의견 버튼 disabled 처리 (#97) (#98)

신규 에러 코드 5개(E000, M016, M017, R106, R108) 추가.
MyMeetingListItem에 preOpinionTemplateConfirmed 필드 추가 후,
메인페이지 예정 약속의 사전 의견 작성 버튼 disabled 처리 반영.

* [feat] 약속회고 생성 전 플로우 UI 및 기능 개발 (#100)

* feat: 약속 완료 시 약속 상세 페이지 UI 변경(#87)

* feat: 약속 회고 상세 페이지 추가 (#87)

* feat:아코디언 컴포넌트 추가(#87)

* design: 아코디언 컴포넌트css 수정(#87)

* feat: 수집된 사전의견 조회 api 개발(#87)

* feat: Dropzone 공통컴포넌트 생성 및 파일 업로드 기능구현(#87)

* design: AI요약 버튼 디자인 수정(#87)

* style: 프리티어(#87)

* refactor: 파일 업로드 검증 강화 및 의존성 정리 (#87)

Dropzone 파일 타입 검증 기능 추가 및 radix-ui 의존성 정리로 코드 품질 개선.

- Dropzone 파일 타입 검증 로직 추가
- 파일 타입 불일치 시 onTypeRejected 콜백 호출
- alert를 toast로 변경하여 일관된 에러 처리
- radix-ui 제거 후 @radix-ui/react-accordion 추가
- Accordion, AlertIcon 컴포넌트 코드 정리
- Import/export 경로 정리 및 불필요한 mock export 제거

* refactor: 에러처리 개선 (#87)

* design: 아코디언css 수정 (#87)

* refactor: GetCollectedAnswersParams 배럴 추가(#87)

* style: 주석제거(#87)

* refactor: AlertIcon 색 커스텀 기능 추가(#87)

* refactor: 회고 아이콘을 SVG 파일로 전환 (#87)

- MeetingRetroIcon, PersonalRetroIcon 컴포넌트를 SVG 파일로 대체
- useCollectedAnswers 훅의 유효성 검사 강화 (정수 체크, pageSize 검증)
- MeetingDetailPage 에러 처리 중복 실행 방지

* refactor: AlertIcon적용 (#87)

* refactor: AlertIcon size prop추가(#87)

* refactor: 주제 목록 에러 처리 개선 (#87)

* fix: 버튼 프로퍼티수정(#87)

* feat: 회고 상태 기반 라우트 분기 처리 추가 (#87)

* feat: 목데이터에 회고상태  추가(#87)

* refactor: params검증로직 페이지에서 처리(#87)

* [feat] 개인회고 작성 및 조회 화면 구현 (#101)

* feat: 개인 회고 작성 화면 구현 (#90)

* feat: 개인 회고 전송 기능 구현 (#90)

* feat: 개인회고 조회 화면 구현 (#90)

* feat: 개인회고 삭제, 수정 구현 (#90)

* chore 미사용 유틸리티 제거 (#90)

* refactor: 코드 포매팅 (#90)

* fix: 코드리뷰 반영 (#90)

* fix: 코드 리뷰 반영 (#90)

* refactor: 코드 포매팅 (#90)

* refactor: 개인회고 폴더 정리 (#90)

* fix: 코드 리뷰 반영 (#90)

* feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93) (#102)

* feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93)

- AI 요약 결과 조회, 수정, 발행 기능 구현
- TopicSummaryCard: 구조화된 개별 input으로 주요포인트 편집
- FormPageHeader: children, onBack prop 추가
- 발행 성공 시 DetailPage로 이동
- AiLoadingOverlay SVG 직접 import 방식으로 변경
- summary 관련 목데이터 추가

* fix: 코드 리뷰 반영 (#93)

- TopicSummaryCard 편집 모드 keyPoints map key를 인덱스에서 stable id로 교체
  (EditableKeyPoint 타입 추가, crypto.randomUUID()로 id 생성)
- mock abort 에러를 DOMException에서 Error('canceled')로 변경해 axios 취소 동작 일치
- 회고 요약 목데이터를 전역 단일 객체에서 meetingId별 Record로 리팩터링
- MeetingRetrospectiveResultPage에 isError 처리 및 다시 시도 버튼 추가
- window.history.replaceState를 navigate(location.pathname, replace)로 교체
- FormPageHeader props를 discriminated union으로 타입 강화

* style: prettier 포맷 수정 (#93)

* fix: 코드 리뷰 반영 2차 (#93)

- 상세 항목(details)에도 EditableDetail 타입으로 stable id 적용
  (추가/삭제 시 input 포커스 혼동 방지)
- handleSaveEdit에서 EditableDetail[] → string[]으로 직렬화 처리
- mock STT abort: aborted 즉시 감지, once 옵션으로 리스너 중복 방지, resolve 시 리스너 정리
- meetingId 유효성 검사 강화 (Number.isInteger && > 0)

* fix: 코드 리뷰 반영 3차 (#93)

- guard를 핸들러 정의 이전으로 이동해 gatheringId/meetingId non-null assertion 제거
- topic prop을 항상 SummaryTopic[]으로 유지해 union 타입 불일치 해결
- EditableDetail/EditableKeyPoint를 components/index.ts에서 함께 re-export

* [fix] 책 상세 페이지, 내 책장 페이지 API 스펙 반영 (#104)

* refactor: 감상 기록 액션 메뉴 분리 (#59)

* refactor: Book/Keywords API 목데이터와 엔드포인트 분리 (#59)

* design: 감상 기록 액션 메뉴 관련 z-index 조정 (#59)

* feat: 별점 필터링에 0점 추가 (#59)

* refactor: 내 책장 무한스크롤 시 useInfiniteScroll 활용 (#59)

* feat: 내 책장에 등록된 책이 없을 경우 툴팁 추가 (#59)

* feat: 기록 타임라인의 사전의견에 답변이 없는 주제 처리 추가 (#59)

* refactor: sticky한 헤더에 대해 drop-shadow-bottom 및 useScrollCollapse 활용 (#59)

* fix: 책 타임라인 api 반영 (#59)

* fix: 책 리스트 조회 api 반영 (#59)

* fix: 책 리스트 삭제 api 반영 및 디자인 수정 (#59)

* fix: 도서 API 스펙 변경에 따른 타입 및 컴포넌트 업데이트 (#59)

* fix: 약속 회고 아이템에서 미연동 액션 메뉴 제거 (#59)

* feat: 도서 읽기 상태 토글에 낙관적 업데이트 적용 (#59)

* refactor: 코드 포매팅 (#59)

* fix: 메인페이지 ReadingBooksSection API 파라미터 및 응답 필드 타입 오류 수정 (#59)

* fix: 내책장 편집 모드 진입 시 filteredBookIds 초기화 (#59)

* fix: mock API 파라미터 계약 및 라우트 상수 관련 수정 (#59)

* fix: PRE_OPINION 아이템 React key 중복 가능성 수정 (#59)

* refactor: RootLayout에서 불필요한 overflow-x-clip 제거 (#59)

* refactor: BookLogList 삭제 로직을 useBookLogDeleteActions 훅으로 분리 (#59)

* [feat] 약속회고 조회 및 댓글 기능 구현  (#103)

* feat: 약속 회고상세 UI개발(#96)

* refactor: 회고 관련 파일 구조 meeting 폴더로 재구성 (#96)

* feat: 댓글 Textarea 높이조절 기능구현(#96)

* feat: 약속회고 댓글 기능 구현 (#96)

- 약속회고 상세 페이지에 댓글 작성/삭제 기능 추가.
- 무한 스크롤로 댓글 목록 조회하며, 약속장 및 댓글 작성자만 삭제 가능.

* fix: 약속회고 댓글 에러 처리 및 안정성 개선 (#96)

- 댓글 조회 실패 시 에러 토스트 및 안내 메시지 표시.
- currentUserId null 체크로 삭제 권한 확인 로직 개선.
- 스켈레톤 count 음수값 방어 처리.
- Textarea format별 height 기본값 적용.

* fix: 약속회고 댓글/상세 조회 mock 처리 개선 (#96)

* refactor: 약속회고 댓글 삭제 확인 모달 개선 및 코드 정리 (#96)

- window.confirm을 글로벌 모달로 변경
- Mock 함수에서 불필요한 meetingId 파라미터 제거
- Textarea 컴포넌트에서 사용하지 않는 ref 제거.

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* [feat] 페이지 접근 권한 전역 관리 구현 (#105)

* feat: 페이지 접근 권한 전역 관리 구현 (#99)

인터셉터에서 권한 에러 감지 시 커스텀 DOM 이벤트를 dispatch하고,
RootLayout의 usePermissionRedirect 훅에서 토스트와 홈 리다이렉트를
처리하여 한 곳에서 통합 관리.

- errors.ts: PAGE_ACCESS_ERROR_CODES Set 추가 (6개 에러 코드)
- interceptors.ts: permission-denied 커스텀 이벤트 dispatch
- usePermissionRedirect: 이벤트 → 토스트 + navigate(HOME)
- RootLayout: usePermissionRedirect 훅 연결
- MeetingRetrospectiveCreatePage: 이중 내비게이션 방지

* fix: 권한 에러 중복 이벤트 억제 및 핸들러 방어 코드 추가 (#99)

* fix: 권한 에러 시 중복 토스트/모달 방지 (#99)

* fix: 약속상세 페이지의 사전의견 버튼에 링크 연동(#106) (#107)

---------

Co-authored-by: choiyoungae <109134495+choiyoungae@users.noreply.github.com>
Co-authored-by: haeun <110523397+haruyam15@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 약속회고 생성 전 플로우 UI 및 기능 개발

3 participants