215 lines
4.8 KiB
TypeScript
215 lines
4.8 KiB
TypeScript
import {
|
|
compareAsc,
|
|
endOfWeek,
|
|
startOfToday,
|
|
startOfWeek,
|
|
isAfter,
|
|
eachDayOfInterval,
|
|
addWeeks,
|
|
parseISO,
|
|
} from "date-fns";
|
|
import { toLocalTime, formatDate } from "./date";
|
|
import { graphql } from "@/gql";
|
|
import { EventFragment, EventCategory, EventOccurrence } from "@/gql/graphql";
|
|
import { PIG_NAMES, PigName, randomElement } from "@/lib/common";
|
|
|
|
export type {
|
|
EventFragment,
|
|
EventCategory,
|
|
EventOccurrence,
|
|
EventOrganizer,
|
|
} from "@/gql/graphql";
|
|
|
|
export type SingularEvent = EventFragment & {
|
|
occurrence: EventOccurrence;
|
|
};
|
|
|
|
const EventFragmentDefinition = graphql(`
|
|
fragment Event on EventPage {
|
|
__typename
|
|
id
|
|
slug
|
|
title
|
|
body {
|
|
id
|
|
blockType
|
|
field
|
|
... on RichTextBlock {
|
|
rawValue
|
|
value
|
|
}
|
|
}
|
|
featuredImage {
|
|
...Image
|
|
}
|
|
pig
|
|
facebookUrl
|
|
ticketUrl
|
|
free
|
|
priceRegular
|
|
priceMember
|
|
priceStudent
|
|
categories {
|
|
... on EventCategory {
|
|
name
|
|
slug
|
|
pig
|
|
}
|
|
}
|
|
occurrences {
|
|
... on EventOccurrence {
|
|
__typename
|
|
id
|
|
start
|
|
end
|
|
venue {
|
|
__typename
|
|
id
|
|
slug
|
|
title
|
|
preposition
|
|
url
|
|
}
|
|
}
|
|
}
|
|
organizers {
|
|
... on EventOrganizer {
|
|
name
|
|
externalUrl
|
|
association {
|
|
... on AssociationPage {
|
|
url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
export const futureEventsQuery = graphql(`
|
|
query futureEvents {
|
|
events: eventIndex {
|
|
... on EventIndex {
|
|
futureEvents {
|
|
... on EventPage {
|
|
...Event
|
|
}
|
|
}
|
|
}
|
|
}
|
|
eventCategories: eventCategories {
|
|
... on EventCategory {
|
|
name
|
|
slug
|
|
showInFilters
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
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 compareDates(a: Date | string, b: Date | string) {
|
|
return compareAsc(new Date(a), new Date(b));
|
|
}
|
|
|
|
export function sortSingularEvents(events: SingularEvent[]) {
|
|
return events.sort((a, b) =>
|
|
compareDates(a.occurrence.start, b.occurrence.start)
|
|
);
|
|
}
|
|
interface EventsByDate {
|
|
[yearMonth: string]: {
|
|
[week: string]: {
|
|
[day: string]: SingularEvent[];
|
|
};
|
|
};
|
|
}
|
|
|
|
export function organizeEventsByDate(events: SingularEvent[]): EventsByDate {
|
|
const sortedEvents = sortSingularEvents(events);
|
|
|
|
const eventsByDate: EventsByDate = {};
|
|
|
|
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 yearMonth = formatDate(currentDate, "yyyy-MM");
|
|
const week = formatDate(currentDate, "w");
|
|
const daysOfWeek = eachDayOfInterval({
|
|
start: currentDate,
|
|
end: endOfWeek(currentDate, { weekStartsOn: 1 }),
|
|
});
|
|
|
|
if (!eventsByDate[yearMonth]) {
|
|
eventsByDate[yearMonth] = {};
|
|
}
|
|
if (!eventsByDate[yearMonth][week]) {
|
|
eventsByDate[yearMonth][week] = {};
|
|
}
|
|
daysOfWeek.forEach((day) => {
|
|
const formattedDay = formatDate(day, "yyyy-MM-dd");
|
|
if (!eventsByDate[yearMonth][week][formattedDay]) {
|
|
eventsByDate[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");
|
|
eventsByDate[yearMonth][week][day].push(event);
|
|
});
|
|
|
|
return eventsByDate;
|
|
}
|
|
|
|
export function getClosestOccurrence(event: EventFragment) {
|
|
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[0];
|
|
}
|
|
|
|
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 categoryPigs = event.categories
|
|
?.map((category) => category.pig)
|
|
.filter((pig) => PIG_NAMES.includes(pig));
|
|
const chosenPig = randomElement(categoryPigs ?? []);
|
|
return typeof chosenPig === "string" ? chosenPig : null;
|
|
}
|
|
return null;
|
|
}
|