[#4] added delegated calendars

This commit is contained in:
Camille Moussu
2025-08-21 15:47:02 +02:00
parent cc262d6816
commit aaba50cf00
4 changed files with 254 additions and 29 deletions
@@ -0,0 +1,193 @@
import { CalendarApi } from "@fullcalendar/core";
import { jest } from "@jest/globals";
import { ThunkDispatch } from "@reduxjs/toolkit";
import "@testing-library/jest-dom";
import { screen } from "@testing-library/react";
import * as appHooks from "../../src/app/hooks";
import CalendarApp from "../../src/components/Calendar/Calendar";
import { renderWithProviders } from "../utils/Renderwithproviders";
import CalendarSelection from "../../src/components/Calendar/CalendarSelection";
describe("CalendarSelection", () => {
const today = new Date();
const start = new Date(today);
start.setHours(10, 0, 0, 0);
const end = new Date(today);
end.setHours(11, 0, 0, 0);
it("renders the all the calendars in list", async () => {
const preloadedState = {
user: {
userData: {
sub: "test",
email: "test@test.com",
sid: "mockSid",
openpaasId: "user1",
},
tokens: { accessToken: "token" }, // required to avoid redirect
},
calendars: {
list: {
"user1/cal1": {
name: "Calendar personnal",
id: "user1/cal1",
color: "#FF0000",
ownerEmails: ["alice@example.com"],
events: {
event1: {
id: "event1",
calId: "user1/cal1",
uid: "event1",
title: "Test Event",
start: start.toISOString(),
end: end.toISOString(),
partstat: "ACCEPTED",
organizer: {
cn: "Alice",
cal_address: "alice@example.com",
},
attendee: [
{
cn: "Alice",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
cal_address: "alice@example.com",
},
],
},
},
},
"user2/cal1": {
name: "Calendar delegated",
delegated: true,
id: "user2/cal1",
color: "#FF0000",
ownerEmails: ["alice@example.com"],
events: {
event1: {
id: "event1",
calId: "user2/cal1",
uid: "event1",
title: "Test Event",
start: start.toISOString(),
end: end.toISOString(),
partstat: "ACCEPTED",
organizer: {
cn: "Alice",
cal_address: "alice@example.com",
},
attendee: [
{
cn: "Alice",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
cal_address: "alice@example.com",
},
],
},
},
},
"user3/cal1": {
name: "Calendar shared",
id: "user3/cal1",
color: "#FF0000",
ownerEmails: ["alice@example.com"],
events: {
event1: {
id: "event1",
calId: "user3/cal1",
uid: "event1",
title: "Test Event",
start: start.toISOString(),
end: end.toISOString(),
partstat: "ACCEPTED",
organizer: {
cn: "Alice",
cal_address: "alice@example.com",
},
attendee: [
{
cn: "Alice",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
cal_address: "alice@example.com",
},
],
},
},
},
},
pending: false,
},
};
renderWithProviders(<CalendarApp />, preloadedState);
expect(screen.getByText("personnalCalendars")).toBeInTheDocument();
expect(screen.getByText("delegatedCalendars")).toBeInTheDocument();
expect(screen.getByText("sharedCalendars")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar personnal")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar delegated")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar shared")).toBeInTheDocument();
});
it("renders only personnal calendars in list", async () => {
const preloadedState = {
user: {
userData: {
sub: "test",
email: "test@test.com",
sid: "mockSid",
openpaasId: "user1",
},
tokens: { accessToken: "token" }, // required to avoid redirect
},
calendars: {
list: {
"user1/cal1": {
name: "Calendar personnal",
id: "user1/cal1",
color: "#FF0000",
ownerEmails: ["alice@example.com"],
events: {
event1: {
id: "event1",
calId: "user1/cal1",
uid: "event1",
title: "Test Event",
start: start.toISOString(),
end: end.toISOString(),
partstat: "ACCEPTED",
organizer: {
cn: "Alice",
cal_address: "alice@example.com",
},
attendee: [
{
cn: "Alice",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
cal_address: "alice@example.com",
},
],
},
},
},
},
pending: false,
},
};
renderWithProviders(<CalendarApp />, preloadedState);
expect(screen.getByText("personnalCalendars")).toBeInTheDocument();
expect(screen.queryByText("delegatedCalendars")).not.toBeInTheDocument();
expect(screen.queryByText("sharedCalendars")).not.toBeInTheDocument();
expect(screen.getByLabelText("Calendar personnal")).toBeInTheDocument();
});
});
+49 -24
View File
@@ -7,11 +7,18 @@ export default function CalendarSelection({
selectedCalendars: string[];
setSelectedCalendars: Function;
}) {
const tokens = useAppSelector((state) => state.user.tokens);
const userId = useAppSelector((state) => state.user.userData.openpaasId);
const dispatch = useAppDispatch();
const calendars = useAppSelector((state) => state.calendars.list);
const personnalCalendars = Object.keys(calendars).filter(
(id) => id.split("/")[0] === userId
);
const delegatedCalendars = Object.keys(calendars).filter(
(id) => id.split("/")[0] !== userId && calendars[id].delegated
);
const sharedCalendars = Object.keys(calendars).filter(
(id) => id.split("/")[0] !== userId && !calendars[id].delegated
);
const handleCalendarToggle = (name: string) => {
setSelectedCalendars((prev: string[]) =>
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
@@ -21,27 +28,8 @@ export default function CalendarSelection({
return (
<div>
<h3>personnalCalendars</h3>
{Object.keys(calendars)
.filter((id) => id.split("/")[0] === userId)
.map((id) => {
return (
<div key={id}>
<label>
<input
type="checkbox"
style={{ backgroundColor: calendars[id].color }}
checked={selectedCalendars.includes(id)}
onChange={() => handleCalendarToggle(id)}
/>
{calendars[id].name}
</label>
</div>
);
})}
<h3>sharedCalendars</h3>
{Object.keys(calendars)
.filter((id) => id.split("/")[0] !== userId)
.map((id) => (
{personnalCalendars.map((id) => {
return (
<div key={id}>
<label>
<input
@@ -53,7 +41,44 @@ export default function CalendarSelection({
{calendars[id].name}
</label>
</div>
))}
);
})}
{delegatedCalendars.length > 0 && (
<>
<h3>delegatedCalendars</h3>
{delegatedCalendars.map((id) => (
<div key={id}>
<label>
<input
type="checkbox"
style={{ backgroundColor: calendars[id].color }}
checked={selectedCalendars.includes(id)}
onChange={() => handleCalendarToggle(id)}
/>
{calendars[id].name}
</label>
</div>
))}
</>
)}
{sharedCalendars.length > 0 && (
<>
<h3>sharedCalendars</h3>
{sharedCalendars.map((id) => (
<div key={id}>
<label>
<input
type="checkbox"
style={{ backgroundColor: calendars[id].color }}
checked={selectedCalendars.includes(id)}
onChange={() => handleCalendarToggle(id)}
/>
{calendars[id].name}
</label>
</div>
))}
</>
)}
</div>
);
}
+11 -5
View File
@@ -6,7 +6,6 @@ import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
import { parseCalendarEvent } from "../Events/eventUtils";
import { deleteEvent, putEvent } from "../Events/EventApi";
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
import { responsiveFontSizes } from "@mui/material";
export const getCalendarsListAsync = createAsyncThunk<
Record<string, Calendars> // Return type
@@ -19,11 +18,17 @@ export const getCalendarsListAsync = createAsyncThunk<
for (const cal of rawCalendars) {
const name = cal["dav:name"];
const description = cal["dav:description"];
const id = cal["calendarserver:source"]
let delegated = false;
let source = cal["calendarserver:source"]
? cal["calendarserver:source"]._links.self.href
.replace("/calendars/", "")
.replace(".json", "")
: cal._links.self.href.replace("/calendars/", "").replace(".json", "");
: cal._links.self.href;
if (cal["calendarserver:delegatedsource"]) {
source = cal["calendarserver:delegatedsource"];
delegated = true;
}
console.log(source);
const id = source.replace("/calendars/", "").replace(".json", "");
const ownerData: any = await getUserDetails(id.split("/")[0]);
const color = cal["apple:color"];
importedCalendars[id] = {
@@ -31,6 +36,7 @@ export const getCalendarsListAsync = createAsyncThunk<
name,
ownerEmails: ownerData.emails,
description,
delegated,
color,
events: {},
};
+1
View File
@@ -3,6 +3,7 @@ import { CalendarEvent } from "../Events/EventsTypes";
export interface Calendars {
id: string;
name: string;
delegated?: boolean;
prodid?: string;
color?: string;
ownerEmails?: string[];