Files
neuf-www/web/src/lib/event.ts
T

298 lines
6.7 KiB
TypeScript

import {
endOfWeek,
startOfToday,
startOfWeek,
isAfter,
eachDayOfInterval,
addWeeks,
parseISO,
} from "date-fns";
import { toLocalTime, formatDate, compareDates } from "./date";
import { graphql, unmaskFragment } from "@/gql";
import {
type EventCategoryFragment,
type EventFragment,
type EventOrganizerFragment,
} from "@/gql/graphql";
import { PIG_NAMES, randomElement } from "@/lib/common";
export type EventOccurrence = EventFragment["occurrences"][number];
export type EventCategory = EventCategoryFragment;
export type EventOrganizer = EventOrganizerFragment;
export type { EventFragment };
export type SingularEvent = EventFragment & {
occurrence: EventOccurrence;
};
export const EventCategoryFragmentDefinition = graphql(`
fragment EventCategory on EventCategory {
__typename
name
slug
pig
showInFilters
}
`);
export const EventOrganizerFragmentDefinition = graphql(`
fragment EventOrganizer on EventOrganizer {
__typename
id
name
slug
externalUrl
association {
... on AssociationPage {
url
}
}
}
`);
const EventFragmentDefinition = graphql(`
fragment Event on EventPage {
__typename
id
slug
seoTitle
searchDescription
title
subtitle
lead
body {
...OneLevelOfBlocks
}
featuredImage {
...Image
}
pig
facebookUrl
ticketUrl
free
priceRegular
priceMember
priceStudent
categories {
... on EventCategory {
...EventCategory
}
}
occurrences(limit: 5000) {
... on EventOccurrence {
__typename
id
start
end
venue {
__typename
id
slug
title
preposition
url
}
venueCustom
}
}
organizers {
... on EventOrganizer {
...EventOrganizer
}
}
}
`);
const EventIndexFragmentDefinition = graphql(`
fragment EventIndex on EventIndex {
__typename
id
slug
seoTitle
searchDescription
title
}
`);
export const eventIndexMetadataQuery = graphql(`
query eventIndexMetadata {
index: eventIndex {
... on EventIndex {
...EventIndex
}
}
}
`);
export const eventsOverviewQuery = graphql(`
query futureEvents {
index: eventIndex {
... on EventIndex {
...EventIndex
}
}
events: eventIndex {
... on EventIndex {
futureEvents {
... on EventPage {
...Event
}
}
}
}
eventCategories: eventCategories(limit: 5000) {
... on EventCategory {
...EventCategory
}
}
eventOrganizers: eventOrganizers(limit: 5000) {
... on EventOrganizer {
...EventOrganizer
}
}
venues: pages(contentType: "venues.VenuePage") {
... on VenuePage {
id
title
slug
preposition
}
}
}
`);
export function getSingularEvents(events: EventFragment[]): SingularEvent[] {
return events
.map((event) => {
return event.occurrences.map((occurrence) => {
const eventOccurrence: any = structuredClone(event);
eventOccurrence.occurrence = occurrence;
return eventOccurrence;
});
})
.flat();
}
export function sortSingularEvents(events: SingularEvent[]) {
return events.sort((a, b) =>
compareDates(a.occurrence.start, b.occurrence.start)
);
}
interface EventCalendar {
[yearMonth: string]: {
[week: string]: {
[day: string]: SingularEvent[];
};
};
}
export function organizeEventsInCalendar(
events: SingularEvent[]
): EventCalendar {
const sortedEvents = sortSingularEvents(events);
const calendar: EventCalendar = {};
const minDate = new Date(sortedEvents[0]?.occurrence.start);
const maxDate = new Date(
sortedEvents[sortedEvents.length - 1]?.occurrence.start
);
let currentDate = startOfWeek(minDate, { weekStartsOn: 1 });
while (currentDate <= maxDate) {
const week = formatDate(currentDate, "w");
const daysOfWeek = eachDayOfInterval({
start: currentDate,
end: endOfWeek(currentDate, { weekStartsOn: 1 }),
});
daysOfWeek.forEach((day) => {
const yearMonth = formatDate(day, "yyyy-MM");
if (!calendar[yearMonth]) {
calendar[yearMonth] = {};
}
if (!calendar[yearMonth][week]) {
calendar[yearMonth][week] = {};
}
const formattedDay = formatDate(day, "yyyy-MM-dd");
if (!calendar[yearMonth][week][formattedDay]) {
calendar[yearMonth][week][formattedDay] = [];
}
});
currentDate = addWeeks(currentDate, 1);
}
sortedEvents.forEach((event) => {
const start = toLocalTime(event.occurrence.start);
const yearMonth = formatDate(start, "yyyy-MM");
const week = formatDate(start, "w");
const day = formatDate(start, "yyyy-MM-dd");
if (!calendar[yearMonth]) {
calendar[yearMonth] = {};
}
if (!calendar[yearMonth][week]) {
calendar[yearMonth][week] = {};
}
if (!calendar[yearMonth][week][day]) {
calendar[yearMonth][week][day] = [];
}
calendar[yearMonth][week][day].push(event);
});
return calendar;
}
interface EventsByDate {
[day: string]: SingularEvent[];
}
export function organizeEventsByDate(events: SingularEvent[]): EventsByDate {
const sortedEvents = sortSingularEvents(events);
const eventsByDate: EventsByDate = {};
sortedEvents.forEach((event) => {
const start = toLocalTime(event.occurrence.start);
const day = formatDate(start, "yyyy-MM-dd");
if (!eventsByDate[day]) {
eventsByDate[day] = [];
}
eventsByDate[day].push(event);
});
return eventsByDate;
}
export function getFutureOccurrences(event: EventFragment): EventOccurrence[] {
const today = startOfToday();
const occurrences = event?.occurrences ?? [];
const futureOccurrences = occurrences.filter((occurrence) =>
isAfter(toLocalTime(occurrence.start), today)
);
futureOccurrences.sort(
(a, b) => parseISO(a.start).getTime() - parseISO(b.start).getTime()
);
return futureOccurrences as EventOccurrence[];
}
export function getEventPig(event: EventFragment): string | null {
if (event.pig === "" || typeof event.pig !== "string") {
return null;
}
if (PIG_NAMES.includes(event.pig)) {
return event.pig;
}
if (event.pig === "automatic") {
const categories = unmaskFragment(
EventCategoryFragmentDefinition,
event.categories
);
const categoryPigs = categories
?.map((category) => category.pig)
.filter((pig) => PIG_NAMES.includes(pig));
const chosenPig = randomElement(categoryPigs ?? []);
return typeof chosenPig === "string" ? chosenPig : null;
}
return null;
}