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 { isValid, format } 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'; import { getNextUpcomingDeadline, getUpcomingDeadlines, getDaysRemaining, getCountdownColorClass } from '@/utils/deadlineUtils'; interface ConferenceDialogProps { conference: Conference; open: boolean; onOpenChange: (open: boolean) => void; } const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogProps) => { console.log('Conference object:', conference); // Get upcoming deadlines and the next upcoming one const upcomingDeadlines = getUpcomingDeadlines(conference); const nextDeadline = getNextUpcomingDeadline(conference); const deadlineDate = nextDeadline ? getDeadlineInLocalTime(nextDeadline.date, nextDeadline.timezone || conference.timezone) : null; 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 createCalendarEvent = (type: 'google' | 'apple') => { try { if (!nextDeadline || !deadlineDate || !isValid(deadlineDate)) { throw new Error('No valid upcoming deadline found'); } // Create an end date 1 hour after the deadline const endDate = new Date(deadlineDate.getTime() + (60 * 60 * 1000)); const formatCalendarDate = (date: Date) => format(date, "yyyyMMdd'T'HHmmss"); const escapeICSText = (value: string) => value .replace(/\\/g, '\\\\') .replace(/\n/g, '\\n') .replace(/,/g, '\\,') .replace(/;/g, '\\;'); const eventTitle = `${conference.title} ${nextDeadline.label}`; const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const descriptionText = `${nextDeadline.label} for ${conference.full_name || conference.title}\n` + `Deadline: ${format(deadlineDate, "MMMM d, yyyy 'at' HH:mm:ss")} (${localTimezone})\n` + `Dates: ${conference.date}\n` + `Location: ${location}\n` + (conference.link ? `Website: ${conference.link}` : ''); if (type === 'google') { const params = new URLSearchParams({ action: 'TEMPLATE', text: eventTitle, dates: `${formatCalendarDate(deadlineDate)}/${formatCalendarDate(endDate)}`, details: descriptionText, location, ctz: localTimezone, sprop: `website:${conference.link || ''}`, }); const url = `https://calendar.google.com/calendar/render?${params.toString()}`; window.open(url, '_blank'); } else { const calendarContent = `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//AI Conference Deadlines//EN BEGIN:VEVENT URL:${escapeICSText(conference.link || '')} DTSTART:${formatCalendarDate(deadlineDate)} DTEND:${formatCalendarDate(endDate)} SUMMARY:${escapeICSText(eventTitle)} DESCRIPTION:${escapeICSText(descriptionText)} LOCATION:${escapeICSText(location)} END:VEVENT END:VCALENDAR`; const link = document.createElement('a'); link.href = `data:text/calendar;charset=utf8,${encodeURIComponent(calendarContent)}`; 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}

Venue

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

Important Deadlines

{upcomingDeadlines.length > 0 ? ( upcomingDeadlines.map((deadline, index) => { const isNext = nextDeadline && deadline.date === nextDeadline.date && deadline.type === nextDeadline.type; const daysRemaining = getDaysRemaining(deadline, conference.timezone); const daysColorClass = getCountdownColorClass(daysRemaining); return (

{deadline.label}: {formatDeadlineDate(deadline.date)} {isNext && (Next)}

{daysRemaining !== null && daysRemaining > 0 && ( {daysRemaining} {daysRemaining === 1 ? 'day' : 'days'} )}
); }) ) : (

No upcoming deadlines

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