feat: add recurring event deletion with three modes

Implement three deletion modes for recurring events:
- single: exclude specific occurrence via EXDATE mechanism
- future: set RRULE UNTIL to stop future occurrences
- all: delete entire event series

Changes include:
- Add exceptionDates field to CalendarEvent model
- Add RecurringDeleteMode type and DeleteRecurringEventDTO
- EventService.deleteRecurring() with mode-based logic using rrule library
- EventController DELETE endpoint accepts mode/occurrenceDate query params
- recurrenceExpander filters out exception dates during expansion
- AI tools support deleteMode and occurrenceDate for proposed deletions
- ChatService.confirmEvent() handles recurring delete modes
- New DeleteEventModal component for unified delete confirmation UI
- Calendar screen integrates modal for both recurring and non-recurring events
This commit is contained in:
2026-01-25 15:19:31 +01:00
parent a42e2a7c1c
commit 2b999d9b0f
35 changed files with 787 additions and 200 deletions

View File

@@ -71,10 +71,19 @@ export function expandRecurringEvents(
true, // inclusive
);
// Build set of exception dates for fast lookup
const exceptionSet = new Set(event.exceptionDates || []);
for (const occurrence of occurrences) {
const occurrenceStart = fromRRuleDate(occurrence);
const occurrenceEnd = new Date(occurrenceStart.getTime() + duration);
// Skip if this occurrence is in the exception dates
const dateKey = formatDateKey(occurrenceStart);
if (exceptionSet.has(dateKey)) {
continue;
}
expanded.push({
...event,
occurrenceStart,
@@ -113,3 +122,11 @@ function formatRRuleDateString(date: Date): string {
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
}
// Format date as YYYY-MM-DD for exception date comparison
function formatDateKey(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}