calendar list display improvments (#115)

* [#60] added collapsible groups

* [#60] factorized code and fixed tests

* fixup! [#60] added collapsible groups

* fixup! [#60] added collapsible groups

---------

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-09-18 18:01:08 +02:00
committed by GitHub
parent b8cef20b9e
commit 8a7d2f742a
3 changed files with 136 additions and 61 deletions
+22 -1
View File
@@ -123,7 +123,7 @@ describe("CalendarSelection", () => {
renderWithProviders(<CalendarApp />, preloadedState); renderWithProviders(<CalendarApp />, preloadedState);
expect(screen.getByText("Personnal Calendars")).toBeInTheDocument(); expect(screen.getByText("Personnal Calendars")).toBeInTheDocument();
expect(screen.getByText("Delegated Calendars")).toBeInTheDocument(); expect(screen.getByText("Delegated Calendars")).toBeInTheDocument();
expect(screen.getByText("Shared Calendars")).toBeInTheDocument(); expect(screen.getByText("Other Calendars")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar personnal")).toBeInTheDocument(); expect(screen.getByLabelText("Calendar personnal")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar delegated")).toBeInTheDocument(); expect(screen.getByLabelText("Calendar delegated")).toBeInTheDocument();
@@ -147,4 +147,25 @@ describe("CalendarSelection", () => {
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
}); });
it("open accordeon when clicking on button only", () => {
renderWithProviders(<CalendarApp />, preloadedState);
expect(screen.getByText("Personnal Calendars")).toBeInTheDocument();
expect(screen.getByText("Delegated Calendars")).toBeInTheDocument();
expect(screen.getByText("Other Calendars")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar personnal")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar delegated")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar shared")).toBeInTheDocument();
const delegatedAccordionSummary = screen
.getByText("Delegated Calendars")
.closest(".MuiAccordionSummary-root");
const addButton = screen.getAllByTestId("AddIcon")[1];
fireEvent.click(addButton);
expect(delegatedAccordionSummary).toHaveAttribute("aria-expanded", "true");
fireEvent.click(addButton);
expect(delegatedAccordionSummary).toHaveAttribute("aria-expanded", "true");
});
}); });
@@ -50,7 +50,7 @@ describe("CalendarSelection", () => {
expect(screen.getByText("Personnal Calendars")).toBeInTheDocument(); expect(screen.getByText("Personnal Calendars")).toBeInTheDocument();
expect(screen.getByText("Delegated Calendars")).toBeInTheDocument(); expect(screen.getByText("Delegated Calendars")).toBeInTheDocument();
expect(screen.getByText("Shared Calendars")).toBeInTheDocument(); expect(screen.getByText("Other Calendars")).toBeInTheDocument();
expect(screen.getByLabelText("Calendar personnal")).toBeChecked(); expect(screen.getByLabelText("Calendar personnal")).toBeChecked();
expect(screen.getByLabelText("Calendar delegated")).not.toBeChecked(); expect(screen.getByLabelText("Calendar delegated")).not.toBeChecked();
@@ -113,7 +113,7 @@ describe("CalendarSelection", () => {
} }
); );
const addButton = screen.getByTestId("AddIcon"); const addButton = screen.getAllByTestId("AddIcon")[0];
fireEvent.click(addButton); fireEvent.click(addButton);
expect(screen.getByRole("presentation")).toBeInTheDocument(); expect(screen.getByRole("presentation")).toBeInTheDocument();
+112 -58
View File
@@ -1,12 +1,78 @@
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
import AccordionSummary from "@mui/material/AccordionSummary";
import Typography from "@mui/material/Typography";
import { useAppSelector } from "../../app/hooks";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { useEffect, useState } from "react"; import { useState } from "react";
import CalendarPopover from "../../features/Calendars/CalendarModal"; import CalendarPopover from "../../features/Calendars/CalendarModal";
import { Calendars } from "../../features/Calendars/CalendarTypes"; import { Calendars } from "../../features/Calendars/CalendarTypes";
import MoreVertIcon from "@mui/icons-material/MoreVert"; import MoreVertIcon from "@mui/icons-material/MoreVert";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
function CalendarAccordion({
title,
calendars,
selectedCalendars,
handleToggle,
showAddButton = false,
onAddClick,
defaultExpanded = false,
setOpen,
}: {
title: string;
calendars: string[];
selectedCalendars: string[];
handleToggle: (id: string) => void;
showAddButton?: boolean;
onAddClick?: Function;
defaultExpanded?: boolean;
setOpen: Function;
}) {
const allCalendars = useAppSelector((state) => state.calendars.list);
if (calendars.length === 0) return null;
const [expended, setExpended] = useState(defaultExpanded);
return (
<Accordion defaultExpanded={defaultExpanded} expanded={expended}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`${title}-content`}
id={`${title}-header`}
className="calendarListHeader"
onClick={() => setExpended(!expended)}
>
<Typography component="h3">{title}</Typography>
{showAddButton && (
<IconButton
component="span"
onClick={(e) => {
expended && e.stopPropagation();
onAddClick && onAddClick();
}}
>
<AddIcon />
</IconButton>
)}
</AccordionSummary>
<AccordionDetails>
{calendars.map((id) =>
CalendarSelector(
allCalendars,
id,
selectedCalendars,
handleToggle,
() => setOpen(id)
)
)}
</AccordionDetails>
</Accordion>
);
}
export default function CalendarSelection({ export default function CalendarSelection({
selectedCalendars, selectedCalendars,
@@ -17,6 +83,7 @@ export default function CalendarSelection({
}) { }) {
const userId = useAppSelector((state) => state.user.userData.openpaasId); const userId = useAppSelector((state) => state.user.userData.openpaasId);
const calendars = useAppSelector((state) => state.calendars.list); const calendars = useAppSelector((state) => state.calendars.list);
const personnalCalendars = Object.keys(calendars).filter( const personnalCalendars = Object.keys(calendars).filter(
(id) => id.split("/")[0] === userId (id) => id.split("/")[0] === userId
); );
@@ -26,6 +93,7 @@ export default function CalendarSelection({
const sharedCalendars = Object.keys(calendars).filter( const sharedCalendars = Object.keys(calendars).filter(
(id) => id.split("/")[0] !== userId && !calendars[id].delegated (id) => id.split("/")[0] !== userId && !calendars[id].delegated
); );
const handleCalendarToggle = (name: string) => { const handleCalendarToggle = (name: string) => {
setSelectedCalendars((prev: string[]) => setSelectedCalendars((prev: string[]) =>
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name] prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
@@ -37,67 +105,53 @@ export default function CalendarSelection({
return ( return (
<> <>
<div> <div>
<div className="calendarListHeader"> <CalendarAccordion
<h3>Personnal Calendars</h3> title="Personnal Calendars"
<Button onClick={() => setAnchorElCal(document.body)}> calendars={personnalCalendars}
<AddIcon /> selectedCalendars={selectedCalendars}
</Button> handleToggle={handleCalendarToggle}
</div> showAddButton
{personnalCalendars.map((id) => onAddClick={() => setAnchorElCal(document.body)}
CalendarSelector( setOpen={(id: string) => {
calendars, setAnchorElCal(document.body);
id, setSelectedCalId(id);
selectedCalendars, }}
handleCalendarToggle, defaultExpanded
() => { />
setAnchorElCal(document.body);
setSelectedCalId(id); <CalendarAccordion
} title="Delegated Calendars"
) calendars={delegatedCalendars}
)} selectedCalendars={selectedCalendars}
{delegatedCalendars.length > 0 && ( handleToggle={handleCalendarToggle}
<> defaultExpanded
<span className="calendarListHeader"> setOpen={(id: string) => {
<h3>Delegated Calendars</h3> setAnchorElCal(document.body);
</span> setSelectedCalId(id);
{delegatedCalendars.map((id) => }}
CalendarSelector( />
calendars,
id, <CalendarAccordion
selectedCalendars, title="Other Calendars"
handleCalendarToggle, calendars={sharedCalendars}
() => { selectedCalendars={selectedCalendars}
setAnchorElCal(document.body); showAddButton
setSelectedCalId(id); onAddClick={() => {
} setAnchorElCal(document.body);
) }}
)} handleToggle={handleCalendarToggle}
</> setOpen={(id: string) => {
)} setAnchorElCal(document.body);
{sharedCalendars.length > 0 && ( setSelectedCalId(id);
<> }}
<span className="calendarListHeader"> />
<h3>Shared Calendars</h3>
</span>
{sharedCalendars.map((id) =>
CalendarSelector(
calendars,
id,
selectedCalendars,
handleCalendarToggle,
() => {
setAnchorElCal(document.body);
setSelectedCalId(id);
}
)
)}
</>
)}
</div> </div>
<CalendarPopover <CalendarPopover
anchorEl={anchorElCal} anchorEl={anchorElCal}
open={Boolean(anchorElCal)} open={Boolean(anchorElCal)}
onClose={() => { onClose={() => {
setSelectedCalId("");
setAnchorElCal(null); setAnchorElCal(null);
}} }}
calendar={calendars[selectedCalId] ?? undefined} calendar={calendars[selectedCalId] ?? undefined}