import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { CalendarDays, Globe, Tag, Clock, AlarmClock, CalendarPlus } from "lucide-react"; import { Conference } from "@/types/conference"; import { formatDistanceToNow, parseISO, isValid, format, parse, addDays } from "date-fns"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useState, useEffect } from "react"; import { getDeadlineInLocalTime } from '@/utils/dateUtils'; interface ConferenceDialogProps { conference: Conference; open: boolean; onOpenChange: (open: boolean) => void; } const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogProps) => { console.log('Conference object:', conference); const deadlineDate = getDeadlineInLocalTime(conference.deadline, conference.timezone); const [countdown, setCountdown] = useState(''); // Replace the current location string creation with this more verbose version const getLocationString = () => { console.log('Venue:', conference.venue); console.log('City:', conference.city); console.log('Country:', conference.country); if (conference.venue) { return conference.venue; } const cityCountryArray = [conference.city, conference.country].filter(Boolean); console.log('City/Country array after filter:', cityCountryArray); const cityCountryString = cityCountryArray.join(", "); console.log('Final location string:', cityCountryString); return cityCountryString || "Location TBD"; // Fallback if everything is empty }; // Use the function result const location = getLocationString(); useEffect(() => { const calculateTimeLeft = () => { if (!deadlineDate || !isValid(deadlineDate)) { setCountdown('TBD'); return; } const now = new Date(); const difference = deadlineDate.getTime() - now.getTime(); if (difference <= 0) { setCountdown('Deadline passed'); return; } const days = Math.floor(difference / (1000 * 60 * 60 * 24)); const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((difference % (1000 * 60)) / 1000); setCountdown(`${days}d ${hours}h ${minutes}m ${seconds}s`); }; calculateTimeLeft(); const timer = setInterval(calculateTimeLeft, 1000); return () => clearInterval(timer); }, [deadlineDate]); const getCountdownColor = () => { if (!deadlineDate || !isValid(deadlineDate)) return "text-neutral-600"; const daysRemaining = Math.ceil((deadlineDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)); if (daysRemaining <= 7) return "text-red-600"; if (daysRemaining <= 30) return "text-orange-600"; return "text-green-600"; }; const parseDateFromString = (dateStr: string) => { try { // Handle formats like "October 19-25, 2025" or "Sept 9-12, 2025" const [monthDay, year] = dateStr.split(", "); const [month, dayRange] = monthDay.split(" "); const [startDay] = dayRange.split("-"); // Construct a date string in a format that can be parsed const dateString = `${month} ${startDay} ${year}`; const date = parse(dateString, 'MMMM d yyyy', new Date()); if (!isValid(date)) { // Try alternative format for abbreviated months return parse(dateString, 'MMM d yyyy', new Date()); } return date; } catch (error) { console.error("Error parsing date:", error); return new Date(); } }; const createCalendarEvent = (type: 'google' | 'apple') => { try { if (!conference.deadline || conference.deadline === 'TBD') { throw new Error('No valid deadline found'); } // Parse the deadline date const deadlineDate = parseISO(conference.deadline); if (!isValid(deadlineDate)) { throw new Error('Invalid deadline date'); } // Create an end date 1 hour after the deadline const endDate = new Date(deadlineDate.getTime() + (60 * 60 * 1000)); const formatDateForGoogle = (date: Date) => format(date, "yyyyMMdd'T'HHmmss'Z'"); const formatDateForApple = (date: Date) => format(date, "yyyyMMdd'T'HHmmss'Z'"); const title = encodeURIComponent(`${conference.title} deadline`); const locationStr = encodeURIComponent(location); const description = encodeURIComponent( `Paper Submission Deadline for ${conference.full_name || conference.title}\n` + (conference.abstract_deadline ? `Abstract Deadline: ${conference.abstract_deadline}\n` : '') + `Dates: ${conference.date}\n` + `Location: ${location}\n` + (conference.link ? `Website: ${conference.link}` : '') ); if (type === 'google') { const url = `https://calendar.google.com/calendar/render?action=TEMPLATE` + `&text=${title}` + `&dates=${formatDateForGoogle(deadlineDate)}/${formatDateForGoogle(endDate)}` + `&details=${description}` + `&location=${locationStr}` + `&sprop=website:${encodeURIComponent(conference.link || '')}`; window.open(url, '_blank'); } else { const url = `data:text/calendar;charset=utf8,BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT URL:${conference.link || ''} DTSTART:${formatDateForApple(deadlineDate)} DTEND:${formatDateForApple(endDate)} SUMMARY:${title} DESCRIPTION:${description} LOCATION:${location} END:VEVENT END:VCALENDAR`; const link = document.createElement('a'); link.href = url; link.download = `${conference.title.toLowerCase().replace(/\s+/g, '-')}-deadline.ics`; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } catch (error) { console.error("Error creating calendar event:", error); alert("Sorry, there was an error creating the calendar event. Please try again."); } }; const generateGoogleMapsUrl = (venue: string | undefined, place: string): string => { return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(venue || place)}`; }; const formatDeadlineDisplay = () => { if (!deadlineDate || !isValid(deadlineDate)) return null; const localTZ = Intl.DateTimeFormat().resolvedOptions().timeZone; return (
{format(deadlineDate, "MMMM d, yyyy 'at' HH:mm:ss")} ({localTZ})
{conference.timezone && conference.timezone !== localTZ && (
Conference timezone: {conference.timezone}
)}
); }; // Add these new functions to handle consistent date conversion const getLocalDeadline = (dateString: string | undefined) => { if (!dateString || dateString === 'TBD') return null; return getDeadlineInLocalTime(dateString, conference.timezone); }; // Format any deadline date consistently const formatDeadlineDate = (dateString: string | undefined) => { if (!dateString || dateString === 'TBD') return dateString || 'TBD'; const localDate = getLocalDeadline(dateString); if (!localDate || !isValid(localDate)) return dateString; const localTZ = Intl.DateTimeFormat().resolvedOptions().timeZone; return `${format(localDate, "MMMM d, yyyy")} (${localTZ})`; }; return ( {conference.title} {conference.year} {conference.full_name}

Dates

{conference.date}

Location

{conference.venue || [conference.city, conference.country].filter(Boolean).join(", ")}

Important Deadlines

{conference.abstract_deadline && (

Abstract: {formatDeadlineDate(conference.abstract_deadline)}

)}

Submission: {formatDeadlineDate(conference.deadline)}

{conference.commitment_deadline && (

Commitment: {formatDeadlineDate(conference.commitment_deadline)}

)} {conference.review_release_date && (

Reviews Released: {formatDeadlineDate(conference.review_release_date)}

)} {(conference.rebuttal_period_start || conference.rebuttal_period_end) && (

Rebuttal Period: {formatDeadlineDate(conference.rebuttal_period_start)} - {formatDeadlineDate(conference.rebuttal_period_end)}

)} {conference.final_decision_date && (

Final Decision: {formatDeadlineDate(conference.final_decision_date)}

)}
{countdown} {formatDeadlineDisplay()}
{Array.isArray(conference.tags) && conference.tags.length > 0 && (
{conference.tags.map((tag) => ( {tag} ))}
)} {conference.note && (
)}
{conference.link && ( )} createCalendarEvent('google')} > Add to Google Calendar createCalendarEvent('apple')} > Add to Apple Calendar
); }; export default ConferenceDialog;