From 40f0179be6ab83f322a9af6df3e95c34a0abfc7e Mon Sep 17 00:00:00 2001 From: lenhanphung Date: Thu, 9 Oct 2025 14:05:19 +0700 Subject: [PATCH] fix: directly delete recurring events when converting to non-recurring - Use direct API call to delete recurring events instead of going through Redux store - Construct event URL directly for more reliable deletion - Add error handling and small delay to ensure complete deletion - Clean up all instances from store after deletion --- src/components/Event/EventFormFields.tsx | 14 +-- src/features/Events/EventDisplay.tsx | 10 +- src/features/Events/EventUpdateModal.tsx | 130 ++++++++++++++--------- 3 files changed, 89 insertions(+), 65 deletions(-) diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx index 2ceba38..744aa1b 100644 --- a/src/components/Event/EventFormFields.tsx +++ b/src/components/Event/EventFormFields.tsx @@ -345,13 +345,13 @@ export default function EventFormFields({ label="All day" /> { - const newShowRepeat = !showRepeat; - setShowRepeat(newShowRepeat); + control={ + { + const newShowRepeat = !showRepeat; + setShowRepeat(newShowRepeat); if (newShowRepeat) { setRepetition({ freq: "daily", diff --git a/src/features/Events/EventDisplay.tsx b/src/features/Events/EventDisplay.tsx index b94a79f..5c2af34 100644 --- a/src/features/Events/EventDisplay.tsx +++ b/src/features/Events/EventDisplay.tsx @@ -46,7 +46,7 @@ import { import { Calendars } from "../Calendars/CalendarTypes"; import { userAttendee } from "../User/userDataTypes"; import { getEvent } from "./EventApi"; -import { formatLocalDateTime } from "./EventModal"; +import { formatLocalDateTime } from "../../components/Event/EventFormFields"; import { CalendarEvent, RepetitionObject } from "./EventsTypes"; export default function EventDisplayModal({ @@ -128,7 +128,7 @@ export default function EventDisplayModal({ onClose({}, "backdropClick"); } setRepetition(event?.repetition ?? ({} as RepetitionObject)); - }, [open, eventId, dispatch, onClose, event]); + }, [open, eventId, dispatch, onClose, event, calendar]); useEffect(() => { const fetchMasterEvent = async () => { const masterEvent = await getEvent(event); @@ -160,8 +160,8 @@ export default function EventDisplayModal({ calId, title, URL: event.URL ?? `/calendars/${calId}/${newEventUID}.ics`, - start: new Date(start), - end: new Date(end), + start: start, + end: end, allday, uid: event.uid ?? newEventUID, description, @@ -523,7 +523,7 @@ export default function EventDisplayModal({ {isOwn && ( diff --git a/src/features/Events/EventUpdateModal.tsx b/src/features/Events/EventUpdateModal.tsx index a406034..c86b2f6 100644 --- a/src/features/Events/EventUpdateModal.tsx +++ b/src/features/Events/EventUpdateModal.tsx @@ -23,7 +23,7 @@ import { addVideoConferenceToDescription } from "../../utils/videoConferenceUtil import EventFormFields, { formatDateTimeInTimezone, } from "../../components/Event/EventFormFields"; -import { getEvent } from "./EventApi"; +import { getEvent, deleteEvent } from "./EventApi"; import { refreshCalendars } from "../../components/Event/utils/eventUtils"; import { getCalendarRange } from "../../utils/dateUtils"; @@ -346,66 +346,90 @@ function EventUpdateModal({ dispatch(removeEvent({ calendarUid: calId, eventUid: event.uid })); } - // Handle recurrence instances - const [, recurrenceId] = event.uid.split("/"); + // Handle recurrence instances + const [, recurrenceId] = event.uid.split("/"); - // Execute API calls in background based on typeOfAction - if (recurrenceId) { - if (typeOfAction === "solo") { - // Update just this instance - dispatch( - updateEventInstanceAsync({ - cal: targetCalendar, - event: { ...newEvent, recurrenceId }, - }) - ); - } else if (typeOfAction === "all") { - // Update all instances - - // Special case: When converting recurring event to non-recurring - if (event.repetition?.freq && !repetition.freq) { - // For repeat -> no-repeat, create a new non-repeating event + // Execute API calls in background based on typeOfAction + if (recurrenceId) { + if (typeOfAction === "solo") { + // Update just this instance dispatch( - putEventAsync({ - cal: targetCalendar, - newEvent: { - ...newEvent, - uid: crypto.randomUUID(), // Generate new ID for the single event - URL: `/calendars/${newCalId || calId}/${crypto.randomUUID()}.ics`, - }, - }) - ); - - // Delete the old repeating series - const baseUID = event.uid.split("/")[0]; - Object.keys(targetCalendar.events).forEach((eventId) => { - if (eventId.split("/")[0] === baseUID) { - dispatch(removeEvent({ calendarUid: calId, eventUid: eventId })); - } - }); - } else { - // Normal update for recurring events - dispatch( - updateSeriesAsync({ + updateEventInstanceAsync({ cal: targetCalendar, event: { ...newEvent, recurrenceId }, }) ); + } else if (typeOfAction === "all") { + // Update all instances + + // Special case: When converting recurring event to non-recurring + if (event.repetition?.freq && !repetition.freq) { + // For repeat -> no-repeat, create a new non-repeating event + dispatch( + putEventAsync({ + cal: targetCalendar, + newEvent: { + ...newEvent, + uid: crypto.randomUUID(), // Generate new ID for the single event + URL: `/calendars/${newCalId || calId}/${crypto.randomUUID()}.ics`, + }, + }) + ); + + // Delete the old repeating series - we need to be more direct and forceful + const baseUID = event.uid.split("/")[0]; + + try { + // Directly use the deleteEvent API without going through the store + const eventBaseURL = `/calendars/${calId}/${baseUID}.ics`; + await deleteEvent(eventBaseURL); + console.log( + "Deleted master event via direct API call:", + eventBaseURL + ); + + // Force a small delay to ensure deletion completes + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Make sure to clean up any instances in the store + Object.keys(targetCalendar.events).forEach((eventId) => { + if (eventId.split("/")[0] === baseUID) { + dispatch( + removeEvent({ calendarUid: calId, eventUid: eventId }) + ); + } + }); + } catch (err) { + console.error("Failed to delete recurring event:", err); + // Even if deletion fails, continue with creating the new event + } + } else { + // Normal update for recurring events + dispatch( + updateSeriesAsync({ + cal: targetCalendar, + event: { ...newEvent, recurrenceId }, + }) + ); + } + + // Refresh calendars to ensure all instances are updated + const calendarRange = getCalendarRange(new Date(start)); + await refreshCalendars( + dispatch, + Object.values(calendarsList), + calendarRange + ); } - - // Refresh calendars to ensure all instances are updated - const calendarRange = getCalendarRange(new Date(start)); - await refreshCalendars(dispatch, Object.values(calendarsList), calendarRange); + } else { + // Non-recurring event + dispatch( + putEventAsync({ + cal: targetCalendar, + newEvent, + }) + ); } - } else { - // Non-recurring event - dispatch( - putEventAsync({ - cal: targetCalendar, - newEvent, - }) - ); - } // Handle calendar change if (newCalId !== calId) {