From a000128a94394056d7348d44028f69386a82b714 Mon Sep 17 00:00:00 2001 From: johe00123 Date: Mon, 11 May 2026 17:35:54 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=98=88=EC=95=BD=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=EC=97=90=20=EB=B0=A9=EB=AC=B8=20=ED=99=95=EC=A0=95=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - markReservationVisited API 호출 함수 추가 - useMarkVisitedMutation React Query 훅 추가 - 방문 예정 탭 카드에 [방문 확정] 버튼 + 클릭 핸들러 추가 - 성공 시 토스트 + 예약 목록 자동 새로고침 --- src/app/reservations/page.tsx | 50 ++++++++++++++++++++++++----------- src/lib/reservationApi.ts | 8 ++++++ src/lib/reservationQuery.ts | 14 +++++++++- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/app/reservations/page.tsx b/src/app/reservations/page.tsx index 1abf4de..4bc1762 100644 --- a/src/app/reservations/page.tsx +++ b/src/app/reservations/page.tsx @@ -19,7 +19,7 @@ import BottomSheet from '@/components/common/BottomSheet'; import Tabs from '@/components/common/Tabs'; import LoginRequired from '@/components/common/LoginRequired'; import { formatDate } from '@/lib/utils'; -import { useReservationsQuery, useCancelReservationMutation } from '@/lib/reservationQuery'; +import { useReservationsQuery, useCancelReservationMutation, useMarkVisitedMutation } from '@/lib/reservationQuery'; import { useCreateReviewMutation, useMyReviewsQuery, @@ -48,12 +48,14 @@ const STATUS_CONFIG: Record< function ReservationCard({ reservation, onCancel, + onMarkVisited, onWriteReview, onEditReview, isReviewed, }: { reservation: Reservation; onCancel: (id: number) => void; + onMarkVisited: (id: number) => void; onWriteReview: (reservation: Reservation) => void; onEditReview: (reservation: Reservation) => void; isReviewed: boolean; @@ -105,23 +107,31 @@ function ReservationCard({ {/* 액션 버튼 */} {isUpcoming && ( -
+
- +
+ + +
)} @@ -166,6 +176,7 @@ export default function ReservationsPage() { const userId = useAuthStore((s) => s.userId) ?? 0; const { data: reservations = [], isLoading } = useReservationsQuery(); const { mutate: cancelReservation } = useCancelReservationMutation(); + const { mutate: markVisited } = useMarkVisitedMutation(); const { data: vacancies = [], isLoading: isVacancyLoading } = useMyVacanciesQuery(); const { mutate: cancelVacancy } = useCancelVacancyMutation(); const [cancelTarget, setCancelTarget] = useState(null); @@ -199,6 +210,14 @@ export default function ReservationsPage() { setCancelTarget(id); }; + const handleMarkVisited = (id: number) => { + markVisited(id, { + onSuccess: () => { + toast.success('방문 처리되었습니다.'); + }, + }); + }; + const confirmCancel = () => { if (cancelTarget === null) return; cancelReservation(cancelTarget, { @@ -370,6 +389,7 @@ export default function ReservationsPage() { key={reservation.id} reservation={reservation} onCancel={handleCancel} + onMarkVisited={handleMarkVisited} onWriteReview={openReviewModal} onEditReview={openEditReviewModal} isReviewed={reviewedReservationIds.has(reservation.id)} diff --git a/src/lib/reservationApi.ts b/src/lib/reservationApi.ts index f95e381..640dcc0 100644 --- a/src/lib/reservationApi.ts +++ b/src/lib/reservationApi.ts @@ -85,3 +85,11 @@ export const updateReservation = async ( const response = await api.patch(`/reservations/${reservationId}`, payload); return unwrap(response, {} as Reservation); }; + +/** + * 사용자가 직접 "방문 확정" 버튼을 눌러 예약을 VISITED 상태로 전환한다. + * 백엔드는 CONFIRMED 상태에서만 허용하며 ReservationVisitedEvent로 알림이 자동 발송된다. + */ +export const markReservationVisited = async (reservationId: number): Promise => { + await api.patch(`/reservations/${reservationId}/visit`); +}; diff --git a/src/lib/reservationQuery.ts b/src/lib/reservationQuery.ts index c91b560..21d1b7e 100644 --- a/src/lib/reservationQuery.ts +++ b/src/lib/reservationQuery.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useAuthStore } from '@/stores/authStore'; -import { createReservation, getReservations, cancelReservation, updateReservation } from './reservationApi'; +import { createReservation, getReservations, cancelReservation, updateReservation, markReservationVisited } from './reservationApi'; import type { ReservationRequest, ReservationUpdateRequest } from './reservationApi'; const reservationsKey = (userId: number | null) => ['reservations', userId]; @@ -55,3 +55,15 @@ export const useCancelReservationMutation = () => { }, }); }; + +export const useMarkVisitedMutation = () => { + const queryClient = useQueryClient(); + const userId = useAuthStore((s) => s.userId); + + return useMutation({ + mutationFn: (reservationId: number) => markReservationVisited(reservationId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: reservationsKey(userId) }); + }, + }); +}; From 05bed95ebc52b94006dc0bf972f13bbedde270d4 Mon Sep 17 00:00:00 2001 From: johe00123 Date: Tue, 12 May 2026 22:45:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EB=B0=A9=EB=AC=B8=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=20=EB=B2=84=ED=8A=BC=20=EC=A4=91=EB=B3=B5=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 진행 중 useMarkVisitedMutation의 isPending으로 버튼 비활성화 - disabled 스타일 및 "처리 중..." 텍스트로 시각적 피드백 추가 - 코드 리뷰 피드백 반영 --- src/app/reservations/page.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app/reservations/page.tsx b/src/app/reservations/page.tsx index 4bc1762..abcd270 100644 --- a/src/app/reservations/page.tsx +++ b/src/app/reservations/page.tsx @@ -49,6 +49,7 @@ function ReservationCard({ reservation, onCancel, onMarkVisited, + isMarkingVisited, onWriteReview, onEditReview, isReviewed, @@ -56,6 +57,7 @@ function ReservationCard({ reservation: Reservation; onCancel: (id: number) => void; onMarkVisited: (id: number) => void; + isMarkingVisited: boolean; onWriteReview: (reservation: Reservation) => void; onEditReview: (reservation: Reservation) => void; isReviewed: boolean; @@ -108,12 +110,15 @@ function ReservationCard({ {/* 액션 버튼 */} {isUpcoming && (
- + {reservation.status === 'CONFIRMED' && ( + + )}