web: extract event filter logic to lib, add tz/DST and year-boundary tests

This commit is contained in:
ponas committed 2026-08-31 23:25:11 +02:00
1 parent 16778eaca2
commit 7df0a646bc
5 files changed
+414 -83

No files matched your search

+13 -83
View File
@@ -9,10 +9,7 @@ import {
} from "nuqs";
import { EventItem } from "./EventItem";
import { EventFilter, EventFilterExplained } from "./EventFilter";
import { unmaskFragment } from "@/gql";
import {
EventCategoryFragmentDefinition,
EventOrganizerFragmentDefinition,
EventOverviewItemFragment,
EventCategory,
SingularEvent,
@@ -20,6 +17,12 @@ import {
organizeEventsInCalendar,
EventOrganizer,
} from "@/lib/event";
import {
filterEvents,
getFilterableCategories,
getFilterableOrganizers,
getFilterableVenues,
} from "@/lib/eventFilter";
import { isTodayOrFuture } from "@/lib/date";
import styles from "./eventContainer.module.scss";
import { formatDate, formatYearMonth } from "@/lib/date";
@@ -29,7 +32,6 @@ import {
differenceInCalendarDays,
parse,
} from "date-fns";
import { unique } from "@/lib/common";
import { Icon } from "../general/Icon";
import { useState } from "react";
import { VenueFragment } from "@/gql/graphql";
@@ -70,40 +72,13 @@ export const EventContainer = ({
resetFilters();
};
/* Allow filtering on all categories that are configured to be shown */
const filterableCategories = eventCategories.filter((x) => x.showInFilters);
/*
Allow filtering on all organizers that have upcoming events
Filtering on an organizer with no upcoming events will work,
and in that case it's included in the dropdown
*/
const allOrganizers = unmaskFragment(
EventOrganizerFragmentDefinition,
events.flatMap((x) => x.organizers),
const filterableCategories = getFilterableCategories(eventCategories);
const filterableOrganizers = getFilterableOrganizers(
events,
eventOrganizers,
organizer,
);
const uniqueOrganizers: string[] = unique(
allOrganizers
.filter((x) => x.__typename === "EventOrganizer")
.map((x) => x.slug)
.filter((x) => typeof x === "string" && x !== ""),
);
const filterableOrganizers = uniqueOrganizers
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
.filter((x) => x !== undefined) as EventOrganizer[];
// If an organizer is selected that's not in the upcoming list, add it
if (
organizer &&
!filterableOrganizers.some((org) => org.slug === organizer)
) {
const selectedOrganizer = eventOrganizers.find(
(org) => org.slug === organizer,
);
if (selectedOrganizer) {
filterableOrganizers.push(selectedOrganizer);
}
}
const filterableVenues = getFilterableVenues(events, venues, venue);
/* Reset organizer if slug is invalid */
useEffect(() => {
@@ -112,26 +87,6 @@ export const EventContainer = ({
}
}, [eventOrganizers, organizer]);
/*
Allow filtering on all venues that have upcoming events
Filtering on a venue with no upcoming events will work,
and in that case it's included in the dropdown
*/
const venueSlugsWithUpcomingEvents = unique(
events
.map((x) => x.occurrences)
.flat()
.filter((x) => x.venue?.__typename === "VenuePage")
.map((x) => x.venue?.slug)
.filter((x) => typeof x === "string"),
);
const filterableVenues = venues
.filter(
(x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue,
)
.map((x) => venues.find((haystack) => haystack.slug === x.slug))
.filter((x) => x !== undefined) as VenueFragment[];
/* Reset venue if slug is invalid */
useEffect(() => {
if (!venues.find((haystack) => haystack.slug === venue)) {
@@ -139,32 +94,7 @@ export const EventContainer = ({
}
}, [venues, venue]);
const filteredEvents = events.filter((event) => {
if (organizer) {
const organizers = unmaskFragment(
EventOrganizerFragmentDefinition,
event.organizers,
);
if (!organizers.some((o) => o.slug === organizer)) {
return false;
}
}
if (categories) {
const eventCategories = unmaskFragment(
EventCategoryFragmentDefinition,
event.categories,
);
if (!eventCategories.some((c) => categories.includes(c.slug))) {
return false;
}
}
if (venue) {
if (!event.occurrences.some((occ) => occ.venue?.slug === venue)) {
return false;
}
}
return true;
});
const filteredEvents = filterEvents(events, { categories, organizer, venue });
const [showFilter, setShowFilter] = useState(false);
function toggleFilter() {