import { compareDesc, getISODay, isAfter, isSameDay, parseISO, startOfToday, } from "date-fns"; import { getClient } from "@/app/client"; import { graphql, unmaskFragment } from "@/gql"; import type { OpeningHoursRangeBlockFragment as OpeningHoursRangeBlock, OpeningHoursSetFragment as OpeningHoursSet, } from "@/gql/graphql"; const MISSING_OPENING_HOURS = { name: "Åpningstider mangler", effectiveFrom: "", effectiveTo: null, announcement: "Åpningstider mangler", items: [], }; const WEEKDAYS = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ]; const WEEKDAYS_NORWEGIAN = [ "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", "søndag", ]; const openingHoursQuery = graphql(` query openingHoursSets { openingHoursSets { ...OpeningHoursSet } } `); export async function fetchOpeningHoursSets() { const { data, error } = await getClient().query(openingHoursQuery, {}); const sets = (data?.openingHoursSets ?? []) as OpeningHoursSet[]; return sets; } export async function getOpeningHours() { const today = startOfToday(); const sets = await fetchOpeningHoursSets(); const validSets = sets .filter((set) => { const from = parseISO(set.effectiveFrom); return isAfter(today, from) || isSameDay(today, from); }) .filter((set) => { if (!set.effectiveTo) { return true; } const to = parseISO(set.effectiveTo); return isAfter(to, today) || isSameDay(today, to); }); if (validSets.length === 0) { return MISSING_OPENING_HOURS as OpeningHoursSet; } if (validSets.length === 1) { return validSets[0]; } // pick the set that msot recently took effect return validSets.sort((a, b) => compareDesc(a.effectiveFrom, b.effectiveFrom), )[0]; } type OpeningHoursGroup = { days: string[]; timeFrom: string | null; timeTo: string | null; custom: string | null; }; type OpeningHoursPerDay = Record; export function groupOpeningHours( week: OpeningHoursPerDay, ): OpeningHoursGroup[] { const grouped: OpeningHoursGroup[] = []; let previous: string | null = null; for (const day of WEEKDAYS) { if (!week.hasOwnProperty(day)) { continue; } const hours = week[day]; if ( hours === null || previous === null || week[previous]?.timeFrom !== hours.timeFrom || week[previous]?.timeTo !== hours.timeTo || week[previous]?.custom !== hours.custom ) { grouped.push({ days: [day], timeFrom: hours.timeFrom ?? null, timeTo: hours.timeTo ?? null, custom: hours.custom ?? null, }); } else { grouped[grouped.length - 1].days.push(day); } previous = day; } return grouped; } export type PrettyOpeningHours = { range: string; time?: string; custom?: string; }; function formatGroupedHours( grouped: OpeningHoursGroup[], ): PrettyOpeningHours[] { return grouped.map((group) => { const startDayIndex = WEEKDAYS.indexOf(group.days[0]); const endDayIndex = WEEKDAYS.indexOf(group.days[group.days.length - 1]); const startDayName = WEEKDAYS_NORWEGIAN[startDayIndex]; const endDayName = group.days.length > 1 ? WEEKDAYS_NORWEGIAN[endDayIndex] : ""; const rangeName = startDayName + (endDayName ? "—" + endDayName : ""); const formattedRange = { range: rangeName, ...(group.timeFrom && group.timeTo ? { time: `${group.timeFrom.slice(0, 5)}—${group.timeTo.slice(0, 5)}`, } : {}), ...(group.custom ? { custom: group.custom } : {}), }; return formattedRange; }); } export function getOpeningHoursForFunction( openingHours: OpeningHoursSet, name: string, ) { const item = openingHours.items?.find((x) => x?.function === name); if (!item || !Array.isArray(item?.week) || item?.week.length !== 1) { return; } const maskedWeek = item.week[0]; if (maskedWeek?.__typename !== "OpeningHoursWeekBlock") { return; } const week = unmaskFragment( OpeningHoursWeekBlockFragmentDefinition, maskedWeek, ); return week; } export function getPrettyOpeningHoursForFunction( openingHours: OpeningHoursSet, name: string, ) { const week = getOpeningHoursForFunction(openingHours, name); if (!week) { return []; } // just trying to satisfy the type checker, this is crap const perDay: OpeningHoursPerDay = { monday: week.monday as OpeningHoursRangeBlock, tuesday: week.tuesday as OpeningHoursRangeBlock, wednesday: week.wednesday as OpeningHoursRangeBlock, thursday: week.thursday as OpeningHoursRangeBlock, friday: week.friday as OpeningHoursRangeBlock, saturday: week.saturday as OpeningHoursRangeBlock, sunday: week.sunday as OpeningHoursRangeBlock, }; const grouped = groupOpeningHours(perDay); return formatGroupedHours(grouped); } export function getTodaysOpeningHoursForFunction( openingHours: OpeningHoursSet, name: string, ): string { const week: any = getOpeningHoursForFunction(openingHours, name); if (!week) { return "?"; } const weekdayIndex = getISODay(startOfToday()) - 1; const weekday = WEEKDAYS[weekdayIndex]; const hours = week[weekday]; if (hours.timeFrom && hours.timeTo) { return `${hours.timeFrom.slice(0, 5)}—${hours.timeTo.slice(0, 5)}`; } if (hours.custom && hours.custom.length) { return hours.custom; } return "Stengt"; } const OpeningHoursSetFragmentDefinition = graphql(` fragment OpeningHoursSet on OpeningHoursSet { name effectiveFrom effectiveTo announcement items { id function week { __typename ... on OpeningHoursWeekBlock { ...OpeningHoursWeekBlock } } } } `); const OpeningHoursRangeBlockFragmentDefinition = graphql(` fragment OpeningHoursRangeBlock on OpeningHoursRangeBlock { timeFrom timeTo custom } `); export const OpeningHoursWeekBlockFragmentDefinition = graphql(` fragment OpeningHoursWeekBlock on OpeningHoursWeekBlock { monday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } tuesday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } wednesday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } thursday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } friday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } saturday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } sunday { ... on OpeningHoursRangeBlock { ...OpeningHoursRangeBlock } } } `);