From 6669eeb45651a82f9da3cbf81c30818dda69ccec Mon Sep 17 00:00:00 2001 From: Camille Moussu Date: Wed, 27 Aug 2025 14:55:14 +0200 Subject: [PATCH] [#7] fixed allday implementation to prevent errors with openpaas --- .../features/Events/EventDisplay.test.tsx | 1 + __test__/features/Events/EventModal.test.tsx | 5 ++ __test__/features/Events/eventUtils.test.ts | 37 +++++++-- src/components/Calendar/Calendar.tsx | 2 +- src/features/Events/EventApi.ts | 2 +- src/features/Events/EventDisplay.tsx | 30 +++++-- src/features/Events/EventDisplayPreview.tsx | 75 +++++++++++++++--- src/features/Events/EventModal.tsx | 79 +++++++++++-------- src/features/Events/EventRepeat.tsx | 28 ++++--- src/features/Events/EventsTypes.ts | 2 +- src/features/Events/eventUtils.ts | 19 +++++ src/utils/apiUtils.ts | 4 +- 12 files changed, 210 insertions(+), 74 deletions(-) diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index 2a368a8..8eb9e90 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -109,6 +109,7 @@ describe("Event Display", () => { const dayOfMonth = day.getDate().toString(); expect(screen.getByText("Test Event")).toBeInTheDocument(); + preview.debug(); expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument(); expect(screen.getByText(new RegExp(month, "i"))).toBeInTheDocument(); expect( diff --git a/__test__/features/Events/EventModal.test.tsx b/__test__/features/Events/EventModal.test.tsx index 57a19b6..3ecfba6 100644 --- a/__test__/features/Events/EventModal.test.tsx +++ b/__test__/features/Events/EventModal.test.tsx @@ -116,7 +116,12 @@ describe("EventPopover", () => { expect(screen.getByLabelText("End")).toBeInTheDocument(); expect(screen.getByLabelText("Description")).toBeInTheDocument(); expect(screen.getByLabelText("Location")).toBeInTheDocument(); + expect(screen.getByText("Show More")).toBeInTheDocument(); + fireEvent.click(screen.getByText("Show More")); expect(screen.getByLabelText("Repetition")).toBeInTheDocument(); + expect(screen.getByLabelText("Alarm")).toBeInTheDocument(); + expect(screen.getByLabelText("Visibility")).toBeInTheDocument(); + expect(screen.getByLabelText("is Busy")).toBeInTheDocument(); expect(screen.getByLabelText("Time Zone")).toBeInTheDocument(); // Calendar options const select = screen.getByLabelText("Calendar"); diff --git a/__test__/features/Events/eventUtils.test.ts b/__test__/features/Events/eventUtils.test.ts index 0dd1b55..2431996 100644 --- a/__test__/features/Events/eventUtils.test.ts +++ b/__test__/features/Events/eventUtils.test.ts @@ -31,7 +31,12 @@ describe("parseCalendarEvent", () => { ["DTSTAMP", {}, "date-time", "2025-07-18T08:00:00Z"], ] as unknown as [string, Record, string, any]; - const result = parseCalendarEvent(rawData, baseColor, calendarId); + const result = parseCalendarEvent( + rawData, + baseColor, + calendarId, + "/calendars/test.ics" + ); expect(result.uid).toBe("event-1"); expect(result.title).toBe("Team Meeting"); @@ -71,7 +76,12 @@ describe("parseCalendarEvent", () => { ["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"], ]; - const result = parseCalendarEvent(rawData, baseColor, calendarId); + const result = parseCalendarEvent( + rawData, + baseColor, + calendarId, + "/calendars/test.ics" + ); expect(result.uid).toBe("event-2/2025-07-18T09:00:00Z"); }); @@ -81,7 +91,12 @@ describe("parseCalendarEvent", () => { ["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"], ]; - const result = parseCalendarEvent(rawDataMissingUid, baseColor, calendarId); + const result = parseCalendarEvent( + rawDataMissingUid, + baseColor, + calendarId, + "/calendars/test.ics" + ); expect(result.error).toMatch(/missing crucial event param/); const rawDataMissingStart: any = [["UID", {}, "text", "event-3"]]; @@ -89,7 +104,8 @@ describe("parseCalendarEvent", () => { const result2 = parseCalendarEvent( rawDataMissingStart, baseColor, - calendarId + calendarId, + "/calendars/test.ics" ); expect(result2.error).toMatch(/missing crucial event param/); }); @@ -102,7 +118,12 @@ describe("parseCalendarEvent", () => { ["ORGANIZER", {}, "cal-address", "jane@example.com"], ] as unknown as [string, Record, string, any]; - const result = parseCalendarEvent(rawData, baseColor, calendarId); + const result = parseCalendarEvent( + rawData, + baseColor, + calendarId, + "/calendars/test.ics" + ); expect(result.attendee).toEqual([ { @@ -135,6 +156,8 @@ describe("calendarEventToJCal", () => { it("should convert a CalendarEvent to JCal format", () => { const mockEvent = { uid: "event-123", + URL: "/calendars/test.ics", + calId: "test/test", title: "Team Meeting", start: new Date("2025-07-23T10:00:00"), end: new Date("2025-07-23T11:00:00"), @@ -246,6 +269,8 @@ describe("calendarEventToJCal", () => { it("should convert a CalendarEvent to JCal format, with all day activated", () => { const mockEvent = { uid: "event-123", + URL: "/calendars/test.ics", + calId: "test/test", title: "Team Meeting", start: new Date("2025-07-23"), end: new Date("2025-07-23"), @@ -285,7 +310,7 @@ describe("calendarEventToJCal", () => { ["summary", {}, "text", "Team Meeting"], ["transp", {}, "text", "OPAQUE"], ["dtstart", { tzid: "Europe/Paris" }, "date", "2025-07-23"], - ["dtend", { tzid: "Europe/Paris" }, "date", "2025-07-23"], + ["dtend", { tzid: "Europe/Paris" }, "date", "2025-07-24"], ["class", {}, "text", "PUBLIC"], ["location", {}, "text", "Room 101"], ["description", {}, "text", "Discuss project roadmap."], diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index 3c17868..e1fec6e 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -372,7 +372,7 @@ export default function CalendarApp() { start: computedNewStart, end: computedNewEnd, } as CalendarEvent; - + console.log(event , newEvent); dispatch( putEventAsync({ cal: calendars[newEvent.calId], newEvent }) ); diff --git a/src/features/Events/EventApi.ts b/src/features/Events/EventApi.ts index 04616de..a288b77 100644 --- a/src/features/Events/EventApi.ts +++ b/src/features/Events/EventApi.ts @@ -17,7 +17,7 @@ export async function putEvent(event: CalendarEvent) { } export async function deleteEvent(eventURL: string) { - const response = await api(eventURL, { + const response = await api(`dav${eventURL}`, { method: "DELETE", }).json(); return response; diff --git a/src/features/Events/EventDisplay.tsx b/src/features/Events/EventDisplay.tsx index 036ecdd..12940bb 100644 --- a/src/features/Events/EventDisplay.tsx +++ b/src/features/Events/EventDisplay.tsx @@ -321,7 +321,20 @@ export default function EventDisplayModal({ setAllDay(!allday)} + onChange={() => { + const endDate = new Date(end); + const startDate = new Date(start); + setAllDay(!allday); + console.log( + endDate.getDate() === startDate.getDate(), + endDate.getDate(), + startDate.getDate() + ); + if (endDate.getDate() === startDate.getDate()) { + endDate.setDate(startDate.getDate() + 1); + setEnd(formatLocalDateTime(endDate)); + } + }} /> } label="All day" @@ -407,8 +420,9 @@ export default function EventDisplayModal({ {showMore && ( <> @@ -436,10 +450,10 @@ export default function EventDisplayModal({ - Class + Visibility - setRepetition(e.target.value) - } - > - No Repetition - Repeat daily - Repeat weekly - Repeat monthly - Repeat yearly - - - + Time Zone setEventClass(e.target.value) @@ -339,10 +345,21 @@ function EventPopover({ Private - + + + is Busy + + )} diff --git a/src/features/Events/EventRepeat.tsx b/src/features/Events/EventRepeat.tsx index 5c2db9f..516e219 100644 --- a/src/features/Events/EventRepeat.tsx +++ b/src/features/Events/EventRepeat.tsx @@ -7,23 +7,29 @@ import { } from "@mui/material"; export default function RepeatEvent({ - eventClass, - setEventClass, + repetition, + setRepetition, + isOwn = true, }: { - eventClass: string; - setEventClass: React.Dispatch>; + repetition: string; + setRepetition: Function; + isOwn?: boolean; }) { return ( - is Busy + Repetition ); diff --git a/src/features/Events/EventsTypes.ts b/src/features/Events/EventsTypes.ts index 22c68b9..2d005be 100644 --- a/src/features/Events/EventsTypes.ts +++ b/src/features/Events/EventsTypes.ts @@ -8,7 +8,7 @@ export interface CalendarEvent { start: Date; // ISO date end?: Date; class?: string; - x_openpass_videoconference?: unknown; + x_openpass_videoconference?: string; title?: string; description?: string; location?: string; diff --git a/src/features/Events/eventUtils.ts b/src/features/Events/eventUtils.ts index 0a55eeb..17ffa30 100644 --- a/src/features/Events/eventUtils.ts +++ b/src/features/Events/eventUtils.ts @@ -13,6 +13,7 @@ export function parseCalendarEvent( ): CalendarEvent { const event: Partial = { color, attendee: [] }; let recurrenceId; + const dateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; for (const [key, params, type, value] of data) { switch (key.toLowerCase()) { @@ -24,9 +25,19 @@ export function parseCalendarEvent( break; case "dtstart": event.start = value; + if (dateRegex.test(value)) { + event.allday = true; + } else { + event.allday = false; + } break; case "dtend": event.end = value; + if (dateRegex.test(value)) { + event.allday = true; + } else { + event.allday = false; + } break; case "class": event.class = value; @@ -115,6 +126,14 @@ export function calendarEventToJCal(event: CalendarEvent): any[] { ]; if (event.end) { + console.log( + event.end, + event.start, + event.end.getTime() === event.start.getTime() + ); + if (event.allday && event.end.getTime() === event.start.getTime()) { + event.end.setDate(event.start.getDate() + 1); + } vevent[1].push([ "dtend", { tzid }, diff --git a/src/utils/apiUtils.ts b/src/utils/apiUtils.ts index 908d8c6..2050e24 100644 --- a/src/utils/apiUtils.ts +++ b/src/utils/apiUtils.ts @@ -44,11 +44,11 @@ export function getLocation() { return window.location.href; } -export function isValidUrl(string: string) { +export function isValidUrl(string?: string) { let url; try { - url = new URL(string); + url = new URL(string ?? ""); } catch (_) { return false; }