Calendar subscription naming (#235)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-10-24 12:47:18 +02:00
committed by GitHub
parent 34363562cc
commit 8813bff48c
6 changed files with 106 additions and 11 deletions
@@ -1,6 +1,7 @@
// __test__/features/calendars/calendarApi.test.ts // __test__/features/calendars/calendarApi.test.ts
import { import {
addSharedCalendar,
getCalendar, getCalendar,
getCalendars, getCalendars,
postCalendar, postCalendar,
@@ -112,4 +113,43 @@ describe("Calendar API", () => {
}, },
}); });
}); });
it("When adding a sharedCal with #default as a name a new name is sent to the back", async () => {
const mockApiPost = jest.spyOn(api, "post");
const calData = {
cal: {
id: "cal123",
"dav:name": "#default",
"apple:color": "#FF5733",
"caldav:description": "Default calendar",
acl: [],
invite: [],
_links: {
self: {
href: "/calendars/owner123/cal123.json",
},
},
},
owner: {
displayName: "John Doe",
email: "john.doe@example.com",
openpaasId: "owner123",
},
color: "#FF5733",
};
await addSharedCalendar("currentUserId", "newCalId123", calData);
expect(mockApiPost).toHaveBeenCalledWith(
"dav/calendars/currentUserId.json",
expect.objectContaining({
body: expect.stringContaining('"dav:name":"John Doe\'s calendar"'),
})
);
const callBody = JSON.parse(String(mockApiPost.mock.calls[0][1]?.body));
expect(callBody["dav:name"]).toBe("John Doe's calendar");
expect(callBody["dav:name"]).not.toBe("#default");
});
}); });
+4 -1
View File
@@ -28,7 +28,10 @@
padding-left 5px padding-left 5px
height 100% height 100%
flex-direction column flex-direction column
overflow auto overflow-y: hidden
.sidebar:hover
overflow-y: auto
.declined-event .declined-event
opacity 0.7 opacity 0.7
+5 -1
View File
@@ -61,7 +61,11 @@ function CalendarItem({
}} }}
/> />
<Box> <Box>
<Typography variant="body1">{cal.cal["dav:name"]}</Typography> <Typography variant="body1">
{cal.cal["dav:name"] === "#default"
? cal.owner.displayName + "'s calendar"
: cal.cal["dav:name"]}
</Typography>
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary">
{cal.owner.email} {cal.owner.email}
</Typography> </Typography>
+38 -6
View File
@@ -12,7 +12,7 @@ import IconButton from "@mui/material/IconButton";
import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import CalendarSearch from "./CalendarSearch"; import CalendarSearch from "./CalendarSearch";
import { Divider, Menu, MenuItem } from "@mui/material"; import { Divider, ListItem, Menu, MenuItem } from "@mui/material";
import { removeCalendarAsync } from "../../features/Calendars/CalendarSlice"; import { removeCalendarAsync } from "../../features/Calendars/CalendarSlice";
import { DeleteCalendarDialog } from "./DeleteCalendarDialog"; import { DeleteCalendarDialog } from "./DeleteCalendarDialog";
@@ -41,13 +41,24 @@ function CalendarAccordion({
if (calendars.length === 0 && !defaultExpanded) return null; if (calendars.length === 0 && !defaultExpanded) return null;
return ( return (
<Accordion defaultExpanded={defaultExpanded} expanded={expended}> <Accordion
defaultExpanded={defaultExpanded}
expanded={expended}
style={{ width: "100%", padding: 0, margin: 0, boxShadow: "none" }}
>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMoreIcon />} expandIcon={<ExpandMoreIcon />}
aria-controls={`${title}-content`} aria-controls={`${title}-content`}
id={`${title}-header`} id={`${title}-header`}
className="calendarListHeader" className="calendarListHeader"
onClick={() => setExpended(!expended)} onClick={() => setExpended(!expended)}
sx={{
"& .MuiAccordionSummary-content": {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
},
}}
> >
<Typography component="h3">{title}</Typography> <Typography component="h3">{title}</Typography>
{showAddButton && ( {showAddButton && (
@@ -62,7 +73,7 @@ function CalendarAccordion({
</IconButton> </IconButton>
)} )}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails style={{ textAlign: "left", padding: 0 }}>
{calendars.map((id) => ( {calendars.map((id) => (
<CalendarSelector <CalendarSelector
key={id} key={id}
@@ -214,7 +225,24 @@ function CalendarSelector({
}; };
return ( return (
<> <>
<div> <ListItem
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
transition: "background-color 0.2s ease",
padding: "8px 24px 8px 16px",
"& .MoreBtn": {
opacity: 0,
},
"&:hover": {
backgroundColor: "#F3F3F6",
"& .MoreBtn": {
opacity: 1,
},
},
}}
>
<label> <label>
<Checkbox <Checkbox
sx={{ sx={{
@@ -227,10 +255,14 @@ function CalendarSelector({
/> />
{calendars[id].name} {calendars[id].name}
</label> </label>
<IconButton onClick={handleClick}> <IconButton
className="MoreBtn"
onClick={handleClick}
style={{ alignSelf: "end" }}
>
<MoreVertIcon /> <MoreVertIcon />
</IconButton> </IconButton>
</div> </ListItem>
<Menu id={id} anchorEl={anchorEl} open={open} onClose={handleClose}> <Menu id={id} anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
+4
View File
@@ -64,6 +64,10 @@ export async function addSharedCalendar(
body: JSON.stringify({ body: JSON.stringify({
id: calId, id: calId,
...cal.cal, ...cal.cal,
"dav:name":
cal.cal["dav:name"] === "#default"
? cal.owner.displayName + "'s calendar"
: cal.cal["dav:name"],
"calendarserver:source": { "calendarserver:source": {
acl: cal.cal.acl, acl: cal.cal.acl,
calendarHomeId: cal.cal.id, calendarHomeId: cal.cal.id,
+15 -3
View File
@@ -39,7 +39,6 @@ export const getCalendarsListAsync = createAsyncThunk<
const rawCalendars = calendars._embedded["dav:calendar"]; const rawCalendars = calendars._embedded["dav:calendar"];
for (const cal of rawCalendars) { for (const cal of rawCalendars) {
const name = cal["dav:name"];
const description = cal["caldav:description"]; const description = cal["caldav:description"];
let delegated = false; let delegated = false;
let source = cal["calendarserver:source"] let source = cal["calendarserver:source"]
@@ -51,8 +50,16 @@ export const getCalendarsListAsync = createAsyncThunk<
delegated = true; delegated = true;
} }
const id = source.replace("/calendars/", "").replace(".json", ""); const id = source.replace("/calendars/", "").replace(".json", "");
const ownerId = id.split("/")[0];
const visibility = getCalendarVisibility(cal["acl"]); const visibility = getCalendarVisibility(cal["acl"]);
const ownerData: any = await getUserDetails(id.split("/")[0]); const ownerData: any = await getUserDetails(ownerId);
const name =
ownerId !== user.id && cal["dav:name"] === "#default"
? `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
ownerData.lastname
}` + "'s calendar"
: cal["dav:name"];
const color = { const color = {
light: cal["apple:color"] ?? "#006BD8", light: cal["apple:color"] ?? "#006BD8",
dark: cal["X-TWAKE-Dark-theme-color"] ?? "#FFF", dark: cal["X-TWAKE-Dark-theme-color"] ?? "#FFF",
@@ -370,7 +377,12 @@ export const addSharedCalendarAsync = createAsyncThunk<
}, },
link: `/calendars/${userId}/${calId}.json`, link: `/calendars/${userId}/${calId}.json`,
desc: cal.cal["caldav:description"], desc: cal.cal["caldav:description"],
name: cal.cal["dav:name"], name:
ownerData.id !== userId && cal.cal["dav:name"] === "#default"
? `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
ownerData.lastname
}` + "'s calendar"
: cal.cal["dav:name"],
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${ owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
ownerData.lastname ownerData.lastname
}`, }`,