11---
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 ;
39import { Label } from " ../form/label" ;
410import { Select } from " ../form/select" ;
511---
612
713<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 >
1128 <Label htmlFor = " track" >Track</Label >
1229 <Select id = " track" name = " track" >
1330 <option value = " " >All</option >
@@ -16,12 +33,10 @@ import { Select } from "../form/select";
1633 ))}
1734 </Select >
1835 </div >
19- )
20- }
36+ )}
2137
22- {
23- allTypes && allTypes .length > 0 && (
24- <div class = " mb-6" >
38+ { allTypes .length > 0 && (
39+ <div >
2540 <Label htmlFor = " type" >Session type</Label >
2641 <Select id = " type" name = " type" >
2742 <option value = " " >All</option >
@@ -30,12 +45,10 @@ import { Select } from "../form/select";
3045 ))}
3146 </Select >
3247 </div >
33- )
34- }
48+ )}
3549
36- {
37- allLevels && allLevels .length > 0 && (
38- <div class = " mb-6" >
50+ { allLevels .length > 0 && (
51+ <div >
3952 <Label htmlFor = " level" >Level</Label >
4053 <Select id = " level" name = " level" >
4154 <option value = " " >All</option >
@@ -44,58 +57,66 @@ import { Select } from "../form/select";
4457 ))}
4558 </Select >
4659 </div >
47- )
48- }
60+ )}
61+ </ div >
4962
5063 <button
5164 type =" button"
5265 id =" reset-filters"
5366 class =" btn underline font-bold hover:text-primary-hover"
54- >Reset Filters</button
5567 >
68+ Reset Filters
69+ </button >
5670</form >
5771
72+ <!-- Results info -->
73+ <div id =" results-info" class =" mb-6 text-lg font-medium" ></div >
74+
5875<script >
76+ interface FilterParams {
77+ track: string;
78+ type: string;
79+ level: string;
80+ search: string;
81+ }
82+
5983 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");
6386
64- function getUrlParams() {
87+ function getUrlParams(): FilterParams {
6588 const params = new URLSearchParams(window.location.search);
6689 return {
6790 track: params.get("track") || "",
6891 type: params.get("type") || "",
6992 level: params.get("level") || "",
93+ search: params.get("search") || "",
7094 };
7195 }
7296
73- function setSelectValues() {
74- const { track, type, level } = getUrlParams();
97+ function setSelectValues(): void {
98+ const { track, type, level, search } = getUrlParams();
7599
76100 forms.forEach((form) => {
77101 const trackSelect = form.querySelector<HTMLSelectElement>("#track");
78102 const typeSelect = form.querySelector<HTMLSelectElement>("#type");
79103 const levelSelect = form.querySelector<HTMLSelectElement>("#level");
104+ const searchInput = form.querySelector<HTMLInputElement>("#search");
80105
81106 if (trackSelect) trackSelect.value = track;
82107 if (typeSelect) typeSelect.value = type;
83108 if (levelSelect) levelSelect.value = level;
109+ if (searchInput) searchInput.value = search;
84110 });
85111 }
86112
87- function updateUrlParams(track: string, type: string, level: string) {
113+ function updateUrlParams(track: string, type: string, level: string, search: string): void {
88114 const params = new URLSearchParams();
89115
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);
99120
100121 const queryString = params.toString();
101122 const newUrl = queryString
@@ -104,67 +125,87 @@ import { Select } from "../form/select";
104125 window.history.pushState({}, "", newUrl);
105126 }
106127
107- function filterSessions() {
128+ function filterSessions(): void {
108129 forms.forEach((form) => {
109130 const trackSelect = form.querySelector<HTMLSelectElement>("#track");
110131 const typeSelect = form.querySelector<HTMLSelectElement>("#type");
111132 const levelSelect = form.querySelector<HTMLSelectElement>("#level");
133+ const searchInput = form.querySelector<HTMLInputElement>("#search");
112134
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() || "";
116139
117- // Update URL parameters with only non-empty values
118- updateUrlParams(track, type, level);
140+ updateUrlParams(track, type, level, search);
119141
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() || "";
124149
125150 const trackMatch = track === "" || sessionTrack === track;
126151 const typeMatch = type === "" || sessionType === type;
127152 const levelMatch = level === "" || sessionLevel === level;
153+ const searchMatch = search === "" || sessionText.includes(search);
128154
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";
134157 });
135158
136- document.querySelectorAll("div.track-group").forEach((group) => {
159+ const trackGroups = document.querySelectorAll<HTMLDivElement>("div.track-group");
160+
161+ trackGroups.forEach((group) => {
137162 const visibleSessions = group.querySelectorAll(
138- "ol.sessions > li:not([style*='display: none'])",
163+ "ol.sessions > li:not([style*='display: none'])"
139164 ).length;
140165
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";
146167 });
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+ }
147178 });
148179 }
149180
150- function applyFiltersFromUrl() {
181+ function applyFiltersFromUrl(): void {
151182 setSelectValues();
152183 filterSessions();
153184 }
154185
155186 forms.forEach((form) => {
156- form.querySelectorAll("select").forEach((select) => {
187+ const selectElements = form.querySelectorAll<HTMLSelectElement>("select");
188+
189+ selectElements.forEach((select) => {
157190 select.addEventListener("change", filterSessions);
158191 });
159192
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();
165207 });
166- filterSessions();
167- });
208+ }
168209 });
169210
170211 applyFiltersFromUrl();
0 commit comments