[#7] fixed allday implementation to prevent errors with openpaas
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -31,7 +31,12 @@ describe("parseCalendarEvent", () => {
|
||||
["DTSTAMP", {}, "date-time", "2025-07-18T08:00:00Z"],
|
||||
] as unknown as [string, Record<string, string>, 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, string>, 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."],
|
||||
|
||||
@@ -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 })
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -321,7 +321,20 @@ export default function EventDisplayModal({
|
||||
<Checkbox
|
||||
disabled={!isOwn}
|
||||
checked={allday}
|
||||
onChange={() => 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 && (
|
||||
<>
|
||||
<RepeatEvent
|
||||
eventClass={eventClass}
|
||||
setEventClass={setEventClass}
|
||||
repetition={repetition}
|
||||
setRepetition={setRepetition}
|
||||
isOwn={isOwn}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
@@ -436,10 +450,10 @@ export default function EventDisplayModal({
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="class">Class</InputLabel>
|
||||
<InputLabel id="Visibility">Visibility</InputLabel>
|
||||
<Select
|
||||
labelId="class"
|
||||
label="class"
|
||||
labelId="Visibility"
|
||||
label="Visibility"
|
||||
value={eventClass}
|
||||
disabled={!isOwn}
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
@@ -459,7 +473,7 @@ export default function EventDisplayModal({
|
||||
disabled={!isOwn}
|
||||
label="is busy"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
setEventClass(e.target.value)
|
||||
console.log(e.target.value)
|
||||
}
|
||||
>
|
||||
<MenuItem value={"free"}>Free</MenuItem>
|
||||
@@ -525,7 +539,7 @@ export function InfoRow({
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}>
|
||||
{icon}
|
||||
<Typography variant="body2" color={error ? "error" : "textPrimary"}>
|
||||
{isValidUrl(data) && <Link href={data}>{text}</Link>}
|
||||
{isValidUrl(data) ? <Link href={data}>{text}</Link> : text}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -162,12 +162,10 @@ export default function EventPreviewModal({
|
||||
|
||||
{/* Time info*/}
|
||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||
{formatDate(event.start)}
|
||||
{formatDate(event.start, event.allday)}
|
||||
{event.end &&
|
||||
` – ${new Date(event.end).toLocaleTimeString(undefined, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}`}
|
||||
formatEnd(event.start, event.end, event.allday) &&
|
||||
` – ${formatEnd(event.start, event.end, event.allday)}`}
|
||||
</Typography>
|
||||
|
||||
{/* Location */}
|
||||
@@ -296,12 +294,63 @@ export default function EventPreviewModal({
|
||||
);
|
||||
}
|
||||
|
||||
function formatDate(date: Date) {
|
||||
return new Date(date).toLocaleString(undefined, {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
function formatDate(date: Date, allday?: boolean) {
|
||||
if (allday) {
|
||||
return new Date(date).toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
} else {
|
||||
return new Date(date).toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatEnd(start: Date, end: Date, allday?: boolean) {
|
||||
const startDate = new Date(start);
|
||||
const endDate = new Date(end);
|
||||
|
||||
const sameDay =
|
||||
startDate.getFullYear() === endDate.getFullYear() &&
|
||||
startDate.getMonth() === endDate.getMonth() &&
|
||||
startDate.getDate() === endDate.getDate();
|
||||
|
||||
if (allday) {
|
||||
console.log(
|
||||
endDate.toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
);
|
||||
return sameDay
|
||||
? null
|
||||
: endDate.toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
} else {
|
||||
if (sameDay) {
|
||||
return endDate.toLocaleTimeString(undefined, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
return endDate.toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,17 +230,36 @@ function EventPopover({
|
||||
type="checkbox"
|
||||
checked={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);
|
||||
console.log("formatedd", formatLocalDateTime(endDate));
|
||||
setEnd(formatLocalDateTime(endDate));
|
||||
}
|
||||
|
||||
const newRange = {
|
||||
startStr: allday ? start.split("T")[0] : start,
|
||||
endStr: allday ? end.split("T")[0] : end,
|
||||
start: new Date(allday ? start.split("T")[0] : start),
|
||||
end: new Date(allday ? end.split("T")[0] : end),
|
||||
allday,
|
||||
...selectedRange,
|
||||
startStr: allday ? start.split("T")[0] : start,
|
||||
endStr: allday
|
||||
? endDate.toISOString().split("T")[0]
|
||||
: endDate.toISOString(),
|
||||
start: new Date(allday ? start.split("T")[0] : start),
|
||||
end: new Date(
|
||||
allday
|
||||
? endDate.toISOString().split("T")[0]
|
||||
: endDate.toISOString()
|
||||
),
|
||||
allDay: allday,
|
||||
};
|
||||
console.log(newRange, selectedRange);
|
||||
setSelectedRange(newRange);
|
||||
calendarRef.current?.select(newRange);
|
||||
}}
|
||||
/>
|
||||
All day
|
||||
@@ -263,27 +282,14 @@ function EventPopover({
|
||||
size="small"
|
||||
margin="dense"
|
||||
/>
|
||||
<AttendeeSelector setAttendees={setAttendees} />
|
||||
{/* Extended options */}
|
||||
{showMore && (
|
||||
<>
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="repeat">Repetition</InputLabel>
|
||||
<Select
|
||||
labelId="repeat"
|
||||
value={repetition}
|
||||
label="Time Zone"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
setRepetition(e.target.value)
|
||||
}
|
||||
>
|
||||
<MenuItem value={""}>No Repetition</MenuItem>
|
||||
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
||||
<MenuItem value={"weekly"}>Repeat weekly</MenuItem>
|
||||
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
||||
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<AttendeeSelector setAttendees={setAttendees} />
|
||||
<RepeatEvent
|
||||
repetition={repetition}
|
||||
setRepetition={setRepetition}
|
||||
/>
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="timezone-select-label">Time Zone</InputLabel>
|
||||
<Select
|
||||
@@ -325,10 +331,10 @@ function EventPopover({
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="class">Class</InputLabel>
|
||||
<InputLabel id="Visibility">Visibility</InputLabel>
|
||||
<Select
|
||||
labelId="class"
|
||||
label="class"
|
||||
labelId="Visibility"
|
||||
label="Visibility"
|
||||
value={eventClass}
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
setEventClass(e.target.value)
|
||||
@@ -339,10 +345,21 @@ function EventPopover({
|
||||
<MenuItem value={"PRIVATE"}>Private</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<RepeatEvent
|
||||
eventClass={eventClass}
|
||||
setEventClass={setEventClass}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="busy">is Busy</InputLabel>
|
||||
<Select
|
||||
labelId="busy"
|
||||
value={eventClass}
|
||||
label="is busy"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
console.log(e.target.value)
|
||||
}
|
||||
>
|
||||
<MenuItem value={"free"}>Free</MenuItem>
|
||||
<MenuItem value={"busy"}>Busy </MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -7,23 +7,29 @@ import {
|
||||
} from "@mui/material";
|
||||
|
||||
export default function RepeatEvent({
|
||||
eventClass,
|
||||
setEventClass,
|
||||
repetition,
|
||||
setRepetition,
|
||||
isOwn = true,
|
||||
}: {
|
||||
eventClass: string;
|
||||
setEventClass: React.Dispatch<React.SetStateAction<string>>;
|
||||
repetition: string;
|
||||
setRepetition: Function;
|
||||
isOwn?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="repeat">is Busy</InputLabel>
|
||||
<InputLabel id="repeat">Repetition</InputLabel>
|
||||
<Select
|
||||
labelId="busy"
|
||||
value={eventClass}
|
||||
label="is busy"
|
||||
onChange={(e: SelectChangeEvent) => setEventClass(e.target.value)}
|
||||
labelId="repeat"
|
||||
value={repetition}
|
||||
disabled={!isOwn}
|
||||
label="Repetition"
|
||||
onChange={(e: SelectChangeEvent) => setRepetition(e.target.value)}
|
||||
>
|
||||
<MenuItem value={"free"}>Free</MenuItem>
|
||||
<MenuItem value={"busy"}>Busy </MenuItem>
|
||||
<MenuItem value={""}>No Repetition</MenuItem>
|
||||
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
||||
<MenuItem value={"weekly"}>Repeat weekly</MenuItem>
|
||||
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
||||
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,6 +13,7 @@ export function parseCalendarEvent(
|
||||
): CalendarEvent {
|
||||
const event: Partial<CalendarEvent> = { 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 },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user