update cal details (#113)
* [#65] added button to access cal details * [#65] added proppatch call * [#65] added tests * fixup! [#65] added proppatch call * [#65] added trimming to name and desc * fixup! [#65] added proppatch call * fixup! [#65] added proppatch call * fixup! [#65] added proppatch call --------- Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -113,7 +113,7 @@ describe("CalendarSelection", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const addButton = screen.getByRole("button");
|
const addButton = screen.getByTestId("AddIcon");
|
||||||
fireEvent.click(addButton);
|
fireEvent.click(addButton);
|
||||||
|
|
||||||
expect(screen.getByRole("presentation")).toBeInTheDocument();
|
expect(screen.getByRole("presentation")).toBeInTheDocument();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
getCalendar,
|
getCalendar,
|
||||||
getCalendars,
|
getCalendars,
|
||||||
postCalendar,
|
postCalendar,
|
||||||
|
proppatchCalendar,
|
||||||
} from "../../../src/features/Calendars/CalendarApi";
|
} from "../../../src/features/Calendars/CalendarApi";
|
||||||
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
||||||
import { api } from "../../../src/utils/apiUtils";
|
import { api } from "../../../src/utils/apiUtils";
|
||||||
@@ -77,4 +78,25 @@ describe("Calendar API", () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("patch Calendar", async () => {
|
||||||
|
const calId = "calId";
|
||||||
|
const calLink = "/calendars/calId.json";
|
||||||
|
const color = "calId";
|
||||||
|
const name = "new cal";
|
||||||
|
const desc = "desc";
|
||||||
|
|
||||||
|
const result = await proppatchCalendar(calLink, { color, name, desc });
|
||||||
|
|
||||||
|
expect(api).toHaveBeenCalledWith(`dav${calLink}`, {
|
||||||
|
method: "PROPPATCH",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"dav:name": "new cal",
|
||||||
|
"caldav:description": "desc",
|
||||||
|
"apple:color": "calId",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { screen, fireEvent } from "@testing-library/react";
|
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
import { useAppDispatch } from "../../../src/app/hooks";
|
|
||||||
import { createCalendar } from "../../../src/features/Calendars/CalendarSlice";
|
|
||||||
import CalendarPopover from "../../../src/features/Calendars/CalendarModal";
|
import CalendarPopover from "../../../src/features/Calendars/CalendarModal";
|
||||||
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||||
@@ -97,10 +95,6 @@ describe("CalendarPopover", () => {
|
|||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
|
||||||
// Inputs should be reset (optional check)
|
|
||||||
expect(screen.getByLabelText(/Name/i)).toHaveValue("");
|
|
||||||
expect(screen.getByLabelText(/Description/i)).toHaveValue("");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls onClose when Cancel clicked", () => {
|
it("calls onClose when Cancel clicked", () => {
|
||||||
@@ -111,3 +105,113 @@ describe("CalendarPopover", () => {
|
|||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("CalendarPopover (editing mode)", () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
|
||||||
|
const baseUser = {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "mockSid",
|
||||||
|
openpaasId: "user1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingCalendar = {
|
||||||
|
id: "user1/cal1",
|
||||||
|
link: "/calendars/user/cal1",
|
||||||
|
name: "Work Calendar",
|
||||||
|
description: "Team meetings",
|
||||||
|
color: "#33B679",
|
||||||
|
owner: "alice",
|
||||||
|
ownerEmails: ["alice@example.com"],
|
||||||
|
events: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefills fields when calendar prop is given", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calendar={existingCalendar}
|
||||||
|
/>,
|
||||||
|
{ user: baseUser }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/Name/i)).toHaveValue("Work Calendar");
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toHaveValue("Team meetings");
|
||||||
|
expect(screen.getByText("Calendar configuration")).toHaveStyle({
|
||||||
|
backgroundColor: "#33B679",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Save button is disabled when name is empty or whitespace only", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
anchorEl={document.createElement("div")}
|
||||||
|
open={true}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
/>,
|
||||||
|
{ user: baseUser }
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||||
|
expect(saveButton).toBeDisabled();
|
||||||
|
// only spaces
|
||||||
|
const nameInput = screen.getByLabelText(/name/i);
|
||||||
|
fireEvent.change(nameInput, { target: { value: " " } });
|
||||||
|
|
||||||
|
expect(saveButton).toBeDisabled();
|
||||||
|
|
||||||
|
// valid name
|
||||||
|
fireEvent.change(nameInput, { target: { value: "Work Calendar" } });
|
||||||
|
expect(saveButton).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows modifying and saving existing calendar", async () => {
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "patchCalendarAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calendar={existingCalendar}
|
||||||
|
/>,
|
||||||
|
{ user: baseUser }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change name
|
||||||
|
fireEvent.change(screen.getByLabelText(/Name/i), {
|
||||||
|
target: { value: "Updated Calendar" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save
|
||||||
|
fireEvent.click(screen.getByText(/Save/i));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(spy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
calId: "user1/cal1",
|
||||||
|
calLink: "/calendars/user/cal1",
|
||||||
|
patch: {
|
||||||
|
color: "#33B679",
|
||||||
|
desc: "Team meetings",
|
||||||
|
name: "Updated Calendar",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(mockOnClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
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 AddIcon from "@mui/icons-material/Add";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import CalendarPopover from "../../features/Calendars/CalendarModal";
|
import CalendarPopover from "../../features/Calendars/CalendarModal";
|
||||||
|
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||||
|
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
|
||||||
export default function CalendarSelection({
|
export default function CalendarSelection({
|
||||||
selectedCalendars,
|
selectedCalendars,
|
||||||
@@ -27,9 +31,9 @@ export default function CalendarSelection({
|
|||||||
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
|
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const [selectedCalId, setSelectedCalId] = useState("");
|
||||||
|
|
||||||
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
|
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
@@ -39,39 +43,35 @@ export default function CalendarSelection({
|
|||||||
<AddIcon />
|
<AddIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{personnalCalendars.map((id) => {
|
{personnalCalendars.map((id) =>
|
||||||
return (
|
CalendarSelector(
|
||||||
<div key={id}>
|
calendars,
|
||||||
<label>
|
id,
|
||||||
<input
|
selectedCalendars,
|
||||||
type="checkbox"
|
handleCalendarToggle,
|
||||||
style={{ backgroundColor: calendars[id].color }}
|
() => {
|
||||||
checked={selectedCalendars.includes(id)}
|
setAnchorElCal(document.body);
|
||||||
onChange={() => handleCalendarToggle(id)}
|
setSelectedCalId(id);
|
||||||
/>
|
}
|
||||||
{calendars[id].name}
|
)
|
||||||
</label>
|
)}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{delegatedCalendars.length > 0 && (
|
{delegatedCalendars.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="calendarListHeader">
|
<span className="calendarListHeader">
|
||||||
<h3>Delegated Calendars</h3>
|
<h3>Delegated Calendars</h3>
|
||||||
</span>
|
</span>
|
||||||
{delegatedCalendars.map((id) => (
|
{delegatedCalendars.map((id) =>
|
||||||
<div key={id}>
|
CalendarSelector(
|
||||||
<label>
|
calendars,
|
||||||
<input
|
id,
|
||||||
type="checkbox"
|
selectedCalendars,
|
||||||
style={{ backgroundColor: calendars[id].color }}
|
handleCalendarToggle,
|
||||||
checked={selectedCalendars.includes(id)}
|
() => {
|
||||||
onChange={() => handleCalendarToggle(id)}
|
setAnchorElCal(document.body);
|
||||||
/>
|
setSelectedCalId(id);
|
||||||
{calendars[id].name}
|
}
|
||||||
</label>
|
)
|
||||||
</div>
|
)}
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{sharedCalendars.length > 0 && (
|
{sharedCalendars.length > 0 && (
|
||||||
@@ -79,27 +79,57 @@ export default function CalendarSelection({
|
|||||||
<span className="calendarListHeader">
|
<span className="calendarListHeader">
|
||||||
<h3>Shared Calendars</h3>
|
<h3>Shared Calendars</h3>
|
||||||
</span>
|
</span>
|
||||||
{sharedCalendars.map((id) => (
|
{sharedCalendars.map((id) =>
|
||||||
<div key={id}>
|
CalendarSelector(
|
||||||
<label>
|
calendars,
|
||||||
<input
|
id,
|
||||||
type="checkbox"
|
selectedCalendars,
|
||||||
style={{ backgroundColor: calendars[id].color }}
|
handleCalendarToggle,
|
||||||
checked={selectedCalendars.includes(id)}
|
() => {
|
||||||
onChange={() => handleCalendarToggle(id)}
|
setAnchorElCal(document.body);
|
||||||
/>
|
setSelectedCalId(id);
|
||||||
{calendars[id].name}
|
}
|
||||||
</label>
|
)
|
||||||
</div>
|
)}
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<CalendarPopover
|
<CalendarPopover
|
||||||
anchorEl={anchorElCal}
|
anchorEl={anchorElCal}
|
||||||
open={Boolean(anchorElCal)}
|
open={Boolean(anchorElCal)}
|
||||||
onClose={() => setAnchorElCal(null)}
|
onClose={() => {
|
||||||
|
setAnchorElCal(null);
|
||||||
|
}}
|
||||||
|
calendar={calendars[selectedCalId] ?? undefined}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CalendarSelector(
|
||||||
|
calendars: Record<string, Calendars>,
|
||||||
|
id: string,
|
||||||
|
selectedCalendars: string[],
|
||||||
|
handleCalendarToggle: (name: string) => void,
|
||||||
|
setOpen: Function
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div key={id}>
|
||||||
|
<label>
|
||||||
|
<Checkbox
|
||||||
|
sx={{
|
||||||
|
color: calendars[id].color,
|
||||||
|
"&.Mui-checked": { color: calendars[id].color },
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
checked={selectedCalendars.includes(id)}
|
||||||
|
onChange={() => handleCalendarToggle(id)}
|
||||||
|
/>
|
||||||
|
{calendars[id].name}
|
||||||
|
</label>
|
||||||
|
<IconButton onClick={() => setOpen()}>
|
||||||
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,3 +51,27 @@ export async function postCalendar(
|
|||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function proppatchCalendar(
|
||||||
|
calLink: string,
|
||||||
|
patch: { name: string; desc: string; color: string }
|
||||||
|
) {
|
||||||
|
const body: Record<string, string> = {};
|
||||||
|
if (patch.name) {
|
||||||
|
body["dav:name"] = patch.name;
|
||||||
|
}
|
||||||
|
if (patch.desc) {
|
||||||
|
body["caldav:description"] = patch.desc;
|
||||||
|
}
|
||||||
|
if (patch.color) {
|
||||||
|
body["apple:color"] = patch.color;
|
||||||
|
}
|
||||||
|
const response = await api(`dav${calLink}`, {
|
||||||
|
method: "PROPPATCH",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { createCalendar, createCalendarAsync } from "./CalendarSlice";
|
import {
|
||||||
|
createCalendarAsync /*, updateCalendarAsync */,
|
||||||
|
patchCalendarAsync,
|
||||||
|
} from "./CalendarSlice";
|
||||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@@ -7,38 +10,76 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Select,
|
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { Calendars } from "./CalendarTypes";
|
||||||
|
|
||||||
function CalendarPopover({
|
function CalendarPopover({
|
||||||
anchorEl,
|
anchorEl,
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
|
calendar,
|
||||||
}: {
|
}: {
|
||||||
anchorEl: HTMLElement | null;
|
anchorEl: HTMLElement | null;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: (Calendar: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
onClose: (
|
||||||
|
event: object | null,
|
||||||
|
reason: "backdropClick" | "escapeKeyDown"
|
||||||
|
) => void;
|
||||||
|
calendar?: Calendars;
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const userId =
|
const userId =
|
||||||
useAppSelector((state) => state.user.userData.openpaasId) ?? "";
|
useAppSelector((state) => state.user.userData.openpaasId) ?? "";
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState(calendar?.name ?? "");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState(calendar?.description ?? "");
|
||||||
const [color, setColor] = useState("");
|
const [color, setColor] = useState(calendar?.color ?? "");
|
||||||
const handleSave = () => {
|
|
||||||
const calId = crypto.randomUUID();
|
|
||||||
if (name) {
|
|
||||||
dispatch(
|
|
||||||
createCalendarAsync({ name, desc: description, color, userId, calId })
|
|
||||||
);
|
|
||||||
onClose({}, "backdropClick");
|
|
||||||
|
|
||||||
// Reset
|
useEffect(() => {
|
||||||
setName("");
|
if (open) {
|
||||||
setDescription("");
|
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 = [
|
const palette = [
|
||||||
"#D50000",
|
"#D50000",
|
||||||
"#E67C73",
|
"#E67C73",
|
||||||
@@ -52,19 +93,14 @@ function CalendarPopover({
|
|||||||
"#8E24AA",
|
"#8E24AA",
|
||||||
"#616161",
|
"#616161",
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
open={open}
|
open={open}
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
onClose={onClose}
|
onClose={(e, reason) => onClose(e, reason)}
|
||||||
anchorOrigin={{
|
anchorOrigin={{ vertical: "center", horizontal: "center" }}
|
||||||
vertical: "center",
|
transformOrigin={{ vertical: "center", horizontal: "center" }}
|
||||||
horizontal: "center",
|
|
||||||
}}
|
|
||||||
transformOrigin={{
|
|
||||||
vertical: "center",
|
|
||||||
horizontal: "center",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box p={2}>
|
<Box p={2}>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -93,22 +129,23 @@ function CalendarPopover({
|
|||||||
rows={2}
|
rows={2}
|
||||||
/>
|
/>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{palette.map((color) => (
|
{palette.map((c) => (
|
||||||
<Button
|
<Button
|
||||||
style={{ backgroundColor: color }}
|
key={c}
|
||||||
onClick={() => setColor(color)}
|
style={{ backgroundColor: c }}
|
||||||
|
onClick={() => setColor(c)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
|
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => onClose({}, "backdropClick")}
|
onClick={(e) => onClose({}, "backdropClick")}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={name && name !== "" ? false : true}
|
disabled={!name.trim()}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
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, postCalendar } from "./CalendarApi";
|
import {
|
||||||
|
getCalendar,
|
||||||
|
getCalendars,
|
||||||
|
postCalendar,
|
||||||
|
proppatchCalendar,
|
||||||
|
} 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";
|
||||||
@@ -17,11 +22,12 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
|
|
||||||
for (const cal of rawCalendars) {
|
for (const cal of rawCalendars) {
|
||||||
const name = cal["dav:name"];
|
const name = cal["dav:name"];
|
||||||
const description = cal["dav:description"];
|
const description = cal["caldav:description"];
|
||||||
let delegated = false;
|
let delegated = false;
|
||||||
let source = cal["calendarserver:source"]
|
let source = cal["calendarserver:source"]
|
||||||
? cal["calendarserver:source"]._links.self.href
|
? cal["calendarserver:source"]._links.self.href
|
||||||
: cal._links.self.href;
|
: cal._links.self.href;
|
||||||
|
const link = cal._links.self.href;
|
||||||
if (cal["calendarserver:delegatedsource"]) {
|
if (cal["calendarserver:delegatedsource"]) {
|
||||||
source = cal["calendarserver:delegatedsource"];
|
source = cal["calendarserver:delegatedsource"];
|
||||||
delegated = true;
|
delegated = true;
|
||||||
@@ -33,6 +39,7 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
importedCalendars[id] = {
|
importedCalendars[id] = {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
link,
|
||||||
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
|
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
|
||||||
ownerData.lastname
|
ownerData.lastname
|
||||||
}`,
|
}`,
|
||||||
@@ -121,6 +128,25 @@ export const getEventAsync = createAsyncThunk<
|
|||||||
event: response,
|
event: response,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
export const patchCalendarAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
calId: string;
|
||||||
|
calLink: string;
|
||||||
|
patch: { name: string; desc: string; color: string };
|
||||||
|
}, // Return type
|
||||||
|
{
|
||||||
|
calId: string;
|
||||||
|
calLink: string;
|
||||||
|
patch: { name: string; desc: string; color: string };
|
||||||
|
} // Arg type
|
||||||
|
>("calendars/patchCalendar", async ({ calId, calLink, patch }) => {
|
||||||
|
const response = await proppatchCalendar(calLink, patch);
|
||||||
|
return {
|
||||||
|
calId,
|
||||||
|
calLink,
|
||||||
|
patch,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export const moveEventAsync = createAsyncThunk<
|
export const moveEventAsync = createAsyncThunk<
|
||||||
{ calId: string; events: CalendarEvent[] }, // Return type
|
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||||
@@ -339,6 +365,32 @@ const CalendarSlice = createSlice({
|
|||||||
name: action.payload.name,
|
name: action.payload.name,
|
||||||
} as unknown as Calendars;
|
} as unknown as Calendars;
|
||||||
})
|
})
|
||||||
|
.addCase(patchCalendarAsync.fulfilled, (state, action) => {
|
||||||
|
state.pending = false;
|
||||||
|
if (action.payload.patch.color) {
|
||||||
|
state.list[action.payload.calId] = {
|
||||||
|
...state.list[action.payload.calId],
|
||||||
|
color: action.payload.patch.color,
|
||||||
|
};
|
||||||
|
Object.keys(state.list[action.payload.calId].events).forEach(
|
||||||
|
(evId) =>
|
||||||
|
(state.list[action.payload.calId].events[evId].color =
|
||||||
|
action.payload.patch.color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (action.payload.patch.desc) {
|
||||||
|
state.list[action.payload.calId] = {
|
||||||
|
...state.list[action.payload.calId],
|
||||||
|
description: action.payload.patch.desc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.payload.patch.name) {
|
||||||
|
state.list[action.payload.calId] = {
|
||||||
|
...state.list[action.payload.calId],
|
||||||
|
name: action.payload.patch.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
.addCase(getCalendarDetailAsync.pending, (state) => {
|
.addCase(getCalendarDetailAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
})
|
})
|
||||||
@@ -357,6 +409,9 @@ const CalendarSlice = createSlice({
|
|||||||
.addCase(deleteEventAsync.pending, (state) => {
|
.addCase(deleteEventAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
})
|
})
|
||||||
|
.addCase(patchCalendarAsync.pending, (state) => {
|
||||||
|
state.pending = true;
|
||||||
|
})
|
||||||
.addCase(createCalendarAsync.pending, (state) => {
|
.addCase(createCalendarAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { CalendarEvent } from "../Events/EventsTypes";
|
|||||||
|
|
||||||
export interface Calendars {
|
export interface Calendars {
|
||||||
id: string;
|
id: string;
|
||||||
|
link: string;
|
||||||
name: string;
|
name: string;
|
||||||
delegated?: boolean;
|
delegated?: boolean;
|
||||||
prodid?: string;
|
prodid?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user