Spaces:
Sleeping
Sleeping
Add ability to select multiple categories
Browse files- src/components/FilterBar.tsx +17 -10
- src/pages/Index.tsx +7 -6
src/components/FilterBar.tsx
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
-
|
2 |
import { useMemo } from "react";
|
3 |
import conferencesData from "@/data/conferences.yml";
|
4 |
import { X } from "lucide-react";
|
5 |
|
6 |
interface FilterBarProps {
|
7 |
-
|
8 |
-
onTagSelect: (
|
9 |
}
|
10 |
|
11 |
-
const FilterBar = ({
|
12 |
const uniqueTags = useMemo(() => {
|
13 |
const tags = new Set<string>();
|
14 |
if (Array.isArray(conferencesData)) {
|
@@ -18,12 +17,12 @@ const FilterBar = ({ selectedTag, onTagSelect }: FilterBarProps) => {
|
|
18 |
}
|
19 |
});
|
20 |
}
|
21 |
-
return
|
22 |
id: tag,
|
23 |
label: tag.split("-").map(word =>
|
24 |
word.charAt(0).toUpperCase() + word.slice(1)
|
25 |
).join(" "),
|
26 |
-
description:
|
27 |
}));
|
28 |
}, []);
|
29 |
|
@@ -35,11 +34,19 @@ const FilterBar = ({ selectedTag, onTagSelect }: FilterBarProps) => {
|
|
35 |
<button
|
36 |
key={filter.id}
|
37 |
title={filter.description}
|
38 |
-
onClick={() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
className={`
|
40 |
px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
41 |
filter-tag
|
42 |
-
${
|
43 |
? "bg-primary text-white shadow-sm filter-tag-active"
|
44 |
: "bg-neutral-50 text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900"
|
45 |
}
|
@@ -49,9 +56,9 @@ const FilterBar = ({ selectedTag, onTagSelect }: FilterBarProps) => {
|
|
49 |
</button>
|
50 |
))}
|
51 |
|
52 |
-
{
|
53 |
<button
|
54 |
-
onClick={() => onTagSelect(
|
55 |
className="px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
56 |
bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700
|
57 |
flex items-center gap-2"
|
|
|
|
|
1 |
import { useMemo } from "react";
|
2 |
import conferencesData from "@/data/conferences.yml";
|
3 |
import { X } from "lucide-react";
|
4 |
|
5 |
interface FilterBarProps {
|
6 |
+
selectedTags: Set<string>;
|
7 |
+
onTagSelect: (tags: Set<string>) => void;
|
8 |
}
|
9 |
|
10 |
+
const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
11 |
const uniqueTags = useMemo(() => {
|
12 |
const tags = new Set<string>();
|
13 |
if (Array.isArray(conferencesData)) {
|
|
|
17 |
}
|
18 |
});
|
19 |
}
|
20 |
+
return Array.from(tags).map(tag => ({
|
21 |
id: tag,
|
22 |
label: tag.split("-").map(word =>
|
23 |
word.charAt(0).toUpperCase() + word.slice(1)
|
24 |
).join(" "),
|
25 |
+
description: `${tag} Conferences`
|
26 |
}));
|
27 |
}, []);
|
28 |
|
|
|
34 |
<button
|
35 |
key={filter.id}
|
36 |
title={filter.description}
|
37 |
+
onClick={() => {
|
38 |
+
const newTags = new Set(selectedTags);
|
39 |
+
if (newTags.has(filter.id)) {
|
40 |
+
newTags.delete(filter.id);
|
41 |
+
} else {
|
42 |
+
newTags.add(filter.id);
|
43 |
+
}
|
44 |
+
onTagSelect(newTags);
|
45 |
+
}}
|
46 |
className={`
|
47 |
px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
48 |
filter-tag
|
49 |
+
${selectedTags.has(filter.id)
|
50 |
? "bg-primary text-white shadow-sm filter-tag-active"
|
51 |
: "bg-neutral-50 text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900"
|
52 |
}
|
|
|
56 |
</button>
|
57 |
))}
|
58 |
|
59 |
+
{selectedTags.size > 0 && (
|
60 |
<button
|
61 |
+
onClick={() => onTagSelect(new Set())}
|
62 |
className="px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
63 |
bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700
|
64 |
flex items-center gap-2"
|
src/pages/Index.tsx
CHANGED
@@ -8,7 +8,7 @@ import { Switch } from "@/components/ui/switch"
|
|
8 |
import { parseISO, isValid, isPast } from "date-fns";
|
9 |
|
10 |
const Index = () => {
|
11 |
-
const [
|
12 |
const [searchQuery, setSearchQuery] = useState("");
|
13 |
const [showPastConferences, setShowPastConferences] = useState(false);
|
14 |
|
@@ -25,20 +25,21 @@ const Index = () => {
|
|
25 |
const isUpcoming = !deadlineDate || !isValid(deadlineDate) || !isPast(deadlineDate);
|
26 |
if (!showPastConferences && !isUpcoming) return false;
|
27 |
|
28 |
-
// Filter by
|
29 |
-
const
|
|
|
30 |
const matchesSearch = searchQuery === "" ||
|
31 |
conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
32 |
(conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
|
33 |
|
34 |
-
return
|
35 |
})
|
36 |
.sort((a: Conference, b: Conference) => {
|
37 |
const dateA = a.deadline && a.deadline !== 'TBD' ? parseISO(a.deadline).getTime() : Infinity;
|
38 |
const dateB = b.deadline && b.deadline !== 'TBD' ? parseISO(b.deadline).getTime() : Infinity;
|
39 |
return dateA - dateB;
|
40 |
});
|
41 |
-
}, [
|
42 |
|
43 |
if (!Array.isArray(conferencesData)) {
|
44 |
return <div>Loading conferences...</div>;
|
@@ -49,7 +50,7 @@ const Index = () => {
|
|
49 |
<Header onSearch={setSearchQuery} />
|
50 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
51 |
<div className="space-y-4 py-4">
|
52 |
-
<FilterBar
|
53 |
<div className="flex items-center gap-2">
|
54 |
<label htmlFor="show-past" className="text-sm text-neutral-600">
|
55 |
Show past conferences
|
|
|
8 |
import { parseISO, isValid, isPast } from "date-fns";
|
9 |
|
10 |
const Index = () => {
|
11 |
+
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
12 |
const [searchQuery, setSearchQuery] = useState("");
|
13 |
const [showPastConferences, setShowPastConferences] = useState(false);
|
14 |
|
|
|
25 |
const isUpcoming = !deadlineDate || !isValid(deadlineDate) || !isPast(deadlineDate);
|
26 |
if (!showPastConferences && !isUpcoming) return false;
|
27 |
|
28 |
+
// Filter by tags and search query
|
29 |
+
const matchesTags = selectedTags.size === 0 ||
|
30 |
+
(Array.isArray(conf.tags) && conf.tags.some(tag => selectedTags.has(tag)));
|
31 |
const matchesSearch = searchQuery === "" ||
|
32 |
conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
33 |
(conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
|
34 |
|
35 |
+
return matchesTags && matchesSearch;
|
36 |
})
|
37 |
.sort((a: Conference, b: Conference) => {
|
38 |
const dateA = a.deadline && a.deadline !== 'TBD' ? parseISO(a.deadline).getTime() : Infinity;
|
39 |
const dateB = b.deadline && b.deadline !== 'TBD' ? parseISO(b.deadline).getTime() : Infinity;
|
40 |
return dateA - dateB;
|
41 |
});
|
42 |
+
}, [selectedTags, searchQuery, showPastConferences]);
|
43 |
|
44 |
if (!Array.isArray(conferencesData)) {
|
45 |
return <div>Loading conferences...</div>;
|
|
|
50 |
<Header onSearch={setSearchQuery} />
|
51 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
52 |
<div className="space-y-4 py-4">
|
53 |
+
<FilterBar selectedTags={selectedTags} onTagSelect={setSelectedTags} />
|
54 |
<div className="flex items-center gap-2">
|
55 |
<label htmlFor="show-past" className="text-sm text-neutral-600">
|
56 |
Show past conferences
|