diff --git a/frontend/src/api/reservation/reservationAPI.ts b/frontend/src/api/reservation/reservationAPI.ts index f2a80d5e..2f3127ff 100644 --- a/frontend/src/api/reservation/reservationAPI.ts +++ b/frontend/src/api/reservation/reservationAPI.ts @@ -4,7 +4,7 @@ import type { PendingReservationCreateRequest, PendingReservationCreateResponse, ReservationDetailRetrieveResponse, - ReservationSummaryRetrieveListResponse + ReservationOverviewListResponse } from './reservationTypes'; export const createPendingReservation = async (request: PendingReservationCreateRequest): Promise => { @@ -20,8 +20,8 @@ export const cancelReservation = async (id: string, cancelReason: string): Promi return await apiClient.post(`/reservations/${id}/cancel`, { cancelReason }, true); }; -export const fetchSummaryByMember = async (): Promise => { - return await apiClient.get('/reservations/summary'); +export const fetchSummaryByMember = async (): Promise => { + return await apiClient.get('/reservations/summary'); } export const fetchDetailById = async (reservationId: string): Promise => { diff --git a/frontend/src/api/reservation/reservationTypes.ts b/frontend/src/api/reservation/reservationTypes.ts index 0de72737..6ffd2a72 100644 --- a/frontend/src/api/reservation/reservationTypes.ts +++ b/frontend/src/api/reservation/reservationTypes.ts @@ -46,30 +46,38 @@ export interface PendingReservationCreateResponse { id: string } -export interface ReservationSummaryRetrieveResponse { +export interface ReservationOverviewResponse { id: string; + storeName: string; themeName: string; date: string; - startAt: string; + startFrom: string; + endAt: string; status: ReservationStatus; } -export interface ReservationSummaryRetrieveListResponse { - reservations: ReservationSummaryRetrieveResponse[]; +export interface ReservationOverviewListResponse { + reservations: ReservationOverviewResponse[]; +} + +export interface ReserverInfo { + name: string; + contact: string; + participantCount: number; + requirement: string; } export interface ReservationDetailRetrieveResponse { id: string; + reserver: ReserverInfo; user: UserContactRetrieveResponse; applicationDateTime: string; payment: PaymentRetrieveResponse; } export interface ReservationDetail { - id: string; - themeName: string; - date: string; - startAt: string; + overview: ReservationOverviewResponse; + reserver: ReserverInfo; user: UserContactRetrieveResponse; applicationDateTime: string; payment?: PaymentRetrieveResponse; @@ -77,4 +85,4 @@ export interface ReservationDetail { export interface MostReservedThemeIdListResponse { themeIds: string[]; -} \ No newline at end of file +} diff --git a/frontend/src/api/user/userAPI.ts b/frontend/src/api/user/userAPI.ts index 84a2422d..67236d75 100644 --- a/frontend/src/api/user/userAPI.ts +++ b/frontend/src/api/user/userAPI.ts @@ -2,9 +2,9 @@ import apiClient from "@_api/apiClient"; import type {UserContactRetrieveResponse, UserCreateRequest, UserCreateResponse} from "./userTypes"; export const signup = async (data: UserCreateRequest): Promise => { - return await apiClient.post('/users', data, false); + return await apiClient.post('/users', data); }; export const fetchContact = async (): Promise => { - return await apiClient.get('/users/contact', true); + return await apiClient.get('/users/contact'); } diff --git a/frontend/src/css/my-reservation-v2.css b/frontend/src/css/my-reservation-v2.css index 7f3e5b93..c0a1931c 100644 --- a/frontend/src/css/my-reservation-v2.css +++ b/frontend/src/css/my-reservation-v2.css @@ -49,10 +49,24 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } +.summary-subdetails-v2 { + display: flex; + flex-direction: column; + margin: 0px; + gap: 0px; +} + +.summary-store-name-v2 { + font-size: 16px; + font-weight: bold; + color: #505a67; + margin: 0 0 5px 0; +} + .summary-details-v2 { display: flex; flex-direction: column; - gap: 4px; + gap: 10px; } .summary-theme-name-v2 { @@ -65,7 +79,7 @@ .summary-datetime-v2 { font-size: 16px; color: #505a67; - margin: 0; + margin-bottom: 5px; } /* --- Status Badge --- */ diff --git a/frontend/src/pages/MyReservationPage.tsx b/frontend/src/pages/MyReservationPage.tsx index 84000f53..9fc7cb0d 100644 --- a/frontend/src/pages/MyReservationPage.tsx +++ b/frontend/src/pages/MyReservationPage.tsx @@ -1,17 +1,18 @@ -import {cancelPayment} from '@_api/payment/paymentAPI'; -import type {PaymentRetrieveResponse} from '@_api/payment/PaymentTypes'; -import {cancelReservation, fetchDetailById, fetchSummaryByMember} from '@_api/reservation/reservationAPI'; +import { cancelPayment } from '@_api/payment/paymentAPI'; +import type { PaymentRetrieveResponse } from '@_api/payment/PaymentTypes'; +import { cancelReservation, fetchDetailById, fetchSummaryByMember } from '@_api/reservation/reservationAPI'; import { - type ReservationDetail, - ReservationStatus, - type ReservationSummaryRetrieveResponse + ReservationStatus, + type ReservationDetail, + type ReservationOverviewResponse } from '@_api/reservation/reservationTypes'; -import React, {useEffect, useState} from 'react'; import '@_css/my-reservation-v2.css'; +import { formatDate, formatDisplayDateTime, formatTime } from '@_util/DateTimeFormatter'; +import React, { useEffect, useState } from 'react'; -const getReservationStatus = (reservation: ReservationSummaryRetrieveResponse): { className: string, text: string } => { +const getReservationStatus = (reservation: ReservationOverviewResponse): { className: string, text: string } => { const now = new Date(); - const reservationDateTime = new Date(`${reservation.date}T${reservation.startAt}`); + const reservationDateTime = new Date(`${reservation.date}T${reservation.startFrom}`); switch (reservation.status) { case ReservationStatus.CANCELED: @@ -22,81 +23,12 @@ const getReservationStatus = (reservation: ReservationSummaryRetrieveResponse): } return { className: 'status-confirmed', text: '예약확정' }; case ReservationStatus.PENDING: - return { className: 'status-pending', text: '입금대기' }; + return { className: 'status-pending', text: '입금대기' }; default: return { className: `status-${reservation.status.toLowerCase()}`, text: reservation.status }; } }; -const formatDisplayDateTime = (dateTime: any): string => { - let date: Date; - - if (typeof dateTime === 'string') { - // ISO 문자열 형식 처리 (LocalDateTime, OffsetDateTime 모두 포함) - date = new Date(dateTime); - } else if (typeof dateTime === 'number') { - // Unix 타임스탬프(초) 형식 처리 - date = new Date(dateTime * 1000); - } else if (Array.isArray(dateTime) && dateTime.length >= 6) { - // 배열 형식 처리: [year, month, day, hour, minute, second, nanosecond?] - const year = dateTime[0]; - const month = dateTime[1] - 1; // JS Date의 월은 0부터 시작 - const day = dateTime[2]; - const hour = dateTime[3]; - const minute = dateTime[4]; - const second = dateTime[5]; - const millisecond = dateTime.length > 6 ? Math.floor(dateTime[6] / 1000000) : 0; - date = new Date(year, month, day, hour, minute, second, millisecond); - } else { - return '유효하지 않은 날짜 형식'; - } - - if (isNaN(date.getTime())) { - return '유효하지 않은 날짜'; - } - - const options: Intl.DateTimeFormatOptions = { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - hour12: true, - second: 'numeric' - }; - return new Intl.DateTimeFormat('ko-KR', options).format(date); -}; - -const formatCardDateTime = (dateStr: string, timeStr: string): string => { - const date = new Date(`${dateStr}T${timeStr}`); - const currentYear = new Date().getFullYear(); - const reservationYear = date.getFullYear(); - - const days = ['일', '월', '화', '수', '목', '금', '토']; - const dayOfWeek = days[date.getDay()]; - const month = date.getMonth() + 1; - const day = date.getDate(); - let hours = date.getHours(); - const minutes = date.getMinutes(); - const ampm = hours >= 12 ? '오후' : '오전'; - hours = hours % 12; - hours = hours ? hours : 12; - - let datePart = ''; - if (currentYear === reservationYear) { - datePart = `${month}월 ${day}일(${dayOfWeek})`; - } else { - datePart = `${reservationYear}년 ${month}월 ${day}일(${dayOfWeek})`; - } - - let timePart = `${ampm} ${hours}시`; - if (minutes !== 0) { - timePart += ` ${minutes}분`; - } - - return `${datePart} ${timePart}`; -}; - // --- Cancellation View Component --- const CancellationView: React.FC<{ reservation: ReservationDetail; @@ -118,7 +50,7 @@ const CancellationView: React.FC<{

취소 정보 확인

-

테마:{reservation.themeName}

+

테마:{reservation.overview.themeName}

신청 일시:{formatDisplayDateTime(reservation.applicationDateTime)}

{reservation.payment &&

결제 금액:{reservation.payment.totalAmount.toLocaleString()}원

}
@@ -147,7 +79,7 @@ const ReservationDetailView: React.FC<{ const renderPaymentSubDetails = (payment: PaymentRetrieveResponse) => { if (!payment.detail) { - return

결제 상세 정보를 찾을 수 없어요. 관리자에게 문의해주세요.

; + return

결제 상세 정보를 찾을 수 없어요. 관리자에게 문의해주세요.

; } const { detail } = payment; @@ -195,10 +127,11 @@ const ReservationDetailView: React.FC<{ <>

예약 정보

-

예약 테마:{reservation.themeName}

-

이용 예정일:{formatCardDateTime(reservation.date, reservation.startAt)}

-

예약자 이름:{reservation.user.name}

-

예약자 이메일:{reservation.user.phone}

+

매장:{reservation.overview.storeName}

+

테마:{reservation.overview.themeName}

+

이용일시:{formatDate(reservation.overview.date)} {formatTime(reservation.overview.startFrom)} ~ {formatTime(reservation.overview.endAt)}

+

예약자 성함:{reservation.reserver.name}

+

예약자 연락처:{reservation.reserver.contact}

예약 신청 일시:{formatDisplayDateTime(reservation.applicationDateTime)}

@@ -243,7 +176,7 @@ const ReservationDetailView: React.FC<{ // --- Main Page Component --- const MyReservationPage: React.FC = () => { - const [reservations, setReservations] = useState([]); + const [reservations, setReservations] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -272,17 +205,15 @@ const MyReservationPage: React.FC = () => { loadReservations(); }, []); - const handleShowDetail = async (id: string, themeName: string, date: string, time: string) => { + const handleShowDetail = async (overview: ReservationOverviewResponse) => { try { setIsDetailLoading(true); setDetailError(null); setModalView('detail'); - const detailData = await fetchDetailById(id); + const detailData = await fetchDetailById(overview.id); setSelectedReservation({ - id: detailData.id, - themeName: themeName, - date: date, - startAt: time, + overview: overview, + reserver: detailData.reserver, user: detailData.user, applicationDateTime: detailData.applicationDateTime, payment: detailData.payment @@ -310,8 +241,8 @@ const MyReservationPage: React.FC = () => { try { setIsCancelling(true); setDetailError(null); - await cancelPayment({ reservationId: selectedReservation.id, cancelReason: reason }); - await cancelReservation(selectedReservation.id, reason); + await cancelPayment({ reservationId: selectedReservation.overview.id, cancelReason: reason }); + await cancelReservation(selectedReservation.overview.id, reason); alert('예약을 취소했어요. 결제 취소까지는 3-5일 정도 소요될 수 있어요.'); handleCloseModal(); await loadReservations(); // Refresh the list @@ -338,15 +269,18 @@ const MyReservationPage: React.FC = () => {
{status.text}
-

{res.themeName}

-

{formatCardDateTime(res.date, res.startAt)}

+

{res.themeName}

+
+

{res.storeName}

+

{formatDate(res.date)} {formatTime(res.startFrom)} ~ {formatTime(res.endAt)}

+
); diff --git a/frontend/src/util/DateTimeFormatter.ts b/frontend/src/util/DateTimeFormatter.ts index 899b3983..50a8164c 100644 --- a/frontend/src/util/DateTimeFormatter.ts +++ b/frontend/src/util/DateTimeFormatter.ts @@ -32,4 +32,43 @@ export const formatTime = (timeStr: string) => { } return timePart; -} \ No newline at end of file +} + +export const formatDisplayDateTime = (dateTime: any): string => { + let date: Date; + + if (typeof dateTime === 'string') { + // ISO 문자열 형식 처리 (LocalDateTime, OffsetDateTime 모두 포함) + date = new Date(dateTime); + } else if (typeof dateTime === 'number') { + // Unix 타임스탬프(초) 형식 처리 + date = new Date(dateTime * 1000); + } else if (Array.isArray(dateTime) && dateTime.length >= 6) { + // 배열 형식 처리: [year, month, day, hour, minute, second, nanosecond?] + const year = dateTime[0]; + const month = dateTime[1] - 1; // JS Date의 월은 0부터 시작 + const day = dateTime[2]; + const hour = dateTime[3]; + const minute = dateTime[4]; + const second = dateTime[5]; + const millisecond = dateTime.length > 6 ? Math.floor(dateTime[6] / 1000000) : 0; + date = new Date(year, month, day, hour, minute, second, millisecond); + } else { + return '유효하지 않은 날짜 형식'; + } + + if (isNaN(date.getTime())) { + return '유효하지 않은 날짜'; + } + + const options: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: true, + second: 'numeric' + }; + return new Intl.DateTimeFormat('ko-KR', options).format(date); +};