-
Notifications
You must be signed in to change notification settings - Fork 3
Refactor: DayCell 컴포넌트 분리 #550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough예약 캘린더의 DayCell를 훅 기반 아키텍처로 전면 리팩터링하고 UI를 프레젠테이셔널 컴포넌트로 분리했습니다. 스케줄/예약 조회·집계·변경 로직을 전용 훅들로 이동했고, useCalendar 반환값에서 날짜별 예약 조회 함수가 제거되었으며 VSCode 설정에 신규 플래그가 추가되었습니다. Changes
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: 최신 카운트/리스트 반영
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Suggested labels
Suggested reviewers
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
✅ Preview Deployment Ready!🔗 Preview URL: https://roam-ready-dad1orn0e-yongmins-projects-bf5f7733.vercel.app This preview will be automatically updated on new commits. |
There was a problem hiding this 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:confirmed와declined탭에서 불필요한 핸들러 전달승인된 예약과 거절된 예약 탭에서는
showApprovalButton과showRejectButton이 모두false로 설정되어 있는데도handleApprove와handleReject핸들러를 전달하고 있습니다. 불필요한 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.
📒 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 제어형 사용 LGTMopen/onOpenChange로 제어형 상태를 유지하고 있어, 외부 상태와 동기화가 명확합니다.
103-112: Popover 제어형 사용 LGTMBackdrop 포함, 포지션 지정 등 구성 적절합니다. 위에서 제안한 onClick 제거 후에도 Trigger가 열림을 잘 제어합니다.
| ], | ||
| "claudeCodeChat.permissions.yoloMode": false |
There was a problem hiding this comment.
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 isdangerouslySkipPermissions[6][2]. - In VS Code and similar editors, the setting is typically namespaced with the extension ID (e.g.,
claudeCode) insettings.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:
or, if using a Docker setup, via environment variable or command args.
claude --dangerously-skip-permissions - 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:
- 1: https://docs.anthropic.com/en/docs/claude-code/ide-integrations
- 2: https://apidog.com/blog/claude-code-gemini-yolo-mode/
- 3: https://www.youtube.com/watch?v=YjW8K-bBcZY
- 4: https://www.youtube.com/watch?v=8dqqa0dLpGU
- 5: https://www.builder.io/blog/claude-code
- 6: https://www.anthropic.com/engineering/claude-code-best-practices
- 7: https://marketplace.visualstudio.com/items?itemName=CodeontheRocks.claude-config
- 8: https://forum.cursor.com/t/yolo-mode-is-amazing/36262
- 9: https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code
- 10: https://www.youtube.com/watch?v=n7iT5r0Sl_Y
조치 필요: 잘못된 설정 키 교체 또는 제거 권장
간단 요약: 제공된 문서 확인 결과 "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.
| ], | |
| "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.
| interface DayCellContentProps { | ||
| day: dayjs.Dayjs; | ||
| cellClasses: string; | ||
| dateClasses: string; | ||
| displayItems: DisplayItem[]; | ||
| onClick: () => void; | ||
| } |
There was a problem hiding this comment.
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.
| // 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), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reject 뮤테이션의 중복된 성공 메시지
reject 뮤테이션의 onSuccess와 handleReject 함수 모두에서 '거절되었습니다.' 토스트 메시지를 표시하고 있어 중복됩니다. 한 곳에서만 처리하도록 수정이 필요합니다.
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.
| // 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.
✅ Preview Deployment Ready!🔗 Preview URL: https://roam-ready-mt694yvgb-yongmins-projects-bf5f7733.vercel.app This preview will be automatically updated on new commits. |
There was a problem hiding this 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.
📒 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가 좋아졌습니다.
👻 관련 이슈 번호
👻 요약
👻 주요 변경 사항
👻 체크리스트
📷 UI 변경 사항
👻 문제 사항
👻 논의 사항
👻 기타 참고 사항
Summary by CodeRabbit
신기능
리팩터
작업
UX