import { useState, useMemo, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { useIsMobile, useScheduleData, useCategories, useCalendar } from '@/hooks';
import { useScheduleStore } from '@/stores';
import { Loading, ScheduleCard } from '@/components';
import { cn, getTodayKST, decodeHtmlEntities } from '@/utils';
import { WEEKDAYS, MIN_YEAR } from '@/constants';
/**
* PC 캘린더 컴포넌트
*/
function PCCalendar({ selectedDate, schedules, categories, onSelectDate, onMonthChange }) {
const { days, year, month, canGoPrev } = useCalendar(selectedDate);
// 날짜별 일정 맵
const scheduleDates = useMemo(() => {
const dateMap = {};
schedules?.forEach((schedule) => {
const date = schedule.date?.split('T')[0];
if (!dateMap[date]) dateMap[date] = [];
const cat = categories?.find((c) => c.id === (schedule.category_id || schedule.category?.id));
dateMap[date].push(cat?.color || '#6b7280');
});
return dateMap;
}, [schedules, categories]);
const isToday = (date) => {
const today = new Date();
return (
date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
);
};
const isSelected = (date) => {
const sel = new Date(selectedDate);
return (
date.getDate() === sel.getDate() &&
date.getMonth() === sel.getMonth() &&
date.getFullYear() === sel.getFullYear()
);
};
const formatDateStr = (date) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
return (
{/* 헤더 */}
{year}년 {month + 1}월
{/* 요일 헤더 */}
{WEEKDAYS.map((day, i) => (
{day}
))}
{/* 날짜 그리드 */}
{days.map((item, index) => {
const dayOfWeek = index % 7;
const dateStr = formatDateStr(item.date);
const colors = scheduleDates[dateStr] || [];
return (
);
})}
);
}
/**
* 스케줄 페이지 (PC/Mobile 통합)
*/
function Schedule() {
const navigate = useNavigate();
const isMobile = useIsMobile();
// Zustand store
const { currentDate: storedCurrentDate, setCurrentDate, selectedDate, setSelectedDate } = useScheduleStore();
// 초기값 설정 - currentDate가 Date 객체가 아닐 수 있으므로 안전하게 변환
const today = getTodayKST();
const actualSelectedDate = selectedDate || today;
const currentDate = storedCurrentDate instanceof Date ? storedCurrentDate : new Date(storedCurrentDate || today);
// 데이터 로드
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
const { data: schedules, isLoading: schedulesLoading } = useScheduleData(year, month);
const { data: categories } = useCategories();
// 선택된 날짜의 일정
const selectedDateSchedules = useMemo(() => {
if (!schedules) return [];
const sel = new Date(actualSelectedDate);
const dateStr = `${sel.getFullYear()}-${String(sel.getMonth() + 1).padStart(2, '0')}-${String(sel.getDate()).padStart(2, '0')}`;
return schedules
.filter((s) => s.date?.split('T')[0] === dateStr)
.sort((a, b) => {
const aIsBirthday = a.is_birthday || String(a.id).startsWith('birthday-');
const bIsBirthday = b.is_birthday || String(b.id).startsWith('birthday-');
if (aIsBirthday && !bIsBirthday) return -1;
if (!aIsBirthday && bIsBirthday) return 1;
return 0;
});
}, [schedules, actualSelectedDate]);
// 월 변경
const changeMonth = (delta) => {
const newDate = new Date(currentDate);
newDate.setMonth(newDate.getMonth() + delta);
// 2017년 1월 이전으로 이동 불가
if (newDate.getFullYear() < MIN_YEAR || (newDate.getFullYear() === MIN_YEAR && newDate.getMonth() < 0)) {
return;
}
setCurrentDate(newDate);
// 이번 달이면 오늘 날짜, 다른 달이면 1일 선택
const now = new Date();
if (newDate.getFullYear() === now.getFullYear() && newDate.getMonth() === now.getMonth()) {
setSelectedDate(getTodayKST());
} else {
const firstDay = `${newDate.getFullYear()}-${String(newDate.getMonth() + 1).padStart(2, '0')}-01`;
setSelectedDate(firstDay);
}
};
// 날짜 선택
const handleSelectDate = (date) => {
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
setSelectedDate(dateStr);
// 월이 다르면 currentDate도 변경
if (date.getMonth() !== currentDate.getMonth() || date.getFullYear() !== currentDate.getFullYear()) {
setCurrentDate(date);
}
};
// PC 레이아웃
if (!isMobile) {
return (
{/* 좌측: 캘린더 */}
{/* 우측: 일정 목록 */}
{/* 헤더 */}
{new Date(actualSelectedDate).getMonth() + 1}월{' '}
{new Date(actualSelectedDate).getDate()}일{' '}
{WEEKDAYS[new Date(actualSelectedDate).getDay()]}요일
{selectedDateSchedules.length}개의 일정
{/* 일정 목록 */}
{schedulesLoading ? (
) : selectedDateSchedules.length === 0 ? (
{new Date(actualSelectedDate).getMonth() + 1}월{' '}
{new Date(actualSelectedDate).getDate()}일 일정이 없습니다
) : (
{selectedDateSchedules.map((schedule) => (
navigate(`/schedule/${schedule.id}`)}
/>
))}
)}
);
}
// 모바일 레이아웃 (간소화된 버전)
return (
<>
{/* 모바일 툴바 */}
{currentDate.getFullYear()}년 {currentDate.getMonth() + 1}월
{/* 모바일 컨텐츠 */}
{schedulesLoading ? (
) : selectedDateSchedules.length === 0 ? (
{new Date(actualSelectedDate).getMonth() + 1}월{' '}
{new Date(actualSelectedDate).getDate()}일 일정이 없습니다
) : (
{selectedDateSchedules.map((schedule) => (
navigate(`/schedule/${schedule.id}`)}
/>
))}
)}
>
);
}
export default Schedule;