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

View File

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

View File

@ -76,6 +76,21 @@ export function formatNorwegianPhoneNumber(phone: string): string {
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(`
fragment OneLevelOfBlocks on StreamFieldInterface {
id