From 29f984b347e07b847ae89c7777cee07b3f7e6ba2 Mon Sep 17 00:00:00 2001 From: Camille Moussu Date: Wed, 15 Oct 2025 15:20:15 +0200 Subject: [PATCH] [#168] added refresh to temp calendars when updating --- .../features/Events/EventDisplay.test.tsx | 1 + .../features/Events/EventRepetition.test.tsx | 1 + src/components/Calendar/CalendarLayout.tsx | 37 +++++------- .../Calendar/handlers/eventHandlers.ts | 48 +++++++++++++-- src/components/Event/utils/eventUtils.tsx | 8 ++- src/features/Calendars/CalendarSlice.ts | 6 ++ src/features/Events/EventDisplayPreview.tsx | 58 ++++++++++++------- src/features/Events/EventModal.tsx | 14 ++++- src/features/Events/EventUpdateModal.tsx | 39 ++++++++----- src/utils/dateUtils.ts | 7 +-- 10 files changed, 151 insertions(+), 68 deletions(-) diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index 60e94f1..b8ec5a4 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -79,6 +79,7 @@ describe("Event Preview Display", () => { organizer: { cn: "test", cal_address: "test@test.com" }, }, }, + ownerEmails: ["test@test.com"], }, "otherCal/cal": { id: "otherCal/cal", diff --git a/__test__/features/Events/EventRepetition.test.tsx b/__test__/features/Events/EventRepetition.test.tsx index 6089e89..5131ed6 100644 --- a/__test__/features/Events/EventRepetition.test.tsx +++ b/__test__/features/Events/EventRepetition.test.tsx @@ -90,6 +90,7 @@ describe("Recurrence Event Behavior Tests", () => { ], }, }, + ownerEmails: ["test@test.com"], }, }, pending: false, diff --git a/src/components/Calendar/CalendarLayout.tsx b/src/components/Calendar/CalendarLayout.tsx index 4d44d42..82988a3 100644 --- a/src/components/Calendar/CalendarLayout.tsx +++ b/src/components/Calendar/CalendarLayout.tsx @@ -1,44 +1,39 @@ -import React, { useRef, useState } from "react"; +import { useRef, useState } from "react"; import { Menubar, MenubarProps } from "../Menubar/Menubar"; import CalendarApp from "./Calendar"; import { useAppDispatch } from "../../app/hooks"; -import { - getCalendarDetailAsync, - getCalendarsListAsync, -} from "../../features/Calendars/CalendarSlice"; -import { - formatDateToYYYYMMDDTHHMMSS, - getCalendarRange, -} from "../../utils/dateUtils"; +import { getCalendarRange } from "../../utils/dateUtils"; import { useAppSelector } from "../../app/hooks"; +import { refreshCalendars } from "../Event/utils/eventUtils"; export default function CalendarLayout() { const calendarRef = useRef(null); const dispatch = useAppDispatch(); const selectedCalendars = useAppSelector((state) => state.calendars.list); + const tempcalendars = useAppSelector((state) => state.calendars.templist); const [currentDate, setCurrentDate] = useState(new Date()); const [currentView, setCurrentView] = useState("timeGridWeek"); const handleRefresh = async () => { - await dispatch(getCalendarsListAsync()); - // Get current calendar range if (calendarRef.current) { const view = calendarRef.current.view; const calendarRange = getCalendarRange(view.activeStart); // Refresh events for selected calendars - Object.keys(selectedCalendars).forEach((id) => { - dispatch( - getCalendarDetailAsync({ - calId: id, - match: { - start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), - end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), - }, - }) + await refreshCalendars( + dispatch, + Object.values(selectedCalendars), + calendarRange + ); + if (tempcalendars) { + await refreshCalendars( + dispatch, + Object.values(tempcalendars), + calendarRange, + "temp" ); - }); + } } }; diff --git a/src/components/Calendar/handlers/eventHandlers.ts b/src/components/Calendar/handlers/eventHandlers.ts index 3a2ca79..b451a36 100644 --- a/src/components/Calendar/handlers/eventHandlers.ts +++ b/src/components/Calendar/handlers/eventHandlers.ts @@ -106,7 +106,7 @@ export const createEventHandlers = (props: EventHandlersProps) => { return true; }; - const handleEventDrop = (arg: any) => { + const handleEventDrop = async (arg: any) => { if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) { return; } @@ -136,7 +136,7 @@ export const createEventHandlers = (props: EventHandlersProps) => { setAfterChoiceFunc( () => async (typeOfAction: "solo" | "all" | undefined) => { if (typeOfAction === "solo") { - dispatch( + await dispatch( updateEventInstanceAsync({ cal: calendar, event: newEvent }) ); dispatch( @@ -159,16 +159,34 @@ export const createEventHandlers = (props: EventHandlersProps) => { Object.values(calendars), calendarRange ); + if (tempcalendars) { + await refreshCalendars( + dispatch, + Object.values(tempcalendars), + calendarRange, + "temp" + ); + } } } ); } else { 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) { return; } @@ -201,7 +219,7 @@ export const createEventHandlers = (props: EventHandlersProps) => { setAfterChoiceFunc( () => async (typeOfAction: "solo" | "all" | undefined) => { if (typeOfAction === "solo") { - dispatch( + await dispatch( updateEventInstanceAsync({ cal: calendar, event: newEvent }) ); dispatch( @@ -225,11 +243,29 @@ export const createEventHandlers = (props: EventHandlersProps) => { Object.values(calendars), calendarRange ); + if (tempcalendars) { + await refreshCalendars( + dispatch, + Object.values(tempcalendars), + calendarRange, + "temp" + ); + } } } ); } 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" + ); } }; diff --git a/src/components/Event/utils/eventUtils.tsx b/src/components/Event/utils/eventUtils.tsx index 38f50c5..454ef75 100644 --- a/src/components/Event/utils/eventUtils.tsx +++ b/src/components/Event/utils/eventUtils.tsx @@ -6,6 +6,7 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { ThunkDispatch } from "@reduxjs/toolkit"; import { + emptyTempCal, getCalendarDetailAsync, getCalendarsListAsync, } from "../../../features/Calendars/CalendarSlice"; @@ -114,9 +115,11 @@ export function stringAvatar(name: string) { export async function refreshCalendars( dispatch: ThunkDispatch, 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( async (cal) => @@ -127,6 +130,7 @@ export async function refreshCalendars( start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), }, + calType, }) ) ); diff --git a/src/features/Calendars/CalendarSlice.ts b/src/features/Calendars/CalendarSlice.ts index 738bc13..bb66df4 100644 --- a/src/features/Calendars/CalendarSlice.ts +++ b/src/features/Calendars/CalendarSlice.ts @@ -422,6 +422,11 @@ const CalendarSlice = createSlice({ removeTempCal: (state, action: PayloadAction) => { delete state.templist[action.payload]; }, + emptyTempCal: (state) => { + Object.keys(state.templist).forEach( + (calId) => (state.templist[calId].events = {}) + ); + }, updateEventLocal: ( state, action: PayloadAction<{ calId: string; event: CalendarEvent }> @@ -702,6 +707,7 @@ export const { createCalendar, updateEventLocal, removeTempCal, + emptyTempCal, setTimeZone, clearFetchCache, } = CalendarSlice.actions; diff --git a/src/features/Events/EventDisplayPreview.tsx b/src/features/Events/EventDisplayPreview.tsx index 6066574..0b0f474 100644 --- a/src/features/Events/EventDisplayPreview.tsx +++ b/src/features/Events/EventDisplayPreview.tsx @@ -36,8 +36,12 @@ import { handleRSVP, } from "../../components/Event/eventHandlers/eventHandlers"; 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 { getCalendarRange } from "../../utils/dateUtils"; export default function EventPreviewModal({ eventId, calId, @@ -110,6 +114,18 @@ export default function EventPreviewModal({ (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 ( <> )} - {user.userData.email === event.organizer?.cal_address && ( - { - if (isRecurring) { - setAfterChoiceFunc(() => () => { + {user.userData.email === event.organizer?.cal_address && + calendar.ownerEmails?.includes(user.userData.email) && ( + { + if (isRecurring) { + setAfterChoiceFunc(() => () => { + setHidePreview(true); + setOpenUpdateModal(true); + }); + setOpenEditModePopup("edit"); + } else { setHidePreview(true); setOpenUpdateModal(true); - }); - setOpenEditModePopup("edit"); - } else { - setHidePreview(true); - setOpenUpdateModal(true); - } - }} - > - - - )} + } + }} + > + + + )} setToggleActionMenu(e.currentTarget)} @@ -190,7 +207,7 @@ export default function EventPreviewModal({ {user.userData.email === event.organizer?.cal_address && ( { + onClick={async () => { if (isRecurring) { setAfterChoiceFunc( () => @@ -209,7 +226,7 @@ export default function EventPreviewModal({ setOpenEditModePopup("edit"); } else { onClose({}, "backdropClick"); - dispatch( + await dispatch( deleteEventAsync({ calId, eventId, @@ -217,6 +234,7 @@ export default function EventPreviewModal({ }) ); } + updateTempList(); }} > Delete event diff --git a/src/features/Events/EventModal.tsx b/src/features/Events/EventModal.tsx index 307be39..ac385f4 100644 --- a/src/features/Events/EventModal.tsx +++ b/src/features/Events/EventModal.tsx @@ -48,6 +48,8 @@ import { getTimezoneOffset, resolveTimezone, } from "../../components/Calendar/TimezoneSelector"; +import { getCalendarRange } from "../../utils/dateUtils"; +import { refreshCalendars } from "../../components/Event/utils/eventUtils"; // Helper component for field with label const FieldWithLabel = React.memo( @@ -123,6 +125,7 @@ function EventPopover({ const organizer = useAppSelector((state) => state.user.organiserData); const userId = useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; + const tempList = useAppSelector((state) => state.calendars.templist); const selectPersonnalCalendars = createSelector( (state) => state.calendars, (calendars) => @@ -398,12 +401,21 @@ function EventPopover({ resetAllStateToDefault(); // Save to API in background - dispatch( + await dispatch( putEventAsync({ cal: userPersonnalCalendars[calendarid], newEvent, }) ); + if (tempList) { + const calendarRange = getCalendarRange(new Date(start)); + refreshCalendars( + dispatch, + Object.values(tempList), + calendarRange, + "temp" + ); + } }; const dialogActions = ( diff --git a/src/features/Events/EventUpdateModal.tsx b/src/features/Events/EventUpdateModal.tsx index ad85c46..20db6fb 100644 --- a/src/features/Events/EventUpdateModal.tsx +++ b/src/features/Events/EventUpdateModal.tsx @@ -1,12 +1,6 @@ import { Box, Button } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import React, { - useEffect, - useState, - useMemo, - useCallback, - useRef, -} from "react"; +import { useEffect, useState, useMemo, useCallback, useRef } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { ResponsiveDialog } from "../../components/Dialog"; import { @@ -56,7 +50,7 @@ function EventUpdateModal({ typeOfAction?: "solo" | "all"; }) { const dispatch = useAppDispatch(); - + const tempList = useAppSelector((state) => state.calendars.templist); // Get event from Redux store (cached data) as fallback const cachedEvent = useAppSelector( (state) => state.calendars.list[calId]?.events[eventId] @@ -482,7 +476,15 @@ function EventUpdateModal({ console.error("Failed to convert recurring to non-recurring:", err); // 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; } @@ -502,7 +504,7 @@ function EventUpdateModal({ }) ); - dispatch( + await dispatch( updateEventInstanceAsync({ cal: targetCalendar, event: { ...newEvent, recurrenceId }, @@ -590,7 +592,7 @@ function EventUpdateModal({ }); // Update server in background with removeOverrides=false - dispatch( + await dispatch( updateSeriesAsync({ cal: targetCalendar, event: { ...newEvent, recurrenceId }, @@ -622,7 +624,7 @@ function EventUpdateModal({ const oldEventUID = event.uid; // API call: putEventAsync will create recurring event and fetch all instances - dispatch(putEventAsync({ cal: targetCalendar, newEvent })) + await dispatch(putEventAsync({ cal: targetCalendar, newEvent })) .unwrap() .then(() => { // Remove old single event AFTER new recurring instances are added to store @@ -639,13 +641,13 @@ function EventUpdateModal({ }); } else { // Normal non-recurring event update - dispatch(putEventAsync({ cal: targetCalendar, newEvent })); + await dispatch(putEventAsync({ cal: targetCalendar, newEvent })); } } // Handle calendar change if (newCalId !== calId) { - dispatch( + await dispatch( moveEventAsync({ cal: targetCalendar, newEvent, @@ -654,6 +656,15 @@ function EventUpdateModal({ ); dispatch(removeEvent({ calendarUid: calId, eventUid: event.uid })); } + if (tempList) { + const calendarRange = getCalendarRange(new Date(start)); + refreshCalendars( + dispatch, + Object.values(tempList), + calendarRange, + "temp" + ); + } }; const dialogActions = ( diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts index 2c6846d..e1dab4b 100644 --- a/src/utils/dateUtils.ts +++ b/src/utils/dateUtils.ts @@ -1,8 +1,7 @@ +import moment from "moment"; + export function formatDateToYYYYMMDDTHHMMSS(date: Date) { - 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}T000000`; + return moment(date).format("YYYYMMDDTHHmmss"); } export function getCalendarRange(date = new Date()) {