[#205] added calendar list component (#258)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-10-31 04:44:54 +01:00
committed by GitHub
parent 2686b13885
commit 39d8085766
7 changed files with 71 additions and 62 deletions
@@ -0,0 +1,14 @@
import React from "react";
import { MenuItem } from "@mui/material";
import { Calendars } from "../../features/Calendars/CalendarTypes";
import { CalendarName } from "./CalendarName";
export function CalendarItemList(
userPersonnalCalendars: Calendars[]
): React.ReactNode {
return Object.values(userPersonnalCalendars).map((calendar) => (
<MenuItem key={calendar.id} value={calendar.id}>
<CalendarName calendar={calendar} />
</MenuItem>
));
}
+17
View File
@@ -0,0 +1,17 @@
import { Box, Typography } from "@mui/material";
import { Calendars } from "../../features/Calendars/CalendarTypes";
import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
export function CalendarName({ calendar }: { calendar: Calendars }) {
return (
<Box style={{ display: "flex", flexDirection: "row", gap: 8 }}>
<SquareRoundedIcon
style={{
color: calendar.color?.light ?? "#3788D8",
width: 24,
height: 24,
}}
/>
<Typography sx={{ wordBreak: "break-word" }}>{calendar.name}</Typography>
</Box>
);
}
+4 -7
View File
@@ -12,6 +12,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAppSelector } from "../../app/hooks"; import { useAppSelector } from "../../app/hooks";
import { CalendarItemList } from "./CalendarItemList";
import { SettingsTab } from "./SettingsTab"; import { SettingsTab } from "./SettingsTab";
export function ImportTab({ export function ImportTab({
@@ -40,8 +41,8 @@ export function ImportTab({
const [importFile, setImportFile] = useState<File | null>(null); const [importFile, setImportFile] = useState<File | null>(null);
const [importUrl, setImportUrl] = useState(""); const [importUrl, setImportUrl] = useState("");
const calendars = useAppSelector((state) => state.calendars.list); const calendars = useAppSelector((state) => state.calendars.list);
const personnalCalendars = Object.keys(calendars).filter( const personnalCalendars = Object.values(calendars).filter(
(id) => id.split("/")[0] === userId (cal) => cal.id.split("/")[0] === userId
); );
useEffect(() => { useEffect(() => {
@@ -109,11 +110,7 @@ export function ImportTab({
onChange={(e) => setImportTarget(e.target.value)} onChange={(e) => setImportTarget(e.target.value)}
> >
<MenuItem value="new">New calendar</MenuItem> <MenuItem value="new">New calendar</MenuItem>
{personnalCalendars.map((id) => ( {CalendarItemList(personnalCalendars)}
<MenuItem key={id} value={id}>
{calendars[id].name}
</MenuItem>
))}
</Select> </Select>
</FormControl> </FormControl>
+7 -10
View File
@@ -33,6 +33,7 @@ import {
addVideoConferenceToDescription, addVideoConferenceToDescription,
} from "../../utils/videoConferenceUtils"; } from "../../utils/videoConferenceUtils";
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete"; import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
import { CalendarItemList } from "../Calendar/CalendarItemList";
// Helper component for field with label // Helper component for field with label
export const FieldWithLabel = React.memo( export const FieldWithLabel = React.memo(
@@ -113,8 +114,8 @@ interface EventFormFieldsProps {
setEventClass: (eventClass: string) => void; setEventClass: (eventClass: string) => void;
timezone: string; timezone: string;
setTimezone: (timezone: string) => void; setTimezone: (timezone: string) => void;
calendarid: number; calendarid: string;
setCalendarid: (calendarid: number) => void; setCalendarid: (calendarid: string) => void;
hasVideoConference: boolean; hasVideoConference: boolean;
setHasVideoConference: (hasVideoConference: boolean) => void; setHasVideoConference: (hasVideoConference: boolean) => void;
meetingLink: string | null; meetingLink: string | null;
@@ -144,7 +145,7 @@ interface EventFormFieldsProps {
newStart: string, newStart: string,
newEnd: string newEnd: string
) => void; ) => void;
onCalendarChange?: (newCalendarId: number) => void; onCalendarChange?: (newCalendarId: string) => void;
// Validation // Validation
onValidationChange?: (isValid: boolean) => void; onValidationChange?: (isValid: boolean) => void;
@@ -370,7 +371,7 @@ export default function EventFormFields({
onAllDayChange?.(newAllDay, newStart, newEnd); onAllDayChange?.(newAllDay, newStart, newEnd);
}; };
const handleCalendarChange = (newCalendarId: number) => { const handleCalendarChange = (newCalendarId: string) => {
setCalendarid(newCalendarId); setCalendarid(newCalendarId);
onCalendarChange?.(newCalendarId); onCalendarChange?.(newCalendarId);
}; };
@@ -738,14 +739,10 @@ export default function EventFormFields({
label={!showMore ? "Calendar" : ""} label={!showMore ? "Calendar" : ""}
displayEmpty displayEmpty
onChange={(e: SelectChangeEvent) => onChange={(e: SelectChangeEvent) =>
handleCalendarChange(Number(e.target.value)) handleCalendarChange(e.target.value)
} }
> >
{Object.keys(userPersonnalCalendars).map((calendar, index) => ( {CalendarItemList(userPersonnalCalendars)}
<MenuItem key={index} value={index}>
{userPersonnalCalendars[index].name}
</MenuItem>
))}
</Select> </Select>
</FormControl> </FormControl>
</FieldWithLabel> </FieldWithLabel>
+12 -21
View File
@@ -1,3 +1,4 @@
import { DateSelectArg } from "@fullcalendar/core";
import CalendarTodayIcon from "@mui/icons-material/CalendarToday"; import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
import CircleIcon from "@mui/icons-material/Circle"; import CircleIcon from "@mui/icons-material/Circle";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
@@ -5,17 +6,16 @@ import EditIcon from "@mui/icons-material/Edit";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined"; import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined";
import LockOutlineIcon from "@mui/icons-material/LockOutline";
import MoreVertIcon from "@mui/icons-material/MoreVert"; import MoreVertIcon from "@mui/icons-material/MoreVert";
import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone"; import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone";
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined"; import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
import RepeatIcon from "@mui/icons-material/Repeat";
import SubjectIcon from "@mui/icons-material/Subject"; import SubjectIcon from "@mui/icons-material/Subject";
import VideocamIcon from "@mui/icons-material/Videocam"; import VideocamIcon from "@mui/icons-material/Videocam";
import RepeatIcon from "@mui/icons-material/Repeat"; import { Box, Typography } from "@mui/material";
import LockOutlineIcon from "@mui/icons-material/LockOutline";
import EventPopover from "./EventModal"; import EventPopover from "./EventModal";
import { DateSelectArg } from "@fullcalendar/core";
import { import {
Box,
Button, Button,
Chip, Chip,
DialogActions, DialogActions,
@@ -23,15 +23,13 @@ import {
Menu, Menu,
MenuItem, MenuItem,
Tooltip, Tooltip,
Typography,
} from "@mui/material"; } from "@mui/material";
import AvatarGroup from "@mui/material/AvatarGroup"; import AvatarGroup from "@mui/material/AvatarGroup";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { deleteEventAsync } from "../Calendars/CalendarSlice"; import { CalendarName } from "../../components/Calendar/CalendarName";
import { dlEvent } from "./EventApi"; import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector";
import EventDisplayModal from "./EventDisplay"; import { updateTempCalendar } from "../../components/Calendar/utils/calendarUtils";
import EventUpdateModal from "./EventUpdateModal";
import ResponsiveDialog from "../../components/Dialog/ResponsiveDialog"; import ResponsiveDialog from "../../components/Dialog/ResponsiveDialog";
import { EditModeDialog } from "../../components/Event/EditModeDialog"; import { EditModeDialog } from "../../components/Event/EditModeDialog";
import EventDuplication from "../../components/Event/EventDuplicate"; import EventDuplication from "../../components/Event/EventDuplicate";
@@ -41,10 +39,12 @@ import {
} from "../../components/Event/eventHandlers/eventHandlers"; } from "../../components/Event/eventHandlers/eventHandlers";
import { InfoRow } from "../../components/Event/InfoRow"; import { InfoRow } from "../../components/Event/InfoRow";
import { renderAttendeeBadge } from "../../components/Event/utils/eventUtils"; import { renderAttendeeBadge } from "../../components/Event/utils/eventUtils";
import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector";
import { getCalendarRange } from "../../utils/dateUtils"; import { getCalendarRange } from "../../utils/dateUtils";
import { updateTempCalendar } from "../../components/Calendar/utils/calendarUtils"; import { deleteEventAsync } from "../Calendars/CalendarSlice";
import { dlEvent } from "./EventApi";
import EventDisplayModal from "./EventDisplay";
import { CalendarEvent } from "./EventsTypes"; import { CalendarEvent } from "./EventsTypes";
import EventUpdateModal from "./EventUpdateModal";
export default function EventPreviewModal({ export default function EventPreviewModal({
eventId, eventId,
calId, calId,
@@ -648,16 +648,7 @@ export default function EventPreviewModal({
<Box sx={{ minWidth: "25px", marginRight: 2 }}> <Box sx={{ minWidth: "25px", marginRight: 2 }}>
<CalendarTodayIcon /> <CalendarTodayIcon />
</Box> </Box>
<CircleIcon <CalendarName calendar={calendar} />
style={{
color: calendar.color?.light ?? "#3788D8",
width: 12,
height: 12,
}}
/>
<Typography sx={{ wordBreak: "break-word" }}>
{calendar.name}
</Typography>
</Box> </Box>
</ResponsiveDialog> </ResponsiveDialog>
<EditModeDialog <EditModeDialog
+9 -16
View File
@@ -52,6 +52,7 @@ function EventPopover({
const userId = const userId =
useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
const tempList = useAppSelector((state) => state.calendars.templist); const tempList = useAppSelector((state) => state.calendars.templist);
const calList = useAppSelector((state) => state.calendars.list);
const selectPersonnalCalendars = createSelector( const selectPersonnalCalendars = createSelector(
(state: any) => state.calendars, (state: any) => state.calendars,
(calendars: any) => (calendars: any) =>
@@ -94,9 +95,7 @@ function EventPopover({
const [start, setStart] = useState(event?.start ? event.start : ""); const [start, setStart] = useState(event?.start ? event.start : "");
const [end, setEnd] = useState(event?.end ? event.end : ""); const [end, setEnd] = useState(event?.end ? event.end : "");
const [calendarid, setCalendarid] = useState( const [calendarid, setCalendarid] = useState(
event?.calId event?.calId ?? userPersonnalCalendars[0]?.id
? userPersonnalCalendars.findIndex((e) => e.id === event?.calId)
: 0
); );
const [allday, setAllDay] = useState(event?.allday ?? false); const [allday, setAllDay] = useState(event?.allday ?? false);
const [repetition, setRepetition] = useState<RepetitionObject>( const [repetition, setRepetition] = useState<RepetitionObject>(
@@ -141,7 +140,7 @@ function EventPopover({
setLocation(""); setLocation("");
setStart(""); setStart("");
setEnd(""); setEnd("");
setCalendarid(0); setCalendarid(userPersonnalCalendars[0].id);
setAllDay(false); setAllDay(false);
setRepetition({} as RepetitionObject); setRepetition({} as RepetitionObject);
setAlarm(""); setAlarm("");
@@ -293,13 +292,7 @@ function EventPopover({
setEnd(""); setEnd("");
} }
setCalendarid( setCalendarid(userPersonnalCalendars[0].id);
event.calId
? userPersonnalCalendarsRef.current.findIndex(
(e) => e.id === event.calId
)
: 0
);
setRepetition(event.repetition ?? ({} as RepetitionObject)); setRepetition(event.repetition ?? ({} as RepetitionObject));
setShowRepeat(event.repetition?.freq ? true : false); setShowRepeat(event.repetition?.freq ? true : false);
setAttendees( setAttendees(
@@ -354,7 +347,7 @@ function EventPopover({
setDescription(""); setDescription("");
setAttendees([]); setAttendees([]);
setLocation(""); setLocation("");
setCalendarid(0); setCalendarid(userPersonnalCalendars[0].id);
setAllDay(false); setAllDay(false);
setRepetition({} as RepetitionObject); setRepetition({} as RepetitionObject);
setAlarm(""); setAlarm("");
@@ -468,9 +461,9 @@ function EventPopover({
const newEventUID = crypto.randomUUID(); const newEventUID = crypto.randomUUID();
const newEvent: CalendarEvent = { const newEvent: CalendarEvent = {
calId: userPersonnalCalendars[calendarid].id, calId: calList[calendarid].id,
title, title,
URL: `/calendars/${userPersonnalCalendars[calendarid].id}/${newEventUID}.ics`, URL: `/calendars/${calList[calendarid].id}/${newEventUID}.ics`,
start: new Date(start).toISOString(), start: new Date(start).toISOString(),
allday, allday,
uid: newEventUID, uid: newEventUID,
@@ -491,7 +484,7 @@ function EventPopover({
}, },
], ],
transp: busy, transp: busy,
color: userPersonnalCalendars[calendarid]?.color, color: calList[calendarid]?.color,
alarm: { trigger: alarm, action: "EMAIL" }, alarm: { trigger: alarm, action: "EMAIL" },
x_openpass_videoconference: meetingLink || undefined, x_openpass_videoconference: meetingLink || undefined,
}; };
@@ -515,7 +508,7 @@ function EventPopover({
// Save to API in background // Save to API in background
await dispatch( await dispatch(
putEventAsync({ putEventAsync({
cal: userPersonnalCalendars[calendarid], cal: calList[calendarid],
newEvent, newEvent,
}) })
); );
+8 -8
View File
@@ -52,6 +52,7 @@ function EventUpdateModal({
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tempList = useAppSelector((state) => state.calendars.templist); const tempList = useAppSelector((state) => state.calendars.templist);
const calList = useAppSelector((state) => state.calendars.list);
// Get event from Redux store (cached data) as fallback // Get event from Redux store (cached data) as fallback
const cachedEvent = useAppSelector( const cachedEvent = useAppSelector(
(state) => state.calendars.list[calId]?.events[eventId] (state) => state.calendars.list[calId]?.events[eventId]
@@ -153,7 +154,9 @@ function EventUpdateModal({
resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone) resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone)
); );
const [newCalId, setNewCalId] = useState(calId); const [newCalId, setNewCalId] = useState(calId);
const [calendarid, setCalendarid] = useState(0); const [calendarid, setCalendarid] = useState(
calId ?? userPersonnalCalendars[0]?.id
);
const [attendees, setAttendees] = useState<userAttendee[]>([]); const [attendees, setAttendees] = useState<userAttendee[]>([]);
const [hasVideoConference, setHasVideoConference] = useState(false); const [hasVideoConference, setHasVideoConference] = useState(false);
@@ -171,7 +174,7 @@ function EventUpdateModal({
setLocation(""); setLocation("");
setStart(""); setStart("");
setEnd(""); setEnd("");
setCalendarid(0); setCalendarid(userPersonnalCalendars[0].id);
setAllDay(false); setAllDay(false);
setRepetition({} as RepetitionObject); setRepetition({} as RepetitionObject);
setAlarm(""); setAlarm("");
@@ -235,10 +238,7 @@ function EventUpdateModal({
} }
// Find correct calendar index // Find correct calendar index
const currentCalIndex = userPersonnalCalendars.findIndex( setCalendarid(calId);
(cal) => cal.id === calId
);
setCalendarid(currentCalIndex >= 0 ? currentCalIndex : 0);
// Handle repetition properly - check both current event and base event // Handle repetition properly - check both current event and base event
const baseEventId = event.uid.split("/")[0]; const baseEventId = event.uid.split("/")[0];
@@ -327,7 +327,7 @@ function EventUpdateModal({
const organizer = event.organizer; const organizer = event.organizer;
const targetCalendar = userPersonnalCalendars[calendarid]; const targetCalendar = calList[calendarid];
if (!targetCalendar) { if (!targetCalendar) {
console.error("Target calendar not found"); console.error("Target calendar not found");
return; return;
@@ -747,7 +747,7 @@ function EventUpdateModal({
userPersonnalCalendars={userPersonnalCalendars} userPersonnalCalendars={userPersonnalCalendars}
timezoneList={timezoneList} timezoneList={timezoneList}
onCalendarChange={(newCalendarId) => { onCalendarChange={(newCalendarId) => {
const selectedCalendar = userPersonnalCalendars[newCalendarId]; const selectedCalendar = calList[newCalendarId];
if (selectedCalendar) { if (selectedCalendar) {
setNewCalId(selectedCalendar.id); setNewCalId(selectedCalendar.id);
} }