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

287 lines
6.7 KiB
TypeScript

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<string, OpeningHoursRangeBlock>;
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
}
}
}
`);