Skip to content

Conversation

@Yongmin0423
Copy link
Contributor

@Yongmin0423 Yongmin0423 commented Aug 14, 2025

👻 관련 이슈 번호

👻 요약

  • DayCell 캄포넌트 분리

👻 주요 변경 사항

  • useDayCellStyles, useModalState, useReservationCounts, useReservationMutations, useReservationQueries 훅으로 분리
  • DayCellContent, ReservationModalContent 분리

👻 체크리스트

  • Assignees에 본인을 등록했나요?
  • 라벨을 사이드 탭에서 등록했나요?
  • PR은 사이드 탭 Projects를 등록 하지마세요.
  • PR은 Milestone을 등록 하지마세요.
  • PR을 보내는 브랜치가 올바른지 확인했나요?
  • 팀원들이 리뷰하기 쉽도록 설명을 자세하게 작성했나요?
  • 변경사항을 충분히 테스트 했나요?
  • 컨벤션에 맞게 구현했나요?

📷 UI 변경 사항

👻 문제 사항

👻 논의 사항

👻 기타 참고 사항

Summary by CodeRabbit

  • 신기능

    • DayCell에 상태별 뱃지·알림점 및 모바일/데스크톱 대응 UI 추가
    • 날짜별 예약 모달 추가: 탭(신청/승인/거절), 건수 표시, 상세 목록과 승인/거절 액션
  • 리팩터

    • 캘린더 셀 로직을 훅/프레젠테이션 컴포넌트로 분리해 성능·일관성 개선
    • 날짜 표시 접근성(aria-label) 및 렌더링 가독성 향상
  • 작업

    • VSCode 설정에 새로운 플래그 추가 및 cSpell 설정 정리
  • UX

    • 로딩 스켈레톤 표시 동작 및 일부 스켈레톤 요소 조정

@Yongmin0423 Yongmin0423 self-assigned this Aug 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

예약 캘린더의 DayCell를 훅 기반 아키텍처로 전면 리팩터링하고 UI를 프레젠테이셔널 컴포넌트로 분리했습니다. 스케줄/예약 조회·집계·변경 로직을 전용 훅들로 이동했고, useCalendar 반환값에서 날짜별 예약 조회 함수가 제거되었으며 VSCode 설정에 신규 플래그가 추가되었습니다.

Changes

Cohort / File(s) Summary
VSCode 설정
./.vscode/settings.json
최상위 설정 claudeCodeChat.permissions.yoloMode=false 추가. cSpell.words 닫힘 표기에 트레일링 콤마 추가.
캘린더 코어 리팩터링
src/domain/Reservation/components/reservation-calendar/DayCell.tsx, src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx
DayCell에서 데이터 패칭/변경 로직 제거 후 훅과 프레젠테이셔널 컴포넌트로 분리. DayCell Props에서 reservation 제거. ReservationCalendar는 useCalendargetReservationForDate 사용·전달 삭제.
새 UI 컴포넌트
src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx, .../ReservationModalContent.tsx
DayCell 뷰와 모달 내용을 분리한 컴포넌트 추가. 상태별 배지, 카운트 표기 및 탭형 상세 UI 구현.
데이터·상태 훅 추가
src/domain/Reservation/hooks/useDayCellStyles.ts, .../useModalState.ts, .../useReservationCounts.ts, .../useReservationMutations.ts, .../useReservationQueries.ts
셀 스타일 계산, 모달 열림/탭 상태, 예약 집계, 승인/거절 변이(캐시 무효화 포함), 날짜별 스케줄·예약 조회를 담당하는 훅들 추가.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Calendar as ReservationCalendar
  participant Cell as DayCell
  participant Hooks as useReservationQueries/useReservationCounts
  participant RQ as React Query
  participant Server
  participant Modal as ReservationModalContent
  participant Mut as useReservationMutations

  User->>Calendar: 캘린더 열람
  Calendar->>Cell: DayCell 렌더
  Cell->>Hooks: schedules/reservations 조회 및 집계 요청
  Hooks->>RQ: getSchedulesByDate / getReservationsByStatus 쿼리
  RQ-->>Server: 데이터 요청(YYYY-MM-DD)
  Server-->>RQ: 스케줄/예약 응답
  RQ-->>Hooks: 응답(캐시)
  Hooks-->>Cell: schedules, reservationsByStatus, counts 반환
  User->>Cell: 날짜 클릭
  Cell->>Modal: 모달 열기 (counts, 리스트, activeTab)
  User->>Modal: 승인/거절 액션
  Modal->>Mut: handleApprove / handleReject 호출
  Mut->>RQ: mutation 실행 및 관련 쿼리 무효화
  RQ-->>Hooks: 무효화 후 재조회 트리거
  Hooks-->>Modal: 최신 카운트/리스트 반영
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • RoamReady#168: 동일 캘린더 컴포넌트군(DayCell/ReservationCalendar, 훅) 변경과 API 서명 변화가 유사함.
  • RoamReady#284: DayCell API 및 useCalendar 반환값 변경 등 본 PR과 직접적으로 겹치는 리팩터링 포함.
  • RoamReady#367: 본 PR과 동일 영역(훅 분리·프레젠테이셔널 컴포넌트 도입)에서의 이전 리팩터링과 흐름이 연관됨.

Suggested labels

refactor

Suggested reviewers

  • sgoldenbird
  • justhighway
  • Seon-K

Poem

토끼가 깡총, 캘린더 위에 뛰고,
훅으로 당근처럼 로직 나눠 담고,
배지 콩콩 수가 반짝 반짝,
모달 탭에 이야기 숨어 있고,
깔끔해진 칸에 당근 한 조각 남겨요. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/549

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

✅ Preview Deployment Ready!

🔗 Preview URL: https://roam-ready-dad1orn0e-yongmins-projects-bf5f7733.vercel.app
📝 Branch: refactor/549
💾 Commit: Refactor: DayCell 컴포넌트 분리
🕐 Deployed at:


This preview will be automatically updated on new commits.

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

🧹 Nitpick comments (14)
src/domain/Reservation/hooks/useReservationCounts.ts (2)

16-28: day 파라미터가 useMemo 의존성 배열에 불필요하게 포함되어 있습니다

reservationCounts를 계산하는 로직에서 day 파라미터를 실제로 사용하지 않는데도 의존성 배열에 포함되어 있습니다. 이로 인해 날짜가 변경될 때마다 불필요한 재계산이 발생할 수 있습니다.

  const reservationCounts = useMemo(() => {
    const counts = schedules?.reduce(
      (acc, schedule) => {
        acc.pending += schedule.count.pending;
        acc.confirmed += schedule.count.confirmed;
        acc.declined += schedule.count.declined;
        return acc;
      },
      { pending: 0, confirmed: 0, declined: 0 },
    ) ?? { pending: 0, confirmed: 0, declined: 0 };

    return counts;
-  }, [schedules, day]);
+  }, [schedules]);

7-10: day 파라미터가 사용되지 않습니다

인터페이스에 정의된 day 속성이 훅 내부에서 전혀 사용되지 않습니다. 이 파라미터를 제거하거나, 향후 사용 계획이 있다면 주석으로 그 의도를 명시해주세요.

interface UseReservationCountsProps {
  schedules: ScheduleItem[] | null;
-  day: Dayjs;
}
src/domain/Reservation/hooks/useDayCellStyles.ts (1)

18-24: 템플릿 리터럴 내 불필요한 공백 제거 필요

cellClasses 문자열에 불필요한 줄바꿈과 공백이 포함되어 있어 클래스명 사이에 여러 개의 공백이 생성될 수 있습니다.

-    const cellClasses = `
-      relative flex aspect-square cursor-pointer flex-col items-center justify-start p-1 md:p-2 text-center font-size-14
-      hover:bg-gray-50 
-      ${!isLastRow ? 'border-b-[0.05rem] border-gray-100' : ''} 
-      ${!isCurrentMonth ? 'bg-neutral-200 text-gray-400 opacity-50' : ''} 
-      ${isToday ? 'border-blue-300 bg-blue-100' : ''}
-    `;
+    const cellClasses = [
+      'relative flex aspect-square cursor-pointer flex-col items-center justify-start p-1 md:p-2 text-center font-size-14',
+      'hover:bg-gray-50',
+      !isLastRow && 'border-b-[0.05rem] border-gray-100',
+      !isCurrentMonth && 'bg-neutral-200 text-gray-400 opacity-50',
+      isToday && 'border-blue-300 bg-blue-100',
+    ].filter(Boolean).join(' ');
src/domain/Reservation/hooks/useReservationQueries.ts (2)

33-41: useEffect 의존성 배열에 누락된 항목이 있을 수 있습니다

useEffect 내부에서 setSelectedScheduleId를 호출하고 있지만 의존성 배열에 포함되어 있지 않습니다. React의 exhaustive-deps 규칙에 따르면 이를 포함해야 합니다.

  useEffect(() => {
    if (schedules && schedules.length > 0 && !selectedScheduleId) {
      // pending 예약이 있는 첫 번째 스케줄 찾기
      const scheduleWithPending = schedules.find((s) => s.count.pending > 0);
      // pending이 있으면 그것을, 없으면 첫 번째 스케줄 선택
      const targetSchedule = scheduleWithPending || schedules[0];
      setSelectedScheduleId(targetSchedule.scheduleId);
    }
-  }, [schedules, selectedScheduleId]);
+  }, [schedules, selectedScheduleId, setSelectedScheduleId]);

62-82: 병렬 API 호출 시 에러 처리 개선 필요

현재 Promise.all을 사용하여 병렬로 API를 호출하고 있지만, 하나의 요청이 실패하면 전체가 실패하게 됩니다. Promise.allSettled를 사용하여 개별 실패를 처리하는 것이 더 안정적일 수 있습니다.

-      const [pendingResults, confirmedResults, declinedResults] =
-        await Promise.all([
-          Promise.all(allPendingPromises),
-          Promise.all(allConfirmedPromises),
-          Promise.all(allDeclinedPromises),
-        ]);
+      const [pendingResults, confirmedResults, declinedResults] =
+        await Promise.all([
+          Promise.allSettled(allPendingPromises),
+          Promise.allSettled(allConfirmedPromises),
+          Promise.allSettled(allDeclinedPromises),
+        ]);

       return {
-        pending: pendingResults
+        pending: pendingResults
+          .filter((result): result is PromiseFulfilledResult<ReservationItem[] | null> => 
+            result.status === 'fulfilled')
+          .map(result => result.value)
           .flat()
           .filter((item): item is ReservationItem => item !== null),
src/domain/Reservation/components/reservation-calendar/ReservationModalContent.tsx (1)

107-112: confirmeddeclined 탭에서 불필요한 핸들러 전달

승인된 예약과 거절된 예약 탭에서는 showApprovalButtonshowRejectButton이 모두 false로 설정되어 있는데도 handleApprovehandleReject 핸들러를 전달하고 있습니다. 불필요한 prop 전달을 제거하는 것이 좋습니다.

        <Tabs.Content value='confirmed'>
          <ReservationDetail
            schedules={schedules || []}
            reservations={reservationsByStatus.confirmed}
            emptyMessage='승인된 예약이 없습니다.'
            showApprovalButton={false}
            showRejectButton={false}
-            onApprove={handleApprove}
-            onReject={handleReject}
            onTimeSlotSelect={handleTimeSlotSelect}
            isLoading={isLoading}
            status='confirmed'
            setIsOpen={setIsOpen}
          />
        </Tabs.Content>

        <Tabs.Content value='declined'>
          <ReservationDetail
            schedules={schedules || []}
            reservations={reservationsByStatus.declined}
            emptyMessage='거절된 예약이 없습니다.'
            showApprovalButton={false}
            showRejectButton={false}
-            onApprove={handleApprove}
-            onReject={handleReject}
            onTimeSlotSelect={handleTimeSlotSelect}
            isLoading={isLoading}
            status='declined'
            setIsOpen={setIsOpen}
          />
        </Tabs.Content>
src/domain/Reservation/hooks/useModalState.ts (2)

10-16: 모달 재오픈 시 탭 상태 초기화 필요 여부 확인 (열 때 'pending'으로 리셋 권장)

다른 날짜 셀을 눌러 모달을 열어도 이전에 보던 탭이 계속 유지됩니다. 의도라면 OK입니다. 보통은 열 때 '신청(pending)' 탭으로 초기화하는 UX가 자연스럽습니다.

아래처럼 handleOpen 시 탭을 초기화하는 방안을 고려해 주세요.

   const handleOpen = useCallback(() => {
-    setIsOpen(true);
+    setActiveTab('pending');
+    setIsOpen(true);
   }, []);

6-8: 탭 상태 유니온 타입을 공용 타입으로 추출해 재사용성 향상

여러 컴포넌트에서 동일 유니온 리터럴을 반복 정의하기보다 공용 타입을 export하여 일관성을 높이는 것을 권장합니다.

다음 타입을 파일 상단(import 아래)에 추가한 뒤, 상태 제네릭과 setActiveTab의 인자로 재사용해 주세요.

export type ModalTab = 'pending' | 'confirmed' | 'declined';

그리고 아래처럼 제네릭을 ModalTab으로 대체합니다.

-  const [activeTab, setActiveTab] = useState<
-    'pending' | 'confirmed' | 'declined'
-  >('pending');
+  const [activeTab, setActiveTab] = useState<ModalTab>('pending');
src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx (1)

48-56: 요일 헤더 key에서 index 제거 제안

WEEKDAYS 항목이 고정·유니크라면 index를 key에 포함할 필요가 없습니다. 불필요한 리렌더 방지 차원에서 다음처럼 간소화 가능합니다.

-          <div
-            key={`${day}-${index}`}
+          <div
+            key={day}
             role='columnheader'
src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx (2)

60-66: 리스트 key에서 index 제거 (안정 키 사용)

status 조합은 최대 3개이고 서로 유니크하므로 index 없이도 안정적인 key를 만들 수 있습니다. index 기반 key는 갯수 변동 시 불필요한 재마운트를 유발할 수 있습니다.

-          {displayItems.map((item, index) => (
+          {displayItems.map((item) => (
             <div
-              key={`${day.format('YYYY-MM-DD')}-${item.status}-${index}-desktop`}
+              key={`${day.format('YYYY-MM-DD')}-${item.status}-desktop`}
               className={`font-size-12 md:font-size-14 w-[90%] truncate rounded-xl px-1 text-center font-medium ${getColorClassByStatus(item.status)}`}
             >
               {STATUS_LABELS[item.status]} {item.count}건
             </div>
           ))}

31-33: a11y: aria-label에 연도 추가 제안

스크린리더에서 월/일만 있으면 맥락 상 혼란이 있을 수 있습니다. 연도까지 포함해 더 명확한 레이블을 권장합니다.

-      aria-label={`${day.format('M월 D일')}`}
+      aria-label={`${day.format('YYYY년 M월 D일')}`}
src/domain/Reservation/components/reservation-calendar/DayCell.tsx (3)

51-53: useCallback 의존성 보강 (경미)

setSelectedScheduleId는 안정적이지만, 린트 규칙과 일관성을 위해 의존성 배열에 포함하는 것을 권장합니다.

-  const handleTimeSlotSelect = useCallback(async (scheduleId: number) => {
-    setSelectedScheduleId(scheduleId);
-  }, []);
+  const handleTimeSlotSelect = useCallback(
+    async (scheduleId: number) => {
+      setSelectedScheduleId(scheduleId);
+    },
+    [setSelectedScheduleId],
+  );

39-49: 성능: 날짜별 다중 쿼리 폭증 가능성 — 월 단위 집계/프리패치 구조 고려

각 DayCell에서 날짜 단위로 스케줄/예약 쿼리를 발생시키면 한 달(보통 42칸) 기준 네트워크 병목이 커집니다. 다음과 같은 구조 전환을 제안합니다.

  • 월 단위로 스케줄/예약 카운트(또는 모달에 필요한 최소 데이터)를 한 번에 조회하는 Aggregated API 추가
  • DayCell에는 집계 데이터만 전달하여 배지/도트 렌더링, 모달 오픈 시에만 상세 쿼리(focus-fetch) 수행
  • React Query select 옵션으로 서버 응답 → displayItems 변환을 서버/쿼리 레벨에서 처리해 컴포넌트 로직 경량화
  • IntersectionObserver 또는 onOpen 시점에만 해당 날짜 상세 프리패치

이로써 초기 렌더 비용과 불필요한 병렬 요청을 크게 줄일 수 있습니다.


82-84: 거절 토스트 중복 노출 가능성 확인 (훅 내부 onSuccess와 핸들러 모두에서 호출)

현재 useReservationMutations 구현상 reject의 onSuccess와 handleReject 양쪽에서 showSuccess('거절되었습니다.')가 호출되어 토스트가 두 번 뜰 수 있습니다. 중복 호출 제거를 권장합니다.

훅 파일에서 onSuccess 또는 handleReject 한 쪽만 유지해 주세요. 예시(훅 파일 수정):

// useReservationMutations.ts (발췌)
const { mutate: reject, isPending: isRejecting } = useMutation({
  mutationFn: (variables: { reservationId: number }) =>
    updateReservationStatus({
      activityId: selectedActivityId!,
      reservationId: variables.reservationId,
      status: 'declined',
    }),
  onSuccess: () => {
    // showSuccess 제거: 핸들러에서만 노출
    queryClient.invalidateQueries({
      queryKey: ['allReservationsByDate', selectedActivityId, day.format('YYYY-MM-DD')],
    });
    queryClient.invalidateQueries({ queryKey: ['schedules'] });
    queryClient.invalidateQueries({ queryKey: ['reservationDashboard'] });
  },
  onError: (error) => console.error('거절 실패:', error),
});

// 핸들러는 그대로 유지
const handleReject = useCallback(
  (reservationId: number) => {
    if (isRejecting) return;
    reject({ reservationId });
    showSuccess('거절되었습니다.');
  },
  [reject, isRejecting, showSuccess],
);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between de1e848 and 0b5a063.

📒 Files selected for processing (10)
  • .vscode/settings.json (1 hunks)
  • src/domain/Reservation/components/reservation-calendar/DayCell.tsx (2 hunks)
  • src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx (1 hunks)
  • src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx (1 hunks)
  • src/domain/Reservation/components/reservation-calendar/ReservationModalContent.tsx (1 hunks)
  • src/domain/Reservation/hooks/useDayCellStyles.ts (1 hunks)
  • src/domain/Reservation/hooks/useModalState.ts (1 hunks)
  • src/domain/Reservation/hooks/useReservationCounts.ts (1 hunks)
  • src/domain/Reservation/hooks/useReservationMutations.ts (1 hunks)
  • src/domain/Reservation/hooks/useReservationQueries.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/domain/Reservation/hooks/useReservationMutations.ts (4)
src/domain/Reservation/types/reservation.ts (1)
  • ReservationItem (8-24)
src/shared/libs/queryClient.ts (1)
  • queryClient (14-24)
src/shared/hooks/useToast.ts (1)
  • useToast (29-50)
src/domain/Reservation/services/reservation-calendar/index.ts (1)
  • updateReservationStatus (109-133)
src/domain/Reservation/components/reservation-calendar/ReservationModalContent.tsx (3)
src/domain/Reservation/services/reservation-calendar/index.ts (1)
  • ScheduleItem (20-29)
src/domain/Reservation/types/reservation.ts (1)
  • ReservationItem (8-24)
src/domain/Reservation/components/reservation-calendar/ReservationDetail.tsx (1)
  • ReservationDetail (40-252)
src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx (1)
src/domain/Reservation/utils/reservation.ts (2)
  • getColorClassByStatus (25-33)
  • STATUS_LABELS (14-18)
src/domain/Reservation/hooks/useReservationCounts.ts (1)
src/domain/Reservation/services/reservation-calendar/index.ts (1)
  • ScheduleItem (20-29)
src/domain/Reservation/hooks/useReservationQueries.ts (2)
src/domain/Reservation/services/reservation-calendar/index.ts (3)
  • ScheduleItem (20-29)
  • getSchedulesByDate (64-79)
  • getReservationsBySchedule (87-107)
src/domain/Reservation/types/reservation.ts (1)
  • ReservationItem (8-24)
src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx (1)
src/domain/Reservation/hooks/useCalendar.ts (1)
  • useCalendar (10-70)
src/domain/Reservation/components/reservation-calendar/DayCell.tsx (7)
src/domain/Reservation/hooks/useModalState.ts (1)
  • useModalState (3-26)
src/domain/Reservation/hooks/useDayCellStyles.ts (1)
  • useDayCellStyles (11-38)
src/domain/Reservation/hooks/useReservationQueries.ts (1)
  • useReservationQueries (17-111)
src/domain/Reservation/hooks/useReservationMutations.ts (1)
  • useReservationMutations (19-115)
src/domain/Reservation/hooks/useReservationCounts.ts (1)
  • useReservationCounts (12-57)
src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx (1)
  • DayCellContent (22-72)
src/domain/Reservation/components/reservation-calendar/ReservationModalContent.tsx (1)
  • ReservationModalContent (34-134)
🔇 Additional comments (4)
src/domain/Reservation/hooks/useModalState.ts (1)

3-25: 훅 분리 및 콜백 안정화 구성, 전반적으로 좋습니다

불필요한 리렌더를 막기 위한 useCallback 사용과 최소 상태 노출(setter 포함) 구성 괜찮습니다. 기본값과 네이밍도 명확합니다.

src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx (1)

30-34: useCalendar 반환값 사용 정리 OK

이제 getReservationForDate를 사용하지 않고 today/days/prevMonth/nextMonth만 구조분해하여 DayCell에 위임하는 방향이 명확합니다. 상위 캘린더는 달 전환과 레이아웃에 집중하도록 잘 리팩토링되었습니다.

src/domain/Reservation/components/reservation-calendar/DayCell.tsx (2)

94-99: BottomSheet 제어형 사용 LGTM

open/onOpenChange로 제어형 상태를 유지하고 있어, 외부 상태와 동기화가 명확합니다.


103-112: Popover 제어형 사용 LGTM

Backdrop 포함, 포지션 지정 등 구성 적절합니다. 위에서 제안한 onClick 제거 후에도 Trigger가 열림을 잘 제어합니다.

Comment on lines +40 to +41
],
"claudeCodeChat.permissions.yoloMode": false
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

확인 필요: claudeCodeChat.permissions.yoloMode 설정 키의 유효성과 팀 합의

워크스페이스 설정에 확장 전용 키를 추가하면, 해당 확장이 미설치된 환경에서 “Unknown configuration setting” 경고가 나타날 수 있습니다. 또한 키 네임스페이스(claudeCodeChat)와 설정명(permissions.yoloMode)이 실제 확장 스키마와 일치하는지 확인이 필요합니다. 팀 차원의 표준이라면:

  • .vscode/extensions.json에 해당 확장을 추천 목록으로 추가
  • README/CONTRIBUTING에 설정 취지와 필요성 명시
    를 권장합니다. 개인 선호라면 User Settings로 이동하는 편이 낫습니다.

아래 두 가지 중 선택하세요.

  • 유지: 위 권장사항 반영 및 키 유효성 검증
  • 제거: 팀 표준이 아니라면 다음 패치로 제거

제거를 선택할 경우 제안 diff:

-  ],
-  "claudeCodeChat.permissions.yoloMode": false
+  ]

키 유효성 검증을 위해 웹 검색이 필요합니다. 아래 쿼리를 실행해 최신 문서를 확인해 주세요. 필요하면 제가 결과를 토대로 맞는 키로 수정 PR 제안 드리겠습니다.


🌐 Web query:

What are the valid VS Code settings keys for the "Claude Code" (or "Claude Dev/Chat") extension, specifically the configuration for "yolo mode" or similar permissions flags? Provide the canonical setting ID and example usage in settings.json.

💡 Result:

The canonical setting to enable "YOLO mode"—which skips all permission prompts and allows Claude Code to execute actions without interactive approval—is configured using the dangerously skip permissions flag. This is generally set at the CLI level, but can also be passed as a configuration option. The canonical key for this for VS Code (and related environments) is:

dangerouslySkipPermissions

Canonical Setting ID

  • Key: "dangerouslySkipPermissions"

Example Usage in settings.json

To enable YOLO mode for Claude Code in your VS Code settings.json, add:

{
  "claudeCode.dangerouslySkipPermissions": true
}

Context and Details

  • The official Anthropic documentation and engineering blog refer to YOLO mode as any configuration that uses the flag or setting allowing bypass of the usual permission checks[6][2][4].
  • The CLI flag is --dangerously-skip-permissions, and the corresponding config key in most environments is dangerouslySkipPermissions[6][2].
  • In VS Code and similar editors, the setting is typically namespaced with the extension ID (e.g., claudeCode) in settings.json.
  • Enabling this setting allows Claude to execute commands such as code edits, file operations, or running scripts without prompting you for each action[6][4].
  • Warning: Anthropic and community sources strongly recommend using this mode only within a sandboxed/dev container and note the significant risks (data loss, security) if used on your main system[6][2][4].

Other Relevant Configuration Info

  • You can also set this permission via the CLI directly:
    claude --dangerously-skip-permissions
    
    or, if using a Docker setup, via environment variable or command args.
  • For extra safety, you may want to combine "YOLO mode" with deny lists or additional confirmation requirements for destructive commands (e.g., rm, as discussed in user forums)[8].
  • There are no official or documented alternative keys such as "yoloMode"; the canonical flag is always centered on "dangerouslySkipPermissions" or --dangerously-skip-permissions[6][2][4].

Summary of Canonical Usage

Setting/Flag Where Used Example
"claudeCode.dangerouslySkipPermissions": true VS Code settings.json see above
--dangerously-skip-permissions CLI claude --dangerously-skip-permissions

Always use this setting with extreme caution and preferably only in isolated/test environments[6][2].

Citations:


조치 필요: 잘못된 설정 키 교체 또는 제거 권장

간단 요약: 제공된 문서 확인 결과 "claudeCodeChat.permissions.yoloMode"는 문서화된 설정이 아니며, YOLO(권한 프롬프트 무시) 관련 공식 키는 "claudeCode.dangerouslySkipPermissions"입니다. 이 옵션은 보안/데이터 손실 위험이 크므로 팀 합의·문서화가 필요합니다.

주의할 위치:

  • .vscode/settings.json — 현재 설정(약 라인 40-41)

권장 조치 (선택 중 택1):

  • 유지(권장 방식): 잘못된 키를 canonical 키로 교체하고 팀 합의 및 문서화
    • .vscode/settings.json에 "claudeCode.dangerouslySkipPermissions": false(또는 true·팀 결정)로 교체
    • .vscode/extensions.json에 권장 확장 추가(예: "anthropic.claude-code")
    • README/CONTRIBUTING에 사용 목적과 위험 고지 기록
  • 제거: 팀 표준이 아니라면 설정 항목을 삭제하여 불필요한 경고/혼란 제거

제안 diff (교체 예: 기본값 false 유지)

-  "claudeCodeChat.permissions.yoloMode": false
+  "claudeCode.dangerouslySkipPermissions": false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
],
"claudeCodeChat.permissions.yoloMode": false
],
"claudeCode.dangerouslySkipPermissions": false
🤖 Prompt for AI Agents
.vscode/settings.json around lines 40-41: the key
"claudeCodeChat.permissions.yoloMode" is not a documented setting and should be
replaced or removed; replace it with the canonical key
"claudeCode.dangerouslySkipPermissions" set to false (or true only after team
approval and risk documentation), or remove the entry entirely if not needed; if
keeping, also add the recommended extension (e.g., "anthropic.claude-code") to
.vscode/extensions.json and add a short note in README/CONTRIBUTING describing
the option purpose and the security/data-loss risks so the team can formally
approve the change.

Comment on lines +14 to +20
interface DayCellContentProps {
day: dayjs.Dayjs;
cellClasses: string;
dateClasses: string;
displayItems: DisplayItem[];
onClick: () => void;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Popover/BottomSheet Trigger와 onClick 중복으로 인한 이중 토글 가능성 제거

이 컴포넌트는 DayCell 쪽 Trigger(팝오버/바텀시트)로 감싸서 사용됩니다. 현재 셀 내부 div에도 onClick이 바인딩되어 있어, 라이브러리의 Trigger 동작과 중복되어 열림 상태 토글이 이중으로 발생할 수 있습니다. onClick을 선택적(props optional)으로 바꾸고 기본적으로는 바인딩을 제거하는 것을 권장합니다.

아래처럼 props를 optional로 변경하고, 컨테이너의 onClick을 제거해 주세요.

 interface DayCellContentProps {
   day: dayjs.Dayjs;
   cellClasses: string;
   dateClasses: string;
   displayItems: DisplayItem[];
-  onClick: () => void;
+  onClick?: () => void;
 }
 
 export default function DayCellContent({
   day,
   cellClasses,
   dateClasses,
   displayItems,
-  onClick,
 }: DayCellContentProps) {
   return (
     <div
       role='gridcell'
       aria-label={`${day.format('M월 D일')}`}
       className={cellClasses}
-      onClick={onClick}
     >

추가로, 사용하는 쪽(DayCell)에서는 Trigger가 열림 제어를 담당하므로 onClick 전달 자체를 제거하는 편이 안전합니다(아래 DayCell.tsx 코멘트 참고).

Also applies to: 30-35

🤖 Prompt for AI Agents
In src/domain/Reservation/components/reservation-calendar/DayCellContent.tsx
around lines 14-20 (and similarly lines 30-35), the onClick prop is currently
required and is bound to the internal container div causing duplicate toggle
when this component is wrapped by a Popover/BottomSheet Trigger; make onClick
optional in the DayCellContentProps type (e.g., onClick?: () => void) and remove
the onClick binding from the internal container div so the Trigger exclusively
controls opening/closing, and then update callers (DayCell) to stop passing an
onClick prop.

Comment on lines +64 to +85
// 4. '단일 거절' 로직을 처리하는 뮤테이션
const { mutate: reject, isPending: isRejecting } = useMutation({
mutationFn: (variables: { reservationId: number }) =>
updateReservationStatus({
activityId: selectedActivityId!,
reservationId: variables.reservationId,
status: 'declined',
}),
onSuccess: () => {
showSuccess('거절되었습니다.');
queryClient.invalidateQueries({
queryKey: [
'allReservationsByDate',
selectedActivityId,
day.format('YYYY-MM-DD'),
],
});
queryClient.invalidateQueries({ queryKey: ['schedules'] });
queryClient.invalidateQueries({ queryKey: ['reservationDashboard'] });
},
onError: (error) => console.error('거절 실패:', error),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

reject 뮤테이션의 중복된 성공 메시지

reject 뮤테이션의 onSuccesshandleReject 함수 모두에서 '거절되었습니다.' 토스트 메시지를 표시하고 있어 중복됩니다. 한 곳에서만 처리하도록 수정이 필요합니다.

  const { mutate: reject, isPending: isRejecting } = useMutation({
    mutationFn: (variables: { reservationId: number }) =>
      updateReservationStatus({
        activityId: selectedActivityId!,
        reservationId: variables.reservationId,
        status: 'declined',
      }),
    onSuccess: () => {
-      showSuccess('거절되었습니다.');
      queryClient.invalidateQueries({
        queryKey: [
          'allReservationsByDate',
          selectedActivityId,
          day.format('YYYY-MM-DD'),
        ],
      });
      queryClient.invalidateQueries({ queryKey: ['schedules'] });
      queryClient.invalidateQueries({ queryKey: ['reservationDashboard'] });
    },
    onError: (error) => console.error('거절 실패:', error),
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 4. '단일 거절' 로직을 처리하는 뮤테이션
const { mutate: reject, isPending: isRejecting } = useMutation({
mutationFn: (variables: { reservationId: number }) =>
updateReservationStatus({
activityId: selectedActivityId!,
reservationId: variables.reservationId,
status: 'declined',
}),
onSuccess: () => {
showSuccess('거절되었습니다.');
queryClient.invalidateQueries({
queryKey: [
'allReservationsByDate',
selectedActivityId,
day.format('YYYY-MM-DD'),
],
});
queryClient.invalidateQueries({ queryKey: ['schedules'] });
queryClient.invalidateQueries({ queryKey: ['reservationDashboard'] });
},
onError: (error) => console.error('거절 실패:', error),
});
// 4. '단일 거절' 로직을 처리하는 뮤테이션
const { mutate: reject, isPending: isRejecting } = useMutation({
mutationFn: (variables: { reservationId: number }) =>
updateReservationStatus({
activityId: selectedActivityId!,
reservationId: variables.reservationId,
status: 'declined',
}),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [
'allReservationsByDate',
selectedActivityId,
day.format('YYYY-MM-DD'),
],
});
queryClient.invalidateQueries({ queryKey: ['schedules'] });
queryClient.invalidateQueries({ queryKey: ['reservationDashboard'] });
},
onError: (error) => console.error('거절 실패:', error),
});
🤖 Prompt for AI Agents
In src/domain/Reservation/hooks/useReservationMutations.ts around lines 64 to
85, the '거절되었습니다.' toast is being shown both in the mutation's onSuccess and in
handleReject; remove the duplicate by deleting the showSuccess('거절되었습니다.') call
from the mutation's onSuccess so the mutation only performs side-effects
(invalidate queries) and let handleReject be the single place that displays the
toast after calling reject; ensure onSuccess still invalidates the same queries
and onError remains unchanged.

@github-actions
Copy link

✅ Preview Deployment Ready!

🔗 Preview URL: https://roam-ready-mt694yvgb-yongmins-projects-bf5f7733.vercel.app
📝 Branch: refactor/549
💾 Commit: Refactor: DayCell 컴포넌트 분리
🕐 Deployed at:


This preview will be automatically updated on new commits.

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

🔭 Outside diff range comments (3)
src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx (3)

46-47: enabled의 Truthy 캐스팅(!!id)은 0 ID를 비활성화합니다 — null 체크로 변경 권장

ID가 0일 수 있는 도메인이라면 현재 조건으로는 쿼리가 비활성화됩니다. 보다 안전하게 null 여부로만 가드하세요.

-    enabled: !!selectedActivityId,
+    enabled: selectedActivityId !== null,

25-47: [권고] TanStack Query v5 사용—이전 데이터 유지(스켈레톤 깜빡임 완화) 적용 권장

프로젝트의 @tanstack/react-query 버전이 5.81.5(v5)로 확인되었습니다.(package.json: /home/jailuser/git/package.json: 5.81.5)

간단 설명: 월/액티비티 변경 시 이전 데이터를 유지하면서 백그라운드에서 재검증하면 스켈레톤 깜빡임을 줄일 수 있습니다. v5에서는 keepPreviousData 유틸을 placeholderData 옵션으로 사용합니다.

수정 포인트:

  • 파일: src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx
  • 위치: useQuery 호출(현재 코드 블록: lines 25-47)

권장 변경(diff):

   } = useQuery({
     queryKey: [
       'reservationDashboard',
       selectedActivityId,
       currentDate.year(),
       currentDate.month(),
     ],
     queryFn: () => {
       const year = currentDate.year();
       const month = currentDate.format('MM');

       return getReservationDashboard(
         selectedActivityId!.toString(),
         year.toString(),
         month,
       );
     },
     enabled: !!selectedActivityId,
+    // v5: 이전 데이터 유지하여 스켈레톤 깜빡임 완화
+    placeholderData: keepPreviousData,
+    // 선택: 빈번한 재검증을 줄이려면 적절한 staleTime 설정 (예: 30초)
+    staleTime: 30_000,
   });

추가 import(파일 상단):

import { useQuery, keepPreviousData } from '@tanstack/react-query';

태그:


69-73: selectedId에 0을 센티널로 넘기는 패턴 점검 — ReservationSelect의 selectedId가 non-nullable입니다

검증 결과: ReservationSelect의 props 선언이 selectedId: number (필수)로 되어 있어, selectedActivityId ?? undefined로 바로 바꾸면 타입 오류가 발생합니다. 따라서 수정(또는 의도 명시)이 필요합니다.

수정 제안 (권장)

  • ReservationSelect의 selectedId를 optional로 변경하고 Dashboard에서 undefined를 넘겨 미선택 상태를 명확히 전달합니다.

주요 위치

  • src/domain/Reservation/components/reservation-calendar/ReservationSelect.tsx — selectedId: number (현재)
  • src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx — selectedId={selectedActivityId ?? 0} (현재)
  • 참고: src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx는 selectedActivityId를 number로 기대(현재 Dashboard는 해당 컴포넌트에 0을 계속 전달함)

권장 변경 예 (간결한 diff)

  • ReservationSelect.tsx
-  selectedId: number;
+  selectedId?: number;
  • ReservationDashboard.tsx
-          selectedId={selectedActivityId ?? 0}
+          selectedId={selectedActivityId ?? undefined}

대안(덜 권장)

  • ReservationSelect 타입을 그대로 두고 0을 계속 센티널로 사용하되(현재 상태), 0이 실제 유효 ID가 아님을 코드/주석으로 명확히 하고 다른 호출부가 없는지 확인.

조치 필요: ReservationSelect 타입 변경 후 다른 호출부(있다면)를 업데이트/검토해 주세요.

🧹 Nitpick comments (1)
src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx (1)

31-35: queryKey의 month는 0-베이스, queryFn은 'MM'(1-베이스) — 표현 통일 권장(가독성/추후 유지보수 목적)

동작상 문제는 없지만, 캐시 키와 fetch 인자를 동일한 포맷으로 유지하면 혼동을 줄일 수 있습니다.

     queryKey: [
       'reservationDashboard',
       selectedActivityId,
-      currentDate.year(),
-      currentDate.month(),
+      currentDate.format('YYYY'),
+      currentDate.format('MM'),
     ],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b5a063 and f03b985.

📒 Files selected for processing (2)
  • src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx (1 hunks)
  • src/domain/Reservation/components/reservation-calendar/ReservationSkeleton.tsx (0 hunks)
💤 Files with no reviewable changes (1)
  • src/domain/Reservation/components/reservation-calendar/ReservationSkeleton.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx (2)
src/domain/Activity/components/detail/skeleton/ReservationSkeleton.tsx (1)
  • ReservationSkeleton (1-70)
src/domain/Reservation/components/reservation-calendar/ReservationCalendar.tsx (1)
  • ReservationCalendar (19-74)
🔇 Additional comments (1)
src/domain/Reservation/components/reservation-calendar/ReservationDashboard.tsx (1)

75-84: 로딩 UI를 인라인으로 이동한 변경, 레이아웃 점프 없이 자연스러워졌습니다

캘린더 영역 내에서 스켈레톤을 토글하는 방식으로 초기 로딩 UX가 좋아졌습니다.

@Yongmin0423 Yongmin0423 merged commit 3f096de into main Aug 14, 2025
2 checks passed
@Yongmin0423 Yongmin0423 deleted the refactor/549 branch August 14, 2025 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants