Skip to content

[feat] 약속 상세 화면 및 기능 구현 #51

Merged
haruyam15 merged 10 commits intomainfrom
feat/meetings-detail-43
Feb 3, 2026
Merged

[feat] 약속 상세 화면 및 기능 구현 #51
haruyam15 merged 10 commits intomainfrom
feat/meetings-detail-43

Conversation

@haruyam15
Copy link
Contributor

@haruyam15 haruyam15 commented Feb 2, 2026

🚀 풀 리퀘스트 제안

📋 작업 내용

약속(Meeting) 상세 화면의 UI/UX 및 기능을 완성했습니다.
사용자는 약속 정보를 확인하고, 참가 신청 및 취소가 가능하며, 약속 상태를 실시간으로 확인할 수 있습니다.

🔧 변경 사항

1. 약속 상세 UI 구현

  • 약속 상세 페이지 레이아웃 및 기본 UI 구성

2. 약속 상세 조회 기능 구현

  • API 연동을 통한 약속 상세 데이터 조회
  • useMeetingDetail 약속 상세 조회 훅 (TanStack Query)
  • 컴포넌트 분리 구조
    • MeetingDetailHeader: 약속 제목 및 책 정보
    • MeetingDetailInfo: 날짜, 시간, 장소, 정원, 참가자 목록
    • MeetingDetailButton: 액션 버튼 (참가/취소/채팅/삭제)

3. 약속 참가/취소 기능

  • 참가/취소 API 연동
    • useJoinMeeting: 약속 참가 신청 훅 (TanStack Query)
    • useCancelJoinMeeting: 약속 참가 취소 훅 (TanStack Query)
    • 액션 버튼에 confirm 모달 및 에러 처리 추가
  • 약속 상태 표시
    • 약속 스케줄 기반 계산 (약속 전/중/후 Badge)
  • 지도 모달 컴포넌트 추가
    • MapModal 컴포넌트 (약속 생성 PR승인 후 지도 API 연동 예정)

📸 스크린샷 (선택 사항)

image

Summary by CodeRabbit

모임 상세 페이지 추가

  • New Features

    • 모임 상세 페이지 출시: 모임 정보, 책 썸네일, 참가자(호스트 포함) 목록, 일정, 위치를 한눈에 조회
    • 상태 배지로 약속 전/중/후 표시
    • 참가 신청·취소 버튼(확인/오류 모달 및 대기 상태 처리) 추가
    • 위치 보기용 모달 추가(지도 연동 예정)
    • 날짜/시간 표기 개선 및 일정 범위 포맷터 도입
  • Chores

    • 모임 관련 라우팅을 모임그룹(모임 소속) 스코프로 변경

- 약속 상세 페이지 API 연동 및 UI 컴포넌트 구현.
- useMeetingDetail 훅으로 데이터 조회 및 상태 관리.
- 약속 정보, 책 정보, 참가자, 버튼 상태를 표시하는 컴포넌트 분리.
- 라우트 파라미터를 gatheringId/meetingId로 명확히 구분.
약속 상세 페이지에 참가신청/취소 기능 및 UI 개선 작업:

- 약속 참가신청/취소 API 연동 (useJoinMeeting, useCancelJoinMeeting 훅 추가)
- 액션 버튼에 참가/취소 로직 구현 (confirm 모달 및 에러 처리 포함)
- 시간 기반 약속 상태 표시 (약속 전/중/후 Badge 자동 계산)
- 지도 모달 컴포넌트 추가 (MapModal, 추후 지도 API 연동 예정)
- 날짜/시간 표시 개선 (시작~종료 시간 분리 표시)
- API 엔드포인트 상수화 (meetings.endpoints.ts)
- 타입명 오타 수정 (MeddtingDetailActionStateType → MeetingDetailActionStateType)
@haruyam15 haruyam15 self-assigned this Feb 2, 2026
@haruyam15 haruyam15 added the feat 새로운 기능 추가 label Feb 2, 2026
@haruyam15 haruyam15 linked an issue Feb 2, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Warning

Rate limit exceeded

@haruyam15 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

모임 상세 조회 및 상호작용을 추가합니다. 엔드포인트 중앙화(MEETINGS_ENDPOINTS), 상세 API·목데이터, React Query 훅(조회/참여/취소), 상세 페이지와 관련 UI(헤더/정보/버튼/지도 모달), 타입·유틸·라우트 및 컴포넌트 재수출 변화를 도입합니다.

Changes

Cohort / File(s) Summary
Pages (공개 변경)
src/pages/Meetings/MeetingDetailPage.tsx, src/pages/Meetings/index.ts
모임 상세 페이지 추가 및 public export 교체(기존 MeetingListPage → MeetingDetailPage). route 파라미터로 meetingId 사용.
Routes & Constants
src/routes/index.tsx, src/shared/constants/routes.ts
라우트 구조를 /gatherings/:gatheringId/meetings/:meetingId 기반으로 변경; MEETING_* 경로들을 함수형 생성기로 전환.
API & Endpoints
src/features/meetings/meetings.api.ts, src/features/meetings/meetings.endpoints.ts
MEETINGS_ENDPOINTS 추가로 엔드포인트 중앙화. getMeetingDetail / joinMeeting / cancelJoinMeeting API 함수 추가 및 기존 경로 대체.
Hooks & Query Keys
src/features/meetings/hooks/useMeetingDetail.ts, src/features/meetings/hooks/useJoinMeeting.ts, src/features/meetings/hooks/useCancelJoinMeeting.ts, src/features/meetings/hooks/meetingQueryKeys.ts, src/features/meetings/hooks/index.ts
meeting detail 조회 쿼리 훅과 join/cancel 뮤테이션 훅 추가. 쿼리키에 detail 헬퍼 추가 및 훅 인덱스에 재수출 확장.
Components — Detail UI
src/features/meetings/components/MeetingDetailHeader.tsx, src/features/meetings/components/MeetingDetailInfo.tsx, src/features/meetings/components/index.ts, src/features/meetings/components/PlaceList.tsx, src/features/meetings/components/PlaceSearchModal.tsx
상태 배지, 책/참여자/시간/장소 정보를 렌더링하는 컴포넌트 추가·수정. PlaceList/PlaceSearchModal의 export 형태 변경 및 components index에 명시적 재수출 추가.
Components — Actions/Modal
src/features/meetings/components/MeetingDetailButton.tsx, src/features/meetings/components/MapModal.tsx
참여/취소 버튼(확인 dialog 및 mutation 연동)과 지도 모달(플레이스홀더) 추가.
Types & Mocks
src/features/meetings/meetings.types.ts, src/features/meetings/meetings.mock.ts, src/features/meetings/index.ts
GetMeetingDetailResponse 등 상세 타입과 actionState, MeetingLocation 등 추가. 상세 목데이터 및 관련 mock getter 추가.
Lib / DateTime
src/features/meetings/lib/dateTimeFormatters.ts, src/features/meetings/lib/dateTimeUtils.ts, src/features/meetings/lib/index.ts, src/features/meetings/lib/formatDateTime.ts
date/time 유틸 재구성: formatDateTime·formatScheduleRange 도입, 기존 파일 제거 및 일부 함수 서명 변경.
Shared UI tweak
src/shared/ui/TextButton.tsx
TextButton base 클래스에서 font-normal 제거(스타일 문자열 조정).

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant MeetingDetailPage
    participant useMeetingDetail
    participant QueryClient
    participant API
    participant MeetingDetailInfo

    Browser->>MeetingDetailPage: /gatherings/:gatheringId/meetings/:meetingId 접속
    MeetingDetailPage->>useMeetingDetail: useMeetingDetail(meetingId)
    useMeetingDetail->>QueryClient: 캐시 조회 (meetingQueryKeys.detail)
    QueryClient->>API: getMeetingDetail(meetingId) 요청
    API-->>QueryClient: GetMeetingDetailResponse 반환
    QueryClient->>useMeetingDetail: 데이터 제공
    useMeetingDetail-->>MeetingDetailPage: data/loading 상태 전달
    MeetingDetailPage->>MeetingDetailInfo: 상세 데이터 전달
    MeetingDetailInfo-->>Browser: UI 렌더링 (책/참여자/시간/장소)
Loading
sequenceDiagram
    participant Browser
    participant MeetingDetailButton
    participant ConfirmModal
    participant useJoinMeeting
    participant QueryClient
    participant API

    Browser->>MeetingDetailButton: "참여" 버튼 클릭
    MeetingDetailButton->>ConfirmModal: openConfirm 호출
    Browser->>ConfirmModal: 사용자 확인
    ConfirmModal->>useJoinMeeting: joinMutation.mutate(meetingId)
    useJoinMeeting->>API: joinMeeting(meetingId) 호출
    API-->>useJoinMeeting: 성공/실패 응답
    alt 성공
        useJoinMeeting->>QueryClient: invalidateQueries(meetingQueryKeys.detail(meetingId))
        QueryClient->>API: 재조회(getMeetingDetail) -> 최신 참여자 반영
    else 실패
        useJoinMeeting-->>Browser: openError 호출
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • choiyoungae
  • mgYang53

문제 → 영향 → 대안:

  • 문제: 라우트·엔드포인트·타입·훅·컴포넌트가 동시에 변경되어 연쇄적 충돌 및 import/export 불일치 가능.
  • 영향: 빌드 오류, 잘못된 라우트 네임/파라미터 사용, 쿼리 키 불일치로 인한 캐시 문제 또는 런타임 오류 발생 가능.
  • 대안: 변경된 ROUTES/MEETINGS_ENDPOINTS/meetingQueryKeys를 기준으로 관련 파일(import 경로·export 형태)을 우선 검증하고, 페이지 로드 및 참여/취소 플로우를 통합 테스트로 확인하세요.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 제목은 약속 상세 화면 및 기능 구현이라는 핵심 변경사항을 명확하게 요약하고 있으며, 실제 변경사항(MeetingDetailPage, 관련 컴포넌트, 훅 추가 등)과 일치합니다.

✏️ 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/meetings-detail-43

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.

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: 8

🤖 Fix all issues with AI agents
In `@src/features/meetings/components/MeetingDetailButton.tsx`:
- Around line 77-81: The help text is currently only shown when isEnabled is
true, so users in the EDIT_TIME_EXPIRED (disabled) state don't see why the
button is disabled; update the render condition in MeetingDetailButton (the JSX
that outputs the <p> with className "text-grey-700 typo-body6 pt-tiny") to show
the message when either isEnabled is true or type === 'EDIT_TIME_EXPIRED' (e.g.,
change the conditional from {isEnabled && (...) } to something like {(isEnabled
|| type === 'EDIT_TIME_EXPIRED') && (...)}) so the explanatory text is visible
for the disabled EDIT_TIME_EXPIRED state while keeping the same message logic.

In `@src/features/meetings/components/MeetingDetailInfo.tsx`:
- Around line 33-35: The code assumes meeting.schedule.displayDate.split(' ~ ')
always yields two parts; update MeetingDetailInfo (where startDate and endDate
are assigned) to validate the split result length, assign startDate = parts[0]
and only set endDate if parts.length > 1, and in render logic (same component
usage around lines that currently use startDate/endDate) conditionally render
the end time or use a fallback/placeholder when endDate is undefined to avoid
printing "undefined".
- Around line 52-56: In MeetingDetailInfo, the class name typo "typo-cation2"
prevents the caption style from applying; update the class on the <span> that
renders participants maxCount from "typo-cation2" to the correct "typo-caption2"
so it matches the project's typography token and restores proper styling.

In `@src/features/meetings/hooks/useMeetingDetail.ts`:
- Around line 31-40: The queryKey currently uses
meetingQueryKeys.detail(meetingId) even when meetingId is invalid, which lets
NaN serialize to null and pollute the cache; update useMeetingDetail so it
computes isValidMeetingId and uses meetingQueryKeys.detail(meetingId) only when
valid, otherwise pass a fixed sentinel key (e.g.,
['meetings','detail','invalid'] or similar) to avoid NaN→null collisions, keep
enabled: isValidMeetingId and the existing queryFn/getMeetingDetail usage
intact; locate the logic in useMeetingDetail and change the queryKey selection
around meetingQueryKeys.detail to the conditional sentinel approach.

In `@src/features/meetings/meetings.endpoints.ts`:
- Around line 1-25: Prettier formatting warnings were raised for the
MEETINGS_ENDPOINTS file; run the formatter and commit the changes to resolve CI
failures—open the file containing the MEETINGS_ENDPOINTS constant and run
`prettier --write` (or your project's equivalent formatting command) to apply
consistent formatting, then stage and commit the updated file so the API
endpoint definitions (APPROVALS, DETAIL, REJECT, CONFIRM, DELETE, JOIN,
CANCEL_JOIN) comply with the project's Prettier rules.

In `@src/features/meetings/meetings.mock.ts`:
- Around line 358-362: The mock function getMockMeetingDetail currently throws a
plain Error when a meeting is missing; update it to throw an error shaped like
the UI expects (include a userMessage field) so the detail page can render a
friendly message, e.g., throw an object containing message and userMessage (or a
subclass/typed error) instead of a plain Error; modify getMockMeetingDetail to
construct and throw that shaped error so callers consuming
GetMeetingDetailResponse can rely on error.userMessage.

In `@src/routes/index.tsx`:
- Around line 94-100: The route path is inserting ROUTES.MEETING_SETTING (a
function) directly into a template, causing the function to be stringified;
update the path to call the function with the route parameter placeholder
instead—e.g. replace
`${ROUTES.GATHERINGS}/:gatheringId${ROUTES.MEETING_SETTING}` with a call like
`${ROUTES.GATHERINGS}${ROUTES.MEETING_SETTING(':gatheringId')}` (or
`${ROUTES.GATHERINGS}/:gatheringId${ROUTES.MEETING_SETTING(':gatheringId')}`
depending on how ROUTES.MEETING_SETTING concatenates) so MeetingSettingPage is
registered at the correct parametric URL.

In `@src/shared/constants/routes.ts`:
- Around line 24-30: ROUTES.MEETINGS, ROUTES.MEETING_CREATE and
ROUTES.MEETING_UPDATE are inconsistent with the router: either remove the unused
constants or add matching router entries; decide which by searching for usages
of MEETINGS, MEETING_CREATE and MEETING_UPDATE and then either (A) remove the
unused constants from src/shared/constants/routes.ts (and update any imports) or
(B) add corresponding routes to the router configuration that match the helpers
(e.g. a route for '/gatherings/:gatheringId/meetings' plus routes for
'/gatherings/:gatheringId/meetings/create' and
'/gatherings/:gatheringId/meetings/:meetingId/update') so the helper functions
(MEETING_CREATE, MEETING_UPDATE, MEETING_DETAIL, MEETING_SETTING) actually
correspond to real routes.
🧹 Nitpick comments (4)
src/features/meetings/lib/formatDateTime.ts (2)

1-2: TODO 코멘트 확인

timeUtils와 병합 예정이라고 되어 있는데, 이 작업을 추적하기 위한 이슈가 있나요?

이슈 생성을 도와드릴까요?


17-29: 실시간 상태 업데이트 고려 필요

getMeetingStatusByTime이 호출 시점의 new Date()를 사용합니다.
→ 사용자가 페이지에 머무르는 동안 상태 배지가 자동 갱신되지 않습니다.
→ 약속 시작/종료 시점에 상태가 즉시 반영되지 않을 수 있습니다.

실시간 반영이 필요하다면 setInterval 또는 useEffect 기반 폴링을 고려해주세요.

src/features/meetings/components/MeetingDetailHeader.tsx (2)

14-21: 불필요한 Fragment 제거 가능

단일 자식 요소만 반환하므로 <>...</> Fragment가 필요하지 않습니다.

♻️ 리팩토링 제안
   return (
-    <>
-      <div className="flex items-start border-b gap-small border-b-grey-300 pb-[10px]">
-        <h3 className="text-black typo-heading3">{children}</h3>
-        <Badge size="small" color={status.color}>
-          {status.text}
-        </Badge>
-      </div>
-    </>
+    <div className="flex items-start border-b gap-small border-b-grey-300 pb-[10px]">
+      <h3 className="text-black typo-heading3">{children}</h3>
+      <Badge size="small" color={status.color}>
+        {status.text}
+      </Badge>
+    </div>
   )

5-8: Props 타입 개선 고려

childrenstring으로 제한하면 ReactNode를 전달할 수 없습니다. 현재는 제목 텍스트만 필요하므로 문제없지만, 확장성을 고려하면 ReactNode로 변경하거나 title: string prop으로 명시하는 것도 방법입니다.

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: 1

🤖 Fix all issues with AI agents
In `@src/features/meetings/components/MeetingDetailInfo.tsx`:
- Line 56: The <dt> for the 참가인원 label in MeetingDetailInfo (the JSX element
with className={`text-grey-600 ${LABEL_WIDTH}`}) is missing the typo-caption1
class; update its className to include "typo-caption1" alongside "text-grey-600"
and LABEL_WIDTH so its font styling matches the other <dt> elements (those
around lines where other <dt>s use typo-caption1).

- 약속 생성 시 스케줄이 없는 경우를 처리하기 위해 schedule 타입을 nullable로 변경.
- MeetingDetailInfo와 MeetingDetailHeader에서 스케줄 데이터 유무에 따라 조건부 렌더링 적용.
- useMeetingDetail 훅에서 유효하지 않은 meetingId로 인한 쿼리 키 충돌 방지 로직 추가.
- 약속 설정 페이지 라우팅 경로 수정.
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.

추가한 코멘트는 사소한 것이니 확인하고 머지해주세요!

고생하셨습니다 👍

- 컴포넌트 export 방식을 named에서 default로 통일
- 약속장 role을 HOST에서 LEADER로 변경
- 날짜/시간 유틸리티 함수 파일 구조 재정리 (dateTimeFormatters, dateTimeUtils 분리)
- 약속 진행 상태(progressStatus) 기반 배지 표시 로직 추가
- 불필요한 null 체크 제거 및 타입 안정성 향상
- 라우팅 경로 구조 개선 (하드코딩 제거)
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] 약속 상세 화면 및 기능 구현

3 participants