import { isLoginRequiredError } from '@_api/apiClient'; import { createSchedule, deleteSchedule, findScheduleById, findSchedules, updateSchedule } from '@_api/schedule/scheduleAPI'; import { ScheduleStatus, type ScheduleDetailRetrieveResponse, type ScheduleRetrieveResponse } from '@_api/schedule/scheduleTypes'; import { fetchAdminThemes } from '@_api/theme/themeAPI'; import type { AdminThemeSummaryRetrieveResponse } from '@_api/theme/themeTypes'; import '@_css/admin-schedule-page.css'; import React, { Fragment, useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; const getScheduleStatusText = (status: ScheduleStatus): string => { switch (status) { case ScheduleStatus.AVAILABLE: return '예약 가능'; case ScheduleStatus.PENDING: return '예약 진행 중'; case ScheduleStatus.RESERVED: return '예약 완료'; case ScheduleStatus.BLOCKED: return '예약 불가'; default: return status; } }; const AdminSchedulePage: React.FC = () => { const [schedules, setSchedules] = useState([]); const [themes, setThemes] = useState([]); const [selectedThemeId, setSelectedThemeId] = useState(''); const [selectedDate, setSelectedDate] = useState(new Date().toLocaleDateString('en-CA')); const [isAdding, setIsAdding] = useState(false); const [newScheduleTime, setNewScheduleTime] = useState(''); const [expandedScheduleId, setExpandedScheduleId] = useState(null); const [detailedSchedules, setDetailedSchedules] = useState<{ [key: string]: ScheduleDetailRetrieveResponse }>({}); const [isLoadingDetails, setIsLoadingDetails] = useState(false); const [isEditing, setIsEditing] = useState(false); const [editingSchedule, setEditingSchedule] = useState(null); 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(() => { fetchAdminThemes() .then(res => { setThemes(res.themes); if (res.themes.length > 0) { setSelectedThemeId(String(res.themes[0].id)); } }) .catch(handleError); }, []); const fetchSchedules = () => { if (selectedDate && selectedThemeId) { findSchedules(selectedDate, selectedThemeId) .then(res => setSchedules(res.schedules)) .catch(err => { setSchedules([]); if (err.response?.status !== 404) { handleError(err); } }); } } useEffect(() => { fetchSchedules(); }, [selectedDate, selectedThemeId]); const handleAddSchedule = async () => { if (!newScheduleTime) { alert('시간을 입력해주세요.'); return; } if (!/\d{2}:\d{2}/.test(newScheduleTime)) { alert('시간 형식이 올바르지 않습니다. HH:MM 형식으로 입력해주세요.'); return; } try { await createSchedule({ date: selectedDate, themeId: selectedThemeId, time: newScheduleTime, }); fetchSchedules(); setIsAdding(false); setNewScheduleTime(''); } catch (error) { handleError(error); } }; const handleDeleteSchedule = async (scheduleId: string) => { if (window.confirm('정말 이 일정을 삭제하시겠습니까?')) { try { await deleteSchedule(scheduleId); setSchedules(schedules.filter(s => s.id !== scheduleId)); setExpandedScheduleId(null); // Close the details view after deletion } catch (error) { handleError(error); } } }; const handleToggleDetails = async (scheduleId: string) => { const isAlreadyExpanded = expandedScheduleId === scheduleId; setIsEditing(false); // Reset editing state whenever toggling if (isAlreadyExpanded) { setExpandedScheduleId(null); } else { setExpandedScheduleId(scheduleId); if (!detailedSchedules[scheduleId]) { setIsLoadingDetails(true); try { const details = await findScheduleById(scheduleId); setDetailedSchedules(prev => ({ ...prev, [scheduleId]: details })); } catch (error) { handleError(error); } finally { setIsLoadingDetails(false); } } } }; const handleEditClick = () => { if (expandedScheduleId && detailedSchedules[expandedScheduleId]) { setEditingSchedule({ ...detailedSchedules[expandedScheduleId] }); setIsEditing(true); } }; const handleCancelEdit = () => { setIsEditing(false); setEditingSchedule(null); }; const handleEditChange = (e: React.ChangeEvent) => { const { name, value } = e.target; if (editingSchedule) { setEditingSchedule({ ...editingSchedule, [name]: value }); } }; const handleSave = async () => { if (!editingSchedule) return; try { await updateSchedule(editingSchedule.id, { time: editingSchedule.time, status: editingSchedule.status, }); // Refresh data const details = await findScheduleById(editingSchedule.id); setDetailedSchedules(prev => ({ ...prev, [editingSchedule.id]: details })); setSchedules(schedules.map(s => s.id === editingSchedule.id ? { ...s, time: details.time, status: details.status } : s)); alert('일정이 성공적으로 업데이트되었습니다.'); setIsEditing(false); } catch (error) { handleError(error); } }; return (

일정 관리

setSelectedDate(e.target.value)} />
{schedules.map(schedule => ( {expandedScheduleId === schedule.id && ( )} ))} {isAdding && ( )}
시간 상태 관리
{schedule.time} {getScheduleStatusText(schedule.status)}
{isLoadingDetails ? (

로딩 중...

) : detailedSchedules[schedule.id] ? (

감사 정보

생성일: {new Date(detailedSchedules[schedule.id].createdAt).toLocaleString()}

수정일: {new Date(detailedSchedules[schedule.id].updatedAt).toLocaleString()}

생성자: {detailedSchedules[schedule.id].createdBy}

수정자: {detailedSchedules[schedule.id].updatedBy}

{isEditing && editingSchedule ? ( // --- EDIT MODE ---
) : ( // --- VIEW MODE ---
)}
) : (

상세 정보를 불러올 수 없습니다.

)}
setNewScheduleTime(e.target.value)} />
); }; export default AdminSchedulePage;