gpt-engineer-app[bot] commited on
Commit
9cc43e7
·
1 Parent(s): 52d73b6

Display conference data on day hover

Browse files

The data for conferences is currently shown below the calendar. This commit aims to improve the user experience by displaying this data on hover or click of a day in the calendar.

Files changed (1) hide show
  1. src/pages/Calendar.tsx +127 -48
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";
@@ -7,13 +6,28 @@ 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
  import Header from "@/components/Header";
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  const CalendarPage = () => {
12
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
13
  const [isYearView, setIsYearView] = useState(false);
14
  const [searchQuery, setSearchQuery] = useState("");
 
 
 
 
15
 
16
- // Helper function to safely parse dates
17
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
18
  if (!dateString) return null;
19
  if (dateString === 'TBD') return null;
@@ -25,7 +39,6 @@ const CalendarPage = () => {
25
 
26
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
27
 
28
- // Handle both "YYYY-MM-DD" and "YYYY-M-D" formats
29
  let normalizedDate = dateStr;
30
  const parts = dateStr.split('-');
31
  if (parts.length === 3) {
@@ -76,7 +89,6 @@ const CalendarPage = () => {
76
 
77
  const getDayEvents = (date: Date) => {
78
  return conferencesData.reduce((acc, conf) => {
79
- // Check if the conference matches the search query
80
  const matchesSearch = searchQuery === "" ||
81
  conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
82
  (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
@@ -105,14 +117,37 @@ const CalendarPage = () => {
105
  }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] });
106
  };
107
 
108
- const events = selectedDate ? getEvents(selectedDate) : [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  const renderDayContent = (day: Date) => {
111
  const dayEvents = getDayEvents(day);
112
  const hasDeadlines = dayEvents.deadlines.length > 0;
113
  const hasConferences = dayEvents.conferences.length > 0;
114
 
115
- return (
116
  <div className="relative w-full h-full flex flex-col items-center">
117
  <span className="mb-1">{format(day, 'd')}</span>
118
  <div className="absolute bottom-0 left-0 right-0 flex gap-0.5 px-1">
@@ -125,6 +160,64 @@ const CalendarPage = () => {
125
  </div>
126
  </div>
127
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  };
129
 
130
  return (
@@ -199,54 +292,40 @@ const CalendarPage = () => {
199
  }}
200
  />
201
  </div>
 
 
 
202
 
203
- {selectedDate && events.length > 0 && (
204
- <div className="mx-auto w-full max-w-3xl space-y-4">
205
- <h2 className="text-xl font-semibold flex items-center gap-2">
206
- <CalendarIcon className="h-5 w-5" />
207
- Events in {format(selectedDate, isYearView ? 'yyyy' : 'MMMM yyyy')}
208
- </h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  <div className="space-y-4">
210
- {events.map((conf: Conference) => {
211
- const deadlineDate = safeParseISO(conf.deadline);
212
- const startDate = safeParseISO(conf.start);
213
- const endDate = safeParseISO(conf.end);
214
-
215
- return (
216
- <div key={conf.id} className="bg-white p-4 rounded-lg shadow-sm">
217
- <h3 className="font-semibold text-lg">{conf.title}</h3>
218
- <div className="space-y-1">
219
- {deadlineDate && (
220
- <p className="text-red-500">
221
- Submission Deadline: {format(deadlineDate, 'MMMM d, yyyy')}
222
- </p>
223
- )}
224
- {startDate && (
225
- <p className="text-purple-600">
226
- Conference Date: {format(startDate, 'MMMM d')}
227
- {endDate ? ` - ${format(endDate, 'MMMM d, yyyy')}` :
228
- `, ${format(startDate, 'yyyy')}`}
229
- </p>
230
- )}
231
- </div>
232
- <div className="mt-2 flex flex-wrap gap-2">
233
- {Array.isArray(conf.tags) && conf.tags.map((tag) => (
234
- <span key={tag} className="inline-flex items-center px-2 py-1 rounded-full
235
- text-xs bg-neutral-100">
236
- <Tag className="h-3 w-3 mr-1" />
237
- {tag}
238
- </span>
239
- ))}
240
- </div>
241
- </div>
242
- );
243
- })}
244
  </div>
245
  </div>
246
  )}
247
  </div>
248
- </div>
249
- </div>
250
  </div>
251
  );
252
  };
 
 
1
  import { useState } from "react";
2
  import conferencesData from "@/data/conferences.yml";
3
  import { Conference } from "@/types/conference";
 
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 {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ } from "@/components/ui/dialog";
15
+ import {
16
+ Tooltip,
17
+ TooltipContent,
18
+ TooltipProvider,
19
+ TooltipTrigger,
20
+ } from "@/components/ui/tooltip";
21
 
22
  const CalendarPage = () => {
23
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
24
  const [isYearView, setIsYearView] = useState(false);
25
  const [searchQuery, setSearchQuery] = useState("");
26
+ const [selectedDayEvents, setSelectedDayEvents] = useState<{ date: Date | null, events: { deadlines: Conference[], conferences: Conference[] } }>({
27
+ date: null,
28
+ events: { deadlines: [], conferences: [] }
29
+ });
30
 
 
31
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
32
  if (!dateString) return null;
33
  if (dateString === 'TBD') return null;
 
39
 
40
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
41
 
 
42
  let normalizedDate = dateStr;
43
  const parts = dateStr.split('-');
44
  if (parts.length === 3) {
 
89
 
90
  const getDayEvents = (date: Date) => {
91
  return conferencesData.reduce((acc, conf) => {
 
92
  const matchesSearch = searchQuery === "" ||
93
  conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
94
  (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
 
117
  }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] });
118
  };
119
 
120
+ const renderEventPreview = (events: { deadlines: Conference[], conferences: Conference[] }) => {
121
+ if (events.deadlines.length === 0 && events.conferences.length === 0) return null;
122
+
123
+ return (
124
+ <div className="p-2 max-w-[200px]">
125
+ {events.deadlines.length > 0 && (
126
+ <div className="mb-2">
127
+ <p className="font-semibold text-red-500">Deadlines:</p>
128
+ {events.deadlines.map(conf => (
129
+ <div key={conf.id} className="text-sm">{conf.title}</div>
130
+ ))}
131
+ </div>
132
+ )}
133
+ {events.conferences.length > 0 && (
134
+ <div>
135
+ <p className="font-semibold text-purple-600">Conferences:</p>
136
+ {events.conferences.map(conf => (
137
+ <div key={conf.id} className="text-sm">{conf.title}</div>
138
+ ))}
139
+ </div>
140
+ )}
141
+ </div>
142
+ );
143
+ };
144
 
145
  const renderDayContent = (day: Date) => {
146
  const dayEvents = getDayEvents(day);
147
  const hasDeadlines = dayEvents.deadlines.length > 0;
148
  const hasConferences = dayEvents.conferences.length > 0;
149
 
150
+ const content = (
151
  <div className="relative w-full h-full flex flex-col items-center">
152
  <span className="mb-1">{format(day, 'd')}</span>
153
  <div className="absolute bottom-0 left-0 right-0 flex gap-0.5 px-1">
 
160
  </div>
161
  </div>
162
  );
163
+
164
+ if (!hasDeadlines && !hasConferences) return content;
165
+
166
+ return (
167
+ <TooltipProvider>
168
+ <Tooltip>
169
+ <TooltipTrigger asChild>
170
+ <div
171
+ className="w-full h-full cursor-pointer"
172
+ onClick={() => setSelectedDayEvents({ date: day, events: dayEvents })}
173
+ >
174
+ {content}
175
+ </div>
176
+ </TooltipTrigger>
177
+ <TooltipContent>
178
+ {renderEventPreview(dayEvents)}
179
+ </TooltipContent>
180
+ </Tooltip>
181
+ </TooltipProvider>
182
+ );
183
+ };
184
+
185
+ const renderEventDetails = (conf: Conference) => {
186
+ const deadlineDate = safeParseISO(conf.deadline);
187
+ const startDate = safeParseISO(conf.start);
188
+ const endDate = safeParseISO(conf.end);
189
+
190
+ return (
191
+ <div className="border-b last:border-b-0 pb-4 last:pb-0 mb-4 last:mb-0">
192
+ <h3 className="font-semibold text-lg">{conf.title}</h3>
193
+ {conf.full_name && (
194
+ <p className="text-sm text-neutral-600 mb-2">{conf.full_name}</p>
195
+ )}
196
+ <div className="space-y-1">
197
+ {deadlineDate && (
198
+ <p className="text-red-500 text-sm">
199
+ Submission Deadline: {format(deadlineDate, 'MMMM d, yyyy')}
200
+ </p>
201
+ )}
202
+ {startDate && (
203
+ <p className="text-purple-600 text-sm">
204
+ Conference Date: {format(startDate, 'MMMM d')}
205
+ {endDate ? ` - ${format(endDate, 'MMMM d, yyyy')}` :
206
+ `, ${format(startDate, 'yyyy')}`}
207
+ </p>
208
+ )}
209
+ </div>
210
+ <div className="mt-2 flex flex-wrap gap-2">
211
+ {Array.isArray(conf.tags) && conf.tags.map((tag) => (
212
+ <span key={tag} className="inline-flex items-center px-2 py-1 rounded-full
213
+ text-xs bg-neutral-100">
214
+ <Tag className="h-3 w-3 mr-1" />
215
+ {tag}
216
+ </span>
217
+ ))}
218
+ </div>
219
+ </div>
220
+ );
221
  };
222
 
223
  return (
 
292
  }}
293
  />
294
  </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
 
299
+ <Dialog
300
+ open={selectedDayEvents.date !== null}
301
+ onOpenChange={() => setSelectedDayEvents({ date: null, events: { deadlines: [], conferences: [] } })}
302
+ >
303
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
304
+ <DialogHeader>
305
+ <DialogTitle>
306
+ Events for {selectedDayEvents.date ? format(selectedDayEvents.date, 'MMMM d, yyyy') : ''}
307
+ </DialogTitle>
308
+ </DialogHeader>
309
+ <div className="space-y-4">
310
+ {selectedDayEvents.events.deadlines.length > 0 && (
311
+ <div>
312
+ <h3 className="text-lg font-semibold text-red-500 mb-3">Submission Deadlines</h3>
313
+ <div className="space-y-4">
314
+ {selectedDayEvents.events.deadlines.map(conf => renderEventDetails(conf))}
315
+ </div>
316
+ </div>
317
+ )}
318
+ {selectedDayEvents.events.conferences.length > 0 && (
319
+ <div>
320
+ <h3 className="text-lg font-semibold text-purple-600 mb-3">Conferences</h3>
321
  <div className="space-y-4">
322
+ {selectedDayEvents.events.conferences.map(conf => renderEventDetails(conf))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  </div>
324
  </div>
325
  )}
326
  </div>
327
+ </DialogContent>
328
+ </Dialog>
329
  </div>
330
  );
331
  };