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"; import Header from "@/components/Header"; import FilterBar from "@/components/FilterBar"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; const categoryColors: Record = { "computer-vision": "bg-orange-500", "machine-learning": "bg-purple-500", "natural-language-processing": "bg-blue-500", "robotics": "bg-green-500", "data-mining": "bg-pink-500", "signal-processing": "bg-cyan-500", "human-computer-interaction": "bg-indigo-500", "web-search": "bg-yellow-500", }; const categoryNames: Record = { "computer-vision": "Computer Vision", "machine-learning": "Machine Learning", "natural-language-processing": "NLP", "robotics": "Robotics", "data-mining": "Data Mining", "signal-processing": "Signal Processing", "human-computer-interaction": "HCI", "web-search": "Web Search", }; const CalendarPage = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [isYearView, setIsYearView] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const [selectedTag, setSelectedTag] = useState("All"); const [selectedDayEvents, setSelectedDayEvents] = useState<{ date: Date | null, events: { deadlines: Conference[], conferences: Conference[] } }>({ date: null, events: { deadlines: [], conferences: [] } }); const safeParseISO = (dateString: string | undefined | number): Date | null => { if (!dateString) return null; if (dateString === 'TBD') return null; try { if (typeof dateString === 'object') { return null; } const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString; let normalizedDate = dateStr; const parts = dateStr.split('-'); if (parts.length === 3) { normalizedDate = `${parts[0]}-${parts[1].padStart(2, '0')}-${parts[2].padStart(2, '0')}`; } const parsedDate = parseISO(normalizedDate); return isValid(parsedDate) ? parsedDate : null; } catch (error) { console.error("Error parsing date:", dateString); return null; } }; const getEvents = (date: Date) => { return conferencesData.filter((conf: Conference) => { const matchesSearch = searchQuery === "" || conf.title.toLowerCase().includes(searchQuery.toLowerCase()) || (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase())); const matchesTag = selectedTag === "All" || (Array.isArray(conf.tags) && conf.tags.includes(selectedTag)); if (!matchesSearch || !matchesTag) return false; const deadlineDate = safeParseISO(conf.deadline); const startDate = safeParseISO(conf.start); const endDate = safeParseISO(conf.end); const dateMatches = isYearView ? isSameYear : isSameMonth; const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date); 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; }); }; const getDayEvents = (date: Date) => { return conferencesData.reduce((acc, conf) => { const matchesSearch = searchQuery === "" || conf.title.toLowerCase().includes(searchQuery.toLowerCase()) || (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase())); const matchesTag = selectedTag === "All" || (Array.isArray(conf.tags) && conf.tags.includes(selectedTag)); if (!matchesSearch || !matchesTag) { return acc; } 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 renderEventPreview = (events: { deadlines: Conference[], conferences: Conference[] }) => { if (events.deadlines.length === 0 && events.conferences.length === 0) return null; return (
{events.deadlines.length > 0 && (

Deadlines:

{events.deadlines.map(conf => (
{conf.title}
))}
)} {events.conferences.length > 0 && (

Conferences:

{events.conferences.map(conf => (
{conf.title}
))}
)}
); }; const renderDayContent = (day: Date) => { const dayEvents = getDayEvents(day); const hasDeadlines = dayEvents.deadlines.length > 0; const hasConferences = dayEvents.conferences.length > 0; const content = (
{format(day, 'd')}
{hasDeadlines && (
)} {dayEvents.conferences.map((conf) => { const startDate = safeParseISO(conf.start); const endDate = safeParseISO(conf.end); if (!startDate || !isSameDay(startDate, day)) { return null; } const categoryColor = conf.tags?.[0] ? categoryColors[conf.tags[0]] || "bg-purple-600" : "bg-purple-600"; let width = '100%'; if (endDate) { const lastDayOfMonth = new Date(day.getFullYear(), day.getMonth() + 1, 0); const effectiveEndDate = endDate < lastDayOfMonth ? endDate : lastDayOfMonth; const daysBetween = Math.ceil((effectiveEndDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)) + 1; width = `calc(100% * ${daysBetween})`; } return (
); })}
); if (!hasDeadlines && !hasConferences) return content; return (
setSelectedDayEvents({ date: day, events: dayEvents })} > {content}
{renderEventPreview(dayEvents)}
); }; const renderEventDetails = (conf: Conference) => { const deadlineDate = safeParseISO(conf.deadline); const startDate = safeParseISO(conf.start); const endDate = safeParseISO(conf.end); return (

{conf.title}

{conf.full_name && (

{conf.full_name}

)}
{deadlineDate && (

Submission Deadline: {format(deadlineDate, 'MMMM d, yyyy')}

)} {startDate && (

Conference Date: {format(startDate, 'MMMM d')} {endDate ? ` - ${format(endDate, 'MMMM d, yyyy')}` : `, ${format(startDate, 'yyyy')}`}

)}
{Array.isArray(conf.tags) && conf.tags.map((tag) => ( {tag} ))}
); }; const categories = Object.entries(categoryColors).filter(([category]) => conferencesData.some(conf => conf.tags?.includes(category)) ); return (

Calendar Overview

setIsYearView(false)} variant="outline" > Month setIsYearView(true)} variant="outline" > Year
Submission Deadlines
{categories.map(([category, color]) => (
{categoryNames[category]}
))}
( ), }} 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" }} />
setSelectedDayEvents({ date: null, events: { deadlines: [], conferences: [] } })} > Events for {selectedDayEvents.date ? format(selectedDayEvents.date, 'MMMM d, yyyy') : ''}
{selectedDayEvents.events.deadlines.length > 0 && (

Submission Deadlines

{selectedDayEvents.events.deadlines.map(conf => renderEventDetails(conf))}
)} {selectedDayEvents.events.conferences.length > 0 && (

Conferences

{selectedDayEvents.events.conferences.map(conf => renderEventDetails(conf))}
)}
); }; export default CalendarPage;