From 6dc5c26165286b8d0d0b5a58aa01130ca32cbdf2 Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Wed, 10 Jul 2024 18:10:44 +0200 Subject: [PATCH] add filter on venue --- web/src/app/arrangementer/page.tsx | 3 ++ web/src/components/events/EventContainer.tsx | 40 ++++++++++++++++++++ web/src/components/events/EventFilter.tsx | 30 +++++++++++++++ web/src/gql/gql.ts | 4 +- web/src/gql/graphql.ts | 4 +- web/src/lib/event.ts | 7 ++++ 6 files changed, 84 insertions(+), 4 deletions(-) diff --git a/web/src/app/arrangementer/page.tsx b/web/src/app/arrangementer/page.tsx index e53a7df..24696f7 100644 --- a/web/src/app/arrangementer/page.tsx +++ b/web/src/app/arrangementer/page.tsx @@ -8,12 +8,14 @@ import { } from "@/lib/event"; import { PageHeader } from "@/components/general/PageHeader"; import { Suspense } from "react"; +import { VenueFragment } from "@/gql/graphql"; export default async function Page() { const { data, error } = await getClient().query(futureEventsQuery, {}); const events = (data?.events?.futureEvents ?? []) as EventFragment[]; const eventCategories = (data?.eventCategories ?? []) as EventCategory[]; const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[]; + const venues = (data?.venues ?? []) as VenueFragment[]; return (
@@ -23,6 +25,7 @@ export default async function Page() { events={events} eventCategories={eventCategories} eventOrganizers={eventOrganizers} + venues={venues} />
diff --git a/web/src/components/events/EventContainer.tsx b/web/src/components/events/EventContainer.tsx index a7d31f7..1ce6a8e 100644 --- a/web/src/components/events/EventContainer.tsx +++ b/web/src/components/events/EventContainer.tsx @@ -18,6 +18,7 @@ import { parse } from "date-fns"; import { unique } from "@/lib/common"; import Icon from "../general/Icon"; import { useState } from "react"; +import { VenueFragment } from "@/gql/graphql"; /* TODO: canonical / alternate URLs https://github.com/47ng/nuqs?tab=readme-ov-file#seo @@ -27,10 +28,12 @@ export const EventContainer = ({ events, eventCategories, eventOrganizers, + venues, }: { events: EventFragment[]; eventCategories: EventCategory[]; eventOrganizers: EventOrganizer[]; + venues: VenueFragment[]; }) => { const [mode, setMode] = useQueryState( "mode", @@ -38,20 +41,46 @@ export const EventContainer = ({ ); const [category, setCategory] = useQueryState("category", parseAsString); const [organizer, setOrganizer] = useQueryState("organizer", parseAsString); + const [venue, setVenue] = useQueryState("venue", parseAsString); + /* 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, but be hidden from dropdown + */ const uniqueOrganizers: string[] = unique( events .map((x) => x.organizers) .flat() .filter((x) => x.__typename === "EventOrganizer") .map((x) => x.slug) + .filter((x) => typeof x === "string") ); const filterableOrganizers = uniqueOrganizers .map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug)) .filter((x) => x !== undefined) as EventOrganizer[]; + /* + 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 = 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 + .map((x) => + venues.find( + (haystack) => haystack.slug === x.slug || haystack.slug === venue + ) + ) + .filter((x) => x !== undefined) as VenueFragment[]; + const filteredEvents = events .filter( (x) => @@ -64,6 +93,14 @@ export const EventContainer = ({ x.categories .map((eventCategory) => eventCategory.slug) .includes(category) + ) + .filter( + (x) => + !venue || + x.occurrences + .map((occurrence) => occurrence.venue?.slug) + .filter((x) => typeof x === "string") + .includes(venue) ); const [showFilter, setShowFilter] = useState(false); @@ -98,6 +135,9 @@ export const EventContainer = ({ eventOrganizers={filterableOrganizers} setOrganizer={setOrganizer} activeOrganizer={organizer} + venues={filterableVenues} + setVenue={setVenue} + activeVenue={venue} isVisible={showFilter} /> {mode === "list" && } diff --git a/web/src/components/events/EventFilter.tsx b/web/src/components/events/EventFilter.tsx index 65e00e6..8a62bef 100644 --- a/web/src/components/events/EventFilter.tsx +++ b/web/src/components/events/EventFilter.tsx @@ -1,6 +1,7 @@ import { EventCategory, EventOrganizer } from "@/lib/event"; import styles from "./eventFilter.module.scss"; +import { VenueFragment } from "@/gql/graphql"; export const EventFilter = ({ eventCategories, @@ -9,6 +10,9 @@ export const EventFilter = ({ eventOrganizers, setOrganizer, activeOrganizer, + venues, + activeVenue, + setVenue, isVisible, }: { eventCategories: EventCategory[]; @@ -17,12 +21,19 @@ export const EventFilter = ({ eventOrganizers: EventOrganizer[]; setOrganizer: (slug: string | null) => void; activeOrganizer: string | null; + venues: VenueFragment[]; + activeVenue: string | null; + setVenue: (slug: string | null) => void; isVisible: boolean; }) => { const onOrganizerSelect = (e: React.ChangeEvent) => { const organizer = e.target.value; setOrganizer(organizer === "" ? null : organizer); }; + const onVenueSelect = (e: React.ChangeEvent) => { + const venue = e.target.value; + setVenue(venue === "" ? null : venue); + }; return (
@@ -52,6 +63,25 @@ export const EventFilter = ({ ))}
+
+ +
+ +
+