nielsr HF staff commited on
Commit
2ae165b
·
2 Parent(s): afdb568 8596d07

Merge branch 'main' of github.com:NielsRogge/ai-deadlines-hub

Browse files
src/components/FilterBar.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { useMemo } from "react";
2
  import conferencesData from "@/data/conferences.yml";
3
 
 
1
+
2
  import { useMemo } from "react";
3
  import conferencesData from "@/data/conferences.yml";
4
 
src/components/ui/tooltip.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import * as React from "react"
2
  import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3
 
@@ -17,7 +18,7 @@ const TooltipContent = React.forwardRef<
17
  ref={ref}
18
  sideOffset={sideOffset}
19
  className={cn(
20
- "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21
  className
22
  )}
23
  {...props}
 
1
+
2
  import * as React from "react"
3
  import * as TooltipPrimitive from "@radix-ui/react-tooltip"
4
 
 
18
  ref={ref}
19
  sideOffset={sideOffset}
20
  className={cn(
21
+ "z-50 overflow-hidden rounded-md bg-white border px-3 py-1.5 text-sm text-neutral-800 shadow-md animate-in fade-in-50 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 duration-200",
22
  className
23
  )}
24
  {...props}
src/pages/Calendar.tsx CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import { useState } from "react";
3
  import conferencesData from "@/data/conferences.yml";
4
  import { Conference } from "@/types/conference";
@@ -6,29 +5,69 @@ import { Calendar as CalendarIcon, Tag } from "lucide-react";
6
  import { Calendar } from "@/components/ui/calendar";
7
  import { parseISO, format, isValid, isSameMonth, isSameYear, isSameDay } from "date-fns";
8
  import { Toggle } from "@/components/ui/toggle";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  const CalendarPage = () => {
11
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
12
- const [isYearView, setIsYearView] = useState(false);
 
 
 
 
 
 
13
 
14
- // Helper function to safely parse dates
15
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
16
  if (!dateString) return null;
17
  if (dateString === 'TBD') return null;
18
 
19
  try {
20
- // If it's a Date object wrapped in a stringified object (from console logs)
21
  if (typeof dateString === 'object') {
22
  return null;
23
  }
24
 
25
- // Convert number to string if needed
26
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
27
 
28
- // Add leading zeros to single-digit months and days
29
- const normalizedDate = dateStr
30
- .replace(/(\d{4})-(\d{1})-(\d{1,2})/, '$1-0$2-0$3')
31
- .replace(/(\d{4})-(\d{2})-(\d{1})/, '$1-$2-0$1');
 
32
 
33
  const parsedDate = parseISO(normalizedDate);
34
  return isValid(parsedDate) ? parsedDate : null;
@@ -38,19 +77,24 @@ const CalendarPage = () => {
38
  }
39
  };
40
 
41
- // Get events for the current month/year
42
  const getEvents = (date: Date) => {
43
  return conferencesData.filter((conf: Conference) => {
 
 
 
 
 
 
 
 
44
  const deadlineDate = safeParseISO(conf.deadline);
45
  const startDate = safeParseISO(conf.start);
46
  const endDate = safeParseISO(conf.end);
47
 
48
  const dateMatches = isYearView ? isSameYear : isSameMonth;
49
 
50
- // Check if deadline is in the selected period
51
  const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date);
52
 
53
- // Check if any part of the conference falls in the selected period
54
  let conferenceInPeriod = false;
55
  if (startDate && endDate) {
56
  let currentDate = new Date(startDate);
@@ -69,9 +113,18 @@ const CalendarPage = () => {
69
  });
70
  };
71
 
72
- // Get all events for day indicators
73
  const getDayEvents = (date: Date) => {
74
  return conferencesData.reduce((acc, conf) => {
 
 
 
 
 
 
 
 
 
 
75
  const deadlineDate = safeParseISO(conf.deadline);
76
  const startDate = safeParseISO(conf.start);
77
  const endDate = safeParseISO(conf.end);
@@ -92,146 +145,235 @@ const CalendarPage = () => {
92
  }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] });
93
  };
94
 
95
- const events = selectedDate ? getEvents(selectedDate) : [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- // Custom day content renderer
98
  const renderDayContent = (day: Date) => {
99
  const dayEvents = getDayEvents(day);
100
  const hasDeadlines = dayEvents.deadlines.length > 0;
101
  const hasConferences = dayEvents.conferences.length > 0;
102
 
103
- return (
104
  <div className="relative w-full h-full flex flex-col items-center">
105
  <span className="mb-1">{format(day, 'd')}</span>
106
- <div className="absolute bottom-0 left-0 right-0 flex gap-0.5 px-1">
107
  {hasDeadlines && (
108
- <div className="h-0.5 flex-1 bg-red-500" title="Deadline" />
109
- )}
110
- {hasConferences && (
111
- <div className="h-0.5 flex-1 bg-purple-600" title="Conference" />
112
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  </div>
114
  </div>
115
  );
116
- };
117
 
118
- return (
119
- <div className="min-h-screen bg-neutral-light p-6">
120
- <div className="max-w-7xl mx-auto">
121
- <div className="flex flex-col items-center mb-8">
122
- <h1 className="text-3xl font-bold mb-4">Calendar Overview</h1>
123
- <div className="flex items-center gap-4">
124
- <Toggle
125
- pressed={!isYearView}
126
- onPressedChange={() => setIsYearView(false)}
127
- variant="outline"
128
- >
129
- Month
130
- </Toggle>
131
- <Toggle
132
- pressed={isYearView}
133
- onPressedChange={() => setIsYearView(true)}
134
- variant="outline"
135
  >
136
- Year
137
- </Toggle>
138
- </div>
139
- </div>
 
 
 
 
 
 
140
 
141
- <div className="flex justify-center gap-6 mb-6">
142
- <div className="flex items-center gap-2">
143
- <div className="w-4 h-1 bg-purple-600" />
144
- <span>Conference Dates</span>
145
- </div>
146
- <div className="flex items-center gap-2">
147
- <div className="w-4 h-1 bg-red-500" />
148
- <span>Submission Deadlines</span>
149
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  </div>
 
 
 
151
 
152
- <div className="grid grid-cols-1 gap-8">
153
- <div className="mx-auto w-full max-w-4xl">
154
- <Calendar
155
- mode="single"
156
- selected={selectedDate}
157
- onSelect={setSelectedDate}
158
- numberOfMonths={isYearView ? 12 : 1}
159
- className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
160
- components={{
161
- Day: ({ date, ...props }) => (
162
- <button {...props} className="w-full h-full p-2">
163
- {renderDayContent(date)}
164
- </button>
165
- ),
166
- }}
167
- classNames={{
168
- months: `grid ${isYearView ? 'grid-cols-3 gap-4' : ''} justify-center`,
169
- month: "space-y-4",
170
- caption: "flex justify-center pt-1 relative items-center mb-4",
171
- caption_label: "text-lg font-semibold",
172
- table: "w-full border-collapse space-y-1",
173
- head_row: "flex",
174
- head_cell: "text-muted-foreground rounded-md w-10 font-normal text-[0.8rem]",
175
- row: "flex w-full mt-2",
176
- cell: `h-10 w-10 text-center text-sm p-0 relative focus-within:relative focus-within:z-20
177
- [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md
178
- last:[&:has([aria-selected])]:rounded-r-md hover:bg-neutral-50`,
179
- day: "h-10 w-10 p-0 font-normal hover:bg-neutral-100 rounded-lg transition-colors",
180
- day_today: "bg-neutral-100 text-primary font-semibold",
181
- nav: "space-x-1 flex items-center",
182
- nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
183
- nav_button_previous: "absolute left-1",
184
- nav_button_next: "absolute right-1",
185
- }}
186
- />
187
  </div>
188
 
189
- {selectedDate && events.length > 0 && (
190
- <div className="mx-auto w-full max-w-3xl space-y-4">
191
- <h2 className="text-xl font-semibold flex items-center gap-2">
192
- <CalendarIcon className="h-5 w-5" />
193
- Events in {format(selectedDate, isYearView ? 'yyyy' : 'MMMM yyyy')}
194
- </h2>
195
- <div className="space-y-4">
196
- {events.map((conf: Conference) => {
197
- const deadlineDate = safeParseISO(conf.deadline);
198
- const startDate = safeParseISO(conf.start);
199
- const endDate = safeParseISO(conf.end);
200
-
201
- return (
202
- <div key={conf.id} className="bg-white p-4 rounded-lg shadow-sm">
203
- <h3 className="font-semibold text-lg">{conf.title}</h3>
204
- <div className="space-y-1">
205
- {deadlineDate && (
206
- <p className="text-red-500">
207
- Submission Deadline: {format(deadlineDate, 'MMMM d, yyyy')}
208
- </p>
209
- )}
210
- {startDate && (
211
- <p className="text-purple-600">
212
- Conference Date: {format(startDate, 'MMMM d')}
213
- {endDate ? ` - ${format(endDate, 'MMMM d, yyyy')}` :
214
- `, ${format(startDate, 'yyyy')}`}
215
- </p>
216
- )}
217
- </div>
218
- <div className="mt-2 flex flex-wrap gap-2">
219
- {conf.tags.map((tag) => (
220
- <span key={tag} className="inline-flex items-center px-2 py-1 rounded-full
221
- text-xs bg-neutral-100">
222
- <Tag className="h-3 w-3 mr-1" />
223
- {tag}
224
- </span>
225
- ))}
226
- </div>
227
- </div>
228
- );
229
- })}
230
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  </div>
232
- )}
233
  </div>
234
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  </div>
236
  );
237
  };
 
 
1
  import { useState } from "react";
2
  import conferencesData from "@/data/conferences.yml";
3
  import { Conference } from "@/types/conference";
 
5
  import { Calendar } from "@/components/ui/calendar";
6
  import { parseISO, format, isValid, isSameMonth, isSameYear, isSameDay } from "date-fns";
7
  import { Toggle } from "@/components/ui/toggle";
8
+ import Header from "@/components/Header";
9
+ import FilterBar from "@/components/FilterBar";
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from "@/components/ui/dialog";
16
+ import {
17
+ Tooltip,
18
+ TooltipContent,
19
+ TooltipProvider,
20
+ TooltipTrigger,
21
+ } from "@/components/ui/tooltip";
22
+
23
+ const categoryColors: Record<string, string> = {
24
+ "computer-vision": "bg-orange-500",
25
+ "machine-learning": "bg-purple-500",
26
+ "natural-language-processing": "bg-blue-500",
27
+ "robotics": "bg-green-500",
28
+ "data-mining": "bg-pink-500",
29
+ "signal-processing": "bg-cyan-500",
30
+ "human-computer-interaction": "bg-indigo-500",
31
+ "web-search": "bg-yellow-500",
32
+ };
33
+
34
+ const categoryNames: Record<string, string> = {
35
+ "computer-vision": "Computer Vision",
36
+ "machine-learning": "Machine Learning",
37
+ "natural-language-processing": "NLP",
38
+ "robotics": "Robotics",
39
+ "data-mining": "Data Mining",
40
+ "signal-processing": "Signal Processing",
41
+ "human-computer-interaction": "HCI",
42
+ "web-search": "Web Search",
43
+ };
44
 
45
  const CalendarPage = () => {
46
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
47
+ const [isYearView, setIsYearView] = useState(true);
48
+ const [searchQuery, setSearchQuery] = useState("");
49
+ const [selectedTag, setSelectedTag] = useState("All");
50
+ const [selectedDayEvents, setSelectedDayEvents] = useState<{ date: Date | null, events: { deadlines: Conference[], conferences: Conference[] } }>({
51
+ date: null,
52
+ events: { deadlines: [], conferences: [] }
53
+ });
54
 
 
55
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
56
  if (!dateString) return null;
57
  if (dateString === 'TBD') return null;
58
 
59
  try {
 
60
  if (typeof dateString === 'object') {
61
  return null;
62
  }
63
 
 
64
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
65
 
66
+ let normalizedDate = dateStr;
67
+ const parts = dateStr.split('-');
68
+ if (parts.length === 3) {
69
+ normalizedDate = `${parts[0]}-${parts[1].padStart(2, '0')}-${parts[2].padStart(2, '0')}`;
70
+ }
71
 
72
  const parsedDate = parseISO(normalizedDate);
73
  return isValid(parsedDate) ? parsedDate : null;
 
77
  }
78
  };
79
 
 
80
  const getEvents = (date: Date) => {
81
  return conferencesData.filter((conf: Conference) => {
82
+ const matchesSearch = searchQuery === "" ||
83
+ conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
84
+ (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
85
+
86
+ const matchesTag = selectedTag === "All" || (Array.isArray(conf.tags) && conf.tags.includes(selectedTag));
87
+
88
+ if (!matchesSearch || !matchesTag) return false;
89
+
90
  const deadlineDate = safeParseISO(conf.deadline);
91
  const startDate = safeParseISO(conf.start);
92
  const endDate = safeParseISO(conf.end);
93
 
94
  const dateMatches = isYearView ? isSameYear : isSameMonth;
95
 
 
96
  const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date);
97
 
 
98
  let conferenceInPeriod = false;
99
  if (startDate && endDate) {
100
  let currentDate = new Date(startDate);
 
113
  });
114
  };
115
 
 
116
  const getDayEvents = (date: Date) => {
117
  return conferencesData.reduce((acc, conf) => {
118
+ const matchesSearch = searchQuery === "" ||
119
+ conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
120
+ (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
121
+
122
+ const matchesTag = selectedTag === "All" || (Array.isArray(conf.tags) && conf.tags.includes(selectedTag));
123
+
124
+ if (!matchesSearch || !matchesTag) {
125
+ return acc;
126
+ }
127
+
128
  const deadlineDate = safeParseISO(conf.deadline);
129
  const startDate = safeParseISO(conf.start);
130
  const endDate = safeParseISO(conf.end);
 
145
  }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] });
146
  };
147
 
148
+ const renderEventPreview = (events: { deadlines: Conference[], conferences: Conference[] }) => {
149
+ if (events.deadlines.length === 0 && events.conferences.length === 0) return null;
150
+
151
+ return (
152
+ <div className="p-2 max-w-[200px]">
153
+ {events.deadlines.length > 0 && (
154
+ <div className="mb-2">
155
+ <p className="font-semibold text-red-500">Deadlines:</p>
156
+ {events.deadlines.map(conf => (
157
+ <div key={conf.id} className="text-sm">{conf.title}</div>
158
+ ))}
159
+ </div>
160
+ )}
161
+ {events.conferences.length > 0 && (
162
+ <div>
163
+ <p className="font-semibold text-purple-600">Conferences:</p>
164
+ {events.conferences.map(conf => (
165
+ <div key={conf.id} className="text-sm">{conf.title}</div>
166
+ ))}
167
+ </div>
168
+ )}
169
+ </div>
170
+ );
171
+ };
172
 
 
173
  const renderDayContent = (day: Date) => {
174
  const dayEvents = getDayEvents(day);
175
  const hasDeadlines = dayEvents.deadlines.length > 0;
176
  const hasConferences = dayEvents.conferences.length > 0;
177
 
178
+ const content = (
179
  <div className="relative w-full h-full flex flex-col items-center">
180
  <span className="mb-1">{format(day, 'd')}</span>
181
+ <div className="absolute bottom-0 left-0 right-0 flex flex-col gap-0.5 px-1">
182
  {hasDeadlines && (
183
+ <div
184
+ className="h-0.5 w-full bg-red-500"
185
+ title="Deadline"
186
+ />
187
  )}
188
+ {dayEvents.conferences.map((conf) => {
189
+ const startDate = safeParseISO(conf.start);
190
+ const endDate = safeParseISO(conf.end);
191
+ const isFirstDay = startDate && isSameDay(startDate, day);
192
+ const isLastDay = endDate && isSameDay(endDate, day);
193
+ const categoryColor = conf.tags?.[0] ? categoryColors[conf.tags[0]] || "bg-purple-600" : "bg-purple-600";
194
+
195
+ return (
196
+ <div
197
+ key={conf.id}
198
+ className={`h-0.5 w-full ${categoryColor} ${!isFirstDay && !isLastDay ? '-ml-1 -mr-1' : ''} ${!isFirstDay ? '-ml-1' : ''} ${!isLastDay ? '-mr-1' : ''}`}
199
+ title={conf.title}
200
+ />
201
+ );
202
+ })}
203
  </div>
204
  </div>
205
  );
 
206
 
207
+ if (!hasDeadlines && !hasConferences) return content;
208
+
209
+ return (
210
+ <TooltipProvider>
211
+ <Tooltip>
212
+ <TooltipTrigger asChild>
213
+ <div
214
+ className="w-full h-full cursor-pointer"
215
+ onClick={() => setSelectedDayEvents({ date: day, events: dayEvents })}
 
 
 
 
 
 
 
 
216
  >
217
+ {content}
218
+ </div>
219
+ </TooltipTrigger>
220
+ <TooltipContent>
221
+ {renderEventPreview(dayEvents)}
222
+ </TooltipContent>
223
+ </Tooltip>
224
+ </TooltipProvider>
225
+ );
226
+ };
227
 
228
+ const renderEventDetails = (conf: Conference) => {
229
+ const deadlineDate = safeParseISO(conf.deadline);
230
+ const startDate = safeParseISO(conf.start);
231
+ const endDate = safeParseISO(conf.end);
232
+
233
+ return (
234
+ <div className="border-b last:border-b-0 pb-4 last:pb-0 mb-4 last:mb-0">
235
+ <h3 className="font-semibold text-lg">{conf.title}</h3>
236
+ {conf.full_name && (
237
+ <p className="text-sm text-neutral-600 mb-2">{conf.full_name}</p>
238
+ )}
239
+ <div className="space-y-1">
240
+ {deadlineDate && (
241
+ <p className="text-red-500 text-sm">
242
+ Submission Deadline: {format(deadlineDate, 'MMMM d, yyyy')}
243
+ </p>
244
+ )}
245
+ {startDate && (
246
+ <p className="text-purple-600 text-sm">
247
+ Conference Date: {format(startDate, 'MMMM d')}
248
+ {endDate ? ` - ${format(endDate, 'MMMM d, yyyy')}` :
249
+ `, ${format(startDate, 'yyyy')}`}
250
+ </p>
251
+ )}
252
+ </div>
253
+ <div className="mt-2 flex flex-wrap gap-2">
254
+ {Array.isArray(conf.tags) && conf.tags.map((tag) => (
255
+ <span key={tag} className="inline-flex items-center px-2 py-1 rounded-full
256
+ text-xs bg-neutral-100">
257
+ <Tag className="h-3 w-3 mr-1" />
258
+ {tag}
259
+ </span>
260
+ ))}
261
  </div>
262
+ </div>
263
+ );
264
+ };
265
 
266
+ const categories = Object.entries(categoryColors).filter(([category]) =>
267
+ conferencesData.some(conf => conf.tags?.includes(category))
268
+ );
269
+
270
+ return (
271
+ <div className="min-h-screen bg-neutral-light">
272
+ <Header onSearch={setSearchQuery} />
273
+ <FilterBar selectedTag={selectedTag} onTagSelect={setSelectedTag} />
274
+ <div className="p-6">
275
+ <div className="max-w-7xl mx-auto">
276
+ <div className="flex flex-col items-center mb-8">
277
+ <h1 className="text-3xl font-bold mb-4">Calendar Overview</h1>
278
+ <div className="flex items-center gap-4">
279
+ <Toggle
280
+ pressed={!isYearView}
281
+ onPressedChange={() => setIsYearView(false)}
282
+ variant="outline"
283
+ >
284
+ Month
285
+ </Toggle>
286
+ <Toggle
287
+ pressed={isYearView}
288
+ onPressedChange={() => setIsYearView(true)}
289
+ variant="outline"
290
+ >
291
+ Year
292
+ </Toggle>
293
+ </div>
 
 
 
 
 
 
 
294
  </div>
295
 
296
+ <div className="flex justify-center flex-wrap gap-4 mb-6">
297
+ <div className="flex items-center gap-2">
298
+ <div className="w-4 h-1 bg-red-500" />
299
+ <span>Submission Deadlines</span>
300
+ </div>
301
+ {categories.map(([category, color]) => (
302
+ <div key={category} className="flex items-center gap-2">
303
+ <div className={`w-4 h-1 ${color}`} />
304
+ <span>{categoryNames[category]}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  </div>
306
+ ))}
307
+ </div>
308
+
309
+ <div className="grid grid-cols-1 gap-8">
310
+ <div className="mx-auto w-full max-w-4xl">
311
+ <Calendar
312
+ mode="single"
313
+ selected={selectedDate}
314
+ onSelect={setSelectedDate}
315
+ numberOfMonths={isYearView ? 12 : 1}
316
+ className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
317
+ components={{
318
+ Day: ({ date, ...props }) => (
319
+ <button {...props} className="w-full h-full p-2">
320
+ {renderDayContent(date)}
321
+ </button>
322
+ ),
323
+ }}
324
+ classNames={{
325
+ months: `grid ${isYearView ? 'grid-cols-3 gap-4' : ''} justify-center`,
326
+ month: "space-y-4",
327
+ caption: "flex justify-center pt-1 relative items-center mb-4",
328
+ caption_label: "text-lg font-semibold",
329
+ table: "w-full border-collapse space-y-1",
330
+ head_row: "flex",
331
+ head_cell: "text-muted-foreground rounded-md w-10 font-normal text-[0.8rem]",
332
+ row: "flex w-full mt-2",
333
+ 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",
334
+ day: "h-10 w-10 p-0 font-normal hover:bg-neutral-100 rounded-lg transition-colors",
335
+ day_today: "bg-neutral-100 text-primary font-semibold",
336
+ nav: "space-x-1 flex items-center",
337
+ nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
338
+ nav_button_previous: "absolute left-1",
339
+ nav_button_next: "absolute right-1"
340
+ }}
341
+ />
342
  </div>
343
+ </div>
344
  </div>
345
  </div>
346
+
347
+ <Dialog
348
+ open={selectedDayEvents.date !== null}
349
+ onOpenChange={() => setSelectedDayEvents({ date: null, events: { deadlines: [], conferences: [] } })}
350
+ >
351
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
352
+ <DialogHeader>
353
+ <DialogTitle>
354
+ Events for {selectedDayEvents.date ? format(selectedDayEvents.date, 'MMMM d, yyyy') : ''}
355
+ </DialogTitle>
356
+ </DialogHeader>
357
+ <div className="space-y-4">
358
+ {selectedDayEvents.events.deadlines.length > 0 && (
359
+ <div>
360
+ <h3 className="text-lg font-semibold text-red-500 mb-3">Submission Deadlines</h3>
361
+ <div className="space-y-4">
362
+ {selectedDayEvents.events.deadlines.map(conf => renderEventDetails(conf))}
363
+ </div>
364
+ </div>
365
+ )}
366
+ {selectedDayEvents.events.conferences.length > 0 && (
367
+ <div>
368
+ <h3 className="text-lg font-semibold text-purple-600 mb-3">Conferences</h3>
369
+ <div className="space-y-4">
370
+ {selectedDayEvents.events.conferences.map(conf => renderEventDetails(conf))}
371
+ </div>
372
+ </div>
373
+ )}
374
+ </div>
375
+ </DialogContent>
376
+ </Dialog>
377
  </div>
378
  );
379
  };