Delete Calendars (#148)

* [#66] added delete popup and logic

* [#66] Homogenize Dialogs components between modify and delete to keep them coherent+ fixed tests

* [#66] added tests

* [#66] text adjusment

* fixup! [#66] added delete popup and logic

---------

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-09-30 10:27:47 +02:00
committed by GitHub
parent b46be3af29
commit 475ab79731
10 changed files with 324 additions and 111 deletions
+10
View File
@@ -102,3 +102,13 @@ export async function proppatchCalendar(
});
return response;
}
export async function removeCalendar(calLink: string) {
const response = await api(`dav${calLink}`, {
method: "DELETE",
headers: {
Accept: "application/json, text/plain, */*",
},
});
return response;
}
-157
View File
@@ -1,157 +0,0 @@
import { useEffect, useState } from "react";
import {
createCalendarAsync /*, updateCalendarAsync */,
patchCalendarAsync,
} from "./CalendarSlice";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import {
Popover,
TextField,
Button,
Box,
Typography,
ButtonGroup,
} from "@mui/material";
import { ColorPicker } from "../../components/Calendar/CalendarColorPicker";
import { Calendars } from "./CalendarTypes";
function CalendarPopover({
anchorEl,
open,
onClose,
calendar,
}: {
anchorEl: HTMLElement | null;
open: boolean;
onClose: (
event: object | null,
reason: "backdropClick" | "escapeKeyDown"
) => void;
calendar?: Calendars;
}) {
const dispatch = useAppDispatch();
const userId =
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
const [name, setName] = useState(calendar?.name ?? "");
const [description, setDescription] = useState(calendar?.description ?? "");
const [color, setColor] = useState(calendar?.color ?? "");
useEffect(() => {
if (open) {
if (calendar) {
setName(calendar.name);
setDescription(calendar.description ?? "");
setColor(calendar.color ?? "");
} else {
setName("");
setDescription("");
setColor("");
}
}
}, [calendar, open]);
const handleSave = () => {
const trimmedName = name.trim();
const trimmedDesc = description.trim();
if (trimmedName) {
const calId = calendar ? calendar.id : crypto.randomUUID();
if (calendar?.id) {
dispatch(
patchCalendarAsync({
calId: calendar.id,
calLink: calendar.link,
patch: { name: trimmedName, desc: trimmedDesc, color },
})
);
} else {
dispatch(
createCalendarAsync({
name: trimmedName,
desc: trimmedDesc,
color,
userId,
calId,
})
);
}
onClose({}, "backdropClick");
}
};
const palette = [
"#D50000",
"#E67C73",
"#F4511E",
"#F6BF26",
"#33B679",
"#0B8043",
"#039BE5",
"#3F51B5",
"#7986CB",
"#8E24AA",
"#616161",
];
return (
<Popover
open={open}
anchorEl={anchorEl}
onClose={(e, reason) => onClose(e, reason)}
anchorOrigin={{ vertical: "center", horizontal: "center" }}
transformOrigin={{ vertical: "center", horizontal: "center" }}
>
<Box p={2}>
<Typography
variant="h6"
gutterBottom
style={{ backgroundColor: color }}
>
Calendar configuration
</Typography>
<TextField
fullWidth
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
size="small"
margin="dense"
/>
<TextField
fullWidth
label="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
size="small"
margin="dense"
multiline
rows={2}
/>
<ColorPicker
onChange={(color) => setColor(color)}
selectedColor={color}
/>
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
<Button
variant="outlined"
onClick={(e) => onClose({}, "backdropClick")}
>
Cancel
</Button>
<Button
disabled={!name.trim()}
variant="contained"
onClick={handleSave}
>
Save
</Button>
</Box>
</Box>
</Popover>
);
}
export default CalendarPopover;
+27
View File
@@ -7,6 +7,7 @@ import {
getCalendars,
postCalendar,
proppatchCalendar,
removeCalendar,
} from "./CalendarApi";
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
import { parseCalendarEvent } from "../Events/eventUtils";
@@ -191,6 +192,22 @@ export const patchCalendarAsync = createAsyncThunk<
};
});
export const removeCalendarAsync = createAsyncThunk<
{
calId: string;
},
{
calId: string;
calLink: string;
}
>("calendars/removeCalendar", async ({ calId, calLink }) => {
const response = await removeCalendar(calLink);
return {
calId,
calLink,
};
});
export const moveEventAsync = createAsyncThunk<
{ calId: string; events: CalendarEvent[] }, // Return type
{ cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type
@@ -258,6 +275,7 @@ export const addSharedCalendarAsync = createAsyncThunk<
{
calId: string;
color: string;
link: string;
name: string;
desc: string;
owner: string;
@@ -278,6 +296,7 @@ export const addSharedCalendarAsync = createAsyncThunk<
.replace("/calendars/", "")
.replace(".json", ""),
color: cal.cal["apple:color"],
link: `/calendars/${userId}/${calId}.json`,
desc: cal.cal["caldav:description"],
name: cal.cal["dav:name"],
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
@@ -527,6 +546,7 @@ const CalendarSlice = createSlice({
state.list[action.payload.calId] = {
color: action.payload.color,
id: action.payload.calId,
link: action.payload.link,
description: action.payload.desc,
name: action.payload.name,
events: {},
@@ -534,6 +554,10 @@ const CalendarSlice = createSlice({
ownerEmails: action.payload.ownerEmails,
} as Calendars;
})
.addCase(removeCalendarAsync.fulfilled, (state, action) => {
state.pending = false;
delete state.list[action.payload.calId];
})
.addCase(getCalendarDetailAsync.pending, (state) => {
state.pending = true;
})
@@ -563,6 +587,9 @@ const CalendarSlice = createSlice({
})
.addCase(addSharedCalendarAsync.pending, (state) => {
state.pending = true;
})
.addCase(removeCalendarAsync.pending, (state) => {
state.pending = true;
});
},
});