support filtering multiple categories

This commit is contained in:
2024-08-18 02:37:04 +02:00
parent 852882d392
commit 5391ba98c5
3 changed files with 62 additions and 35 deletions

View File

@ -1,6 +1,11 @@
"use client"; "use client";
import { useQueryState, parseAsStringLiteral, parseAsString } from "nuqs"; import {
useQueryState,
parseAsStringLiteral,
parseAsString,
parseAsArrayOf,
} from "nuqs";
import { EventItem } from "./EventItem"; import { EventItem } from "./EventItem";
import { EventFilter, EventFilterExplained } from "./EventFilter"; import { EventFilter, EventFilterExplained } from "./EventFilter";
import { import {
@ -44,12 +49,15 @@ export const EventContainer = ({
"mode", "mode",
parseAsStringLiteral(["list", "calendar"]).withDefault("list") parseAsStringLiteral(["list", "calendar"]).withDefault("list")
); );
const [category, setCategory] = useQueryState("category", parseAsString); const [categories, setCategories] = useQueryState(
"category",
parseAsArrayOf(parseAsString, ",")
);
const [organizer, setOrganizer] = useQueryState("organizer", parseAsString); const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
const [venue, setVenue] = useQueryState("venue", parseAsString); const [venue, setVenue] = useQueryState("venue", parseAsString);
const resetFilters = () => { const resetFilters = () => {
setCategory(null); setCategories(null);
setOrganizer(null); setOrganizer(null);
setVenue(null); setVenue(null);
}; };
@ -105,10 +113,10 @@ export const EventContainer = ({
) )
.filter( .filter(
(x) => (x) =>
!category || !categories ||
x.categories x.categories
.map((eventCategory) => eventCategory.slug) .map((eventCategory) => eventCategory.slug)
.includes(category) .filter((x) => categories.includes(x)).length !== 0
) )
.filter( .filter(
(x) => (x) =>
@ -147,7 +155,7 @@ export const EventContainer = ({
</button> </button>
</div> </div>
<div className={styles.filterButtons}> <div className={styles.filterButtons}>
{(category || organizer || venue) && ( {(categories || organizer || venue) && (
<button onClick={onResetFilters} className="button tertiary"> <button onClick={onResetFilters} className="button tertiary">
<span>Vis alle</span> <span>Vis alle</span>
<Icon type="noFilter" /> <Icon type="noFilter" />
@ -161,8 +169,8 @@ export const EventContainer = ({
</div> </div>
<EventFilter <EventFilter
eventCategories={filterableCategories} eventCategories={filterableCategories}
setCategory={setCategory} setCategories={setCategories}
activeCategory={category} activeCategories={categories}
eventOrganizers={filterableOrganizers} eventOrganizers={filterableOrganizers}
setOrganizer={setOrganizer} setOrganizer={setOrganizer}
activeOrganizer={organizer} activeOrganizer={organizer}
@ -173,7 +181,7 @@ export const EventContainer = ({
/> />
<EventFilterExplained <EventFilterExplained
eventCategories={filterableCategories} eventCategories={filterableCategories}
activeCategory={category} activeCategories={categories}
eventOrganizers={filterableOrganizers} eventOrganizers={filterableOrganizers}
activeOrganizer={organizer} activeOrganizer={organizer}
venues={filterableVenues} venues={filterableVenues}
@ -188,9 +196,7 @@ export const EventContainer = ({
const EventList = ({ events }: { events: EventFragment[] }) => { const EventList = ({ events }: { events: EventFragment[] }) => {
if (events.length === 0) { if (events.length === 0) {
return ( return <div className={styles.noEvents}>Ingen kommende arrangementer.</div>;
<div className={styles.noEvents}>Ingen kommende arrangementer.</div>
);
} }
return ( return (
<ul className={styles.eventList}> <ul className={styles.eventList}>

View File

@ -2,12 +2,12 @@ import { EventCategory, EventOrganizer } from "@/lib/event";
import styles from "./eventFilter.module.scss"; import styles from "./eventFilter.module.scss";
import { VenueFragment } from "@/gql/graphql"; import { VenueFragment } from "@/gql/graphql";
import { Icon } from "../general/Icon"; import { formatHumanReadableList } from "@/lib/common";
export const EventFilter = ({ export const EventFilter = ({
eventCategories, eventCategories,
setCategory, setCategories,
activeCategory, activeCategories,
eventOrganizers, eventOrganizers,
setOrganizer, setOrganizer,
activeOrganizer, activeOrganizer,
@ -17,8 +17,8 @@ export const EventFilter = ({
isVisible, isVisible,
}: { }: {
eventCategories: EventCategory[]; eventCategories: EventCategory[];
setCategory: (slug: string | null) => void; setCategories: (slug: string[] | null) => void;
activeCategory: string | null; activeCategories: string[] | null;
eventOrganizers: EventOrganizer[]; eventOrganizers: EventOrganizer[];
setOrganizer: (slug: string | null) => void; setOrganizer: (slug: string | null) => void;
activeOrganizer: string | null; activeOrganizer: string | null;
@ -35,6 +35,12 @@ export const EventFilter = ({
const venue = e.target.value; const venue = e.target.value;
setVenue(venue === "" ? null : venue); setVenue(venue === "" ? null : venue);
}; };
const toggleCategory = (category: string) => {
const newCategories = activeCategories?.includes(category)
? activeCategories?.filter((x) => x !== category)
: [...(activeCategories ?? []), category];
setCategories(newCategories.length === 0 ? null : newCategories);
};
return ( return (
<div className={styles.eventFilter} data-show={isVisible}> <div className={styles.eventFilter} data-show={isVisible}>
@ -48,12 +54,8 @@ export const EventFilter = ({
<li key={category.slug}> <li key={category.slug}>
<button <button
className="button toggler" className="button toggler"
data-active={activeCategory === category.slug} data-active={activeCategories?.includes(category.slug)}
onClick={() => onClick={() => toggleCategory(category.slug)}
setCategory(
activeCategory === category.slug ? null : category.slug
)
}
> >
{category.name} {category.name}
</button> </button>
@ -102,34 +104,38 @@ export const EventFilter = ({
export const EventFilterExplained = ({ export const EventFilterExplained = ({
eventCategories, eventCategories,
activeCategory, activeCategories,
eventOrganizers, eventOrganizers,
activeOrganizer, activeOrganizer,
venues, venues,
activeVenue, activeVenue,
}: { }: {
eventCategories: EventCategory[]; eventCategories: EventCategory[];
activeCategory: string | null; activeCategories: string[] | null;
eventOrganizers: EventOrganizer[]; eventOrganizers: EventOrganizer[];
activeOrganizer: string | null; activeOrganizer: string | null;
venues: VenueFragment[]; venues: VenueFragment[];
activeVenue: string | null; activeVenue: string | null;
}) => { }) => {
const category = const categories = eventCategories.filter((x) =>
activeCategory && eventCategories.find((x) => x.slug === activeCategory); activeCategories?.includes(x.slug)
);
const organizer = const organizer =
activeOrganizer && eventOrganizers.find((x) => x.slug === activeOrganizer); activeOrganizer && eventOrganizers.find((x) => x.slug === activeOrganizer);
const venue = activeVenue && venues.find((x) => x.slug === activeVenue); const venue = activeVenue && venues.find((x) => x.slug === activeVenue);
const categoryList = formatHumanReadableList(categories.map((x) => x.name));
const hasCategories = categoryList.length !== 0;
let text = ""; let text = "";
if (category && organizer && venue) { if (hasCategories && organizer && venue) {
text = `${category.name} av ${organizer.name} ${venue.preposition} ${venue.title}`; text = `${categoryList} av ${organizer.name} ${venue.preposition} ${venue.title}`;
} else if (category && organizer) { } else if (hasCategories && organizer) {
text = `${category.name} av ${organizer.name}`; text = `${categoryList} av ${organizer.name}`;
} else if (category && venue) { } else if (hasCategories && venue) {
text = `${category.name} ${venue.preposition} ${venue.title}`; text = `${categoryList} ${venue.preposition} ${venue.title}`;
} else if (category) { } else if (hasCategories) {
text = `${category.name}`; text = `${categoryList}`;
} else if (organizer && venue) { } else if (organizer && venue) {
text = `Arrangeres av ${organizer.name} ${venue.preposition} ${venue.title}`; text = `Arrangeres av ${organizer.name} ${venue.preposition} ${venue.title}`;
} else if (organizer) { } else if (organizer) {

View File

@ -76,6 +76,21 @@ export function formatNorwegianPhoneNumber(phone: string): string {
return phone; return phone;
} }
export function formatHumanReadableList(array: (string | number)[]): string {
const length = array.length;
if (length === 0) {
return "";
}
if (length === 1) {
return array[0].toString();
}
if (length === 2) {
return array.join(" og ");
}
return array.slice(0, -1).join(", ") + " og " + array[length - 1];
}
const OneLevelOfBlocksFragmentDefinition = graphql(` const OneLevelOfBlocksFragmentDefinition = graphql(`
fragment OneLevelOfBlocks on StreamFieldInterface { fragment OneLevelOfBlocks on StreamFieldInterface {
id id