import CalendarTodayIcon from "@mui/icons-material/CalendarToday"; import CircleIcon from "@mui/icons-material/Circle"; import CloseIcon from "@mui/icons-material/Close"; import EditIcon from "@mui/icons-material/Edit"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone"; import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined"; import SubjectIcon from "@mui/icons-material/Subject"; import VideocamIcon from "@mui/icons-material/Videocam"; import RepeatIcon from "@mui/icons-material/Repeat"; import LockOutlineIcon from "@mui/icons-material/LockOutline"; import { Box, Button, ButtonGroup, Chip, DialogActions, Divider, IconButton, Menu, MenuItem, Tooltip, Typography, } from "@mui/material"; import AvatarGroup from "@mui/material/AvatarGroup"; import { useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { deleteEventAsync } from "../Calendars/CalendarSlice"; import { dlEvent } from "./EventApi"; import EventDisplayModal from "./EventDisplay"; import EventUpdateModal from "./EventUpdateModal"; import ResponsiveDialog from "../../components/Dialog/ResponsiveDialog"; import { EditModeDialog } from "../../components/Event/EditModeDialog"; import EventDuplication from "../../components/Event/EventDuplicate"; import { handleDelete, handleRSVP, } from "../../components/Event/eventHandlers/eventHandlers"; import { InfoRow } from "../../components/Event/InfoRow"; import { renderAttendeeBadge } from "../../components/Event/utils/eventUtils"; import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector"; import { getCalendarRange } from "../../utils/dateUtils"; import { updateTempCalendar } from "../../components/Calendar/utils/calendarUtils"; import { CalendarEvent } from "./EventsTypes"; export default function EventPreviewModal({ eventId, calId, tempEvent, open, onClose, }: { eventId: string; calId: string; tempEvent?: boolean; open: boolean; onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void; }) { const dispatch = useAppDispatch(); const calendars = useAppSelector((state) => state.calendars); const timezone = useAppSelector((state) => state.calendars.timeZone) ?? Intl.DateTimeFormat().resolvedOptions().timeZone; const calendarList = Object.values( useAppSelector((state) => state.calendars.list) ); const calendar = tempEvent ? calendars.templist[calId] : calendars.list[calId]; const event = calendar.events[eventId]; const user = useAppSelector((state) => state.user); const isRecurring = event?.uid?.includes("/"); const isOwn = calendar.ownerEmails?.includes(user.userData.email); const [showAllAttendees, setShowAllAttendees] = useState(false); const [openFullDisplay, setOpenFullDisplay] = useState(false); const [openUpdateModal, setOpenUpdateModal] = useState(false); const [hidePreview, setHidePreview] = useState(false); const [openEditModePopup, setOpenEditModePopup] = useState( null ); const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>( undefined ); const [afterChoiceFunc, setAfterChoiceFunc] = useState(); const [toggleActionMenu, setToggleActionMenu] = useState( null ); const mailSpaUrl = (window as any).MAIL_SPA_URL ?? null; useEffect(() => { if (!event || !calendar) { onClose({}, "backdropClick"); } }, [event, calendar, onClose]); if (!event || !calendar) return null; const attendeeDisplayLimit = 3; const attendees = event.attendee?.filter( (a) => a.cal_address !== event.organizer?.cal_address ) || []; const visibleAttendees = showAllAttendees ? attendees : attendees.slice(0, attendeeDisplayLimit); const currentUserAttendee = event.attendee?.find( (person) => person.cal_address === user.userData.email ); const organizer = event.attendee?.find( (a) => a.cal_address === event.organizer?.cal_address ); const updateTempList = async () => { if (calendars.templist) { const calendarRange = getCalendarRange(new Date(event.start)); await updateTempCalendar( calendars.templist, event, dispatch, calendarRange ); } }; return ( <> onClose({}, "backdropClick")} showHeaderActions={false} style={{ overflow: "auto" }} title={ event.title && ( <> {(window as any).DEBUG && ( { const icsContent = await dlEvent(event); const blob = new Blob([icsContent], { type: "text/calendar", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `${eventId}.ics`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }} > )} {user.userData.email === event.organizer?.cal_address && calendar.ownerEmails?.includes(user.userData.email) && ( { if (isRecurring) { setAfterChoiceFunc(() => () => { setHidePreview(true); setOpenUpdateModal(true); }); setOpenEditModePopup("edit"); } else { setHidePreview(true); setOpenUpdateModal(true); } }} > )} {((event.class !== "PRIVATE" && !isOwn) || isOwn) && ( setToggleActionMenu(e.currentTarget)} > )} setToggleActionMenu(null)} anchorEl={toggleActionMenu} > {mailSpaUrl && attendees.length > 0 && ( window.open( `${mailSpaUrl}/mailto/?uri=mailto:${event.attendee .map((a) => a.cal_address) .filter((mail) => mail !== user.userData.email) .join(",")}?subject=${event.title}` ) } > Email attendees )} {user.userData.email === event.organizer?.cal_address && ( { if (isRecurring) { setAfterChoiceFunc( () => (typeOfAction?: "solo" | "all" | undefined) => handleDelete( isRecurring, typeOfAction, onClose, dispatch, calendar, event, calId, eventId ) ); setOpenEditModePopup("edit"); } else { onClose({}, "backdropClick"); await dispatch( deleteEventAsync({ calId, eventId, eventURL: event.URL, }) ); } updateTempList(); }} > Delete event )} onClose({}, "backdropClick")} > {event.class === "PRIVATE" && (isOwn ? ( ) : ( ))} {event.title} {event.transp === "TRANSPARENT" && ( } label="Free" /> )} {formatDate(event.start, event.allday)} {event.end && formatEnd(event.start, event.end, event.allday) && ` – ${formatEnd(event.start, event.end, event.allday)} ${!event.allday && getTimezoneOffset(timezone)}`} ) } actions={ <> {currentUserAttendee && ( Attending? )} } > {((event.class !== "PRIVATE" && !isOwn) || isOwn) && ( <> {/* Video */} {event.x_openpass_videoconference && ( } content={ } /> )} {/* Attendees */} {attendees?.length > 0 && ( <> } content={ {attendees.length} guests { attendees.filter((a) => a.partstat === "ACCEPTED") .length }{" "} yes,{" "} { attendees.filter((a) => a.partstat === "DECLINED") .length }{" "} no {!showAllAttendees && ( {organizer && renderAttendeeBadge( organizer, "org", showAllAttendees, true )} {visibleAttendees.map((a, idx) => renderAttendeeBadge( a, idx.toString(), showAllAttendees ) )} )} setShowAllAttendees(!showAllAttendees)} > {showAllAttendees ? "Show less" : `Show more `} } /> )} {showAllAttendees && organizer && renderAttendeeBadge(organizer, "org", showAllAttendees, true)} {showAllAttendees && visibleAttendees.map((a, idx) => renderAttendeeBadge(a, idx.toString(), showAllAttendees) )} {/* Location */} {event.location && ( } text={event.location} /> )} {/* Description */} {event.description && ( } text={event.description} /> )} {/* ALARM */} {event.alarm && ( } text={`${event.alarm.trigger} before by ${event.alarm.action}`} /> )} {/* Repetition */} {event.repetition && ( } text={makeRecurrenceString(event)} /> )} )} {event.class === "PRIVATE" && !isOwn && ( This is a private event. Details are hidden. )} {/* Error */} {event.error && ( } text={event.error} error /> )} {/* Calendar color dot */} {calendar.name} {currentUserAttendee && !event.repetition && ( <> )} { setTypeOfAction(type); afterChoiceFunc && afterChoiceFunc(type); }} /> setOpenFullDisplay(false)} eventId={eventId} calId={calId} typeOfAction={typeOfAction} /> { setOpenUpdateModal(false); setHidePreview(false); }} onCloseAll={() => { setOpenUpdateModal(false); onClose({}, "backdropClick"); }} eventId={eventId} calId={calId} typeOfAction={typeOfAction} /> ); } function makeRecurrenceString(event: CalendarEvent): string | undefined { if (!event.repetition) { return; } const recur = [`Reccurent Event · ${event.repetition.freq}`]; const recurType: Record = { daily: "days", monthly: "months", yearly: "years", }; if (event.repetition.byday) { recur.push(`on ${event.repetition.byday.join(", ")}`); } if (event.repetition.interval && event.repetition.interval > 1) { recur.push( `every ${event.repetition.interval} ${recurType[event.repetition.freq]}` ); } if (event.repetition.occurrences) { recur.push(`for ${event.repetition.occurrences} occurences`); } if (event.repetition.endDate) { recur.push(`until ${event.repetition.endDate}`); } return recur.join(", "); } function formatDate(date: Date | string, allday?: boolean) { if (allday) { return new Date(date).toLocaleDateString(undefined, { year: "numeric", month: "long", weekday: "long", day: "numeric", }); } else { return new Date(date).toLocaleString(undefined, { year: "numeric", month: "long", weekday: "long", day: "numeric", hour: "2-digit", minute: "2-digit", }); } } function formatEnd(start: Date | string, end: Date | string, allday?: boolean) { const startDate = new Date(start); const endDate = new Date(end); const sameDay = startDate.getFullYear() === endDate.getFullYear() && startDate.getMonth() === endDate.getMonth() && startDate.getDate() === endDate.getDate(); if (allday) { return sameDay ? null : endDate.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } else { if (sameDay) { return endDate.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", }); } return endDate.toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } }