Skip to content

Commit eb6a957

Browse files
List of Sessions page improvements (#1136)
Some of the requirements from #1089 are solved by this PR. New search for sessions. Visible results counter. --------- Co-authored-by: Mia Bajić <[email protected]>
1 parent 425a1b8 commit eb6a957

File tree

2 files changed

+106
-64
lines changed

2 files changed

+106
-64
lines changed

src/components/sessions/filter.astro

Lines changed: 105 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
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;
39
import { Label } from "../form/label";
410
import { 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();

src/components/sessions/list-sessions.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Session = {
1717
};
1818
id: string;
1919
};
20+
2021
---
2122

2223
<ol class="sessions">

0 commit comments

Comments
 (0)