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">
|