diff --git a/src/app/reservations/page.tsx b/src/app/reservations/page.tsx index 1abf4de..abcd270 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,16 @@ const STATUS_CONFIG: Record< function ReservationCard({ reservation, onCancel, + onMarkVisited, + isMarkingVisited, onWriteReview, onEditReview, isReviewed, }: { reservation: Reservation; onCancel: (id: number) => void; + onMarkVisited: (id: number) => void; + isMarkingVisited: boolean; onWriteReview: (reservation: Reservation) => void; onEditReview: (reservation: Reservation) => void; isReviewed: boolean; @@ -105,23 +109,34 @@ function ReservationCard({ {/* 액션 버튼 */} {isUpcoming && ( -
- - +
+ {reservation.status === 'CONFIRMED' && ( + + )} +
+ + +
)} @@ -166,6 +181,7 @@ export default function ReservationsPage() { const userId = useAuthStore((s) => s.userId) ?? 0; const { data: reservations = [], isLoading } = useReservationsQuery(); const { mutate: cancelReservation } = useCancelReservationMutation(); + const { mutate: markVisited, isPending: isMarkingVisited } = useMarkVisitedMutation(); const { data: vacancies = [], isLoading: isVacancyLoading } = useMyVacanciesQuery(); const { mutate: cancelVacancy } = useCancelVacancyMutation(); const [cancelTarget, setCancelTarget] = useState(null); @@ -199,6 +215,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 +394,8 @@ export default function ReservationsPage() { key={reservation.id} reservation={reservation} onCancel={handleCancel} + onMarkVisited={handleMarkVisited} + isMarkingVisited={isMarkingVisited} onWriteReview={openReviewModal} onEditReview={openEditReviewModal} isReviewed={reviewedReservationIds.has(reservation.id)} diff --git a/src/lib/reservationApi.ts b/src/lib/reservationApi.ts index 5364cd9..1bbccac 100644 --- a/src/lib/reservationApi.ts +++ b/src/lib/reservationApi.ts @@ -92,3 +92,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) }); + }, + }); +};