[#64] added creation modal to list and api call

This commit is contained in:
Camille Moussu
2025-09-11 16:59:03 +02:00
committed by Benoit TELLIER
parent d1140a47b2
commit 9d43c15ee0
6 changed files with 152 additions and 93 deletions
+11 -14
View File
@@ -14,6 +14,7 @@ import { CalendarEvent } from "../../features/Events/EventsTypes";
import CalendarSelection from "./CalendarSelection"; import CalendarSelection from "./CalendarSelection";
import { import {
getCalendarDetailAsync, getCalendarDetailAsync,
getCalendarsListAsync,
getEventAsync, getEventAsync,
putEventAsync, putEventAsync,
updateEventLocal, updateEventLocal,
@@ -92,13 +93,15 @@ export default function CalendarApp() {
let filteredEvents: CalendarEvent[] = []; let filteredEvents: CalendarEvent[] = [];
selectedCalendars.forEach((id) => { selectedCalendars.forEach((id) => {
filteredEvents = filteredEvents if (calendars[id].events) {
.concat( filteredEvents = filteredEvents
Object.keys(calendars[id].events).map( .concat(
(eventid) => calendars[id].events[eventid] Object.keys(calendars[id].events).map(
(eventid) => calendars[id].events[eventid]
)
) )
) .filter((event) => !(event.status === "CANCELLED"));
.filter((event) => !(event.status === "CANCELLED")); }
}); });
useEffect(() => { useEffect(() => {
@@ -125,7 +128,6 @@ export default function CalendarApp() {
const [openEventDisplay, setOpenEventDisplay] = useState(false); const [openEventDisplay, setOpenEventDisplay] = useState(false);
const [eventDisplayedId, setEventDisplayedId] = useState(""); const [eventDisplayedId, setEventDisplayedId] = useState("");
const [eventDisplayedCalId, setEventDisplayedCalId] = useState(""); const [eventDisplayedCalId, setEventDisplayedCalId] = useState("");
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>( const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>(
null null
); );
@@ -248,7 +250,6 @@ export default function CalendarApp() {
selectedCalendars={selectedCalendars} selectedCalendars={selectedCalendars}
setSelectedCalendars={setSelectedCalendars} setSelectedCalendars={setSelectedCalendars}
/> />
<button onClick={() => setAnchorElCal(document.body)}>+</button>
</div> </div>
<div className="calendar"> <div className="calendar">
<ImportAlert /> <ImportAlert />
@@ -270,7 +271,8 @@ export default function CalendarApp() {
customButtons={{ customButtons={{
refresh: { refresh: {
text: "↻", text: "↻",
click: () => { click: async () => {
await dispatch(getCalendarsListAsync());
selectedCalendars.forEach((id) => { selectedCalendars.forEach((id) => {
if (!pending && rangeKey) { if (!pending && rangeKey) {
dispatch( dispatch(
@@ -521,11 +523,6 @@ export default function CalendarApp() {
setSelectedRange={setSelectedRange} setSelectedRange={setSelectedRange}
calendarRef={calendarRef} calendarRef={calendarRef}
/> />
<CalendarPopover
anchorEl={anchorElCal}
open={Boolean(anchorElCal)}
onClose={() => setAnchorElCal(null)}
/>
{openEventDisplay && eventDisplayedId && eventDisplayedCalId && ( {openEventDisplay && eventDisplayedId && eventDisplayedCalId && (
<EventPreviewModal <EventPreviewModal
eventId={eventDisplayedId} eventId={eventDisplayedId}
+66 -49
View File
@@ -1,4 +1,8 @@
import { Button } from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import AddIcon from "@mui/icons-material/Add";
import { useEffect, useState } from "react";
import CalendarPopover from "../../features/Calendars/CalendarModal";
export default function CalendarSelection({ export default function CalendarSelection({
selectedCalendars, selectedCalendars,
@@ -24,32 +28,20 @@ export default function CalendarSelection({
); );
}; };
useEffect(() => {}, [calendars]);
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
return ( return (
<div> <>
<span className="calendarListHeader"> <div>
<h3>Personnal Calendars</h3> <div className="calendarListHeader">
</span> <h3>Personnal Calendars</h3>
{personnalCalendars.map((id) => { <Button onClick={() => setAnchorElCal(document.body)}>
return ( <AddIcon />
<div key={id}> </Button>
<label> </div>
<input {personnalCalendars.map((id) => {
type="checkbox" return (
style={{ backgroundColor: calendars[id].color }}
checked={selectedCalendars.includes(id)}
onChange={() => handleCalendarToggle(id)}
/>
{calendars[id].name}
</label>
</div>
);
})}
{delegatedCalendars.length > 0 && (
<>
<span className="calendarListHeader">
<h3>Delegated Calendars</h3>
</span>
{delegatedCalendars.map((id) => (
<div key={id}> <div key={id}>
<label> <label>
<input <input
@@ -61,29 +53,54 @@ export default function CalendarSelection({
{calendars[id].name} {calendars[id].name}
</label> </label>
</div> </div>
))} );
</> })}
)} {delegatedCalendars.length > 0 && (
{sharedCalendars.length > 0 && ( <>
<> <span className="calendarListHeader">
<span className="calendarListHeader"> <h3>Delegated Calendars</h3>
<h3>Shared Calendars</h3> </span>
</span> {delegatedCalendars.map((id) => (
{sharedCalendars.map((id) => ( <div key={id}>
<div key={id}> <label>
<label> <input
<input type="checkbox"
type="checkbox" style={{ backgroundColor: calendars[id].color }}
style={{ backgroundColor: calendars[id].color }} checked={selectedCalendars.includes(id)}
checked={selectedCalendars.includes(id)} onChange={() => handleCalendarToggle(id)}
onChange={() => handleCalendarToggle(id)} />
/> {calendars[id].name}
{calendars[id].name} </label>
</label> </div>
</div> ))}
))} </>
</> )}
)} {sharedCalendars.length > 0 && (
</div> <>
<span className="calendarListHeader">
<h3>Shared Calendars</h3>
</span>
{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>
<CalendarPopover
anchorEl={anchorElCal}
open={Boolean(anchorElCal)}
onClose={() => setAnchorElCal(null)}
/>
</>
); );
} }
+21
View File
@@ -30,3 +30,24 @@ export async function getCalendar(
const calendar = await response.json(); const calendar = await response.json();
return calendar; return calendar;
} }
export async function postCalendar(
userId: string,
calId: string,
color: string,
name: string,
desc: string
) {
const response = await api.post(`dav/calendars/${userId}.json`, {
headers: {
Accept: "application/json, text/plain, */*",
},
body: JSON.stringify({
id: calId,
"dav:name": name,
"apple:color": color,
"caldav:description": desc,
}),
});
return response;
}
+9 -5
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { createCalendar } from "./CalendarSlice"; import { createCalendar, createCalendarAsync } from "./CalendarSlice";
import { useAppDispatch } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { import {
Popover, Popover,
TextField, TextField,
@@ -21,14 +21,18 @@ function CalendarPopover({
onClose: (Calendar: {}, reason: "backdropClick" | "escapeKeyDown") => void; onClose: (Calendar: {}, reason: "backdropClick" | "escapeKeyDown") => void;
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const userId =
useAppSelector((state) => state.user.userData.openpaasId) ?? "";
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [color, setColor] = useState(""); const [color, setColor] = useState("");
const [timeZone, setTimeZone] = useState(""); const [timeZone, setTimeZone] = useState("");
const timezones = Intl.supportedValuesOf?.("timeZone") ?? []; const timezones = Intl.supportedValuesOf?.("timeZone") ?? [];
const handleSave = () => { const handleSave = () => {
dispatch(createCalendar({ name, description, color })); const calId = crypto.randomUUID();
dispatch(
createCalendarAsync({ name, desc: description, color, userId, calId })
);
onClose({}, "backdropClick"); onClose({}, "backdropClick");
// Reset // Reset
@@ -68,7 +72,7 @@ function CalendarPopover({
gutterBottom gutterBottom
style={{ backgroundColor: color }} style={{ backgroundColor: color }}
> >
Create a Calendar Calendar configuration
</Typography> </Typography>
<TextField <TextField
fullWidth fullWidth
+22 -4
View File
@@ -1,7 +1,7 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Calendars } from "./CalendarTypes"; import { Calendars } from "./CalendarTypes";
import { CalendarEvent } from "../Events/EventsTypes"; import { CalendarEvent } from "../Events/EventsTypes";
import { getCalendar, getCalendars } from "./CalendarApi"; import { getCalendar, getCalendars, postCalendar } from "./CalendarApi";
import { getOpenPaasUser, getUserDetails } from "../User/userAPI"; import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
import { parseCalendarEvent } from "../Events/eventUtils"; import { parseCalendarEvent } from "../Events/eventUtils";
import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi"; import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi";
@@ -157,6 +157,14 @@ export const deleteEventAsync = createAsyncThunk<
return { calId, eventId }; return { calId, eventId };
}); });
export const createCalendarAsync = createAsyncThunk<
{ userId: string; calId: string; color: string; name: string; desc: string }, // Return type
{ userId: string; calId: string; color: string; name: string; desc: string } // Arg type
>("calendars/createCalendar", async ({ userId, calId, color, name, desc }) => {
const response = await postCalendar(userId, calId, color, name, desc);
return { userId, calId, color, name, desc };
});
const CalendarSlice = createSlice({ const CalendarSlice = createSlice({
name: "calendars", name: "calendars",
initialState: { list: {} as Record<string, Calendars>, pending: false }, initialState: { list: {} as Record<string, Calendars>, pending: false },
@@ -206,9 +214,7 @@ const CalendarSlice = createSlice({
getCalendarsListAsync.fulfilled, getCalendarsListAsync.fulfilled,
(state, action: PayloadAction<Record<string, Calendars>>) => { (state, action: PayloadAction<Record<string, Calendars>>) => {
state.pending = false; state.pending = false;
Object.keys(action.payload).forEach((id) => { state.list = action.payload;
state.list[id] = action.payload[id];
});
} }
) )
.addCase( .addCase(
@@ -324,6 +330,15 @@ const CalendarSlice = createSlice({
]; ];
} }
}) })
.addCase(createCalendarAsync.fulfilled, (state, action) => {
state.pending = false;
state.list[`${action.payload.userId}/${action.payload.calId}`] = {
color: action.payload.color,
id: `${action.payload.userId}/${action.payload.calId}`,
description: action.payload.desc,
name: action.payload.name,
} as unknown as Calendars;
})
.addCase(getCalendarDetailAsync.pending, (state) => { .addCase(getCalendarDetailAsync.pending, (state) => {
state.pending = true; state.pending = true;
}) })
@@ -341,6 +356,9 @@ const CalendarSlice = createSlice({
}) })
.addCase(deleteEventAsync.pending, (state) => { .addCase(deleteEventAsync.pending, (state) => {
state.pending = true; state.pending = true;
})
.addCase(createCalendarAsync.pending, (state) => {
state.pending = true;
}); });
}, },
}); });
+23 -21
View File
@@ -18,28 +18,30 @@ export default function ImportAlert() {
return ( return (
<> <>
{Object.keys(calendars).map((calendarId) => {Object.keys(calendars).map((calendarId) =>
Object.keys(calendars[calendarId].events) calendars[calendarId]?.events
.filter((id) => calendars[calendarId].events[id].error) ? Object.keys(calendars[calendarId]?.events)
.map((id) => { .filter((id) => calendars[calendarId]?.events[id].error)
const isVisible = .map((id) => {
visibleAlerts[calendars[calendarId].events[id].uid] ?? true; // default to visible const isVisible =
visibleAlerts[calendars[calendarId].events[id].uid] ?? true; // default to visible
return ( return (
<Collapse <Collapse
in={isVisible} in={isVisible}
key={calendars[calendarId].events[id].uid} key={calendars[calendarId].events[id].uid}
> >
<Alert <Alert
severity="error" severity="error"
onClose={() => onClose={() =>
toggleEventAlert(calendars[calendarId].events[id].uid) toggleEventAlert(calendars[calendarId].events[id].uid)
} }
> >
{calendars[calendarId].events[id].error} {calendars[calendarId].events[id].error}
</Alert> </Alert>
</Collapse> </Collapse>
); );
}) })
: []
)} )}
</> </>
); );