import {isLoginRequiredError} from '@_api/apiClient'; import {findAvailableThemesByDate, findSchedules, holdSchedule} from '@_api/schedule/scheduleAPI'; import {type ScheduleRetrieveResponse, ScheduleStatus} from '@_api/schedule/scheduleTypes'; import {findThemesByIds} from '@_api/theme/themeAPI'; import {mapThemeResponse, 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 {formatDate, formatTime} from 'src/util/DateTimeFormatter'; const ReservationStep1Page: React.FC = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [viewDate, setViewDate] = useState(new Date()); // For carousel const [themes, setThemes] = useState([]); const [selectedTheme, setSelectedTheme] = useState(null); const [schedules, setSchedules] = useState([]); const [selectedSchedule, setSelectedSchedule] = useState(null); const [isThemeModalOpen, setIsThemeModalOpen] = useState(false); 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(() => { if (selectedDate) { const dateStr = selectedDate.toLocaleDateString('en-CA'); // yyyy-mm-dd findAvailableThemesByDate(dateStr) .then(res => { console.log('Available themes response:', res); const themeIds: string[] = res.themeIds; console.log('Available theme IDs:', themeIds); if (themeIds.length > 0) { return findThemesByIds({ themeIds }); } else { return Promise.resolve({ themes: [] }); } }) .then(themeResponse => { setThemes(themeResponse.themes.map(mapThemeResponse)); }) .catch((err) => { if (isLoginRequiredError(err)) { setThemes([]); } else { const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.'; alert(message); console.error(err); } }) .finally(() => { setSelectedTheme(null); setSchedules([]); setSelectedSchedule(null); }); } }, [selectedDate]); useEffect(() => { if (selectedDate && selectedTheme) { const dateStr = selectedDate.toLocaleDateString('en-CA'); findSchedules(dateStr, selectedTheme.id) .then(res => { setSchedules(res.schedules); setSelectedSchedule(null); }) .catch((err) => { if (isLoginRequiredError(err)) { setSchedules([]); } else { const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.'; alert(message); console.error(err); } setSelectedSchedule(null); }); } }, [selectedDate, selectedTheme]); const handleNextStep = () => { if (!selectedDate || !selectedTheme || !selectedSchedule) { alert('날짜, 테마, 시간을 모두 선택해주세요.'); return; } if (selectedSchedule.status !== ScheduleStatus.AVAILABLE) { alert('예약할 수 없는 시간입니다.'); return; } setIsConfirmModalOpen(true); }; const handleConfirmReservation = () => { if (!selectedSchedule) return; holdSchedule(selectedSchedule.id) .then(() => { navigate('/reservation/form', { state: { scheduleId: selectedSchedule.id, theme: selectedTheme, date: selectedDate.toLocaleDateString('en-CA'), time: selectedSchedule.time, } }); }) .catch(handleError) .finally(() => setIsConfirmModalOpen(false)); }; const handleDateSelect = (date: Date) => { const today = new Date(); today.setHours(0, 0, 0, 0); if (date < today) { alert("지난 날짜는 선택할 수 없습니다."); return; } setSelectedDate(date); } 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 openThemeModal = (theme: ThemeInfoResponse) => { setSelectedTheme(theme); setIsThemeModalOpen(true); }; const getStatusText = (status: ScheduleStatus) => { switch (status) { case ScheduleStatus.AVAILABLE: return '예약가능'; case ScheduleStatus.HOLD: return '예약 진행중'; default: return '예약불가'; } }; const isButtonDisabled = !selectedDate || !selectedTheme || !selectedSchedule || selectedSchedule.status !== ScheduleStatus.AVAILABLE; return (

예약하기

1. 날짜 선택

{renderDateCarousel()}

2. 테마 선택

{themes.map(theme => (
setSelectedTheme(theme)} >

{theme.name}

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

난이도: {theme.difficulty}

참여 가능 인원: {theme.minParticipants} ~ {theme.maxParticipants}명

예상 소요 시간: {theme.expectedMinutesFrom} ~ {theme.expectedMinutesTo}분

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

))}

3. 시간 선택

{schedules.length > 0 ? schedules.map(schedule => (
schedule.status === ScheduleStatus.AVAILABLE && setSelectedSchedule(schedule)} > {schedule.time} {getStatusText(schedule.status)}
)) :
선택 가능한 시간이 없습니다.
}
{isThemeModalOpen && selectedTheme && (
setIsThemeModalOpen(false)}>
e.stopPropagation()}> {selectedTheme.name}

{selectedTheme.name}

테마 정보

난이도: {selectedTheme.difficulty}

참여 인원: {selectedTheme.minParticipants} ~ {selectedTheme.maxParticipants}명

소요 시간: {selectedTheme.expectedMinutesFrom} ~ {selectedTheme.expectedMinutesTo}분

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

소개

{selectedTheme.description}

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

예약 정보를 확인해주세요

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

테마: {selectedTheme!!.name}

시간: {formatTime(selectedSchedule!!.time)}

)}
); }; export default ReservationStep1Page;