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:
@@ -10,11 +10,16 @@ import {
|
||||
UpdateEventDTO,
|
||||
EventAction,
|
||||
CreateMessageDTO,
|
||||
RecurringDeleteMode,
|
||||
} from "@calchat/shared";
|
||||
import { ChatRepository, EventRepository, AIProvider } from "./interfaces";
|
||||
import { EventService } from "./EventService";
|
||||
import { getWeeksOverview, getMonthOverview } from "../utils/eventFormatters";
|
||||
|
||||
type TestResponse = { content: string; proposedChanges?: ProposedEventChange[] };
|
||||
type TestResponse = {
|
||||
content: string;
|
||||
proposedChanges?: ProposedEventChange[];
|
||||
};
|
||||
|
||||
// Test response index (cycles through responses)
|
||||
let responseIndex = 0;
|
||||
@@ -25,8 +30,7 @@ const staticResponses: TestResponse[] = [
|
||||
// === MULTI-EVENT TEST RESPONSES ===
|
||||
// Response 0: 3 Meetings an verschiedenen Tagen
|
||||
{
|
||||
content:
|
||||
"Alles klar! Ich erstelle dir 3 Team-Meetings für diese Woche:",
|
||||
content: "Alles klar! Ich erstelle dir 3 Team-Meetings für diese Woche:",
|
||||
proposedChanges: [
|
||||
{
|
||||
id: "multi-1-a",
|
||||
@@ -62,8 +66,7 @@ const staticResponses: TestResponse[] = [
|
||||
},
|
||||
// Response 1: 5 Termine für einen Projekttag
|
||||
{
|
||||
content:
|
||||
"Ich habe deinen kompletten Projekttag am Dienstag geplant:",
|
||||
content: "Ich habe deinen kompletten Projekttag am Dienstag geplant:",
|
||||
proposedChanges: [
|
||||
{
|
||||
id: "multi-2-a",
|
||||
@@ -119,8 +122,7 @@ const staticResponses: TestResponse[] = [
|
||||
},
|
||||
// Response 2: 2 wiederkehrende Termine
|
||||
{
|
||||
content:
|
||||
"Ich erstelle dir zwei wiederkehrende Fitness-Termine:",
|
||||
content: "Ich erstelle dir zwei wiederkehrende Fitness-Termine:",
|
||||
proposedChanges: [
|
||||
{
|
||||
id: "multi-3-a",
|
||||
@@ -209,7 +211,8 @@ const staticResponses: TestResponse[] = [
|
||||
title: "Arzttermin Dr. Müller",
|
||||
startTime: getDay("Wednesday", 1, 9, 30),
|
||||
endTime: getDay("Wednesday", 1, 10, 30),
|
||||
description: "Routineuntersuchung - Versichertenkarte nicht vergessen",
|
||||
description:
|
||||
"Routineuntersuchung - Versichertenkarte nicht vergessen",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -403,6 +406,7 @@ export class ChatService {
|
||||
constructor(
|
||||
private chatRepo: ChatRepository,
|
||||
private eventRepo: EventRepository,
|
||||
private eventService: EventService,
|
||||
private aiProvider: AIProvider,
|
||||
) {}
|
||||
|
||||
@@ -462,9 +466,15 @@ export class ChatService {
|
||||
event?: CreateEventDTO,
|
||||
eventId?: string,
|
||||
updates?: UpdateEventDTO,
|
||||
deleteMode?: RecurringDeleteMode,
|
||||
occurrenceDate?: string,
|
||||
): Promise<ChatResponse> {
|
||||
// Update specific proposal with respondedAction
|
||||
await this.chatRepo.updateProposalResponse(messageId, proposalId, "confirm");
|
||||
await this.chatRepo.updateProposalResponse(
|
||||
messageId,
|
||||
proposalId,
|
||||
"confirm",
|
||||
);
|
||||
|
||||
// Perform the actual event operation
|
||||
let content: string;
|
||||
@@ -478,10 +488,25 @@ export class ChatService {
|
||||
? `Der Termin "${updatedEvent.title}" wurde aktualisiert.`
|
||||
: "Termin nicht gefunden.";
|
||||
} else if (action === "delete" && eventId) {
|
||||
await this.eventRepo.delete(eventId);
|
||||
// Use deleteRecurring for proper handling of recurring events
|
||||
const mode = deleteMode || "all";
|
||||
await this.eventService.deleteRecurring(
|
||||
eventId,
|
||||
userId,
|
||||
mode,
|
||||
occurrenceDate,
|
||||
);
|
||||
|
||||
// Build appropriate response message
|
||||
let deleteDescription = "";
|
||||
if (deleteMode === "single") {
|
||||
deleteDescription = " (dieses Vorkommen)";
|
||||
} else if (deleteMode === "future") {
|
||||
deleteDescription = " (dieses und zukünftige Vorkommen)";
|
||||
}
|
||||
content = event?.title
|
||||
? `Der Termin "${event.title}" wurde gelöscht.`
|
||||
: "Der Termin wurde gelöscht.";
|
||||
? `Der Termin "${event.title}"${deleteDescription} wurde gelöscht.`
|
||||
: `Der Termin${deleteDescription} wurde gelöscht.`;
|
||||
} else {
|
||||
content = "Ungültige Aktion.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user