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; }