1
1
---
2
- const { allTracks, allTypes, allLevels } = Astro .props ;
2
+ interface FilterSessionsProps {
3
+ allTracks? : string [];
4
+ allTypes? : string [];
5
+ allLevels? : string [];
6
+ }
7
+
8
+ const { allTracks = [], allTypes = [], allLevels = [] }: FilterSessionsProps = Astro .props ;
3
9
import { Label } from " ../form/label" ;
4
10
import { Select } from " ../form/select" ;
5
11
---
6
12
7
13
<form class =" mb-12 filter-sessions" >
8
- {
9
- allTracks && allTracks .length > 0 && (
10
- <div class = " mb-6" >
14
+ <div class =" w-full mb-6" >
15
+ <Label htmlFor =" search" >Search</Label >
16
+ <input
17
+ type =" text"
18
+ id =" search"
19
+ name =" search"
20
+ class =" block w-full bg-transparent text-lg h-16 py-2 pr-16 pl-4 border-[3px] border-primary appearance-none focus:outline-none focus:border-black focus-visible:bg-white"
21
+ placeholder =" Search sessions..."
22
+ />
23
+ </div >
24
+
25
+ <div class =" flex flex-wrap gap-x-4 gap-y-4 mb-6" >
26
+ { allTracks .length > 0 && (
27
+ <div >
11
28
<Label htmlFor = " track" >Track</Label >
12
29
<Select id = " track" name = " track" >
13
30
<option value = " " >All</option >
@@ -16,12 +33,10 @@ import { Select } from "../form/select";
16
33
))}
17
34
</Select >
18
35
</div >
19
- )
20
- }
36
+ )}
21
37
22
- {
23
- allTypes && allTypes .length > 0 && (
24
- <div class = " mb-6" >
38
+ { allTypes .length > 0 && (
39
+ <div >
25
40
<Label htmlFor = " type" >Session type</Label >
26
41
<Select id = " type" name = " type" >
27
42
<option value = " " >All</option >
@@ -30,12 +45,10 @@ import { Select } from "../form/select";
30
45
))}
31
46
</Select >
32
47
</div >
33
- )
34
- }
48
+ )}
35
49
36
- {
37
- allLevels && allLevels .length > 0 && (
38
- <div class = " mb-6" >
50
+ { allLevels .length > 0 && (
51
+ <div >
39
52
<Label htmlFor = " level" >Level</Label >
40
53
<Select id = " level" name = " level" >
41
54
<option value = " " >All</option >
@@ -44,58 +57,66 @@ import { Select } from "../form/select";
44
57
))}
45
58
</Select >
46
59
</div >
47
- )
48
- }
60
+ )}
61
+ </ div >
49
62
50
63
<button
51
64
type =" button"
52
65
id =" reset-filters"
53
66
class =" btn underline font-bold hover:text-primary-hover"
54
- >Reset Filters</button
55
67
>
68
+ Reset Filters
69
+ </button >
56
70
</form >
57
71
72
+ <!-- Results info -->
73
+ <div id =" results-info" class =" mb-6 text-lg font-medium" ></div >
74
+
58
75
<script >
76
+ interface FilterParams {
77
+ track: string;
78
+ type: string;
79
+ level: string;
80
+ search: string;
81
+ }
82
+
59
83
document.addEventListener("DOMContentLoaded", () => {
60
- const forms = document.querySelectorAll<HTMLFormElement>(
61
- "form.filter-sessions",
62
- );
84
+ const forms = document.querySelectorAll<HTMLFormElement>("form.filter-sessions");
85
+ const resultsInfo = document.getElementById("results-info");
63
86
64
- function getUrlParams() {
87
+ function getUrlParams(): FilterParams {
65
88
const params = new URLSearchParams(window.location.search);
66
89
return {
67
90
track: params.get("track") || "",
68
91
type: params.get("type") || "",
69
92
level: params.get("level") || "",
93
+ search: params.get("search") || "",
70
94
};
71
95
}
72
96
73
- function setSelectValues() {
74
- const { track, type, level } = getUrlParams();
97
+ function setSelectValues(): void {
98
+ const { track, type, level, search } = getUrlParams();
75
99
76
100
forms.forEach((form) => {
77
101
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
78
102
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
79
103
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
104
+ const searchInput = form.querySelector<HTMLInputElement>("#search");
80
105
81
106
if (trackSelect) trackSelect.value = track;
82
107
if (typeSelect) typeSelect.value = type;
83
108
if (levelSelect) levelSelect.value = level;
109
+ if (searchInput) searchInput.value = search;
84
110
});
85
111
}
86
112
87
- function updateUrlParams(track: string, type: string, level: string) {
113
+ function updateUrlParams(track: string, type: string, level: string, search: string): void {
88
114
const params = new URLSearchParams();
89
115
90
- if (track) {
91
- params.set("track", track);
92
- }
93
- if (type) {
94
- params.set("type", type);
95
- }
96
- if (level) {
97
- params.set("level", level);
98
- }
116
+ if (track) params.set("track", track);
117
+ if (type) params.set("type", type);
118
+ if (level) params.set("level", level);
119
+ if (search) params.set("search", search);
99
120
100
121
const queryString = params.toString();
101
122
const newUrl = queryString
@@ -104,67 +125,87 @@ import { Select } from "../form/select";
104
125
window.history.pushState({}, "", newUrl);
105
126
}
106
127
107
- function filterSessions() {
128
+ function filterSessions(): void {
108
129
forms.forEach((form) => {
109
130
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
110
131
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
111
132
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
133
+ const searchInput = form.querySelector<HTMLInputElement>("#search");
112
134
113
- const track = trackSelect ? trackSelect.value : "";
114
- const type = typeSelect ? typeSelect.value : "";
115
- const level = levelSelect ? levelSelect.value : "";
135
+ const track = trackSelect?.value || "";
136
+ const type = typeSelect?.value || "";
137
+ const level = levelSelect?.value || "";
138
+ const search = searchInput?.value.trim().toLowerCase() || "";
116
139
117
- // Update URL parameters with only non-empty values
118
- updateUrlParams(track, type, level);
140
+ updateUrlParams(track, type, level, search);
119
141
120
- document.querySelectorAll("ol.sessions > li").forEach((session) => {
121
- const sessionTrack = session.getAttribute("data-track");
122
- const sessionType = session.getAttribute("data-type");
123
- const sessionLevel = session.getAttribute("data-level");
142
+ const sessionItems = document.querySelectorAll<HTMLLIElement>("ol.sessions > li");
143
+
144
+ sessionItems.forEach((session) => {
145
+ const sessionTrack = session.getAttribute("data-track") || "";
146
+ const sessionType = session.getAttribute("data-type") || "";
147
+ const sessionLevel = session.getAttribute("data-level") || "";
148
+ const sessionText = session.textContent?.toLowerCase() || "";
124
149
125
150
const trackMatch = track === "" || sessionTrack === track;
126
151
const typeMatch = type === "" || sessionType === type;
127
152
const levelMatch = level === "" || sessionLevel === level;
153
+ const searchMatch = search === "" || sessionText.includes(search);
128
154
129
- if (trackMatch && typeMatch && levelMatch) {
130
- (session as HTMLElement).style.display = "block";
131
- } else {
132
- (session as HTMLElement).style.display = "none";
133
- }
155
+ session.style.display =
156
+ trackMatch && typeMatch && levelMatch && searchMatch ? "block" : "none";
134
157
});
135
158
136
- document.querySelectorAll("div.track-group").forEach((group) => {
159
+ const trackGroups = document.querySelectorAll<HTMLDivElement>("div.track-group");
160
+
161
+ trackGroups.forEach((group) => {
137
162
const visibleSessions = group.querySelectorAll(
138
- "ol.sessions > li:not([style*='display: none'])",
163
+ "ol.sessions > li:not([style*='display: none'])"
139
164
).length;
140
165
141
- if (visibleSessions === 0) {
142
- (group as HTMLElement).style.display = "none";
143
- } else {
144
- (group as HTMLElement).style.display = "block";
145
- }
166
+ group.style.display = visibleSessions > 0 ? "block" : "none";
146
167
});
168
+
169
+ const visibleCount = document.querySelectorAll(
170
+ "ol.sessions > li:not([style*='display: none'])"
171
+ ).length;
172
+
173
+ if (resultsInfo) {
174
+ resultsInfo.textContent = visibleCount > 0
175
+ ? `${visibleCount} result${visibleCount === 1 ? "" : "s"} found.`
176
+ : "No results found.";
177
+ }
147
178
});
148
179
}
149
180
150
- function applyFiltersFromUrl() {
181
+ function applyFiltersFromUrl(): void {
151
182
setSelectValues();
152
183
filterSessions();
153
184
}
154
185
155
186
forms.forEach((form) => {
156
- form.querySelectorAll("select").forEach((select) => {
187
+ const selectElements = form.querySelectorAll<HTMLSelectElement>("select");
188
+
189
+ selectElements.forEach((select) => {
157
190
select.addEventListener("change", filterSessions);
158
191
});
159
192
160
- const resetButton =
161
- form.querySelector<HTMLButtonElement>("#reset-filters");
162
- resetButton?.addEventListener("click", () => {
163
- form.querySelectorAll("select").forEach((select) => {
164
- (select as HTMLSelectElement).value = "";
193
+ const searchInput = form.querySelector<HTMLInputElement>("#search");
194
+ if (searchInput) {
195
+ searchInput.addEventListener("input", filterSessions);
196
+ }
197
+
198
+ const resetButton = form.querySelector<HTMLButtonElement>("#reset-filters");
199
+ if (resetButton) {
200
+ resetButton.addEventListener("click", () => {
201
+ selectElements.forEach((select) => {
202
+ select.value = "";
203
+ });
204
+
205
+ if (searchInput) searchInput.value = "";
206
+ filterSessions();
165
207
});
166
- filterSessions();
167
- });
208
+ }
168
209
});
169
210
170
211
applyFiltersFromUrl();
0 commit comments