[🐛Fix] QA(8/29) 반영#202
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughReact Query 훅(useReservationsByActivity)으로 최대 12개월 예약을 집계하고, 날짜 클릭 시 해당 일자 예약에서 scheduleId 배열을 추출해 상태로 관리하도록 캘린더 데이터 흐름을 변경했으며, 검색 상태를 Zustand(store)로 통합하고 드롭다운에 close 콜백 패턴을 도입했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant RC as ReserveCalendarPage
participant Q as useReservationsByActivity (React Query)
participant API as getReservationsByMonthApi
participant CR as ContentReservation
U->>RC: 액티비티 선택
RC->>Q: selectedActivityId, month로 쿼리 시작
loop 최대 12개월
Q->>API: 월별 예약 조회
API-->>Q: MonthReservations
end
Q-->>RC: { allReservations, earliestDate }
RC->>RC: 캘린더 데이터 및 month/year 업데이트
U->>RC: 특정 날짜 클릭
RC->>API: 해당 날짜 예약 조회
API-->>RC: 일자 예약 리스트
RC->>RC: unique scheduleId[] 추출 및 저장
RC->>CR: scheduleId[] 전달
sequenceDiagram
participant U as 사용자
participant S as Search 컴포넌트
participant Z as useSearchStore (Zustand)
participant H as Header
U->>S: 키워드/지역/카테고리 입력
S->>Z: setKeyword/setRegion/setCategory
U->>S: 드롭다운 옵션 선택
S->>S: 선택 후 close() 호출
U->>H: 로고 클릭
H->>Z: reset()
H->>H: router.push('/activities')
sequenceDiagram
participant U as 사용자
participant A as AllActivities
participant D as Dropdown (render-prop)
U->>A: 정렬 드롭다운 열기
A->>D: children(fn close)
U->>D: 정렬 옵션 클릭
D->>A: handleSortChange()
D->>D: close()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
✨ 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 (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/features/activities/libs/stores/searchStore.ts (1)
13-21: 입력 정규화(트림)로 상태 일관성 확보검색값 앞뒤 공백으로 인한 불필요한 파라미터 변형을 막기 위해 setter에서 trim을 적용하는 것을 권장합니다.
- setKeyword: (value) => set({ keyword: value }), - setRegion: (value) => set({ region: value }), - setCategory: (value) => set({ category: value }), + setKeyword: (value) => set({ keyword: value.trim() }), + setRegion: (value) => set({ region: value.trim() }), + setCategory: (value) => set({ category: value.trim() }),선택 사항: 초기 상태를 상수로 분리해 reset 재사용성을 높일 수 있습니다.
const initialState = { keyword: '', region: '', category: '' } as const; // ... reset: () => set(initialState),src/features/activities/components/search.tsx (3)
23-25: Zustand 사용 패턴 개선 및 URL→스토어 동기화 제안
- 전체 스토어 구독 대신 셀렉터를 사용하면 불필요한 리렌더를 줄일 수 있습니다.
- /activities?keyword=... 등으로 진입 시 검색창에 현재 쿼리가 비어 보이는 불일치를 방지하려면 초기 마운트에서 URL 파라미터를 스토어로 동기화하세요.
셀렉터 사용 예:
const keyword = useSearchStore((s) => s.keyword); const setKeyword = useSearchStore((s) => s.setKeyword); const region = useSearchStore((s) => s.region); const setRegion = useSearchStore((s) => s.setRegion); const category = useSearchStore((s) => s.category); const setCategory = useSearchStore((s) => s.setCategory);URL→스토어 1회 동기화 예:
useEffect(() => { const k = searchParams.get('keyword') ?? ''; const r = searchParams.get('region') ?? ''; const c = searchParams.get('category') ?? ''; // 이미 값이 있는 경우는 덮어쓰지 않도록 가드 if (!keyword && (k || r || c)) { setKeyword(k); setRegion(r); setCategory(c); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 최초 진입시에만원하시면 제가 반영 PR 패치를 작성해 드리겠습니다.
110-124: 옵션 선택 시 닫힘 처리: 좋습니다. 접근성 힌트 추가 제안현재 동작은 적절합니다. 선택 상태 노출을 위해 aria-pressed를 추가하면 보조기기 호환성이 좋아집니다.
- <button + <button key={option} type="button" className={optionBtnClass} + aria-pressed={region === option} onClick={() => { setRegion(option); close(); }} >
149-163: 카테고리 옵션도 동일하게 접근성 속성 적용 권장region과 동일하게 aria-pressed를 추가하면 좋습니다.
- <button + <button key={option} type="button" className={optionBtnClass} + aria-pressed={category === option} onClick={() => { setCategory(option); close(); }} >
📜 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 sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
src/app/my/reserve-calendar/page.tsx(6 hunks)src/features/activities/components/all-activities.tsx(1 hunks)src/features/activities/components/search.tsx(4 hunks)src/features/activities/libs/stores/searchStore.ts(1 hunks)src/features/reservation-state/components/content-reservation.tsx(3 hunks)src/shared/components/header/header.tsx(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/features/reservation-state/components/content-reservation.tsx (2)
src/features/booking-detail/components/booking-card.tsx (1)
BookingCardProps(42-133)src/shared/components/calendar/components/calendar-with-reservations.tsx (1)
reservationArray(38-195)
src/features/activities/components/all-activities.tsx (2)
src/features/activities/libs/constants/activitiesConstants.ts (1)
SORT_OPTIONS(19-23)src/features/activityId/components/owner-drop-down.tsx (3)
close(46-68)router(49-52)ownerId(11-90)
src/features/activities/components/search.tsx (3)
src/features/activities/libs/stores/searchStore.ts (1)
useSearchStore(13-21)src/features/activities/components/search-result.tsx (1)
searchParams(10-89)src/app/activities/page-content.tsx (1)
searchParams(25-62)
src/shared/components/header/header.tsx (1)
src/features/activities/libs/stores/searchStore.ts (1)
useSearchStore(13-21)
src/features/activities/libs/stores/searchStore.ts (3)
src/features/my/my-activities/lib/stores/useActivityIdStore.ts (2)
ActivityIdState(2-5)set(8-11)src/features/activityId/libs/stores/useScheduleIdStore.ts (1)
set(10-13)src/shared/components/calendar/libs/stores/useCalendarStore.ts (1)
set(20-37)
src/app/my/reserve-calendar/page.tsx (6)
src/shared/types/reservation.ts (1)
Reservation(12-27)src/shared/components/calendar/libs/types/data.ts (1)
MonthReservations(1-15)src/features/activities/libs/api/getReserveMonthApi.ts (1)
getReservationsByMonthApi(17-46)src/features/auth/stores/useAuthStore.ts (1)
useAuthStore(26-66)src/features/activities/libs/api/getReserveDayApi.ts (1)
getReservations(1-23)src/shared/components/calendar/components/calendar-with-reservations.tsx (1)
reservationArray(38-195)
🔇 Additional comments (14)
src/features/activities/components/all-activities.tsx (1)
87-108: 드롭다운 render-prop 전환으로 선택 즉시 닫힘 UX 정착: 좋습니다.close 콜백을 통해 정렬 옵션 선택 후 드롭다운을 닫는 흐름이 일관되고 명확합니다.
src/shared/components/header/header.tsx (4)
10-10: 검색 스토어 연동: 적절합니다.헤더에서 전역 검색 상태를 리셋하는 의도가 명확합니다.
31-31: reset 셀렉터 사용: 적절합니다.필요한 액션만 셀렉트하여 불필요한 리렌더를 줄입니다.
132-132: 로그인 버튼 커서 포인터 적용: OK클릭 가능성 시각화가 개선되었습니다.
143-143: 회원가입 버튼 커서 포인터 적용: OK일관된 인터랙션 피드백입니다.
src/features/activities/components/search.tsx (2)
75-75: 드롭다운 트리거에 cursor-pointer 추가: OK클릭 가능성 피드백이 개선되었습니다.
81-81: 옵션 버튼에 cursor-pointer 추가: OK일관된 호버/포인터 경험입니다.
src/features/reservation-state/components/content-reservation.tsx (3)
74-78: 날짜 필터링 로직이 정확합니다선택한 날짜와 일치하는 예약만 필터링하여
onlySelectedDate에 저장하고 이를 상태로 설정하는 로직이 올바르게 구현되었습니다.
80-86: 시간 슬롯 생성 로직 개선됨날짜별로 필터링된 예약 데이터(
onlySelectedDate)를 기반으로 시간 슬롯을 생성하도록 변경되어, 선택한 날짜의 시간대만 정확하게 표시됩니다.
211-215: 드롭다운 옵션 렌더링 일관성 개선
timeSlot변수명을slot으로 변경하여 코드의 일관성이 향상되었습니다.src/app/my/reserve-calendar/page.tsx (4)
37-72: React Query 훅 구현이 효율적입니다
useReservationsByActivity훅이 최대 12개월간의 예약 데이터를 집계하여 반환하는 로직이 잘 구현되었습니다. 가장 빠른 날짜를 찾으면 즉시 중단하는 최적화도 적용되어 있습니다.
130-155: 날짜 클릭 핸들러의 스케줄 ID 처리 개선선택한 날짜의 예약을 조회하여 고유한 스케줄 ID들을 추출하는 로직이 올바르게 구현되었습니다. 에러 처리와 빈 배열 초기화도 적절합니다.
269-269: scheduleId prop 타입 변경 확인됨
ContentReservation컴포넌트에 전달되는scheduleId가 배열로 변경되어 여러 시간대의 예약을 올바르게 처리할 수 있게 되었습니다.
158-172: 무한 루프 발생하지 않음 확인됨
setYear와setMonth는 Zustand store에서 생성된 안정적 레퍼런스를 가지므로, 의존성 배열에 포함되어도 effect는data변경 시에만 실행됩니다.
| <button | ||
| key={value} | ||
| onClick={() => { | ||
| handleSortChange( | ||
| value as 'latest' | 'price_asc' | 'price_desc', | ||
| ); | ||
| close(); | ||
| }} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
button 기본 type 지정 및 불필요한 타입 단언 제거 제안
- form 문맥에서의 암묵적 submit 방지를 위해 type="button"을 명시해 주세요.
- value as 'latest' | 'price_asc' | 'price_desc' 캐스트는 상수 타입을 제대로 선언하면 불필요합니다.
[issetential_refactor_typo]
다음 diff로 버튼 type을 추가하세요:
- <button
+ <button type="button"
key={value}
onClick={() => {
- handleSortChange(
- value as 'latest' | 'price_asc' | 'price_desc',
- );
+ handleSortChange(value);
close();
}}추가로, 캐스트 제거를 위해 상수에 literal 타입을 부여하세요(다른 파일 수정):
activitiesConstants.ts
export const SORT_OPTIONS = [
{ label: '최신순', value: 'latest' },
{ label: '낮은 가격순', value: 'price_asc' },
{ label: '높은 가격순', value: 'price_desc' },
] as const;
export type SortValue = typeof SORT_OPTIONS[number]['value'];그리고 본 컴포넌트에서 상태와 핸들러의 타입을 정교화하면 캐스트가 사라집니다:
const [selectedSort, setSelectedSort] = useState<SortValue>('latest');
const handleSortChange = (value: SortValue) => { ... };| <Link | ||
| href="/activities" | ||
| className="flex items-center gap-3" | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| resetSearch(); | ||
| router.push('/activities'); | ||
| }} | ||
| > |
There was a problem hiding this comment.
🛠️ Refactor suggestion
로고 클릭 시 기본 동작 방지로 새 탭 열기 불가(UX 회귀) — 수정 권장
e.preventDefault() + router.push()로 인해 Cmd/Ctrl+Click, 마우스 중클릭 등 새 탭 열기가 막힙니다. Link 고유 내비게이션은 유지하고, 보조키/중클릭 시에는 리셋을 수행하지 않도록 가드하세요.
- <Link
+ <Link
href="/activities"
className="flex items-center gap-3"
- onClick={(e) => {
- e.preventDefault();
- resetSearch();
- router.push('/activities');
- }}
+ onClick={(e) => {
+ // 보조키/중클릭은 현재 탭 상태를 건드리지 않음
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;
+ resetSearch();
+ // Link가 라우팅을 처리하므로 preventDefault/router.push 불필요
+ }}
>📝 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.
| <Link | |
| href="/activities" | |
| className="flex items-center gap-3" | |
| onClick={(e) => { | |
| e.preventDefault(); | |
| resetSearch(); | |
| router.push('/activities'); | |
| }} | |
| > | |
| <Link | |
| href="/activities" | |
| className="flex items-center gap-3" | |
| onClick={(e) => { | |
| // 보조키/중클릭은 현재 탭 상태를 건드리지 않음 | |
| if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return; | |
| resetSearch(); | |
| // Link가 라우팅을 처리하므로 preventDefault/router.push 불필요 | |
| }} | |
| > |
🤖 Prompt for AI Agents
In src/shared/components/header/header.tsx around lines 47 to 55, the current
onClick handler calls e.preventDefault() and router.push(), which blocks
Cmd/Ctrl+Click and middle‑click new tab behavior; change the handler to allow
default Link behavior and only call resetSearch() for a plain left click (guard
with e.button === 0 and !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey),
and remove the preventDefault() and router.push() calls so that modifier/new‑tab
navigation works while still resetting search on a normal click.
✨ 요약
📝 상세 내용
✅ 체크리스트
💡 추가 수정 사항
Summary by CodeRabbit