add filter on venue
This commit is contained in:
@ -8,12 +8,14 @@ import {
|
|||||||
} from "@/lib/event";
|
} from "@/lib/event";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { VenueFragment } from "@/gql/graphql";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(futureEventsQuery, {});
|
const { data, error } = await getClient().query(futureEventsQuery, {});
|
||||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
||||||
const eventCategories = (data?.eventCategories ?? []) as EventCategory[];
|
const eventCategories = (data?.eventCategories ?? []) as EventCategory[];
|
||||||
const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[];
|
const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[];
|
||||||
|
const venues = (data?.venues ?? []) as VenueFragment[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="site-main" id="main">
|
<main className="site-main" id="main">
|
||||||
@ -23,6 +25,7 @@ export default async function Page() {
|
|||||||
events={events}
|
events={events}
|
||||||
eventCategories={eventCategories}
|
eventCategories={eventCategories}
|
||||||
eventOrganizers={eventOrganizers}
|
eventOrganizers={eventOrganizers}
|
||||||
|
venues={venues}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
|
@ -18,6 +18,7 @@ import { parse } from "date-fns";
|
|||||||
import { unique } from "@/lib/common";
|
import { unique } from "@/lib/common";
|
||||||
import Icon from "../general/Icon";
|
import Icon from "../general/Icon";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { VenueFragment } from "@/gql/graphql";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: canonical / alternate URLs https://github.com/47ng/nuqs?tab=readme-ov-file#seo
|
TODO: canonical / alternate URLs https://github.com/47ng/nuqs?tab=readme-ov-file#seo
|
||||||
@ -27,10 +28,12 @@ export const EventContainer = ({
|
|||||||
events,
|
events,
|
||||||
eventCategories,
|
eventCategories,
|
||||||
eventOrganizers,
|
eventOrganizers,
|
||||||
|
venues,
|
||||||
}: {
|
}: {
|
||||||
events: EventFragment[];
|
events: EventFragment[];
|
||||||
eventCategories: EventCategory[];
|
eventCategories: EventCategory[];
|
||||||
eventOrganizers: EventOrganizer[];
|
eventOrganizers: EventOrganizer[];
|
||||||
|
venues: VenueFragment[];
|
||||||
}) => {
|
}) => {
|
||||||
const [mode, setMode] = useQueryState(
|
const [mode, setMode] = useQueryState(
|
||||||
"mode",
|
"mode",
|
||||||
@ -38,20 +41,46 @@ export const EventContainer = ({
|
|||||||
);
|
);
|
||||||
const [category, setCategory] = useQueryState("category", parseAsString);
|
const [category, setCategory] = useQueryState("category", parseAsString);
|
||||||
const [organizer, setOrganizer] = useQueryState("organizer", 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);
|
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(
|
const uniqueOrganizers: string[] = unique(
|
||||||
events
|
events
|
||||||
.map((x) => x.organizers)
|
.map((x) => x.organizers)
|
||||||
.flat()
|
.flat()
|
||||||
.filter((x) => x.__typename === "EventOrganizer")
|
.filter((x) => x.__typename === "EventOrganizer")
|
||||||
.map((x) => x.slug)
|
.map((x) => x.slug)
|
||||||
|
.filter((x) => typeof x === "string")
|
||||||
);
|
);
|
||||||
const filterableOrganizers = uniqueOrganizers
|
const filterableOrganizers = uniqueOrganizers
|
||||||
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
||||||
.filter((x) => x !== undefined) as EventOrganizer[];
|
.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
|
const filteredEvents = events
|
||||||
.filter(
|
.filter(
|
||||||
(x) =>
|
(x) =>
|
||||||
@ -64,6 +93,14 @@ export const EventContainer = ({
|
|||||||
x.categories
|
x.categories
|
||||||
.map((eventCategory) => eventCategory.slug)
|
.map((eventCategory) => eventCategory.slug)
|
||||||
.includes(category)
|
.includes(category)
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(x) =>
|
||||||
|
!venue ||
|
||||||
|
x.occurrences
|
||||||
|
.map((occurrence) => occurrence.venue?.slug)
|
||||||
|
.filter((x) => typeof x === "string")
|
||||||
|
.includes(venue)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showFilter, setShowFilter] = useState(false);
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
@ -98,6 +135,9 @@ export const EventContainer = ({
|
|||||||
eventOrganizers={filterableOrganizers}
|
eventOrganizers={filterableOrganizers}
|
||||||
setOrganizer={setOrganizer}
|
setOrganizer={setOrganizer}
|
||||||
activeOrganizer={organizer}
|
activeOrganizer={organizer}
|
||||||
|
venues={filterableVenues}
|
||||||
|
setVenue={setVenue}
|
||||||
|
activeVenue={venue}
|
||||||
isVisible={showFilter}
|
isVisible={showFilter}
|
||||||
/>
|
/>
|
||||||
{mode === "list" && <EventList events={filteredEvents} />}
|
{mode === "list" && <EventList events={filteredEvents} />}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EventCategory, EventOrganizer } from "@/lib/event";
|
import { EventCategory, EventOrganizer } from "@/lib/event";
|
||||||
|
|
||||||
import styles from "./eventFilter.module.scss";
|
import styles from "./eventFilter.module.scss";
|
||||||
|
import { VenueFragment } from "@/gql/graphql";
|
||||||
|
|
||||||
export const EventFilter = ({
|
export const EventFilter = ({
|
||||||
eventCategories,
|
eventCategories,
|
||||||
@ -9,6 +10,9 @@ export const EventFilter = ({
|
|||||||
eventOrganizers,
|
eventOrganizers,
|
||||||
setOrganizer,
|
setOrganizer,
|
||||||
activeOrganizer,
|
activeOrganizer,
|
||||||
|
venues,
|
||||||
|
activeVenue,
|
||||||
|
setVenue,
|
||||||
isVisible,
|
isVisible,
|
||||||
}: {
|
}: {
|
||||||
eventCategories: EventCategory[];
|
eventCategories: EventCategory[];
|
||||||
@ -17,12 +21,19 @@ export const EventFilter = ({
|
|||||||
eventOrganizers: EventOrganizer[];
|
eventOrganizers: EventOrganizer[];
|
||||||
setOrganizer: (slug: string | null) => void;
|
setOrganizer: (slug: string | null) => void;
|
||||||
activeOrganizer: string | null;
|
activeOrganizer: string | null;
|
||||||
|
venues: VenueFragment[];
|
||||||
|
activeVenue: string | null;
|
||||||
|
setVenue: (slug: string | null) => void;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const onOrganizerSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const onOrganizerSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const organizer = e.target.value;
|
const organizer = e.target.value;
|
||||||
setOrganizer(organizer === "" ? null : organizer);
|
setOrganizer(organizer === "" ? null : organizer);
|
||||||
};
|
};
|
||||||
|
const onVenueSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const venue = e.target.value;
|
||||||
|
setVenue(venue === "" ? null : venue);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.eventFilter} data-show={isVisible}>
|
<div className={styles.eventFilter} data-show={isVisible}>
|
||||||
@ -52,6 +63,25 @@ export const EventFilter = ({
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.filterItem}>
|
||||||
|
<label htmlFor="venue" className={`suphead ${styles.heading}`}>
|
||||||
|
Lokale
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
name="venue"
|
||||||
|
value={activeVenue ?? ""}
|
||||||
|
onChange={onVenueSelect}
|
||||||
|
>
|
||||||
|
<option value="">Vis alle</option>
|
||||||
|
{venues.map((venue) => (
|
||||||
|
<option key={venue.slug} value={venue.slug}>
|
||||||
|
{venue.title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className={styles.filterItem}>
|
<div className={styles.filterItem}>
|
||||||
<label htmlFor="organizer" className={`suphead ${styles.heading}`}>
|
<label htmlFor="organizer" className={`suphead ${styles.heading}`}>
|
||||||
Arrangør
|
Arrangør
|
||||||
|
@ -38,7 +38,7 @@ const documents = {
|
|||||||
"\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc,
|
"\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc,
|
||||||
"\n fragment ContactEntity on ContactEntity {\n id\n name\n contactType\n title\n email\n phoneNumber\n image {\n ...Image\n }\n }\n": types.ContactEntityFragmentDoc,
|
"\n fragment ContactEntity on ContactEntity {\n id\n name\n contactType\n title\n email\n phoneNumber\n image {\n ...Image\n }\n }\n": types.ContactEntityFragmentDoc,
|
||||||
"\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n pig\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n organizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.EventFragmentDoc,
|
"\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n pig\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n organizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.EventFragmentDoc,
|
||||||
"\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.FutureEventsDocument,
|
"\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n venues: pages(contentType: \"venues.VenuePage\") {\n ... on VenuePage {\n id\n title\n slug\n }\n }\n }\n": types.FutureEventsDocument,
|
||||||
"\n fragment News on NewsPage {\n __typename\n id\n slug\n title\n firstPublishedAt\n excerpt\n featuredImage {\n ...Image\n }\n body {\n ...Blocks\n }\n }\n": types.NewsFragmentDoc,
|
"\n fragment News on NewsPage {\n __typename\n id\n slug\n title\n firstPublishedAt\n excerpt\n featuredImage {\n ...Image\n }\n body {\n ...Blocks\n }\n }\n": types.NewsFragmentDoc,
|
||||||
"\n fragment NewsIndex on NewsIndex {\n __typename\n id\n slug\n title\n lead\n }\n": types.NewsIndexFragmentDoc,
|
"\n fragment NewsIndex on NewsIndex {\n __typename\n id\n slug\n title\n lead\n }\n": types.NewsIndexFragmentDoc,
|
||||||
"\n query news {\n index: newsIndex {\n ... on NewsIndex {\n ...NewsIndex\n }\n }\n news: pages(contentType: \"news.NewsPage\") {\n ... on NewsPage {\n ...News\n }\n }\n }\n": types.NewsDocument,
|
"\n query news {\n index: newsIndex {\n ... on NewsIndex {\n ...NewsIndex\n }\n }\n news: pages(contentType: \"news.NewsPage\") {\n ... on NewsPage {\n ...News\n }\n }\n }\n": types.NewsDocument,
|
||||||
@ -165,7 +165,7 @@ export function graphql(source: "\n fragment Event on EventPage {\n __typena
|
|||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(source: "\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n"];
|
export function graphql(source: "\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n venues: pages(contentType: \"venues.VenuePage\") {\n ... on VenuePage {\n id\n title\n slug\n }\n }\n }\n"): (typeof documents)["\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n venues: pages(contentType: \"venues.VenuePage\") {\n ... on VenuePage {\n id\n title\n slug\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
File diff suppressed because one or more lines are too long
@ -118,6 +118,13 @@ export const futureEventsQuery = graphql(`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
venues: pages(contentType: "venues.VenuePage") {
|
||||||
|
... on VenuePage {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user