[#3] added pretty attendee list and tests + fixed broken tests due to dates
This commit is contained in:
@@ -47,10 +47,10 @@ describe("MiniCalendar", () => {
|
|||||||
|
|
||||||
it("renders mini calendar with today in orange", async () => {
|
it("renders mini calendar with today in orange", async () => {
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
const today = new Date().getDate().toString();
|
const today = new Date();
|
||||||
const todayTile = screen
|
const dateTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
|
||||||
.getAllByText(today)
|
|
||||||
.find((el) => el.tagName.toLowerCase() === "abbr");
|
const todayTile = screen.getByTestId(dateTestId);
|
||||||
expect(todayTile?.parentElement).toHaveClass("today");
|
expect(todayTile?.parentElement).toHaveClass("today");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,13 +65,10 @@ describe("MiniCalendar", () => {
|
|||||||
const date = new Date(sunday);
|
const date = new Date(sunday);
|
||||||
date.setDate(sunday.getDate() + i);
|
date.setDate(sunday.getDate() + i);
|
||||||
const dateTestId = `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
const dateTestId = `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||||
preview.debug();
|
|
||||||
|
|
||||||
const tile = screen.getByTestId(dateTestId);
|
const tile = screen.getByTestId(dateTestId);
|
||||||
|
|
||||||
if (date.getTime() !== today.setHours(0, 0, 0, 0)) {
|
if (date.getTime() !== today.setHours(0, 0, 0, 0)) {
|
||||||
preview.debug();
|
|
||||||
|
|
||||||
expect(tile?.parentElement).toHaveClass("selectedWeek");
|
expect(tile?.parentElement).toHaveClass("selectedWeek");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,10 +81,10 @@ describe("MiniCalendar", () => {
|
|||||||
const dayViewButton = await screen.findByTitle(/day view/i);
|
const dayViewButton = await screen.findByTitle(/day view/i);
|
||||||
fireEvent.click(dayViewButton);
|
fireEvent.click(dayViewButton);
|
||||||
|
|
||||||
const today = new Date().getDate().toString();
|
const today = new Date();
|
||||||
const todayTile = screen
|
const dateTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
|
||||||
.getAllByText(today)
|
|
||||||
.find((el) => el.tagName.toLowerCase() === "abbr");
|
const todayTile = screen.getByTestId(dateTestId);
|
||||||
expect(todayTile?.parentElement).toHaveClass("selectedWeek");
|
expect(todayTile?.parentElement).toHaveClass("selectedWeek");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,23 +161,19 @@ describe("Found Bugs", () => {
|
|||||||
const previousMonthButton = screen.getByText("<");
|
const previousMonthButton = screen.getByText("<");
|
||||||
fireEvent.click(nextMonthButton);
|
fireEvent.click(nextMonthButton);
|
||||||
fireEvent.click(previousMonthButton);
|
fireEvent.click(previousMonthButton);
|
||||||
const shownDay = screen.getByText((content, element) => {
|
|
||||||
return (
|
|
||||||
element?.className.toLowerCase().includes("fc-daygrid-day-number") ??
|
|
||||||
false
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const selectedTile = screen.getByText((content, element) => {
|
const selectedTile = screen.getByText((content, element) => {
|
||||||
return element?.className.includes("selectedWeek") ?? false;
|
return element?.className.includes("selectedWeek") ?? false;
|
||||||
});
|
});
|
||||||
const supposedSelectedTile = screen
|
const ariaLabel = screen.getByRole("columnheader");
|
||||||
.getAllByText((content, element) => {
|
const shownDayDate = new Date(
|
||||||
return element?.tagName.toLowerCase() === "abbr";
|
ariaLabel.getAttribute("data-date") as string
|
||||||
})
|
);
|
||||||
.find((el) => el.innerHTML === shownDay.innerHTML);
|
const dateTestId = `date-${shownDayDate.getFullYear()}-${shownDayDate.getMonth()}-${shownDayDate.getDate()}`;
|
||||||
|
|
||||||
|
const supposedSelectedTile = screen.getByTestId(dateTestId);
|
||||||
|
|
||||||
expect(selectedTile.children[0].innerHTML).toBe(
|
expect(selectedTile.children[0].innerHTML).toBe(
|
||||||
supposedSelectedTile?.innerHTML
|
supposedSelectedTile.parentElement?.children[0]?.innerHTML
|
||||||
);
|
);
|
||||||
expect(supposedSelectedTile?.parentElement).toHaveClass("selectedWeek");
|
expect(supposedSelectedTile?.parentElement).toHaveClass("selectedWeek");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
import EventPopover from "../../../src/features/Events/EventModal";
|
||||||
|
import { DateSelectArg } from "@fullcalendar/core";
|
||||||
|
import preview from "jest-preview";
|
||||||
|
import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils";
|
||||||
|
import EventDisplayModal from "../../../src/features/Events/EventDisplay";
|
||||||
|
|
||||||
|
describe("Event Display", () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
const day = new Date();
|
||||||
|
const RealDateToLocaleString = Date.prototype.toLocaleString;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
organiserData: {
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "mailto:test@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: {
|
||||||
|
list: {
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
id: "667037022b752d0026472254/cal1",
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
id: "event1",
|
||||||
|
title: "Test Event",
|
||||||
|
start: day.toISOString(),
|
||||||
|
end: day.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"otherCal/cal": {
|
||||||
|
id: "otherCal/cal",
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
id: "event1",
|
||||||
|
title: "Test Event Other cal",
|
||||||
|
start: day.toISOString(),
|
||||||
|
end: day.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders correctly event data", () => {
|
||||||
|
jest
|
||||||
|
.spyOn(Date.prototype, "toLocaleString")
|
||||||
|
.mockImplementation(function (
|
||||||
|
this: Date,
|
||||||
|
locales?: Intl.LocalesArgument,
|
||||||
|
options?: Intl.DateTimeFormatOptions | undefined
|
||||||
|
): string {
|
||||||
|
return RealDateToLocaleString.call(this, "en-UK", options);
|
||||||
|
});
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
const weekday = day.toLocaleString("en-UK", { weekday: "long" });
|
||||||
|
const month = day.toLocaleString("en-UK", { month: "long" });
|
||||||
|
const dayOfMonth = day.getDate().toString();
|
||||||
|
|
||||||
|
expect(screen.getByText("Test Event")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(new RegExp(month, "i"))).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(new RegExp(`\\b${dayOfMonth}\\b`))
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText(/\d{2}:\d{2} – \d{2}:\d{2}/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Calendar 1")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByTestId("CloseIcon"));
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("Shows delete button only when calendar is own", () => {
|
||||||
|
// Renders the other cal event
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"otherCal/cal"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument();
|
||||||
|
// Renders the personnal cal event
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("calls delete when Delete clicked", async () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "deleteEventAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("DeleteIcon"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const receivedPayload = spy.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(receivedPayload).toEqual({
|
||||||
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
eventId: "event1",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -165,7 +165,6 @@ describe("EventPopover", () => {
|
|||||||
fireEvent.change(screen.getByLabelText("Location"), {
|
fireEvent.change(screen.getByLabelText("Location"), {
|
||||||
target: { value: newEvent.location },
|
target: { value: newEvent.location },
|
||||||
});
|
});
|
||||||
preview.debug();
|
|
||||||
const spy = jest
|
const spy = jest
|
||||||
.spyOn(eventThunks, "putEventAsync")
|
.spyOn(eventThunks, "putEventAsync")
|
||||||
.mockImplementation((payload) => {
|
.mockImplementation((payload) => {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
CardContent,
|
CardContent,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
@@ -20,6 +22,9 @@ import LocationOnIcon from "@mui/icons-material/LocationOn";
|
|||||||
import VideocamIcon from "@mui/icons-material/Videocam";
|
import VideocamIcon from "@mui/icons-material/Videocam";
|
||||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||||
import CircleIcon from "@mui/icons-material/Circle";
|
import CircleIcon from "@mui/icons-material/Circle";
|
||||||
|
import CancelIcon from "@mui/icons-material/Cancel";
|
||||||
|
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||||
|
import { userAttendee } from "../User/userDataTypes";
|
||||||
|
|
||||||
function EventDisplayModal({
|
function EventDisplayModal({
|
||||||
eventId,
|
eventId,
|
||||||
@@ -34,44 +39,58 @@ function EventDisplayModal({
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||||
}) {
|
}) {
|
||||||
if (calId && eventId) {
|
const dispatch = useAppDispatch();
|
||||||
const dispatch = useAppDispatch();
|
const calendar = useAppSelector((state) => state.calendars.list[calId]);
|
||||||
const calendar = useAppSelector((state) => state.calendars.list[calId]);
|
const event = useAppSelector(
|
||||||
const event = useAppSelector(
|
(state) => state.calendars.list[calId]?.events[eventId]
|
||||||
(state) => state.calendars.list[calId].events[eventId]
|
);
|
||||||
);
|
const user = useAppSelector((state) => state.user);
|
||||||
const user = useAppSelector((state) => state.user);
|
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
|
||||||
const attendeeDisplayLimit = 3;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!event || !calendar) {
|
if (!event || !calendar) {
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
}
|
}
|
||||||
}, [event, calendar, onClose]);
|
}, [event, calendar, onClose]);
|
||||||
|
|
||||||
if (!event || !calendar) return null;
|
if (!event || !calendar) return null;
|
||||||
|
|
||||||
const visibleAttendees = showAllAttendees
|
const attendeeDisplayLimit = 3;
|
||||||
? event.attendee
|
|
||||||
: event.attendee.slice(0, attendeeDisplayLimit);
|
const attendees =
|
||||||
console.log(event);
|
event.attendee?.filter(
|
||||||
return (
|
(a) => a.cal_address !== event.organizer?.cal_address
|
||||||
<Popover open={open} anchorEl={anchorEl} onClose={onClose}>
|
) || [];
|
||||||
<Card sx={{ width: 300, p: 2, position: "relative" }}>
|
|
||||||
{/* Top-right buttons */}
|
const visibleAttendees = showAllAttendees
|
||||||
<Box
|
? attendees
|
||||||
sx={{
|
: attendees.slice(0, attendeeDisplayLimit);
|
||||||
position: "absolute",
|
|
||||||
top: 8,
|
const currentUserAttendee = event.attendee?.find(
|
||||||
right: 8,
|
(person) => person.cal_address === user.userData.email
|
||||||
display: "flex",
|
);
|
||||||
gap: 1,
|
|
||||||
}}
|
const organizer = event.attendee?.find(
|
||||||
>
|
(a) => a.cal_address === event.organizer?.cal_address
|
||||||
<IconButton size="small">
|
);
|
||||||
<EditIcon fontSize="small" />
|
|
||||||
</IconButton>
|
return (
|
||||||
|
<Popover open={open} anchorEl={anchorEl} onClose={onClose}>
|
||||||
|
<Card sx={{ width: 300, p: 2, position: "relative" }}>
|
||||||
|
{/* Top-right buttons */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
display: "flex",
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton size="small">
|
||||||
|
<EditIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
{calendar.id.split("/")[0] === user.userData.openpaasId && (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -81,146 +100,240 @@ function EventDisplayModal({
|
|||||||
>
|
>
|
||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
)}
|
||||||
size="small"
|
<IconButton size="small" onClick={() => onClose({}, "backdropClick")}>
|
||||||
onClick={() => onClose({}, "backdropClick")}
|
<CloseIcon fontSize="small" />
|
||||||
>
|
</IconButton>
|
||||||
<CloseIcon fontSize="small" />
|
</Box>
|
||||||
</IconButton>
|
|
||||||
|
<CardContent sx={{ pt: 1.5 }}>
|
||||||
|
{event.title && (
|
||||||
|
<Typography variant="h6" fontWeight="bold" gutterBottom>
|
||||||
|
{event.title}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Time info*/}
|
||||||
|
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||||
|
{formatDate(event.start)}
|
||||||
|
{event.end &&
|
||||||
|
` – ${new Date(event.end).toLocaleTimeString(undefined, {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})}`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
{event.location && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<LocationOnIcon sx={{ fontSize: 18 }} />}
|
||||||
|
text={event.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Video */}
|
||||||
|
{event.x_openpass_videoconference && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<VideocamIcon sx={{ fontSize: 18 }} />}
|
||||||
|
text="Video conference available"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Attendees */}
|
||||||
|
{event.attendee?.length > 0 && (
|
||||||
|
<Box sx={{ mb: 1 }}>
|
||||||
|
<Typography variant="subtitle2">Attendees:</Typography>
|
||||||
|
{organizer && renderAttendeeBadge(organizer, "org", true)}
|
||||||
|
{visibleAttendees.map((a, idx) =>
|
||||||
|
renderAttendeeBadge(a, idx.toString())
|
||||||
|
)}
|
||||||
|
{attendees.length > attendeeDisplayLimit && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
sx={{ cursor: "pointer", mt: 0.5 }}
|
||||||
|
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
||||||
|
>
|
||||||
|
{showAllAttendees
|
||||||
|
? "Show less"
|
||||||
|
: `Show more (${
|
||||||
|
attendees.length - attendeeDisplayLimit
|
||||||
|
} more)`}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
{event.error && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />}
|
||||||
|
text={event.error}
|
||||||
|
error
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Calendar color dot */}
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 2 }}>
|
||||||
|
<CalendarTodayIcon sx={{ fontSize: 16 }} />
|
||||||
|
<CircleIcon
|
||||||
|
sx={{
|
||||||
|
color: calendar.color ?? "#3788D8",
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2">{calendar.name}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1.5 }}>
|
<Divider sx={{ mb: 1 }} />
|
||||||
{/* Title */}
|
|
||||||
{event.title && (
|
|
||||||
<Typography variant="h6" fontWeight="bold" gutterBottom>
|
|
||||||
{event.title}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Time info */}
|
{/* RSVP */}
|
||||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
{currentUserAttendee && (
|
||||||
{formatDate(event.start)}
|
<Box>
|
||||||
{event.end &&
|
|
||||||
` – ${new Date(event.end).toLocaleTimeString(undefined, {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}`}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{event.location && (
|
|
||||||
<Box
|
|
||||||
sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}
|
|
||||||
>
|
|
||||||
<LocationOnIcon sx={{ fontSize: 18 }} />
|
|
||||||
<Typography variant="body2">{event.location}</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
{event.description && (
|
|
||||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
{event.description}
|
Will you attend?
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
<ButtonGroup size="small" fullWidth>
|
||||||
|
<Button>Accept</Button>
|
||||||
{/* Video conference */}
|
<Button>Maybe</Button>
|
||||||
{event.x_openpass_videoconference && (
|
<Button>Decline</Button>
|
||||||
<Box
|
</ButtonGroup>
|
||||||
sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}
|
|
||||||
>
|
|
||||||
<VideocamIcon sx={{ fontSize: 18 }} />
|
|
||||||
<Typography variant="body2">
|
|
||||||
Video conference available
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Attendees */}
|
|
||||||
{/* Attendees with toggle */}
|
|
||||||
{event.attendee?.length > 0 && (
|
|
||||||
<Box sx={{ mb: 1 }}>
|
|
||||||
<Typography variant="subtitle2">Attendees:</Typography>
|
|
||||||
{visibleAttendees.map((a, idx) => (
|
|
||||||
<Typography key={idx} variant="body2">
|
|
||||||
{a.cn}
|
|
||||||
</Typography>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{event.attendee.length > attendeeDisplayLimit && (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="primary"
|
|
||||||
sx={{ cursor: "pointer", mt: 0.5 }}
|
|
||||||
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
|
||||||
>
|
|
||||||
{showAllAttendees
|
|
||||||
? "Show less"
|
|
||||||
: `Show more (${
|
|
||||||
event.attendee.length - attendeeDisplayLimit
|
|
||||||
} more)`}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Error */}
|
|
||||||
{event.error && (
|
|
||||||
<Box
|
|
||||||
sx={{ display: "flex", alignItems: "center", gap: 1, mt: 1 }}
|
|
||||||
>
|
|
||||||
<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />
|
|
||||||
<Typography variant="body2" color="error">
|
|
||||||
{event.error}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Colored dot and calendar icon row */}
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 2 }}>
|
|
||||||
<CalendarTodayIcon sx={{ fontSize: 16 }} />
|
|
||||||
<CircleIcon
|
|
||||||
sx={{
|
|
||||||
color: calendar.color ?? "#3788D8",
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography variant="body2">{calendar.name}</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<Divider sx={{ mb: 1 }} />
|
{/* Description */}
|
||||||
|
{event.description && (
|
||||||
{/* Attendance options */}
|
<Typography variant="body2" sx={{ mt: 1 }}>
|
||||||
{event.attendee.find(
|
{event.description}
|
||||||
(person) => person.cal_address === user.userData.email
|
</Typography>
|
||||||
) && (
|
)}
|
||||||
<Box>
|
</CardContent>
|
||||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
</Card>
|
||||||
Will you attend?
|
</Popover>
|
||||||
</Typography>
|
);
|
||||||
<ButtonGroup size="small" fullWidth>
|
|
||||||
<Button>Accept</Button>
|
|
||||||
<Button>Maybe</Button>
|
|
||||||
<Button>Decline</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (date: Date) =>
|
function InfoRow({
|
||||||
new Date(date).toLocaleString(undefined, {
|
icon,
|
||||||
|
text,
|
||||||
|
error = false,
|
||||||
|
}: {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
text: string;
|
||||||
|
error?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}>
|
||||||
|
{icon}
|
||||||
|
<Typography variant="body2" color={error ? "error" : "textPrimary"}>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAttendeeBadge(
|
||||||
|
a: userAttendee,
|
||||||
|
key: string,
|
||||||
|
isOrganizer?: boolean
|
||||||
|
) {
|
||||||
|
const statusIcon =
|
||||||
|
a.partstat === "ACCEPTED" ? (
|
||||||
|
<CheckCircleIcon fontSize="inherit" color="success" />
|
||||||
|
) : a.partstat === "DECLINED" ? (
|
||||||
|
<CancelIcon fontSize="inherit" color="error" />
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={key}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1.5,
|
||||||
|
mb: 0.5,
|
||||||
|
p: 0.5,
|
||||||
|
borderRadius: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
overlap="circular"
|
||||||
|
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||||
|
badgeContent={
|
||||||
|
statusIcon && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 0,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: "50%",
|
||||||
|
padding: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{statusIcon}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Avatar {...stringAvatar(a.cn || a.cal_address)} />
|
||||||
|
</Badge>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
noWrap
|
||||||
|
sx={{
|
||||||
|
maxWidth: "180px",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{a.cn || a.cal_address}
|
||||||
|
</Typography>
|
||||||
|
{isOrganizer && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontStyle: "italic" }}
|
||||||
|
>
|
||||||
|
Organizer
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date: Date) {
|
||||||
|
return new Date(date).toLocaleString(undefined, {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToColor(string: string) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
hash = string.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = "#";
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
|
color += `00${value.toString(16)}`.slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringAvatar(name: string) {
|
||||||
|
return {
|
||||||
|
sx: { width: 24, height: 24, fontSize: 18, bgcolor: stringToColor(name) },
|
||||||
|
children: name[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default EventDisplayModal;
|
export default EventDisplayModal;
|
||||||
|
|||||||
Reference in New Issue
Block a user