diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index be282fb..93c9a3b 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -20,6 +20,7 @@ import { } from "../../utils/dateUtils"; import { Calendars } from "../../features/Calendars/CalendarTypes"; import { push } from "redux-first-history"; +import EventDisplayModal from "../../features/Events/EventDisplay"; export default function CalendarApp() { const calendarRef = useRef(null); @@ -93,6 +94,11 @@ export default function CalendarApp() { }, [rangeKey, selectedCalendars]); const [anchorEl, setAnchorEl] = useState(null); + const [anchorElEventDisplay, setAnchorElEventDisplay] = + useState(null); + const [openEventDisplay, setOpenEventDisplay] = useState(false); + const [eventDisplayedId, setEventDisplayedId] = useState(""); + const [eventDisplayedCalId, setEventDisplayedCalId] = useState(""); const [anchorElCal, setAnchorElCal] = useState(null); const [selectedRange, setSelectedRange] = useState( null @@ -108,6 +114,10 @@ export default function CalendarApp() { setAnchorEl(null); setSelectedRange(null); }; + const handleCloseEventDisplay = () => { + setAnchorElEventDisplay(null); + setOpenEventDisplay(false); + }; const handleMonthUp = () => { setSelectedMiniDate( @@ -273,6 +283,10 @@ export default function CalendarApp() { window.open(info.event.url); } else { console.log(info.event); + setOpenEventDisplay(true); + setAnchorElEventDisplay(info.el); + setEventDisplayedId(info.event.extendedProps.uid); + setEventDisplayedCalId(info.event.extendedProps.calId); } }} headerToolbar={{ @@ -294,6 +308,13 @@ export default function CalendarApp() { open={Boolean(anchorElCal)} onClose={() => setAnchorElCal(null)} /> + ); diff --git a/src/features/Calendars/CalendarSlice.ts b/src/features/Calendars/CalendarSlice.ts index 1bf82ba..9385665 100644 --- a/src/features/Calendars/CalendarSlice.ts +++ b/src/features/Calendars/CalendarSlice.ts @@ -136,6 +136,8 @@ const CalendarSlice = createSlice({ Object.keys(state.list[action.payload.calId].events).forEach((id) => { state.list[action.payload.calId].events[id].color = state.list[action.payload.calId].color; + state.list[action.payload.calId].events[id].calId = + action.payload.calId; }); } ) diff --git a/src/features/Events/EventDisplay.tsx b/src/features/Events/EventDisplay.tsx new file mode 100644 index 0000000..eb8df5f --- /dev/null +++ b/src/features/Events/EventDisplay.tsx @@ -0,0 +1,221 @@ +import React, { useEffect, useState } from "react"; +import { addEvent } from "../Calendars/CalendarSlice"; +import { CalendarEvent } from "./EventsTypes"; +import { DateSelectArg } from "@fullcalendar/core"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import { + Popover, + TextField, + Button, + Box, + Typography, + Select, + MenuItem, + SelectChangeEvent, + Avatar, + ButtonGroup, + Card, + CardContent, + Divider, + IconButton, +} from "@mui/material"; +import EventModal from "./EventModal"; +import EditIcon from "@mui/icons-material/Edit"; +import DeleteIcon from "@mui/icons-material/Delete"; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import CloseIcon from "@mui/icons-material/Close"; +import VisibilityIcon from "@mui/icons-material/Visibility"; +import CalendarTodayIcon from "@mui/icons-material/CalendarToday"; +import LocationOnIcon from "@mui/icons-material/LocationOn"; +import VideocamIcon from "@mui/icons-material/Videocam"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import CircleIcon from "@mui/icons-material/Circle"; + +function EventDisplayModal({ + eventId, + calId, + anchorEl, + open, + onClose, +}: { + eventId: string; + calId: string; + anchorEl: HTMLElement | null; + open: boolean; + onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void; +}) { + if (calId && eventId) { + const calendar = useAppSelector((state) => state.calendars.list[calId]); + const event = useAppSelector( + (state) => state.calendars.list[calId].events[eventId] + ); + const user = useAppSelector((state) => state.user); + const [showAllAttendees, setShowAllAttendees] = useState(false); + const attendeeDisplayLimit = 3; + + const visibleAttendees = showAllAttendees + ? event.attendee + : event.attendee.slice(0, attendeeDisplayLimit); + console.log(event); + return ( + + + {/* Top-right buttons */} + + + + + + + + onClose({}, "backdropClick")} + > + + + + + + {/* Title */} + {event.title && ( + + {event.title} + + )} + + {/* Time info */} + + {formatDate(event.start)} + {event.end && + ` – ${new Date(event.end).toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + })}`} + + + {event.location && ( + + + {event.location} + + )} + + {/* Description */} + {event.description && ( + + {event.description} + + )} + + {/* Video conference */} + {event.x_openpass_videoconference && ( + + + + Video conference available + + + )} + + {/* Attendees */} + {/* Attendees with toggle */} + {event.attendee?.length > 0 && ( + + Attendees: + {visibleAttendees.map((a, idx) => ( + + {a.cn} + + ))} + + {event.attendee.length > attendeeDisplayLimit && ( + setShowAllAttendees(!showAllAttendees)} + > + {showAllAttendees + ? "Show less" + : `Show more (${ + event.attendee.length - attendeeDisplayLimit + } more)`} + + )} + + )} + + {/* Error */} + {event.error && ( + + + + {event.error} + + + )} + + {/* Colored dot and calendar icon row */} + + + + {calendar.name} + + + + + {/* Attendance options */} + {event.attendee.find( + (person) => person.cal_address === `mailto:${user.userData.email}` + ) && ( + + + Will you attend? + + + + + + + + )} + + + + ); + } else { + return <>; + } +} + +const formatDate = (date: Date) => + new Date(date).toLocaleString(undefined, { + weekday: "long", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + +export default EventDisplayModal; diff --git a/src/features/Events/EventModal.tsx b/src/features/Events/EventModal.tsx index 0dbb61d..c9f828a 100644 --- a/src/features/Events/EventModal.tsx +++ b/src/features/Events/EventModal.tsx @@ -70,6 +70,7 @@ function EventPopover({ const handleSave = async () => { const newEvent: CalendarEvent = { + calId: calendarid, title, start: new Date(start), allday, diff --git a/src/features/Events/EventsTypes.ts b/src/features/Events/EventsTypes.ts index c838ab5..8680709 100644 --- a/src/features/Events/EventsTypes.ts +++ b/src/features/Events/EventsTypes.ts @@ -1,6 +1,7 @@ import { userAttendee, userOrganiser } from "../User/userDataTypes"; export interface CalendarEvent { + calId: string; uid: string; transp?: string; start: Date; // ISO date diff --git a/src/features/User/userDataTypes.ts b/src/features/User/userDataTypes.ts index 86aa6c2..a7fb841 100644 --- a/src/features/User/userDataTypes.ts +++ b/src/features/User/userDataTypes.ts @@ -1,5 +1,8 @@ export interface userData { email: string; + family_name: string; + given_name: string; + name: string; sid: string; sub: string; openpaasId?: string;