import {isLoginRequiredError} from '@_api/apiClient'; import {fetchSidoList, fetchSigunguList} from '@_api/region/regionAPI'; import {type SidoResponse, type SigunguResponse} from '@_api/region/regionTypes'; import {fetchSchedules, holdSchedule} from '@_api/schedule/scheduleAPI'; import {ScheduleStatus, type ScheduleWithThemeResponse} from '@_api/schedule/scheduleTypes'; import {getStores} from '@_api/store/storeAPI'; import {type SimpleStoreResponse} from '@_api/store/storeTypes'; import {fetchThemeById} from '@_api/theme/themeAPI'; import {DifficultyKoreanMap, type ThemeInfoResponse} from '@_api/theme/themeTypes'; import '@_css/reservation-v2-1.css'; import React, {useEffect, useState} from 'react'; import {useLocation, useNavigate} from 'react-router-dom'; import {type ReservationData} from '@_api/reservation/reservationTypes'; import {formatDate} from 'src/util/DateTimeFormatter'; const ReservationStep1Page: React.FC = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [viewDate, setViewDate] = useState(new Date()); const [sidoList, setSidoList] = useState([]); const [sigunguList, setSigunguList] = useState([]); const [storeList, setStoreList] = useState([]); const [selectedSido, setSelectedSido] = useState(''); const [selectedSigungu, setSelectedSigungu] = useState(''); const [selectedStore, setSelectedStore] = useState(null); const [schedulesByTheme, setSchedulesByTheme] = useState>({}); const [selectedSchedule, setSelectedSchedule] = useState(null); const [isThemeModalOpen, setIsThemeModalOpen] = useState(false); const [modalThemeDetails, setModalThemeDetails] = useState(null); const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); const navigate = useNavigate(); const location = useLocation(); const handleError = (err: any) => { if (isLoginRequiredError(err)) { alert('로그인이 필요해요.'); navigate('/login', {state: {from: location}}); } else { const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.'; alert(message); console.error(err); } }; useEffect(() => { fetchSidoList().then(res => setSidoList(res.sidoList)).catch(handleError); }, []); useEffect(() => { if (selectedSido) { fetchSigunguList(selectedSido).then(res => setSigunguList(res.sigunguList)).catch(handleError); } else { setSigunguList([]); } setSelectedSigungu(''); }, [selectedSido]); useEffect(() => { if (selectedSido) { getStores(selectedSido, selectedSigungu) .then(res => setStoreList(res.stores)) .catch(handleError); } else { setStoreList([]); } setSelectedStore(null); }, [selectedSido, selectedSigungu]); useEffect(() => { if (selectedDate && selectedStore) { const dateStr = selectedDate.toLocaleDateString('en-CA'); fetchSchedules(selectedStore.id, dateStr) .then(res => { const grouped = res.schedules.reduce((acc, schedule) => { const key = schedule.themeName; if (!acc[key]) acc[key] = []; acc[key].push(schedule); return acc; }, {} as Record); setSchedulesByTheme(grouped); }) .catch(handleError); } else { setSchedulesByTheme({}); } setSelectedSchedule(null); }, [selectedDate, selectedStore]); const handleDateSelect = (date: Date) => { const today = new Date(); today.setHours(0, 0, 0, 0); if (date < today) { alert("지난 날짜는 선택할 수 없습니다."); return; } setSelectedDate(date); }; const handleNextStep = () => { if (!selectedSchedule) { alert('예약할 시간을 선택해주세요.'); return; } setIsConfirmModalOpen(true); }; const handleConfirmReservation = () => { if (!selectedSchedule) return; holdSchedule(selectedSchedule.id) .then(() => { fetchThemeById(selectedSchedule.themeId).then(res => { const reservationData: ReservationData = { scheduleId: selectedSchedule.id, store: { id: selectedStore!.id, name: selectedStore!.name, }, theme: { id: res.id, name: res.name, price: res.price, minParticipants: res.minParticipants, maxParticipants: res.maxParticipants, }, date: selectedDate.toLocaleDateString('en-CA'), startFrom: selectedSchedule.startFrom, endAt: selectedSchedule.endAt, }; navigate('/reservation/form', {state: reservationData}); }).catch(handleError); }) .catch(handleError); }; const openThemeModal = (themeId: string) => { fetchThemeById(themeId) .then(themeDetails => { setModalThemeDetails(themeDetails); setIsThemeModalOpen(true); }) .catch(handleError); }; const renderDateCarousel = () => { const dates = []; const today = new Date(); today.setHours(0, 0, 0, 0); for (let i = 0; i < 7; i++) { const date = new Date(viewDate); date.setDate(viewDate.getDate() + i); dates.push(date); } const handlePrev = () => { const newViewDate = new Date(viewDate); newViewDate.setDate(viewDate.getDate() - 1); if (newViewDate < today) { alert("지난 날짜는 조회할 수 없습니다."); return; } setViewDate(newViewDate); } const handleNext = () => { const newViewDate = new Date(viewDate); newViewDate.setDate(viewDate.getDate() + 1); setViewDate(newViewDate); } const goToToday = () => { setViewDate(new Date()); setSelectedDate(new Date()); } return (
{dates.map(date => { const isSelected = selectedDate.toDateString() === date.toDateString(); const isPast = date < today; return (
handleDateSelect(date)} >
{['일', '월', '화', '수', '목', '금', '토'][date.getDay()]}
{date.getDate()}
); })}
); }; const getStatusText = (status: ScheduleStatus) => { switch (status) { case ScheduleStatus.AVAILABLE: return '예약가능'; case ScheduleStatus.HOLD: return '예약 진행중'; default: return '예약불가'; } }; return (

예약하기

1. 날짜 선택

{renderDateCarousel()}

2. 매장 선택

3. 시간 선택

{Object.keys(schedulesByTheme).length > 0 ? ( Object.entries(schedulesByTheme).map(([themeName, schedules]) => (

{themeName}

{schedules.map(schedule => (
schedule.status === ScheduleStatus.AVAILABLE && setSelectedSchedule(schedule)} > {`${schedule.startFrom} ~ ${schedule.endAt}`} {getStatusText(schedule.status)}
))}
)) ) : (
선택한 조건으로 예약 가능한 시간이 없습니다.
)}
{isThemeModalOpen && modalThemeDetails && (
setIsThemeModalOpen(false)}>
e.stopPropagation()}> {modalThemeDetails.name}

{modalThemeDetails.name}

테마 정보

난이도:{DifficultyKoreanMap[modalThemeDetails.difficulty]}

이용 가능 인원:{modalThemeDetails.minParticipants} ~ {modalThemeDetails.maxParticipants}명

1인당 요금:{modalThemeDetails.price.toLocaleString()}원

예상 시간:{modalThemeDetails.expectedMinutesFrom} ~ {modalThemeDetails.expectedMinutesTo}분

이용 가능 시간:{modalThemeDetails.availableMinutes}분

소개

{modalThemeDetails.description}

)} {isConfirmModalOpen && selectedSchedule && (
setIsConfirmModalOpen(false)}>
e.stopPropagation()}>

예약 정보를 확인해주세요

날짜:{formatDate(selectedDate.toLocaleDateString('ko-KR'))}

매장:{selectedStore?.name}

테마:{selectedSchedule.themeName}

시간:{`${selectedSchedule.startFrom} ~ ${selectedSchedule.endAt}`}

)}
); }; export default ReservationStep1Page;