Spaces:
Running
Running
Add legend filter
Browse files- src/pages/Calendar.tsx +66 -20
src/pages/Calendar.tsx
CHANGED
@@ -49,6 +49,7 @@ const CalendarPage = () => {
|
|
49 |
date: null,
|
50 |
events: { deadlines: [], conferences: [] }
|
51 |
});
|
|
|
52 |
|
53 |
const safeParseISO = (dateString: string | undefined | number): Date | null => {
|
54 |
if (!dateString) return null;
|
@@ -84,7 +85,11 @@ const CalendarPage = () => {
|
|
84 |
conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
85 |
(conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
|
86 |
|
87 |
-
|
|
|
|
|
|
|
|
|
88 |
|
89 |
const deadlineDate = safeParseISO(conf.deadline);
|
90 |
const startDate = safeParseISO(conf.start);
|
@@ -115,15 +120,20 @@ const CalendarPage = () => {
|
|
115 |
const getDayEvents = (date: Date) => {
|
116 |
const deadlines = conferencesData.filter(conf => {
|
117 |
const deadlineDate = safeParseISO(conf.deadline);
|
118 |
-
|
|
|
|
|
119 |
});
|
120 |
|
121 |
const conferences = conferencesData.filter(conf => {
|
122 |
const startDate = safeParseISO(conf.start);
|
123 |
const endDate = safeParseISO(conf.end);
|
|
|
|
|
124 |
return startDate && endDate &&
|
125 |
date >= startDate &&
|
126 |
-
date <= endDate
|
|
|
127 |
});
|
128 |
|
129 |
return {
|
@@ -167,7 +177,9 @@ const CalendarPage = () => {
|
|
167 |
.filter(conf => {
|
168 |
const startDate = safeParseISO(conf.start);
|
169 |
const endDate = safeParseISO(conf.end);
|
170 |
-
|
|
|
|
|
171 |
})
|
172 |
.map(conf => {
|
173 |
const startDate = safeParseISO(conf.start);
|
@@ -348,6 +360,52 @@ const CalendarPage = () => {
|
|
348 |
conferencesData.some(conf => conf.tags?.includes(category))
|
349 |
);
|
350 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
return (
|
352 |
<div className="min-h-screen bg-neutral-light">
|
353 |
<Header onSearch={setSearchQuery} />
|
@@ -423,35 +481,23 @@ const CalendarPage = () => {
|
|
423 |
<div className="max-w-7xl mx-auto">
|
424 |
<div className="flex flex-col items-center mb-8">
|
425 |
<h1 className="text-3xl font-bold mb-4">Calendar Overview</h1>
|
426 |
-
<div className="flex items-center gap-4">
|
427 |
<Toggle
|
428 |
pressed={!isYearView}
|
429 |
onPressedChange={() => setIsYearView(false)}
|
430 |
variant="outline"
|
431 |
>
|
432 |
-
Month
|
433 |
</Toggle>
|
434 |
<Toggle
|
435 |
pressed={isYearView}
|
436 |
onPressedChange={() => setIsYearView(true)}
|
437 |
variant="outline"
|
438 |
>
|
439 |
-
Year
|
440 |
</Toggle>
|
441 |
</div>
|
442 |
-
|
443 |
-
|
444 |
-
<div className="flex justify-center flex-wrap gap-4 mb-6">
|
445 |
-
<div className="flex items-center gap-2">
|
446 |
-
<div className="w-4 h-1 bg-red-500" />
|
447 |
-
<span>Submission Deadlines</span>
|
448 |
-
</div>
|
449 |
-
{categories.map(([category, color]) => (
|
450 |
-
<div key={category} className="flex items-center gap-2">
|
451 |
-
<div className={`w-4 h-1 ${color}`} />
|
452 |
-
<span>{categoryNames[category]}</span>
|
453 |
-
</div>
|
454 |
-
))}
|
455 |
</div>
|
456 |
|
457 |
<div className="grid grid-cols-1 gap-8">
|
|
|
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 |
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 |
+
|
92 |
+
if (!matchesSearch || !matchesCategory) return false;
|
93 |
|
94 |
const deadlineDate = safeParseISO(conf.deadline);
|
95 |
const startDate = safeParseISO(conf.start);
|
|
|
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);
|
130 |
const endDate = safeParseISO(conf.end);
|
131 |
+
const matchesCategory = selectedCategories.size === 0 ||
|
132 |
+
(Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
|
133 |
return startDate && endDate &&
|
134 |
date >= startDate &&
|
135 |
+
date <= endDate &&
|
136 |
+
matchesCategory;
|
137 |
});
|
138 |
|
139 |
return {
|
|
|
177 |
.filter(conf => {
|
178 |
const startDate = safeParseISO(conf.start);
|
179 |
const endDate = safeParseISO(conf.end);
|
180 |
+
const matchesCategory = selectedCategories.size === 0 ||
|
181 |
+
(Array.isArray(conf.tags) && conf.tags.some(tag => selectedCategories.has(tag)));
|
182 |
+
return startDate && endDate && date >= startDate && date <= endDate && matchesCategory;
|
183 |
})
|
184 |
.map(conf => {
|
185 |
const startDate = safeParseISO(conf.start);
|
|
|
360 |
conferencesData.some(conf => conf.tags?.includes(category))
|
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>
|
406 |
+
);
|
407 |
+
};
|
408 |
+
|
409 |
return (
|
410 |
<div className="min-h-screen bg-neutral-light">
|
411 |
<Header onSearch={setSearchQuery} />
|
|
|
481 |
<div className="max-w-7xl mx-auto">
|
482 |
<div className="flex flex-col items-center mb-8">
|
483 |
<h1 className="text-3xl font-bold mb-4">Calendar Overview</h1>
|
484 |
+
<div className="flex items-center gap-4 mb-6">
|
485 |
<Toggle
|
486 |
pressed={!isYearView}
|
487 |
onPressedChange={() => setIsYearView(false)}
|
488 |
variant="outline"
|
489 |
>
|
490 |
+
Month View
|
491 |
</Toggle>
|
492 |
<Toggle
|
493 |
pressed={isYearView}
|
494 |
onPressedChange={() => setIsYearView(true)}
|
495 |
variant="outline"
|
496 |
>
|
497 |
+
Year View
|
498 |
</Toggle>
|
499 |
</div>
|
500 |
+
{renderLegend()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
501 |
</div>
|
502 |
|
503 |
<div className="grid grid-cols-1 gap-8">
|