'use client'; import { useState, useMemo, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Plus, ExternalLink, ChevronLeft, ChevronRight, CalendarDays } from 'lucide-react'; import { ScheduleCalendar } from '@/components/common/ScheduleCalendar'; import type { ScheduleEvent } from '@/components/common/ScheduleCalendar/types'; import { getCalendarEventsForYear, type CalendarEvent } from '@/constants/calendarEvents'; import { useCalendarScheduleStore } from '@/stores/useCalendarScheduleStore'; import { CollapsibleDashboardCard } from '../components'; import type { CalendarScheduleItem, CalendarViewType, CalendarDeptFilterType, CalendarTaskFilterType, TodayIssueListItem, TodayIssueListBadgeType, } from '../types'; interface CalendarSectionProps { schedules: CalendarScheduleItem[]; issues?: TodayIssueListItem[]; onScheduleClick?: (schedule: CalendarScheduleItem) => void; onScheduleEdit?: (schedule: CalendarScheduleItem) => void; } // 일정 타입별 색상 const SCHEDULE_TYPE_COLORS: Record = { schedule: 'blue', order: 'green', construction: 'purple', issue: 'red', other: 'gray', holiday: 'red', tax: 'orange', }; // 이슈 뱃지별 색상 const ISSUE_BADGE_COLORS: Record = { '수주등록': 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300', '추심이슈': 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300', '안전재고': 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300', '지출 승인대기': 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300', '세금 신고': 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300', '결재 요청': 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300', '신규거래처': 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300', '입금': 'bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300', '출금': 'bg-pink-100 text-pink-700 dark:bg-pink-900/40 dark:text-pink-300', '기타': 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300', }; // 부서 필터 옵션 const DEPT_FILTER_OPTIONS: { value: CalendarDeptFilterType; label: string }[] = [ { value: 'all', label: '전체' }, { value: 'department', label: '부' }, { value: 'personal', label: '개인' }, ]; // 업무 필터 옵션 (이슈 추가) type ExtendedTaskFilterType = CalendarTaskFilterType | 'issue'; const TASK_FILTER_OPTIONS: { value: ExtendedTaskFilterType; label: string }[] = [ { value: 'all', label: '전체' }, { value: 'schedule', label: '일정' }, { value: 'order', label: '발주' }, { value: 'construction', label: '시공' }, { value: 'issue', label: '이슈' }, ]; export function CalendarSection({ schedules, issues = [], onScheduleClick, onScheduleEdit, }: CalendarSectionProps) { const router = useRouter(); const [selectedDate, setSelectedDate] = useState(new Date()); const [currentDate, setCurrentDate] = useState(new Date()); const [viewType, setViewType] = useState('month'); const [deptFilter, setDeptFilter] = useState('all'); const [taskFilter, setTaskFilter] = useState('all'); // 스토어에서 공휴일/세무일정 가져오기 (API 연동) const schedulesByYear = useCalendarScheduleStore((s) => s.schedulesByYear); // TODO: 백엔드 API(/api/v1/calendar-schedules) 구현 후 주석 해제 // const fetchSchedules = useCalendarScheduleStore((s) => s.fetchSchedules); // useEffect(() => { // const year = currentDate.getFullYear(); // fetchSchedules(year); // }, [currentDate, fetchSchedules]); // 날짜가 있는 이슈만 필터링 const issuesWithDate = useMemo(() => { return issues.filter((issue) => issue.date); }, [issues]); // 필터링된 스케줄 const filteredSchedules = useMemo(() => { // 이슈 필터일 경우 스케줄 제외 if (taskFilter === 'issue') { return []; } let result = schedules; // 업무 필터 if (taskFilter !== 'all') { result = result.filter((s) => s.type === taskFilter); } // 부서 필터 (실제 구현시 부서/개인 로직 추가 필요) // 현재는 기본 구현만 return result; }, [schedules, taskFilter, deptFilter]); // 필터링된 이슈 const filteredIssues = useMemo(() => { // 이슈 필터가 아니고 all도 아닌 경우 이슈 제외 if (taskFilter !== 'all' && taskFilter !== 'issue') { return []; } return issuesWithDate; }, [issuesWithDate, taskFilter]); // 현재 연도의 공휴일/세금일정 (스토어 우선, 폴백 상수) const staticEvents: ScheduleEvent[] = useMemo(() => { const year = currentDate.getFullYear(); const events = getCalendarEventsForYear(year); return events.map((event: CalendarEvent) => ({ id: `${event.type}-${event.date}`, title: event.type === 'holiday' ? `🔴 ${event.name}` : `🟠 ${event.name}`, startDate: event.date, endDate: event.date, color: SCHEDULE_TYPE_COLORS[event.type] || 'gray', data: { ...event, _type: event.type as 'holiday' | 'tax' }, })); // schedulesByYear를 의존성에 추가하여 스토어 갱신 시 리렌더링 }, [currentDate, schedulesByYear]); // ScheduleCalendar용 이벤트 변환 (스케줄 + 이슈 + 공휴일/세금 통합) const calendarEvents: ScheduleEvent[] = useMemo(() => { const scheduleEvents = filteredSchedules.map((schedule) => ({ id: schedule.id, // 기획서: [부서명] 제목 형식 title: schedule.department ? `[${schedule.department}] ${schedule.title}` : schedule.title, startDate: schedule.startDate, endDate: schedule.endDate, color: SCHEDULE_TYPE_COLORS[schedule.type] || 'gray', data: { ...schedule, _type: 'schedule' as const }, })); const issueEvents = filteredIssues.map((issue) => ({ id: issue.id, title: `[${issue.badge}] ${issue.content}`, startDate: issue.date!, endDate: issue.date!, color: 'red', data: { ...issue, _type: 'issue' as const }, })); return [...staticEvents, ...scheduleEvents, ...issueEvents]; }, [staticEvents, filteredSchedules, filteredIssues]); // 선택된 날짜의 일정 + 이슈 + 공휴일/세금 목록 const selectedDateItems = useMemo(() => { if (!selectedDate) return { schedules: [], issues: [], staticEvents: [] }; // 로컬 타임존 기준으로 날짜 문자열 생성 (UTC 변환 방지) const year = selectedDate.getFullYear(); const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); const day = String(selectedDate.getDate()).padStart(2, '0'); const dateStr = `${year}-${month}-${day}`; const dateSchedules = filteredSchedules.filter((schedule) => { return schedule.startDate <= dateStr && schedule.endDate >= dateStr; }); const dateIssues = filteredIssues.filter((issue) => issue.date === dateStr); // 공휴일/세금일정 const dateStaticEvents = staticEvents.filter((event) => event.startDate === dateStr); return { schedules: dateSchedules, issues: dateIssues, staticEvents: dateStaticEvents }; }, [selectedDate, filteredSchedules, filteredIssues, staticEvents]); // 총 건수 계산 const totalItemCount = selectedDateItems.schedules.length + selectedDateItems.issues.length + selectedDateItems.staticEvents.length; // 날짜 포맷 (기획서: "1월 6일 화요일") const formatSelectedDate = (date: Date) => { const month = date.getMonth() + 1; const day = date.getDate(); const dayNames = ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일']; const dayName = dayNames[date.getDay()]; return `${month}월 ${day}일 ${dayName}`; }; // 일정 상세 정보 포맷 (기획서: 부서명 | 날짜 | 시간) const formatScheduleDetail = (schedule: CalendarScheduleItem) => { const parts: string[] = []; // 부서명 또는 담당자명 if (schedule.department) { parts.push(schedule.department); } else if (schedule.personName) { parts.push(schedule.personName); } // 날짜 (여러 날인 경우) if (schedule.startDate !== schedule.endDate) { parts.push(`${schedule.startDate}~${schedule.endDate}`); } // 시간 if (schedule.startTime && schedule.endTime) { parts.push(`${schedule.startTime} ~ ${schedule.endTime}`); } else if (schedule.startTime) { parts.push(schedule.startTime); } return parts.join(' | '); }; const handleDateClick = (date: Date) => { setSelectedDate(date); }; const handleEventClick = (event: ScheduleEvent) => { const schedule = event.data as CalendarScheduleItem; onScheduleClick?.(schedule); }; const handleMonthChange = (date: Date) => { setCurrentDate(date); }; // 모바일 리스트뷰: 현재 월의 모든 날짜와 이벤트 const monthDaysWithEvents = useMemo(() => { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const dayNames = ['일', '월', '화', '수', '목', '금', '토']; const today = new Date(); today.setHours(0, 0, 0, 0); const days: Array<{ date: Date; dateStr: string; label: string; isToday: boolean; isWeekend: boolean; events: ScheduleEvent[]; }> = []; for (let d = 1; d <= daysInMonth; d++) { const date = new Date(year, month, d); const mm = String(month + 1).padStart(2, '0'); const dd = String(d).padStart(2, '0'); const dateStr = `${year}-${mm}-${dd}`; const dayOfWeek = date.getDay(); const dayEvents = calendarEvents.filter( (ev) => ev.startDate <= dateStr && ev.endDate >= dateStr ); days.push({ date, dateStr, label: `${d}일 ${dayNames[dayOfWeek]}요일`, isToday: date.getTime() === today.getTime(), isWeekend: dayOfWeek === 0 || dayOfWeek === 6, events: dayEvents, }); } return days; }, [currentDate, calendarEvents]); const handleMobilePrevMonth = () => { const prev = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1); setCurrentDate(prev); }; const handleMobileNextMonth = () => { const next = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); setCurrentDate(next); }; return ( } title="캘린더" subtitle="일정 관리" > {/* 모바일: 필터+월네비 */}
{/* 부서 필터 */} {/* 업무 필터 */}
{/* 월 네비게이션 */}
{currentDate.getFullYear()}년 {currentDate.getMonth() + 1}월
{/* 데스크탑: 필터 */}
{/* 모바일: 리스트뷰 */}
{/* 일별 리스트 */}
{monthDaysWithEvents.map((day) => { const hasEvents = day.events.length > 0; const isSelected = selectedDate && day.date.getTime() === selectedDate.getTime(); return (
handleDateClick(day.date)} > {/* 날짜 + 일정등록 버튼 */}
{day.label} {day.isToday && (오늘)}
{isSelected && ( )}
{/* 이벤트 목록 (날짜 아래) */} {hasEvents ? (
{(isSelected ? day.events : day.events.slice(0, 3)).map((ev) => { const evData = ev.data as Record; const evType = evData?._type as string; const colorMap: Record = { holiday: 'bg-red-500', tax: 'bg-orange-500', schedule: 'bg-blue-500', order: 'bg-green-500', construction: 'bg-purple-500', issue: 'bg-red-400', }; const dotColor = colorMap[evType] || 'bg-gray-400'; const title = evData?.name as string || evData?.title as string || ev.title; const cleanTitle = title?.replace(/^[🔴🟠]\s*/, '') || ''; return (
{cleanTitle}
); })} {!isSelected && day.events.length > 3 && (
+{day.events.length - 3}건
)}
) : null}
); })}
{/* 데스크탑: 기존 캘린더 + 상세 */}
{/* 캘린더 영역 */}
{/* 선택된 날짜 일정 + 이슈 목록 */}

{selectedDate ? formatSelectedDate(selectedDate) : '날짜를 선택하세요'}

총 {totalItemCount}건
{totalItemCount === 0 ? (
선택한 날짜에 일정이 없습니다.
) : (
{selectedDateItems.staticEvents.map((event) => { const eventData = event.data as CalendarEvent & { _type: string }; const isHoliday = eventData.type === 'holiday'; return (
{isHoliday ? '🔴' : '🟠'} {eventData.name}
{isHoliday ? '공휴일' : '세금 신고 마감일'}
); })} {selectedDateItems.schedules.map((schedule) => (
onScheduleClick?.(schedule)} >
{schedule.title}
{formatScheduleDetail(schedule)}
))} {selectedDateItems.issues.map((issue) => (
{ if (issue.path) router.push(`/ko${issue.path}`); }} >
{issue.badge} {issue.content}
{issue.time} {issue.path && ( 상세보기 )}
))}
)}
); }