mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-24 10:51:27 +00:00
Fix recurring event duplication bug
When a recurring event has a modified occurrence, the calendar was showing both the original and modified versions. This happened because the code didn't properly handle RECURRENCE-ID exceptions in iCal data. Changes: - Modified extractScheduleEventsInRange() to collect exception events first and filter them from recurring patterns - Updated expandEventOccurrences() to skip dates that have exceptions - Applied same fix to extractScheduleEvents() for proper date range filtering Fixes issue where modified recurring events appeared twice in the UI. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3ef9cc2ebe
commit
39d266e9b5
1 changed files with 79 additions and 5 deletions
|
|
@ -28,7 +28,39 @@ const extractScheduleEventsInRange = (
|
||||||
rangeEnd: ICAL.Time,
|
rangeEnd: ICAL.Time,
|
||||||
): ScheduleEvent[] => {
|
): ScheduleEvent[] => {
|
||||||
const vevents = icalComp.getAllSubcomponents("vevent")
|
const vevents = icalComp.getAllSubcomponents("vevent")
|
||||||
return vevents
|
|
||||||
|
// First, collect all exception events (events with RECURRENCE-ID)
|
||||||
|
const exceptions = new Map<string, Set<string>>() // uid -> set of recurrence-id strings
|
||||||
|
const exceptionEvents: ScheduleEvent[] = []
|
||||||
|
|
||||||
|
vevents.forEach((vevent) => {
|
||||||
|
const recurrenceId = vevent.getFirstPropertyValue("recurrence-id")
|
||||||
|
if (recurrenceId) {
|
||||||
|
const uid = vevent.getFirstPropertyValue("uid")
|
||||||
|
const event = new ICAL.Event(vevent)
|
||||||
|
|
||||||
|
// Check if exception is in range
|
||||||
|
if (event.startDate.compare(rangeEnd) <= 0 && event.endDate.compare(rangeStart) >= 0) {
|
||||||
|
exceptionEvents.push({
|
||||||
|
start: event.startDate.toJSDate(),
|
||||||
|
end: event.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: recurrenceId.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track exception for filtering recurring events
|
||||||
|
if (!exceptions.has(uid)) {
|
||||||
|
exceptions.set(uid, new Set())
|
||||||
|
}
|
||||||
|
exceptions.get(uid)!.add(recurrenceId.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process regular and recurring events
|
||||||
|
const regularEvents = vevents
|
||||||
|
.filter(vevent => !vevent.getFirstPropertyValue("recurrence-id"))
|
||||||
.flatMap<ScheduleEvent>((vevent) => {
|
.flatMap<ScheduleEvent>((vevent) => {
|
||||||
const event = new ICAL.Event(vevent)
|
const event = new ICAL.Event(vevent)
|
||||||
if (!event.isRecurring()) {
|
if (!event.isRecurring()) {
|
||||||
|
|
@ -41,8 +73,11 @@ const extractScheduleEventsInRange = (
|
||||||
recurrenceId: undefined,
|
recurrenceId: undefined,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
return expandEventOccurrences(event, rangeStart, rangeEnd)
|
const eventExceptions = exceptions.get(event.uid) || new Set()
|
||||||
|
return expandEventOccurrences(event, rangeStart, rangeEnd, eventExceptions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return [...regularEvents, ...exceptionEvents]
|
||||||
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,6 +85,7 @@ const expandEventOccurrences = (
|
||||||
event: ICAL.Event,
|
event: ICAL.Event,
|
||||||
rangeStart: ICAL.Time,
|
rangeStart: ICAL.Time,
|
||||||
rangeEnd: ICAL.Time,
|
rangeEnd: ICAL.Time,
|
||||||
|
exceptions?: Set<string>,
|
||||||
): ScheduleEvent[] => {
|
): ScheduleEvent[] => {
|
||||||
const occurrences: ScheduleEvent[] = []
|
const occurrences: ScheduleEvent[] = []
|
||||||
const iterator = event.iterator()
|
const iterator = event.iterator()
|
||||||
|
|
@ -58,6 +94,10 @@ const expandEventOccurrences = (
|
||||||
if (!next) break
|
if (!next) break
|
||||||
if (next.compare(rangeEnd) > 0) break
|
if (next.compare(rangeEnd) > 0) break
|
||||||
if (next.compare(rangeStart) < 0) continue
|
if (next.compare(rangeStart) < 0) continue
|
||||||
|
|
||||||
|
// Skip this occurrence if it has an exception
|
||||||
|
if (exceptions && exceptions.has(next.toString())) continue
|
||||||
|
|
||||||
const details = event.getOccurrenceDetails(next)
|
const details = event.getOccurrenceDetails(next)
|
||||||
occurrences.push({
|
occurrences.push({
|
||||||
start: details.startDate.toJSDate(),
|
start: details.startDate.toJSDate(),
|
||||||
|
|
@ -73,8 +113,40 @@ const expandEventOccurrences = (
|
||||||
const extractScheduleEvents = (icalComp: ICAL.Component): ScheduleEvent[] => {
|
const extractScheduleEvents = (icalComp: ICAL.Component): ScheduleEvent[] => {
|
||||||
const vevents = icalComp.getAllSubcomponents("vevent")
|
const vevents = icalComp.getAllSubcomponents("vevent")
|
||||||
const rangeEnd = ICAL.Time.fromDateString("2026-01-01")
|
const rangeEnd = ICAL.Time.fromDateString("2026-01-01")
|
||||||
|
const rangeStart = ICAL.Time.fromDateString("2020-01-01")
|
||||||
|
|
||||||
return vevents
|
// First, collect all exception events (events with RECURRENCE-ID)
|
||||||
|
const exceptions = new Map<string, Set<string>>() // uid -> set of recurrence-id strings
|
||||||
|
const exceptionEvents: ScheduleEvent[] = []
|
||||||
|
|
||||||
|
vevents.forEach((vevent) => {
|
||||||
|
const recurrenceId = vevent.getFirstPropertyValue("recurrence-id")
|
||||||
|
if (recurrenceId) {
|
||||||
|
const uid = vevent.getFirstPropertyValue("uid")
|
||||||
|
const event = new ICAL.Event(vevent)
|
||||||
|
|
||||||
|
// Check if exception is in range
|
||||||
|
if (event.startDate.compare(rangeEnd) <= 0 && event.endDate.compare(rangeStart) >= 0) {
|
||||||
|
exceptionEvents.push({
|
||||||
|
start: event.startDate.toJSDate(),
|
||||||
|
end: event.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: recurrenceId.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track exception for filtering recurring events
|
||||||
|
if (!exceptions.has(uid)) {
|
||||||
|
exceptions.set(uid, new Set())
|
||||||
|
}
|
||||||
|
exceptions.get(uid)!.add(recurrenceId.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process regular and recurring events
|
||||||
|
const regularEvents = vevents
|
||||||
|
.filter(vevent => !vevent.getFirstPropertyValue("recurrence-id"))
|
||||||
.flatMap<ScheduleEvent>((vevent) => {
|
.flatMap<ScheduleEvent>((vevent) => {
|
||||||
const event = new ICAL.Event(vevent)
|
const event = new ICAL.Event(vevent)
|
||||||
if (event.iterator().complete) {
|
if (event.iterator().complete) {
|
||||||
|
|
@ -84,11 +156,13 @@ const extractScheduleEvents = (icalComp: ICAL.Component): ScheduleEvent[] => {
|
||||||
summary: event.summary,
|
summary: event.summary,
|
||||||
description: event.description,
|
description: event.description,
|
||||||
recurrenceId: event.uid || event.startDate.toString(),
|
recurrenceId: event.uid || event.startDate.toString(),
|
||||||
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
return expandEventOccurrences(event, rangeEnd)
|
const eventExceptions = exceptions.get(event.uid) || new Set()
|
||||||
|
return expandEventOccurrences(event, rangeStart, rangeEnd, eventExceptions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return [...regularEvents, ...exceptionEvents]
|
||||||
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue