import { useState } from "react"; import conferencesData from "@/data/conferences.yml"; import { Conference } from "@/types/conference"; import { Calendar as CalendarIcon, Tag } from "lucide-react"; import { Calendar } from "@/components/ui/calendar"; import { parseISO, format, isValid, isSameMonth, isSameYear, isSameDay } from "date-fns"; import { Toggle } from "@/components/ui/toggle"; const CalendarPage = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [isYearView, setIsYearView] = useState(false); // Helper function to safely parse dates const safeParseISO = (dateString: string | undefined | number): Date | null => { if (!dateString || dateString === 'TBD') return null; const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString; try { const normalizedDate = dateStr.replace(/(\d{4})-(\d{1})-(\d{1,2})/, '$1-0$2-$3') .replace(/(\d{4})-(\d{2})-(\d{1})/, '$1-$2-0$3'); const parsedDate = parseISO(normalizedDate); return isValid(parsedDate) ? parsedDate : null; } catch (error) { console.error("Error parsing date:", dateString); return null; } }; // Get events for the current month/year const getEvents = (date: Date) => { return conferencesData.filter((conf: Conference) => { const deadlineDate = safeParseISO(conf.deadline); const startDate = safeParseISO(conf.start); const endDate = safeParseISO(conf.end); const dateMatches = isYearView ? isSameYear : isSameMonth; // Check if deadline is in the selected period const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date); // Check if any part of the conference falls in the selected period let conferenceInPeriod = false; if (startDate && endDate) { let currentDate = new Date(startDate); while (currentDate <= endDate) { if (dateMatches(currentDate, date)) { conferenceInPeriod = true; break; } currentDate.setDate(currentDate.getDate() + 1); } } else if (startDate) { conferenceInPeriod = dateMatches(startDate, date); } return deadlineInPeriod || conferenceInPeriod; }); }; // Get all events for day indicators const getDayEvents = (date: Date) => { return conferencesData.reduce((acc, conf) => { const deadlineDate = safeParseISO(conf.deadline); const startDate = safeParseISO(conf.start); const endDate = safeParseISO(conf.end); if (deadlineDate && isSameDay(deadlineDate, date)) { acc.deadlines.push(conf); } if (startDate && endDate) { if (date >= startDate && date <= endDate) { acc.conferences.push(conf); } } else if (startDate && isSameDay(startDate, date)) { acc.conferences.push(conf); } return acc; }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] }); }; const events = selectedDate ? getEvents(selectedDate) : []; // Custom day content renderer const renderDayContent = (day: Date) => { const dayEvents = getDayEvents(day); const hasDeadlines = dayEvents.deadlines.length > 0; const hasConferences = dayEvents.conferences.length > 0; return (
{format(day, 'd')}
{hasDeadlines && (
)} {hasConferences && (
)}
); }; return (

Calendar Overview

setIsYearView(false)} variant="outline" > Month setIsYearView(true)} variant="outline" > Year
Conference Dates
Submission Deadlines
( ), }} classNames={{ months: `grid ${isYearView ? 'grid-cols-3 gap-4' : ''} justify-center`, month: "space-y-4", caption: "flex justify-center pt-1 relative items-center mb-4", caption_label: "text-lg font-semibold", table: "w-full border-collapse space-y-1", head_row: "flex", head_cell: "text-muted-foreground rounded-md w-10 font-normal text-[0.8rem]", row: "flex w-full mt-2", cell: `h-10 w-10 text-center text-sm p-0 relative focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md hover:bg-neutral-50`, day: "h-10 w-10 p-0 font-normal hover:bg-neutral-100 rounded-lg transition-colors", day_today: "bg-neutral-100 text-primary font-semibold", nav: "space-x-1 flex items-center", nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100", nav_button_previous: "absolute left-1", nav_button_next: "absolute right-1", }} />
{selectedDate && events.length > 0 && (

Events in {format(selectedDate, isYearView ? 'yyyy' : 'MMMM yyyy')}

{events.map((conf: Conference) => (

{conf.title}

{conf.deadline && safeParseISO(conf.deadline) && (

Submission Deadline: {format(safeParseISO(conf.deadline)!, 'MMMM d, yyyy')}

)} {conf.start && (

Conference Date: {format(safeParseISO(conf.start)!, 'MMMM d')} {conf.end ? ` - ${format(safeParseISO(conf.end)!, 'MMMM d, yyyy')}` : `, ${format(safeParseISO(conf.start)!, 'yyyy')}`}

)}
{conf.tags.map((tag) => ( {tag} ))}
))}
)}
); }; export default CalendarPage;