[#168] added refresh to temp calendars when updating

This commit is contained in:
Camille Moussu
2025-10-15 15:20:15 +02:00
committed by Benoit TELLIER
parent 0f82711d7d
commit 29f984b347
10 changed files with 151 additions and 68 deletions
@@ -79,6 +79,7 @@ describe("Event Preview Display", () => {
organizer: { cn: "test", cal_address: "test@test.com" }, organizer: { cn: "test", cal_address: "test@test.com" },
}, },
}, },
ownerEmails: ["test@test.com"],
}, },
"otherCal/cal": { "otherCal/cal": {
id: "otherCal/cal", id: "otherCal/cal",
@@ -90,6 +90,7 @@ describe("Recurrence Event Behavior Tests", () => {
], ],
}, },
}, },
ownerEmails: ["test@test.com"],
}, },
}, },
pending: false, pending: false,
+16 -21
View File
@@ -1,44 +1,39 @@
import React, { useRef, useState } from "react"; import { useRef, useState } from "react";
import { Menubar, MenubarProps } from "../Menubar/Menubar"; import { Menubar, MenubarProps } from "../Menubar/Menubar";
import CalendarApp from "./Calendar"; import CalendarApp from "./Calendar";
import { useAppDispatch } from "../../app/hooks"; import { useAppDispatch } from "../../app/hooks";
import { import { getCalendarRange } from "../../utils/dateUtils";
getCalendarDetailAsync,
getCalendarsListAsync,
} from "../../features/Calendars/CalendarSlice";
import {
formatDateToYYYYMMDDTHHMMSS,
getCalendarRange,
} from "../../utils/dateUtils";
import { useAppSelector } from "../../app/hooks"; import { useAppSelector } from "../../app/hooks";
import { refreshCalendars } from "../Event/utils/eventUtils";
export default function CalendarLayout() { export default function CalendarLayout() {
const calendarRef = useRef<any>(null); const calendarRef = useRef<any>(null);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedCalendars = useAppSelector((state) => state.calendars.list); const selectedCalendars = useAppSelector((state) => state.calendars.list);
const tempcalendars = useAppSelector((state) => state.calendars.templist);
const [currentDate, setCurrentDate] = useState<Date>(new Date()); const [currentDate, setCurrentDate] = useState<Date>(new Date());
const [currentView, setCurrentView] = useState<string>("timeGridWeek"); const [currentView, setCurrentView] = useState<string>("timeGridWeek");
const handleRefresh = async () => { const handleRefresh = async () => {
await dispatch(getCalendarsListAsync());
// Get current calendar range // Get current calendar range
if (calendarRef.current) { if (calendarRef.current) {
const view = calendarRef.current.view; const view = calendarRef.current.view;
const calendarRange = getCalendarRange(view.activeStart); const calendarRange = getCalendarRange(view.activeStart);
// Refresh events for selected calendars // Refresh events for selected calendars
Object.keys(selectedCalendars).forEach((id) => { await refreshCalendars(
dispatch( dispatch,
getCalendarDetailAsync({ Object.values(selectedCalendars),
calId: id, calendarRange
match: { );
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), if (tempcalendars) {
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), await refreshCalendars(
}, dispatch,
}) Object.values(tempcalendars),
calendarRange,
"temp"
); );
}); }
} }
}; };
@@ -106,7 +106,7 @@ export const createEventHandlers = (props: EventHandlersProps) => {
return true; return true;
}; };
const handleEventDrop = (arg: any) => { const handleEventDrop = async (arg: any) => {
if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) { if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) {
return; return;
} }
@@ -136,7 +136,7 @@ export const createEventHandlers = (props: EventHandlersProps) => {
setAfterChoiceFunc( setAfterChoiceFunc(
() => async (typeOfAction: "solo" | "all" | undefined) => { () => async (typeOfAction: "solo" | "all" | undefined) => {
if (typeOfAction === "solo") { if (typeOfAction === "solo") {
dispatch( await dispatch(
updateEventInstanceAsync({ cal: calendar, event: newEvent }) updateEventInstanceAsync({ cal: calendar, event: newEvent })
); );
dispatch( dispatch(
@@ -159,16 +159,34 @@ export const createEventHandlers = (props: EventHandlersProps) => {
Object.values(calendars), Object.values(calendars),
calendarRange calendarRange
); );
if (tempcalendars) {
await refreshCalendars(
dispatch,
Object.values(tempcalendars),
calendarRange,
"temp"
);
}
} }
} }
); );
} else { } else {
dispatch(updateEventLocal({ calId: newEvent.calId, event: newEvent })); dispatch(updateEventLocal({ calId: newEvent.calId, event: newEvent }));
dispatch(putEventAsync({ cal: calendars[newEvent.calId], newEvent })); await dispatch(
putEventAsync({ cal: calendars[newEvent.calId], newEvent })
);
}
if (tempcalendars) {
await refreshCalendars(
dispatch,
Object.values(tempcalendars),
calendarRange,
"temp"
);
} }
}; };
const handleEventResize = (arg: any) => { const handleEventResize = async (arg: any) => {
if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) { if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) {
return; return;
} }
@@ -201,7 +219,7 @@ export const createEventHandlers = (props: EventHandlersProps) => {
setAfterChoiceFunc( setAfterChoiceFunc(
() => async (typeOfAction: "solo" | "all" | undefined) => { () => async (typeOfAction: "solo" | "all" | undefined) => {
if (typeOfAction === "solo") { if (typeOfAction === "solo") {
dispatch( await dispatch(
updateEventInstanceAsync({ cal: calendar, event: newEvent }) updateEventInstanceAsync({ cal: calendar, event: newEvent })
); );
dispatch( dispatch(
@@ -225,11 +243,29 @@ export const createEventHandlers = (props: EventHandlersProps) => {
Object.values(calendars), Object.values(calendars),
calendarRange calendarRange
); );
if (tempcalendars) {
await refreshCalendars(
dispatch,
Object.values(tempcalendars),
calendarRange,
"temp"
);
}
} }
} }
); );
} else { } else {
dispatch(putEventAsync({ cal: calendars[newEvent.calId], newEvent })); await dispatch(
putEventAsync({ cal: calendars[newEvent.calId], newEvent })
);
}
if (tempcalendars) {
await refreshCalendars(
dispatch,
Object.values(tempcalendars),
calendarRange,
"temp"
);
} }
}; };
+6 -2
View File
@@ -6,6 +6,7 @@ import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { ThunkDispatch } from "@reduxjs/toolkit"; import { ThunkDispatch } from "@reduxjs/toolkit";
import { import {
emptyTempCal,
getCalendarDetailAsync, getCalendarDetailAsync,
getCalendarsListAsync, getCalendarsListAsync,
} from "../../../features/Calendars/CalendarSlice"; } from "../../../features/Calendars/CalendarSlice";
@@ -114,9 +115,11 @@ export function stringAvatar(name: string) {
export async function refreshCalendars( export async function refreshCalendars(
dispatch: ThunkDispatch<any, any, any>, dispatch: ThunkDispatch<any, any, any>,
calendars: Calendars[], calendars: Calendars[],
calendarRange: { start: Date; end: Date } calendarRange: { start: Date; end: Date },
calType?: "temp"
) { ) {
await dispatch(getCalendarsListAsync()); !calType && (await dispatch(getCalendarsListAsync()));
calType && dispatch(emptyTempCal());
calendars.map( calendars.map(
async (cal) => async (cal) =>
@@ -127,6 +130,7 @@ export async function refreshCalendars(
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
}, },
calType,
}) })
) )
); );
+6
View File
@@ -422,6 +422,11 @@ const CalendarSlice = createSlice({
removeTempCal: (state, action: PayloadAction<string>) => { removeTempCal: (state, action: PayloadAction<string>) => {
delete state.templist[action.payload]; delete state.templist[action.payload];
}, },
emptyTempCal: (state) => {
Object.keys(state.templist).forEach(
(calId) => (state.templist[calId].events = {})
);
},
updateEventLocal: ( updateEventLocal: (
state, state,
action: PayloadAction<{ calId: string; event: CalendarEvent }> action: PayloadAction<{ calId: string; event: CalendarEvent }>
@@ -702,6 +707,7 @@ export const {
createCalendar, createCalendar,
updateEventLocal, updateEventLocal,
removeTempCal, removeTempCal,
emptyTempCal,
setTimeZone, setTimeZone,
clearFetchCache, clearFetchCache,
} = CalendarSlice.actions; } = CalendarSlice.actions;
+38 -20
View File
@@ -36,8 +36,12 @@ import {
handleRSVP, handleRSVP,
} from "../../components/Event/eventHandlers/eventHandlers"; } from "../../components/Event/eventHandlers/eventHandlers";
import { InfoRow } from "../../components/Event/InfoRow"; import { InfoRow } from "../../components/Event/InfoRow";
import { renderAttendeeBadge } from "../../components/Event/utils/eventUtils"; import {
refreshCalendars,
renderAttendeeBadge,
} from "../../components/Event/utils/eventUtils";
import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector"; import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector";
import { getCalendarRange } from "../../utils/dateUtils";
export default function EventPreviewModal({ export default function EventPreviewModal({
eventId, eventId,
calId, calId,
@@ -110,6 +114,18 @@ export default function EventPreviewModal({
(a) => a.cal_address === event.organizer?.cal_address (a) => a.cal_address === event.organizer?.cal_address
); );
const updateTempList = () => {
if (calendars.templist) {
const calendarRange = getCalendarRange(new Date(event.start));
refreshCalendars(
dispatch,
Object.values(calendars.templist),
calendarRange,
"temp"
);
}
};
return ( return (
<> <>
<ResponsiveDialog <ResponsiveDialog
@@ -143,25 +159,26 @@ export default function EventPreviewModal({
<FileDownloadOutlinedIcon /> <FileDownloadOutlinedIcon />
</IconButton> </IconButton>
)} )}
{user.userData.email === event.organizer?.cal_address && ( {user.userData.email === event.organizer?.cal_address &&
<IconButton calendar.ownerEmails?.includes(user.userData.email) && (
size="small" <IconButton
onClick={() => { size="small"
if (isRecurring) { onClick={() => {
setAfterChoiceFunc(() => () => { if (isRecurring) {
setAfterChoiceFunc(() => () => {
setHidePreview(true);
setOpenUpdateModal(true);
});
setOpenEditModePopup("edit");
} else {
setHidePreview(true); setHidePreview(true);
setOpenUpdateModal(true); setOpenUpdateModal(true);
}); }
setOpenEditModePopup("edit"); }}
} else { >
setHidePreview(true); <EditIcon />
setOpenUpdateModal(true); </IconButton>
} )}
}}
>
<EditIcon />
</IconButton>
)}
<IconButton <IconButton
size="small" size="small"
onClick={(e) => setToggleActionMenu(e.currentTarget)} onClick={(e) => setToggleActionMenu(e.currentTarget)}
@@ -190,7 +207,7 @@ export default function EventPreviewModal({
<EventDuplication event={event} onClose={onClose} /> <EventDuplication event={event} onClose={onClose} />
{user.userData.email === event.organizer?.cal_address && ( {user.userData.email === event.organizer?.cal_address && (
<MenuItem <MenuItem
onClick={() => { onClick={async () => {
if (isRecurring) { if (isRecurring) {
setAfterChoiceFunc( setAfterChoiceFunc(
() => () =>
@@ -209,7 +226,7 @@ export default function EventPreviewModal({
setOpenEditModePopup("edit"); setOpenEditModePopup("edit");
} else { } else {
onClose({}, "backdropClick"); onClose({}, "backdropClick");
dispatch( await dispatch(
deleteEventAsync({ deleteEventAsync({
calId, calId,
eventId, eventId,
@@ -217,6 +234,7 @@ export default function EventPreviewModal({
}) })
); );
} }
updateTempList();
}} }}
> >
Delete event Delete event
+13 -1
View File
@@ -48,6 +48,8 @@ import {
getTimezoneOffset, getTimezoneOffset,
resolveTimezone, resolveTimezone,
} from "../../components/Calendar/TimezoneSelector"; } from "../../components/Calendar/TimezoneSelector";
import { getCalendarRange } from "../../utils/dateUtils";
import { refreshCalendars } from "../../components/Event/utils/eventUtils";
// Helper component for field with label // Helper component for field with label
const FieldWithLabel = React.memo( const FieldWithLabel = React.memo(
@@ -123,6 +125,7 @@ function EventPopover({
const organizer = useAppSelector((state) => state.user.organiserData); const organizer = useAppSelector((state) => state.user.organiserData);
const userId = const userId =
useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
const tempList = useAppSelector((state) => state.calendars.templist);
const selectPersonnalCalendars = createSelector( const selectPersonnalCalendars = createSelector(
(state) => state.calendars, (state) => state.calendars,
(calendars) => (calendars) =>
@@ -398,12 +401,21 @@ function EventPopover({
resetAllStateToDefault(); resetAllStateToDefault();
// Save to API in background // Save to API in background
dispatch( await dispatch(
putEventAsync({ putEventAsync({
cal: userPersonnalCalendars[calendarid], cal: userPersonnalCalendars[calendarid],
newEvent, newEvent,
}) })
); );
if (tempList) {
const calendarRange = getCalendarRange(new Date(start));
refreshCalendars(
dispatch,
Object.values(tempList),
calendarRange,
"temp"
);
}
}; };
const dialogActions = ( const dialogActions = (
+25 -14
View File
@@ -1,12 +1,6 @@
import { Box, Button } from "@mui/material"; import { Box, Button } from "@mui/material";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import React, { import { useEffect, useState, useMemo, useCallback, useRef } from "react";
useEffect,
useState,
useMemo,
useCallback,
useRef,
} from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { ResponsiveDialog } from "../../components/Dialog"; import { ResponsiveDialog } from "../../components/Dialog";
import { import {
@@ -56,7 +50,7 @@ function EventUpdateModal({
typeOfAction?: "solo" | "all"; typeOfAction?: "solo" | "all";
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tempList = useAppSelector((state) => state.calendars.templist);
// Get event from Redux store (cached data) as fallback // Get event from Redux store (cached data) as fallback
const cachedEvent = useAppSelector( const cachedEvent = useAppSelector(
(state) => state.calendars.list[calId]?.events[eventId] (state) => state.calendars.list[calId]?.events[eventId]
@@ -482,7 +476,15 @@ function EventUpdateModal({
console.error("Failed to convert recurring to non-recurring:", err); console.error("Failed to convert recurring to non-recurring:", err);
// Keep modal open on error, user can retry or cancel // Keep modal open on error, user can retry or cancel
} }
if (tempList) {
const calendarRange = getCalendarRange(new Date(start));
refreshCalendars(
dispatch,
Object.values(tempList),
calendarRange,
"temp"
);
}
return; return;
} }
@@ -502,7 +504,7 @@ function EventUpdateModal({
}) })
); );
dispatch( await dispatch(
updateEventInstanceAsync({ updateEventInstanceAsync({
cal: targetCalendar, cal: targetCalendar,
event: { ...newEvent, recurrenceId }, event: { ...newEvent, recurrenceId },
@@ -590,7 +592,7 @@ function EventUpdateModal({
}); });
// Update server in background with removeOverrides=false // Update server in background with removeOverrides=false
dispatch( await dispatch(
updateSeriesAsync({ updateSeriesAsync({
cal: targetCalendar, cal: targetCalendar,
event: { ...newEvent, recurrenceId }, event: { ...newEvent, recurrenceId },
@@ -622,7 +624,7 @@ function EventUpdateModal({
const oldEventUID = event.uid; const oldEventUID = event.uid;
// API call: putEventAsync will create recurring event and fetch all instances // API call: putEventAsync will create recurring event and fetch all instances
dispatch(putEventAsync({ cal: targetCalendar, newEvent })) await dispatch(putEventAsync({ cal: targetCalendar, newEvent }))
.unwrap() .unwrap()
.then(() => { .then(() => {
// Remove old single event AFTER new recurring instances are added to store // Remove old single event AFTER new recurring instances are added to store
@@ -639,13 +641,13 @@ function EventUpdateModal({
}); });
} else { } else {
// Normal non-recurring event update // Normal non-recurring event update
dispatch(putEventAsync({ cal: targetCalendar, newEvent })); await dispatch(putEventAsync({ cal: targetCalendar, newEvent }));
} }
} }
// Handle calendar change // Handle calendar change
if (newCalId !== calId) { if (newCalId !== calId) {
dispatch( await dispatch(
moveEventAsync({ moveEventAsync({
cal: targetCalendar, cal: targetCalendar,
newEvent, newEvent,
@@ -654,6 +656,15 @@ function EventUpdateModal({
); );
dispatch(removeEvent({ calendarUid: calId, eventUid: event.uid })); dispatch(removeEvent({ calendarUid: calId, eventUid: event.uid }));
} }
if (tempList) {
const calendarRange = getCalendarRange(new Date(start));
refreshCalendars(
dispatch,
Object.values(tempList),
calendarRange,
"temp"
);
}
}; };
const dialogActions = ( const dialogActions = (
+3 -4
View File
@@ -1,8 +1,7 @@
import moment from "moment";
export function formatDateToYYYYMMDDTHHMMSS(date: Date) { export function formatDateToYYYYMMDDTHHMMSS(date: Date) {
const year = date.getFullYear(); return moment(date).format("YYYYMMDDTHHmmss");
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}${month}${day}T000000`;
} }
export function getCalendarRange(date = new Date()) { export function getCalendarRange(date = new Date()) {