File size: 8,146 Bytes
64b8b1b
 
 
 
2782f86
64b8b1b
b2510cd
64b8b1b
 
 
 
67688f8
5251633
67688f8
 
5251633
 
67688f8
5251633
 
 
 
 
 
 
 
 
 
 
67688f8
 
b2510cd
 
 
 
 
 
 
 
 
 
 
64b8b1b
 
2782f86
 
 
 
 
64b8b1b
67688f8
 
b2510cd
67688f8
 
2782f86
64b8b1b
b2510cd
 
 
 
 
 
 
 
 
 
2782f86
64b8b1b
 
2782f86
 
 
 
 
64b8b1b
 
 
 
 
 
67688f8
 
b2510cd
67688f8
 
b2510cd
 
 
 
 
 
 
 
 
67688f8
b2510cd
64b8b1b
 
 
b2510cd
2782f86
64b8b1b
 
 
 
2782f86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64b8b1b
 
 
 
2782f86
 
 
 
 
 
 
 
64b8b1b
2782f86
 
64b8b1b
 
2782f86
b2510cd
2782f86
 
 
 
b2510cd
2782f86
 
64b8b1b
 
 
 
2782f86
b2510cd
 
2782f86
 
 
b2510cd
2782f86
b2510cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2782f86
b2510cd
 
 
2782f86
 
64b8b1b
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202

import { useState } from "react";
import conferencesData from "@/data/conferences.yml";
import { Conference } from "@/types/conference";
import { Calendar as CalendarIcon, Tag, CircleDot } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { parseISO, format, isValid, startOfMonth, endOfMonth, isSameMonth } from "date-fns";

const CalendarPage = () => {
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
  
  // Helper function to safely parse dates
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
    if (!dateString || dateString === 'TBD') return null;
    
    // Convert to string if it's a number
    const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
    
    try {
      // Try to parse the date, handling different formats
      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 all events (conferences and deadlines) for a given month
  const getMonthEvents = (date: Date) => {
    return conferencesData.filter((conf: Conference) => {
      const deadlineDate = safeParseISO(conf.deadline);
      const startDate = safeParseISO(conf.start);

      return (deadlineDate && isSameMonth(deadlineDate, date)) ||
             (startDate && isSameMonth(startDate, date));
    });
  };

  // Get all unique dates (deadlines and conference dates)
  const getDatesWithEvents = () => {
    const dates = {
      conferences: new Set<string>(),
      deadlines: new Set<string>()
    };
    
    conferencesData.forEach((conf: Conference) => {
      const deadlineDate = safeParseISO(conf.deadline);
      const startDate = safeParseISO(conf.start);
      const endDate = safeParseISO(conf.end);

      if (deadlineDate) {
        dates.deadlines.add(format(deadlineDate, 'yyyy-MM-dd'));
      }
      
      // If conference has both start and end dates, add all dates in between
      if (startDate && endDate) {
        let currentDate = startDate;
        while (currentDate <= endDate) {
          dates.conferences.add(format(currentDate, 'yyyy-MM-dd'));
          currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
        }
      } else if (startDate) {
        // If only start date is available, add just that date
        dates.conferences.add(format(startDate, 'yyyy-MM-dd'));
      }
    });

    return {
      conferences: Array.from(dates.conferences).map(date => parseISO(date)),
      deadlines: Array.from(dates.deadlines).map(date => parseISO(date))
    };
  };

  // Get conferences for selected date
  const getConferencesForDate = (date: Date) => {
    const formattedDate = format(date, 'yyyy-MM-dd');
    return conferencesData.filter((conf: Conference) => {
      const deadlineDate = safeParseISO(conf.deadline);
      const startDate = safeParseISO(conf.start);
      const endDate = safeParseISO(conf.end);

      const deadlineDateStr = deadlineDate ? format(deadlineDate, 'yyyy-MM-dd') : null;
      const isDeadlineMatch = deadlineDateStr === formattedDate;
      
      // Check if the date falls within the conference duration
      let isConferenceDate = false;
      if (startDate && endDate) {
        isConferenceDate = date >= startDate && date <= endDate;
      } else if (startDate) {
        isConferenceDate = format(startDate, 'yyyy-MM-dd') === formattedDate;
      }

      return isDeadlineMatch || isConferenceDate;
    });
  };

  const monthEvents = selectedDate ? getMonthEvents(selectedDate) : [];
  const datesWithEvents = getDatesWithEvents();

  return (
    <div className="min-h-screen bg-neutral-light p-6">
      <div className="max-w-7xl mx-auto">
        <h1 className="text-3xl font-bold mb-8 text-center">Calendar Overview</h1>
        
        {/* Color Legend */}
        <div className="flex justify-center gap-6 mb-6">
          <div className="flex items-center gap-2">
            <CircleDot className="h-4 w-4 text-purple-600" />
            <span>Conference Dates</span>
          </div>
          <div className="flex items-center gap-2">
            <CircleDot className="h-4 w-4 text-red-500" />
            <span>Submission Deadlines</span>
          </div>
        </div>

        <div className="grid grid-cols-1 gap-8">
          <div className="mx-auto w-full max-w-3xl">
            <Calendar
              mode="single"
              selected={selectedDate}
              onSelect={setSelectedDate}
              className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
              classNames={{
                month: "w-full",
                table: "w-full",
                cell: "h-14 w-14 text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
                day: "h-14 w-14 p-0 font-normal aria-selected:opacity-100 hover:bg-neutral-100 rounded-lg transition-colors",
                day_today: "bg-neutral-100 text-primary font-semibold"
              }}
              modifiers={{
                conference: datesWithEvents.conferences,
                deadline: datesWithEvents.deadlines
              }}
              modifiersStyles={{
                conference: {
                  backgroundColor: '#DDD6FE', // purple-200
                  color: '#7C3AED', // purple-600
                  fontWeight: 'bold'
                },
                deadline: {
                  backgroundColor: '#FEE2E2', // red-100
                  color: '#EF4444', // red-500
                  fontWeight: 'bold'
                }
              }}
            />
          </div>

          {/* Month Events */}
          {selectedDate && (
            <div className="mx-auto w-full max-w-3xl space-y-4">
              <h2 className="text-xl font-semibold flex items-center gap-2">
                <CalendarIcon className="h-5 w-5" />
                Events in {format(selectedDate, 'MMMM yyyy')}
              </h2>
              {monthEvents.length === 0 ? (
                <p className="text-neutral-600">No events this month.</p>
              ) : (
                <div className="space-y-4">
                  {monthEvents.map((conf: Conference) => (
                    <div key={conf.id} className="bg-white p-4 rounded-lg shadow-sm">
                      <h3 className="font-semibold text-lg">{conf.title}</h3>
                      <div className="space-y-1">
                        {conf.deadline && safeParseISO(conf.deadline) && isSameMonth(safeParseISO(conf.deadline)!, selectedDate) && (
                          <p className="text-red-500">
                            Submission Deadline: {format(safeParseISO(conf.deadline)!, 'MMMM d, yyyy')}
                          </p>
                        )}
                        {conf.start && (
                          <p className="text-purple-600">
                            Conference Date: {format(safeParseISO(conf.start)!, 'MMMM d')}
                            {conf.end ? ` - ${format(safeParseISO(conf.end)!, 'MMMM d, yyyy')}` : `, ${format(safeParseISO(conf.start)!, 'yyyy')}`}
                          </p>
                        )}
                      </div>
                      <div className="mt-2 flex flex-wrap gap-2">
                        {conf.tags.map((tag) => (
                          <span key={tag} className="tag text-sm">
                            <Tag className="h-3 w-3 mr-1" />
                            {tag}
                          </span>
                        ))}
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

export default CalendarPage;