nielsr HF staff commited on
Commit
1a97096
·
1 Parent(s): 6eb5e90

Improve calendar filters

Browse files
Files changed (2) hide show
  1. src/pages/Calendar.tsx +77 -42
  2. src/styles/globals.css +19 -0
src/pages/Calendar.tsx CHANGED
@@ -49,7 +49,10 @@ const CalendarPage = () => {
49
  date: null,
50
  events: { deadlines: [], conferences: [] }
51
  });
52
- const [selectedCategories, setSelectedCategories] = useState<Set<string>>(new Set());
 
 
 
53
 
54
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
55
  if (!dateString) return null;
@@ -85,7 +88,6 @@ const CalendarPage = () => {
85
  conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
86
  (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
87
 
88
- // Add category filter
89
  const matchesCategory = selectedCategories.size === 0 ||
90
  (Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
91
 
@@ -97,7 +99,7 @@ const CalendarPage = () => {
97
 
98
  const dateMatches = isYearView ? isSameYear : isSameMonth;
99
 
100
- const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date);
101
 
102
  let conferenceInPeriod = false;
103
  if (startDate && endDate) {
@@ -118,12 +120,12 @@ const CalendarPage = () => {
118
  };
119
 
120
  const getDayEvents = (date: Date) => {
121
- const deadlines = conferencesData.filter(conf => {
122
  const deadlineDate = safeParseISO(conf.deadline);
123
  const matchesCategory = selectedCategories.size === 0 ||
124
  (Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
125
  return deadlineDate && isSameDay(deadlineDate, date) && matchesCategory;
126
- });
127
 
128
  const conferences = conferencesData.filter(conf => {
129
  const startDate = safeParseISO(conf.start);
@@ -214,7 +216,7 @@ const CalendarPage = () => {
214
  const conferenceStyles = getConferenceLineStyle(date);
215
 
216
  // Get deadline style
217
- const hasDeadline = dayEvents.deadlines.length > 0;
218
 
219
  const handleDayClick = (e: React.MouseEvent) => {
220
  e.preventDefault(); // Prevent default calendar behavior
@@ -361,45 +363,78 @@ const CalendarPage = () => {
361
  );
362
 
363
  const renderLegend = () => {
364
- const categories = Object.entries(categoryColors);
365
-
366
  return (
367
- <div className="flex flex-wrap gap-4 items-center mb-6">
368
- <div className="flex items-center gap-2">
369
- <div className="w-4 h-1 bg-red-500" />
370
- <span className="text-sm">Submission Deadlines</span>
371
- </div>
372
- {categories.map(([category, color]) => {
373
- const isSelected = selectedCategories.has(category);
374
- return (
375
- <button
376
- key={category}
377
- onClick={() => {
378
- const newCategories = new Set(selectedCategories);
379
- if (isSelected) {
380
- newCategories.delete(category);
381
- } else {
382
- newCategories.add(category);
383
- }
384
- setSelectedCategories(newCategories);
385
- }}
386
- className={`flex items-center gap-2 px-3 py-1.5 rounded-lg transition-colors ${
387
- isSelected
388
- ? 'bg-neutral-100 ring-1 ring-neutral-200'
389
- : 'hover:bg-neutral-50'
390
- }`}
391
- >
392
- <div className={`w-4 h-1 ${color}`} />
393
- <span className="text-sm">{categoryNames[category]}</span>
394
- </button>
395
- )
396
- })}
397
- {selectedCategories.size > 0 && (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  <button
399
- onClick={() => setSelectedCategories(new Set())}
400
- className="text-sm text-neutral-500 hover:text-neutral-700"
 
 
 
 
 
401
  >
402
- Clear filters
403
  </button>
404
  )}
405
  </div>
 
49
  date: null,
50
  events: { deadlines: [], conferences: [] }
51
  });
52
+ const [selectedCategories, setSelectedCategories] = useState<Set<string>>(
53
+ new Set(Object.keys(categoryColors))
54
+ );
55
+ const [showDeadlines, setShowDeadlines] = useState(true);
56
 
57
  const safeParseISO = (dateString: string | undefined | number): Date | null => {
58
  if (!dateString) return null;
 
88
  conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
89
  (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
90
 
 
91
  const matchesCategory = selectedCategories.size === 0 ||
92
  (Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
93
 
 
99
 
100
  const dateMatches = isYearView ? isSameYear : isSameMonth;
101
 
102
+ const deadlineInPeriod = showDeadlines && deadlineDate && dateMatches(deadlineDate, date);
103
 
104
  let conferenceInPeriod = false;
105
  if (startDate && endDate) {
 
120
  };
121
 
122
  const getDayEvents = (date: Date) => {
123
+ const deadlines = showDeadlines ? conferencesData.filter(conf => {
124
  const deadlineDate = safeParseISO(conf.deadline);
125
  const matchesCategory = selectedCategories.size === 0 ||
126
  (Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
127
  return deadlineDate && isSameDay(deadlineDate, date) && matchesCategory;
128
+ }) : [];
129
 
130
  const conferences = conferencesData.filter(conf => {
131
  const startDate = safeParseISO(conf.start);
 
216
  const conferenceStyles = getConferenceLineStyle(date);
217
 
218
  // Get deadline style
219
+ const hasDeadline = showDeadlines && dayEvents.deadlines.length > 0;
220
 
221
  const handleDayClick = (e: React.MouseEvent) => {
222
  e.preventDefault(); // Prevent default calendar behavior
 
363
  );
364
 
365
  const renderLegend = () => {
 
 
366
  return (
367
+ <div className="flex flex-wrap gap-3 justify-center items-center mb-4">
368
+ <TooltipProvider>
369
+ <Tooltip>
370
+ <TooltipTrigger asChild>
371
+ <button
372
+ onClick={() => setShowDeadlines(!showDeadlines)}
373
+ className={`
374
+ flex items-center gap-2 px-3 py-1.5
375
+ rounded-lg border border-red-200
376
+ bg-white hover:bg-red-50
377
+ transition-all duration-200
378
+ cursor-pointer
379
+ ${showDeadlines ? 'ring-2 ring-primary ring-offset-2' : ''}
380
+ `}
381
+ >
382
+ <div className="w-3 h-3 bg-red-500 rounded-full" />
383
+ <span className="text-sm">Submission Deadlines</span>
384
+ </button>
385
+ </TooltipTrigger>
386
+ <TooltipContent>
387
+ <p>Click to toggle submission deadlines</p>
388
+ </TooltipContent>
389
+ </Tooltip>
390
+ </TooltipProvider>
391
+
392
+ {Object.entries(categoryColors).map(([tag, color]) => (
393
+ <TooltipProvider key={tag}>
394
+ <Tooltip>
395
+ <TooltipTrigger asChild>
396
+ <button
397
+ onClick={() => {
398
+ const newCategories = new Set(selectedCategories);
399
+ if (newCategories.has(tag)) {
400
+ newCategories.delete(tag);
401
+ } else {
402
+ newCategories.add(tag);
403
+ }
404
+ setSelectedCategories(newCategories);
405
+ }}
406
+ className={`
407
+ flex items-center gap-2 px-3 py-1.5
408
+ rounded-lg border border-neutral-200
409
+ bg-white hover:bg-neutral-50
410
+ transition-all duration-200
411
+ cursor-pointer
412
+ ${selectedCategories.has(tag) ? 'ring-2 ring-primary ring-offset-2' : ''}
413
+ `}
414
+ >
415
+ <div className={`w-3 h-3 rounded-full ${color}`} />
416
+ <span className="text-sm">{categoryNames[tag] || tag}</span>
417
+ </button>
418
+ </TooltipTrigger>
419
+ <TooltipContent>
420
+ <p>Click to toggle {categoryNames[tag] || tag}</p>
421
+ </TooltipContent>
422
+ </Tooltip>
423
+ </TooltipProvider>
424
+ ))}
425
+
426
+ {/* Only show Reset when some filters are deselected */}
427
+ {(selectedCategories.size < Object.keys(categoryColors).length || !showDeadlines) && (
428
  <button
429
+ onClick={() => {
430
+ setSelectedCategories(new Set(Object.keys(categoryColors)));
431
+ setShowDeadlines(true);
432
+ }}
433
+ className="text-sm text-neutral-500 hover:text-neutral-700
434
+ px-3 py-1.5 rounded-lg border border-neutral-200
435
+ hover:bg-neutral-50 transition-colors"
436
  >
437
+ Reset filters
438
  </button>
439
  )}
440
  </div>
src/styles/globals.css ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Add these styles to make the filter tags more interactive */
2
+ .filter-tag {
3
+ @apply transition-all duration-200;
4
+ }
5
+
6
+ .filter-tag:hover {
7
+ @apply transform scale-105;
8
+ }
9
+
10
+ /* Add a subtle pulse animation for the active filters */
11
+ @keyframes subtle-pulse {
12
+ 0% { transform: scale(1); }
13
+ 50% { transform: scale(1.02); }
14
+ 100% { transform: scale(1); }
15
+ }
16
+
17
+ .filter-tag-active {
18
+ animation: subtle-pulse 2s infinite;
19
+ }