Skip to content
169 changes: 105 additions & 64 deletions src/components/sessions/filter.astro
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
---
const { allTracks, allTypes, allLevels } = Astro.props;
interface FilterSessionsProps {
allTracks?: string[];
allTypes?: string[];
allLevels?: string[];
}

const { allTracks = [], allTypes = [], allLevels = [] }: FilterSessionsProps = Astro.props;
import { Label } from "../form/label";
import { Select } from "../form/select";
---

<form class="mb-12 filter-sessions">
{
allTracks && allTracks.length > 0 && (
<div class="mb-6">
<div class="w-full mb-6">
<Label htmlFor="search">Search</Label>
<input
type="text"
id="search"
name="search"
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"
placeholder="Search sessions..."
/>
</div>

<div class="flex flex-wrap gap-x-4 gap-y-4 mb-6">
{allTracks.length > 0 && (
<div>
<Label htmlFor="track">Track</Label>
<Select id="track" name="track">
<option value="">All</option>
Expand All @@ -16,12 +33,10 @@ import { Select } from "../form/select";
))}
</Select>
</div>
)
}
)}

{
allTypes && allTypes.length > 0 && (
<div class="mb-6">
{allTypes.length > 0 && (
<div>
<Label htmlFor="type">Session type</Label>
<Select id="type" name="type">
<option value="">All</option>
Expand All @@ -30,12 +45,10 @@ import { Select } from "../form/select";
))}
</Select>
</div>
)
}
)}

{
allLevels && allLevels.length > 0 && (
<div class="mb-6">
{allLevels.length > 0 && (
<div>
<Label htmlFor="level">Level</Label>
<Select id="level" name="level">
<option value="">All</option>
Expand All @@ -44,58 +57,66 @@ import { Select } from "../form/select";
))}
</Select>
</div>
)
}
)}
</div>

<button
type="button"
id="reset-filters"
class="btn underline font-bold hover:text-primary-hover"
>Reset Filters</button
>
Reset Filters
</button>
</form>

<!-- Results info -->
<div id="results-info" class="mb-6 text-lg font-medium"></div>

<script>
interface FilterParams {
track: string;
type: string;
level: string;
search: string;
}

document.addEventListener("DOMContentLoaded", () => {
const forms = document.querySelectorAll<HTMLFormElement>(
"form.filter-sessions",
);
const forms = document.querySelectorAll<HTMLFormElement>("form.filter-sessions");
const resultsInfo = document.getElementById("results-info");

function getUrlParams() {
function getUrlParams(): FilterParams {
const params = new URLSearchParams(window.location.search);
return {
track: params.get("track") || "",
type: params.get("type") || "",
level: params.get("level") || "",
search: params.get("search") || "",
};
}

function setSelectValues() {
const { track, type, level } = getUrlParams();
function setSelectValues(): void {
const { track, type, level, search } = getUrlParams();

forms.forEach((form) => {
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
const searchInput = form.querySelector<HTMLInputElement>("#search");

if (trackSelect) trackSelect.value = track;
if (typeSelect) typeSelect.value = type;
if (levelSelect) levelSelect.value = level;
if (searchInput) searchInput.value = search;
});
}

function updateUrlParams(track: string, type: string, level: string) {
function updateUrlParams(track: string, type: string, level: string, search: string): void {
const params = new URLSearchParams();

if (track) {
params.set("track", track);
}
if (type) {
params.set("type", type);
}
if (level) {
params.set("level", level);
}
if (track) params.set("track", track);
if (type) params.set("type", type);
if (level) params.set("level", level);
if (search) params.set("search", search);

const queryString = params.toString();
const newUrl = queryString
Expand All @@ -104,67 +125,87 @@ import { Select } from "../form/select";
window.history.pushState({}, "", newUrl);
}

function filterSessions() {
function filterSessions(): void {
forms.forEach((form) => {
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
const searchInput = form.querySelector<HTMLInputElement>("#search");

const track = trackSelect ? trackSelect.value : "";
const type = typeSelect ? typeSelect.value : "";
const level = levelSelect ? levelSelect.value : "";
const track = trackSelect?.value || "";
const type = typeSelect?.value || "";
const level = levelSelect?.value || "";
const search = searchInput?.value.trim().toLowerCase() || "";

// Update URL parameters with only non-empty values
updateUrlParams(track, type, level);
updateUrlParams(track, type, level, search);

document.querySelectorAll("ol.sessions > li").forEach((session) => {
const sessionTrack = session.getAttribute("data-track");
const sessionType = session.getAttribute("data-type");
const sessionLevel = session.getAttribute("data-level");
const sessionItems = document.querySelectorAll<HTMLLIElement>("ol.sessions > li");

sessionItems.forEach((session) => {
const sessionTrack = session.getAttribute("data-track") || "";
const sessionType = session.getAttribute("data-type") || "";
const sessionLevel = session.getAttribute("data-level") || "";
const sessionText = session.textContent?.toLowerCase() || "";

const trackMatch = track === "" || sessionTrack === track;
const typeMatch = type === "" || sessionType === type;
const levelMatch = level === "" || sessionLevel === level;
const searchMatch = search === "" || sessionText.includes(search);

if (trackMatch && typeMatch && levelMatch) {
(session as HTMLElement).style.display = "block";
} else {
(session as HTMLElement).style.display = "none";
}
session.style.display =
trackMatch && typeMatch && levelMatch && searchMatch ? "block" : "none";
});

document.querySelectorAll("div.track-group").forEach((group) => {
const trackGroups = document.querySelectorAll<HTMLDivElement>("div.track-group");

trackGroups.forEach((group) => {
const visibleSessions = group.querySelectorAll(
"ol.sessions > li:not([style*='display: none'])",
"ol.sessions > li:not([style*='display: none'])"
).length;

if (visibleSessions === 0) {
(group as HTMLElement).style.display = "none";
} else {
(group as HTMLElement).style.display = "block";
}
group.style.display = visibleSessions > 0 ? "block" : "none";
});

const visibleCount = document.querySelectorAll(
"ol.sessions > li:not([style*='display: none'])"
).length;

if (resultsInfo) {
resultsInfo.textContent = visibleCount > 0
? `${visibleCount} result${visibleCount === 1 ? "" : "s"} found.`
: "No results found.";
}
});
}

function applyFiltersFromUrl() {
function applyFiltersFromUrl(): void {
setSelectValues();
filterSessions();
}

forms.forEach((form) => {
form.querySelectorAll("select").forEach((select) => {
const selectElements = form.querySelectorAll<HTMLSelectElement>("select");

selectElements.forEach((select) => {
select.addEventListener("change", filterSessions);
});

const resetButton =
form.querySelector<HTMLButtonElement>("#reset-filters");
resetButton?.addEventListener("click", () => {
form.querySelectorAll("select").forEach((select) => {
(select as HTMLSelectElement).value = "";
const searchInput = form.querySelector<HTMLInputElement>("#search");
if (searchInput) {
searchInput.addEventListener("input", filterSessions);
}

const resetButton = form.querySelector<HTMLButtonElement>("#reset-filters");
if (resetButton) {
resetButton.addEventListener("click", () => {
selectElements.forEach((select) => {
select.value = "";
});

if (searchInput) searchInput.value = "";
filterSessions();
});
filterSessions();
});
}
});

applyFiltersFromUrl();
Expand Down
1 change: 1 addition & 0 deletions src/components/sessions/list-sessions.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Session = {
};
id: string;
};

---

<ol class="sessions">
Expand Down