fix: resolve infinite loop and week calculation issues in EventModal
- Fix infinite loop in EventModal useEffect by using useRef for userPersonnalCalendars - Fix weekStart calculation in putEventAsync to use Monday as first day (consistent with FullCalendar firstDay=1) - Add computeWeekRange utility function for consistent week calculations - Improve state reset logic when closing EventModal popup - Ensure repeat checkbox state is properly reset to prevent stale repetition data
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
|||||||
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
||||||
import { parseCalendarEvent } from "../Events/eventUtils";
|
import { parseCalendarEvent } from "../Events/eventUtils";
|
||||||
import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi";
|
import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi";
|
||||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
|
import { formatDateToYYYYMMDDTHHMMSS, computeWeekRange } from "../../utils/dateUtils";
|
||||||
import { User } from "../../components/Attendees/PeopleSearch";
|
import { User } from "../../components/Attendees/PeopleSearch";
|
||||||
|
|
||||||
export const getCalendarsListAsync = createAsyncThunk<
|
export const getCalendarsListAsync = createAsyncThunk<
|
||||||
@@ -124,12 +124,8 @@ export const putEventAsync = createAsyncThunk<
|
|||||||
await putEvent(newEvent, cal.ownerEmails ? cal.ownerEmails[0] : undefined);
|
await putEvent(newEvent, cal.ownerEmails ? cal.ownerEmails[0] : undefined);
|
||||||
const eventDate = new Date(newEvent.start);
|
const eventDate = new Date(newEvent.start);
|
||||||
|
|
||||||
const weekStart = new Date(eventDate);
|
// Calculate week range based on Monday as first day (consistent with FullCalendar firstDay={1})
|
||||||
weekStart.setHours(0, 0, 0, 0);
|
const { start: weekStart, end: weekEnd } = computeWeekRange(eventDate);
|
||||||
weekStart.setDate(eventDate.getDate() - eventDate.getDay());
|
|
||||||
|
|
||||||
const weekEnd = new Date(weekStart);
|
|
||||||
weekEnd.setDate(weekStart.getDate() + 7);
|
|
||||||
|
|
||||||
const calEvents = (await getCalendar(cal.id, {
|
const calEvents = (await getCalendar(cal.id, {
|
||||||
start: formatDateToYYYYMMDDTHHMMSS(weekStart),
|
start: formatDateToYYYYMMDDTHHMMSS(weekStart),
|
||||||
@@ -210,11 +206,14 @@ export const moveEventAsync = createAsyncThunk<
|
|||||||
{ cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type
|
{ cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type
|
||||||
>("calendars/moveEvent", async ({ cal, newEvent, newURL }) => {
|
>("calendars/moveEvent", async ({ cal, newEvent, newURL }) => {
|
||||||
await moveEvent(newEvent, newURL);
|
await moveEvent(newEvent, newURL);
|
||||||
|
|
||||||
|
// Calculate week range based on Monday as first day (consistent with FullCalendar firstDay={1})
|
||||||
|
const eventDate = new Date(newEvent.start);
|
||||||
|
const { start: weekStart, end: weekEnd } = computeWeekRange(eventDate);
|
||||||
|
|
||||||
const calEvents = (await getCalendar(cal.id, {
|
const calEvents = (await getCalendar(cal.id, {
|
||||||
start: formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)),
|
start: formatDateToYYYYMMDDTHHMMSS(weekStart),
|
||||||
end: formatDateToYYYYMMDDTHHMMSS(
|
end: formatDateToYYYYMMDDTHHMMSS(weekEnd),
|
||||||
new Date(new Date(newEvent.start).getTime() + 86400000)
|
|
||||||
),
|
|
||||||
})) as Record<string, any>;
|
})) as Record<string, any>;
|
||||||
const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap(
|
const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap(
|
||||||
(eventdata: any) => {
|
(eventdata: any) => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
ContentCopy as CopyIcon,
|
ContentCopy as CopyIcon,
|
||||||
Close as DeleteIcon,
|
Close as DeleteIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo, useCallback, useRef } from "react";
|
||||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||||
import AttendeeSelector from "../../components/Attendees/AttendeeSearch";
|
import AttendeeSelector from "../../components/Attendees/AttendeeSearch";
|
||||||
import { ResponsiveDialog } from "../../components/Dialog";
|
import { ResponsiveDialog } from "../../components/Dialog";
|
||||||
@@ -129,6 +129,7 @@ function EventPopover({
|
|||||||
selectPersonnalCalendars
|
selectPersonnalCalendars
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Helper function to resolve timezone aliases
|
// Helper function to resolve timezone aliases
|
||||||
const resolveTimezone = (tzName: string): string => {
|
const resolveTimezone = (tzName: string): string => {
|
||||||
if (TIMEZONES.zones[tzName]) {
|
if (TIMEZONES.zones[tzName]) {
|
||||||
@@ -209,6 +210,37 @@ function EventPopover({
|
|||||||
event?.x_openpass_videoconference || null
|
event?.x_openpass_videoconference || null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Use ref to track if we've already initialized to avoid infinite loop
|
||||||
|
const isInitializedRef = useRef(false);
|
||||||
|
const userPersonnalCalendarsRef = useRef(userPersonnalCalendars);
|
||||||
|
|
||||||
|
// Update ref when userPersonnalCalendars changes
|
||||||
|
useEffect(() => {
|
||||||
|
userPersonnalCalendarsRef.current = userPersonnalCalendars;
|
||||||
|
}, [userPersonnalCalendars]);
|
||||||
|
|
||||||
|
const resetAllStateToDefault = useCallback(() => {
|
||||||
|
setShowMore(false);
|
||||||
|
setShowDescription(false);
|
||||||
|
setShowRepeat(false);
|
||||||
|
setTitle("");
|
||||||
|
setDescription("");
|
||||||
|
setAttendees([]);
|
||||||
|
setLocation("");
|
||||||
|
setStart("");
|
||||||
|
setEnd("");
|
||||||
|
setCalendarid(0);
|
||||||
|
setAllDay(false);
|
||||||
|
setRepetition({} as RepetitionObject);
|
||||||
|
setAlarm("");
|
||||||
|
setEventClass("PUBLIC");
|
||||||
|
setBusy("OPAQUE");
|
||||||
|
setImportant(false);
|
||||||
|
setTimezone(timezoneList.browserTz);
|
||||||
|
setHasVideoConference(false);
|
||||||
|
setMeetingLink(null);
|
||||||
|
}, [timezoneList.browserTz]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedRange) {
|
if (selectedRange) {
|
||||||
setStart(selectedRange ? formatLocalDateTime(selectedRange.start) : "");
|
setStart(selectedRange ? formatLocalDateTime(selectedRange.start) : "");
|
||||||
@@ -216,33 +248,78 @@ function EventPopover({
|
|||||||
}
|
}
|
||||||
}, [selectedRange]);
|
}, [selectedRange]);
|
||||||
|
|
||||||
|
// Initialize state when event prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(event?.title ?? "");
|
if (event) {
|
||||||
setAttendees(
|
// Editing existing event - populate fields with event data
|
||||||
event?.attendee
|
setTitle(event.title ?? "");
|
||||||
? event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
|
setDescription(event.description ?? "");
|
||||||
: []
|
setLocation(event.location ?? "");
|
||||||
);
|
setStart(event.start ? event.start : "");
|
||||||
// Update video conference state when editing different events
|
setEnd(event.end ? event.end : "");
|
||||||
setHasVideoConference(event?.x_openpass_videoconference ? true : false);
|
setCalendarid(
|
||||||
setMeetingLink(event?.x_openpass_videoconference || null);
|
event.calId
|
||||||
// Update description to include video conference footer if exists
|
? userPersonnalCalendarsRef.current.findIndex((e) => e.id === event.calId)
|
||||||
if (event?.x_openpass_videoconference && event?.description) {
|
: 0
|
||||||
const hasVideoFooter = event.description.includes("Visio:");
|
);
|
||||||
if (!hasVideoFooter) {
|
setAllDay(event.allday ?? false);
|
||||||
setDescription(
|
setRepetition(event.repetition ?? ({} as RepetitionObject));
|
||||||
addVideoConferenceToDescription(
|
setShowRepeat(event.repetition?.freq ? true : false);
|
||||||
event.description,
|
setAttendees(
|
||||||
event.x_openpass_videoconference
|
event.attendee
|
||||||
)
|
? event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
|
||||||
);
|
: []
|
||||||
} else {
|
);
|
||||||
setDescription(event.description);
|
setAlarm(event.alarm?.trigger ?? "");
|
||||||
|
setEventClass(event.class ?? "PUBLIC");
|
||||||
|
setBusy(event.transp ?? "OPAQUE");
|
||||||
|
setTimezone(event.timezone ? resolveTimezone(event.timezone) : timezoneList.browserTz);
|
||||||
|
setHasVideoConference(event.x_openpass_videoconference ? true : false);
|
||||||
|
setMeetingLink(event.x_openpass_videoconference || null);
|
||||||
|
|
||||||
|
// Update description to include video conference footer if exists
|
||||||
|
if (event.x_openpass_videoconference && event.description) {
|
||||||
|
const hasVideoFooter = event.description.includes("Visio:");
|
||||||
|
if (!hasVideoFooter) {
|
||||||
|
setDescription(
|
||||||
|
addVideoConferenceToDescription(
|
||||||
|
event.description,
|
||||||
|
event.x_openpass_videoconference
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setDescription(event.description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setDescription(event?.description ?? "");
|
|
||||||
}
|
}
|
||||||
}, [event, organizer?.cal_address]);
|
}, [event, organizer?.cal_address, timezoneList.browserTz]);
|
||||||
|
|
||||||
|
// Reset state when creating new event (event is undefined)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!event && isInitializedRef.current) {
|
||||||
|
// Creating new event - reset all fields to default
|
||||||
|
setShowMore(false);
|
||||||
|
setShowDescription(false);
|
||||||
|
setShowRepeat(false);
|
||||||
|
setTitle("");
|
||||||
|
setDescription("");
|
||||||
|
setAttendees([]);
|
||||||
|
setLocation("");
|
||||||
|
setStart("");
|
||||||
|
setEnd("");
|
||||||
|
setCalendarid(0);
|
||||||
|
setAllDay(false);
|
||||||
|
setRepetition({} as RepetitionObject);
|
||||||
|
setAlarm("");
|
||||||
|
setEventClass("PUBLIC");
|
||||||
|
setBusy("OPAQUE");
|
||||||
|
setImportant(false);
|
||||||
|
setTimezone(timezoneList.browserTz);
|
||||||
|
setHasVideoConference(false);
|
||||||
|
setMeetingLink(null);
|
||||||
|
}
|
||||||
|
isInitializedRef.current = true;
|
||||||
|
}, [event, timezoneList.browserTz]);
|
||||||
|
|
||||||
const handleAddVideoConference = () => {
|
const handleAddVideoConference = () => {
|
||||||
const newMeetingLink = generateMeetingLink();
|
const newMeetingLink = generateMeetingLink();
|
||||||
@@ -280,19 +357,7 @@ function EventPopover({
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
// Reset state
|
resetAllStateToDefault();
|
||||||
setShowMore(false);
|
|
||||||
setShowDescription(false);
|
|
||||||
setShowRepeat(false);
|
|
||||||
setTitle("");
|
|
||||||
setDescription("");
|
|
||||||
setAttendees([]);
|
|
||||||
setLocation("");
|
|
||||||
setCalendarid(0);
|
|
||||||
setImportant(false);
|
|
||||||
setTimezone(timezoneList.browserTz);
|
|
||||||
setHasVideoConference(false);
|
|
||||||
setMeetingLink(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -337,19 +402,8 @@ function EventPopover({
|
|||||||
// Close popup immediately
|
// Close popup immediately
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
|
|
||||||
// Reset state
|
// Reset all state to default values
|
||||||
setShowMore(false);
|
resetAllStateToDefault();
|
||||||
setShowDescription(false);
|
|
||||||
setShowRepeat(false);
|
|
||||||
setTitle("");
|
|
||||||
setDescription("");
|
|
||||||
setAttendees([]);
|
|
||||||
setLocation("");
|
|
||||||
setCalendarid(0);
|
|
||||||
setImportant(false);
|
|
||||||
setTimezone(timezoneList.browserTz);
|
|
||||||
setHasVideoConference(false);
|
|
||||||
setMeetingLink(null);
|
|
||||||
|
|
||||||
// Save to API in background
|
// Save to API in background
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|||||||
@@ -52,3 +52,10 @@ export const computeStartOfTheWeek = (date: Date): Date => {
|
|||||||
startOfWeek.setHours(0, 0, 0, 0);
|
startOfWeek.setHours(0, 0, 0, 0);
|
||||||
return startOfWeek;
|
return startOfWeek;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const computeWeekRange = (date: Date): { start: Date; end: Date } => {
|
||||||
|
const weekStart = computeStartOfTheWeek(date);
|
||||||
|
const weekEnd = new Date(weekStart);
|
||||||
|
weekEnd.setDate(weekStart.getDate() + 7);
|
||||||
|
return { start: weekStart, end: weekEnd };
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user