Files
FEDEO/frontend/utils/eventRecurrence.ts

79 lines
2.5 KiB
TypeScript

import dayjs from "dayjs"
export const EVENT_REPEAT_INTERVALS = [
"Keine Wiederholung",
"Täglich",
"Wöchentlich",
"2-wöchentlich",
"Monatlich",
"Jährlich"
] as const
const DEFAULT_REPEAT_INTERVAL = "Keine Wiederholung"
const MAX_OCCURRENCES = 500
const addInterval = (date: dayjs.Dayjs, repeatInterval: string) => {
switch (repeatInterval) {
case "Täglich":
return date.add(1, "day")
case "Wöchentlich":
return date.add(1, "week")
case "2-wöchentlich":
return date.add(2, "week")
case "Monatlich":
return date.add(1, "month")
case "Jährlich":
return date.add(1, "year")
default:
return null
}
}
export const normalizeRepeatInterval = (value?: string | null) =>
EVENT_REPEAT_INTERVALS.includes(value as typeof EVENT_REPEAT_INTERVALS[number])
? value as typeof EVENT_REPEAT_INTERVALS[number]
: DEFAULT_REPEAT_INTERVAL
export const expandRecurringEvent = <T>(
event: {
startDate: string | Date
endDate?: string | Date | null
repeatInterval?: string | null
},
rangeStart: string | Date,
rangeEnd: string | Date,
buildOccurrence: (occurrenceStart: dayjs.Dayjs, occurrenceEnd: dayjs.Dayjs | null, occurrenceIndex: number) => T
) => {
const repeatInterval = normalizeRepeatInterval(event.repeatInterval)
const baseStart = dayjs(event.startDate)
const baseEnd = event.endDate ? dayjs(event.endDate) : null
const visibleStart = dayjs(rangeStart)
const visibleEnd = dayjs(rangeEnd)
const durationMs = baseEnd ? Math.max(baseEnd.diff(baseStart), 0) : 0
if (repeatInterval === DEFAULT_REPEAT_INTERVAL) {
const occurrenceEnd = baseEnd ? baseStart.add(durationMs, "millisecond") : null
if (baseStart.isAfter(visibleEnd) || (occurrenceEnd && occurrenceEnd.isBefore(visibleStart))) return []
return [buildOccurrence(baseStart, occurrenceEnd, 0)]
}
const occurrences: T[] = []
let occurrenceStart = baseStart
let occurrenceIndex = 0
while (occurrenceIndex < MAX_OCCURRENCES && occurrenceStart.isBefore(visibleEnd.add(1, "day"))) {
const occurrenceEnd = baseEnd ? occurrenceStart.add(durationMs, "millisecond") : null
if (!occurrenceStart.isAfter(visibleEnd) && !(occurrenceEnd && occurrenceEnd.isBefore(visibleStart))) {
occurrences.push(buildOccurrence(occurrenceStart, occurrenceEnd, occurrenceIndex))
}
const nextStart = addInterval(occurrenceStart, repeatInterval)
if (!nextStart || nextStart.isSame(occurrenceStart)) break
occurrenceStart = nextStart
occurrenceIndex += 1
}
return occurrences
}