Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -40,7 +40,7 @@ describe("CalendarSelection", () => {
|
|||||||
"user1/cal1": {
|
"user1/cal1": {
|
||||||
name: "Calendar personnal",
|
name: "Calendar personnal",
|
||||||
id: "user1/cal1",
|
id: "user1/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
@@ -72,7 +72,7 @@ describe("CalendarSelection", () => {
|
|||||||
name: "Calendar delegated",
|
name: "Calendar delegated",
|
||||||
delegated: true,
|
delegated: true,
|
||||||
id: "user2/cal1",
|
id: "user2/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
@@ -103,7 +103,7 @@ describe("CalendarSelection", () => {
|
|||||||
"user3/cal1": {
|
"user3/cal1": {
|
||||||
name: "Calendar shared",
|
name: "Calendar shared",
|
||||||
id: "user3/cal1",
|
id: "user3/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
@@ -292,7 +292,7 @@ describe("calendar Availability search", () => {
|
|||||||
"user1/cal1": {
|
"user1/cal1": {
|
||||||
name: "Calendar personnal",
|
name: "Calendar personnal",
|
||||||
id: "user1/cal1",
|
id: "user1/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {},
|
events: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe("CalendarApp integration", () => {
|
|||||||
"667037022b752d0026472254/cal1": {
|
"667037022b752d0026472254/cal1": {
|
||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FFFFFF", dark: "#000000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
@@ -123,7 +123,7 @@ describe("CalendarApp integration", () => {
|
|||||||
"667037022b752d0026472254/cal1": {
|
"667037022b752d0026472254/cal1": {
|
||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FFFFFF", dark: "#000000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
@@ -157,6 +157,10 @@ describe("CalendarApp integration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders lock icon for private events on the calendar", async () => {
|
it("renders lock icon for private events on the calendar", async () => {
|
||||||
|
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||||
|
configurable: true,
|
||||||
|
value: 120,
|
||||||
|
});
|
||||||
const preloadedState = createPreloadedState({
|
const preloadedState = createPreloadedState({
|
||||||
class: "PRIVATE",
|
class: "PRIVATE",
|
||||||
title: "Private Event",
|
title: "Private Event",
|
||||||
@@ -166,15 +170,16 @@ describe("CalendarApp integration", () => {
|
|||||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||||
preloadedState
|
preloadedState
|
||||||
);
|
);
|
||||||
|
const card = screen.getByTestId("event-card-event1");
|
||||||
const eventEls = screen.getAllByText("Private Event");
|
const lockIcon = within(card).getByTestId("LockOutlineIcon");
|
||||||
const found = eventEls.some((eventEl) =>
|
expect(lockIcon).toBeInTheDocument();
|
||||||
within(eventEl.parentElement as HTMLElement).queryByTestId("lock-icon")
|
|
||||||
);
|
|
||||||
expect(found).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders lock icon for confidential events on the calendar", async () => {
|
it("renders lock icon for confidential events on the calendar", async () => {
|
||||||
|
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||||
|
configurable: true,
|
||||||
|
value: 120,
|
||||||
|
});
|
||||||
const preloadedState = createPreloadedState({
|
const preloadedState = createPreloadedState({
|
||||||
class: "CONFIDENTIAL",
|
class: "CONFIDENTIAL",
|
||||||
title: "Confidential Event",
|
title: "Confidential Event",
|
||||||
@@ -185,14 +190,16 @@ describe("CalendarApp integration", () => {
|
|||||||
preloadedState
|
preloadedState
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventEls = screen.getAllByText("Confidential Event");
|
const card = screen.getByTestId("event-card-event1");
|
||||||
const found = eventEls.some((eventEl) =>
|
const lockIcon = within(card).getByTestId("LockOutlineIcon");
|
||||||
within(eventEl.parentElement as HTMLElement).queryByTestId("lock-icon")
|
expect(lockIcon).toBeInTheDocument();
|
||||||
);
|
|
||||||
expect(found).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does NOT render a lock icon for public events on the calendar", async () => {
|
it("does NOT render a lock icon for public events on the calendar", async () => {
|
||||||
|
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||||
|
configurable: true,
|
||||||
|
value: 120,
|
||||||
|
});
|
||||||
const preloadedState = createPreloadedState({
|
const preloadedState = createPreloadedState({
|
||||||
class: "PUBLIC",
|
class: "PUBLIC",
|
||||||
title: "Public Event",
|
title: "Public Event",
|
||||||
@@ -203,11 +210,9 @@ describe("CalendarApp integration", () => {
|
|||||||
preloadedState
|
preloadedState
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventEls = screen.getAllByText("Public Event");
|
const card = screen.getByTestId("event-card-event1");
|
||||||
const found = eventEls.some((eventEl) =>
|
const lockIcon = within(card).queryByTestId("LockOutlineIcon");
|
||||||
within(eventEl.parentElement as HTMLElement).queryByTestId("lock-icon")
|
expect(lockIcon).not.toBeInTheDocument();
|
||||||
);
|
|
||||||
expect(found).toBe(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does render a title for events without any attendees or user as organizer", async () => {
|
it("does render a title for events without any attendees or user as organizer", async () => {
|
||||||
@@ -229,7 +234,7 @@ describe("CalendarApp integration", () => {
|
|||||||
"667037022b752d0026472254/cal1": {
|
"667037022b752d0026472254/cal1": {
|
||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["alice@example.com"],
|
ownerEmails: ["alice@example.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("MiniCalendar", () => {
|
|||||||
list: {
|
list: {
|
||||||
"667037022b752d0026472254/cal1": {
|
"667037022b752d0026472254/cal1": {
|
||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
color: "#FF0000",
|
color: { light: "#FF0000", dark: "#000" },
|
||||||
ownerEmails: ["test@test.com"],
|
ownerEmails: ["test@test.com"],
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
|
|||||||
@@ -50,19 +50,12 @@
|
|||||||
.sidebar:hover
|
.sidebar:hover
|
||||||
scrollbar-color: rgba(0, 0, 0, 0.5) transparent
|
scrollbar-color: rgba(0, 0, 0, 0.5) transparent
|
||||||
|
|
||||||
.declined-event
|
// .declined-event
|
||||||
opacity 0.7
|
// opacity 0.7
|
||||||
|
|
||||||
.needs-action-event
|
// .needs-action-event
|
||||||
opacity 0.7
|
// opacity 0.7
|
||||||
border dashed 1px #ffffff
|
// border dashed 1px #ffffff
|
||||||
|
|
||||||
.fc-event-main span,
|
|
||||||
.fc-daygrid-event div
|
|
||||||
display block !important
|
|
||||||
white-space nowrap !important
|
|
||||||
overflow hidden !important
|
|
||||||
text-overflow ellipsis !important
|
|
||||||
|
|
||||||
[data-theme="dark"] .fc-timegrid-slot::after
|
[data-theme="dark"] .fc-timegrid-slot::after
|
||||||
border-top 1px dotted rgba(255, 255, 255, 0.2)
|
border-top 1px dotted rgba(255, 255, 255, 0.2)
|
||||||
|
|||||||
@@ -251,37 +251,26 @@ tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller
|
|||||||
|
|
||||||
/* FullCalendar "more" popover restyle */
|
/* FullCalendar "more" popover restyle */
|
||||||
|
|
||||||
.fc-daygrid-day-events
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
height 70%
|
|
||||||
|
|
||||||
.fc-daygrid-day-bottom
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
font-weight 500
|
|
||||||
font-size 12px
|
|
||||||
line-height 16px
|
|
||||||
letter-spacing 0.5px
|
|
||||||
color #6D7885
|
|
||||||
|
|
||||||
.fc-more-popover
|
.fc-more-popover
|
||||||
z-index 700 !important
|
z-index 700 !important
|
||||||
border-radius 4px
|
border-radius 8px
|
||||||
background #fff
|
background #fff
|
||||||
overflow hidden
|
overflow hidden
|
||||||
font-family Inter
|
font-family Inter, sans-serif
|
||||||
width 244px
|
width 244px
|
||||||
|
box-shadow 0 4px 16px rgba(0,0,0,0.1)
|
||||||
|
|
||||||
.fc-popover-header
|
.fc-popover-header
|
||||||
background #FCFCFC
|
background #FCFCFC
|
||||||
|
display flex
|
||||||
justify-content space-between
|
justify-content space-between
|
||||||
|
align-items center
|
||||||
width 244px
|
width 244px
|
||||||
height 40px
|
height 40px
|
||||||
padding 12px
|
padding 12px
|
||||||
|
border-bottom 1px solid #eee
|
||||||
|
|
||||||
.fc-popover-title
|
.fc-popover-title
|
||||||
font-family Inter
|
|
||||||
font-weight 500
|
font-weight 500
|
||||||
font-size 14px
|
font-size 14px
|
||||||
color #243B55
|
color #243B55
|
||||||
@@ -290,36 +279,66 @@ tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller
|
|||||||
|
|
||||||
.fc-popover-close
|
.fc-popover-close
|
||||||
color #605D62
|
color #605D62
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
.fc-popover-body
|
.fc-popover-body
|
||||||
max-height 208px
|
max-height 208px
|
||||||
overflow auto
|
overflow auto
|
||||||
padding 0 !important
|
padding 0 !important
|
||||||
|
background #fff
|
||||||
|
|
||||||
|
.MuiCard-root
|
||||||
|
box-shadow none !important
|
||||||
|
border none !important
|
||||||
|
border-radius 8px
|
||||||
|
background-color transparent !important
|
||||||
|
margin 2px 6px
|
||||||
|
transition background 0.15s ease
|
||||||
|
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background #f7f7f8
|
||||||
|
|
||||||
|
.MuiCardHeader-root
|
||||||
|
padding 4px 8px
|
||||||
|
.MuiTypography-root
|
||||||
|
white-space nowrap
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
font-family: Inter !important
|
||||||
|
font-weight: 400 !important
|
||||||
|
font-style: Regular !important
|
||||||
|
font-size: 14px !important
|
||||||
|
line-height: 20px !important
|
||||||
|
letter-spacing: 0.25px !important
|
||||||
|
vertical-align: middle !important
|
||||||
|
color #1C1B1F !important
|
||||||
|
.compactStartTime
|
||||||
|
display none
|
||||||
|
|
||||||
|
.MuiCardContent-root
|
||||||
|
padding 0 8px 4px 8px
|
||||||
|
font-size 12px
|
||||||
|
color #6D7885
|
||||||
|
line-height 16px
|
||||||
|
|
||||||
|
/* Hide FullCalendar’s extra margin block */
|
||||||
.fc-more-popover-misc
|
.fc-more-popover-misc
|
||||||
height 0 !important
|
height 0 !important
|
||||||
margin 0 !important
|
margin 0 !important
|
||||||
padding 0 !important
|
padding 0 !important
|
||||||
.fc-daygrid-event
|
|
||||||
&:hover
|
|
||||||
background #f7f7f8
|
|
||||||
div
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
padding 4px 6px 4px 12px
|
|
||||||
border-radius 8px
|
|
||||||
color #1C1B1F!important
|
|
||||||
background-color transparent!important
|
|
||||||
border none
|
|
||||||
color #334155
|
|
||||||
font-size 14px
|
|
||||||
font-weight 400
|
|
||||||
line-height 20px
|
|
||||||
letter-spacing 0.25px
|
|
||||||
vertical-align middle
|
|
||||||
transition background 0.15s ease, color 0.15s ease
|
|
||||||
|
|
||||||
span
|
// Custom event chips
|
||||||
white-space nowrap
|
.fc-event
|
||||||
overflow hidden
|
background-color transparent
|
||||||
text-overflow ellipsis
|
border none
|
||||||
|
width 95%
|
||||||
|
height 100%
|
||||||
|
align-content left
|
||||||
|
.fc-event-main
|
||||||
|
display flex
|
||||||
|
flex-direction row
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
.fc-timegrid-event-harness-inset .fc-timegrid-event
|
||||||
|
box-shadow none
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { CalendarApi, NowIndicatorContentArg } from "@fullcalendar/core";
|
import { CalendarApi, NowIndicatorContentArg } from "@fullcalendar/core";
|
||||||
import { updateSlotLabelVisibility } from "../utils/calendarUtils";
|
|
||||||
import { createMouseHandlers } from "./mouseHandlers";
|
import { createMouseHandlers } from "./mouseHandlers";
|
||||||
import { userAttendee } from "../../../features/User/userDataTypes";
|
import { userAttendee } from "../../../features/User/userDataTypes";
|
||||||
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
||||||
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
|
||||||
import AccessTimeIcon from "@mui/icons-material/AccessTime";
|
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
|
||||||
import { EventErrorHandler } from "../../Error/EventErrorHandler";
|
import { EventErrorHandler } from "../../Error/EventErrorHandler";
|
||||||
|
import { EventChip } from "../../Event/EventChip/EventChip";
|
||||||
|
|
||||||
export interface ViewHandlersProps {
|
export interface ViewHandlersProps {
|
||||||
calendarRef: React.RefObject<CalendarApi | null>;
|
calendarRef: React.RefObject<CalendarApi | null>;
|
||||||
@@ -108,79 +105,12 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEventContent = (arg: any) => {
|
const handleEventContent = (arg: any) => {
|
||||||
const event = arg.event;
|
return React.createElement(EventChip, {
|
||||||
const props = event._def.extendedProps;
|
arg,
|
||||||
const {
|
calendars,
|
||||||
calId,
|
tempcalendars,
|
||||||
temp,
|
errorHandler,
|
||||||
attendee: attendees = [],
|
});
|
||||||
class: classification,
|
|
||||||
} = props;
|
|
||||||
try {
|
|
||||||
const calendarsSource = temp ? tempcalendars : calendars;
|
|
||||||
const calendar = calendarsSource[calId];
|
|
||||||
if (!calendar) return null;
|
|
||||||
|
|
||||||
const isPrivate = ["PRIVATE", "CONFIDENTIAL"].includes(classification);
|
|
||||||
const ownerEmails = new Set(
|
|
||||||
calendar.ownerEmails?.map((e) => e.toLowerCase())
|
|
||||||
);
|
|
||||||
const delegated = calendar.delegated;
|
|
||||||
let Icon = null;
|
|
||||||
let titleStyle: React.CSSProperties = {
|
|
||||||
color: event._def.extendedProps.colors?.dark,
|
|
||||||
};
|
|
||||||
|
|
||||||
const showSpecialDisplay = attendees.filter((att: userAttendee) =>
|
|
||||||
ownerEmails.has(att.cal_address.toLowerCase())
|
|
||||||
);
|
|
||||||
// if no special display
|
|
||||||
if (attendees.length && !delegated && !showSpecialDisplay.length) {
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
{ style: { display: "flex", alignItems: "center" } },
|
|
||||||
React.createElement("span", null, event.title)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (showSpecialDisplay?.[0]?.partstat) {
|
|
||||||
case "DECLINED":
|
|
||||||
Icon = null;
|
|
||||||
titleStyle.textDecoration = "line-through";
|
|
||||||
break;
|
|
||||||
case "TENTATIVE":
|
|
||||||
Icon = HelpOutlineIcon;
|
|
||||||
break;
|
|
||||||
case "NEEDS-ACTION":
|
|
||||||
Icon = AccessTimeIcon;
|
|
||||||
break;
|
|
||||||
case "ACCEPTED":
|
|
||||||
Icon = null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RenderEventTitle(
|
|
||||||
titleStyle,
|
|
||||||
isPrivate,
|
|
||||||
Icon,
|
|
||||||
arg,
|
|
||||||
event,
|
|
||||||
calendar
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
const message =
|
|
||||||
e instanceof Error ? e.message : "Unknown error during rendering";
|
|
||||||
errorHandler.reportError(
|
|
||||||
event._def.extendedProps.uid || event.id,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
{ style: { display: "flex", alignItems: "center" } },
|
|
||||||
React.createElement("span", null, event.title)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEventDidMount = (arg: any) => {
|
const handleEventDidMount = (arg: any) => {
|
||||||
@@ -228,65 +158,3 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
|
|||||||
handleEventDidMount,
|
handleEventDidMount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function RenderEventTitle(
|
|
||||||
titleStyle: React.CSSProperties,
|
|
||||||
isPrivate: boolean,
|
|
||||||
Icon: any,
|
|
||||||
arg: any,
|
|
||||||
event: any,
|
|
||||||
calendar: Calendars
|
|
||||||
): React.ReactNode {
|
|
||||||
const isMonthView = arg.view.type === "dayGridMonth";
|
|
||||||
const startTime = event._instance.range.start.toLocaleTimeString(undefined, {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
|
|
||||||
const icons: React.ReactNode[] = [];
|
|
||||||
if (isPrivate) {
|
|
||||||
icons.push(
|
|
||||||
React.createElement(LockIcon, {
|
|
||||||
"data-testid": "lock-icon",
|
|
||||||
fontSize: "small",
|
|
||||||
style: { marginRight: "4px" },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (Icon) {
|
|
||||||
icons.push(
|
|
||||||
React.createElement(Icon, {
|
|
||||||
fontSize: "small",
|
|
||||||
style: { marginRight: "4px" },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerStyle: React.CSSProperties = isMonthView
|
|
||||||
? {
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: calendar.color?.light,
|
|
||||||
color: "white",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "1px",
|
|
||||||
width: "100%",
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
};
|
|
||||||
|
|
||||||
const contentText =
|
|
||||||
isMonthView && !event._def.extendedProps.allday
|
|
||||||
? `${startTime} ${event.title}`
|
|
||||||
: event.title;
|
|
||||||
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
{ style: containerStyle },
|
|
||||||
...icons,
|
|
||||||
React.createElement("span", { style: titleStyle }, contentText)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ export const eventToFullCalendarFormat = (
|
|||||||
.concat(filteredTempEvents.map((e) => ({ ...e, temp: true })))
|
.concat(filteredTempEvents.map((e) => ({ ...e, temp: true })))
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
if (e.calId.split("/")[0] === userId) {
|
if (e.calId.split("/")[0] === userId) {
|
||||||
return { ...e, colors: e.color, color: e.color?.light, editable: true };
|
return { ...e, colors: e.color, editable: true };
|
||||||
}
|
}
|
||||||
return { ...e, colors: e.color, color: e.color?.light, editable: false };
|
return { ...e, colors: e.color, editable: false };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, Typography } from "@mui/material";
|
||||||
|
import { EventErrorHandler } from "../../Error/EventErrorHandler";
|
||||||
|
|
||||||
|
export function ErrorEventChip({
|
||||||
|
event,
|
||||||
|
errorHandler,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
event: any;
|
||||||
|
errorHandler: EventErrorHandler;
|
||||||
|
error: any;
|
||||||
|
}) {
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : "Unknown error during rendering";
|
||||||
|
errorHandler.reportError(event._def.extendedProps.uid || event.id, message);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
px: 0.5,
|
||||||
|
py: 0.2,
|
||||||
|
borderRadius: "4px",
|
||||||
|
boxShadow: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2">{event.title}</Typography>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { stringAvatar } from "../utils/eventUtils";
|
||||||
|
import { ErrorEventChip } from "./ErrorEventChip";
|
||||||
|
import {
|
||||||
|
DisplayedIcons,
|
||||||
|
EventChipProps,
|
||||||
|
getBestColor,
|
||||||
|
getCardStyle,
|
||||||
|
getEventDuration,
|
||||||
|
getEventTimes,
|
||||||
|
getOwnerAttendee,
|
||||||
|
getTitleStyle,
|
||||||
|
IconDisplayConfig,
|
||||||
|
} from "./EventChipUtils";
|
||||||
|
import { SimpleEventChip } from "./SimpleEventChip";
|
||||||
|
|
||||||
|
const COMPACT_WIDTH_THRESHOLD = 100;
|
||||||
|
const PRIVATE_CLASSIFICATIONS = ["PRIVATE", "CONFIDENTIAL"];
|
||||||
|
|
||||||
|
export const EVENT_DURATION = {
|
||||||
|
SHORT: 15,
|
||||||
|
MEDIUM: 30,
|
||||||
|
LONG: 60,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function useCompactMode(
|
||||||
|
cardRef: React.RefObject<HTMLDivElement | null>
|
||||||
|
): boolean {
|
||||||
|
const [showCompact, setShowCompact] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkWidth = () => {
|
||||||
|
if (cardRef.current) {
|
||||||
|
const width = cardRef.current.offsetWidth;
|
||||||
|
setShowCompact(width < COMPACT_WIDTH_THRESHOLD);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkWidth();
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(checkWidth);
|
||||||
|
if (cardRef.current) {
|
||||||
|
resizeObserver.observe(cardRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return showCompact;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventChip({
|
||||||
|
arg,
|
||||||
|
calendars,
|
||||||
|
tempcalendars,
|
||||||
|
errorHandler,
|
||||||
|
}: EventChipProps) {
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null);
|
||||||
|
const showCompact = useCompactMode(cardRef);
|
||||||
|
|
||||||
|
const event = arg.event;
|
||||||
|
const props = event._def.extendedProps;
|
||||||
|
const {
|
||||||
|
calId,
|
||||||
|
temp,
|
||||||
|
attendee: attendees = [],
|
||||||
|
class: classification,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Calendar validation
|
||||||
|
const calendarsSource = temp ? tempcalendars : calendars;
|
||||||
|
const calendar = calendarsSource[calId];
|
||||||
|
if (!calendar) return null;
|
||||||
|
|
||||||
|
// Event properties
|
||||||
|
const isPrivate = PRIVATE_CLASSIFICATIONS.includes(classification);
|
||||||
|
const ownerEmails = new Set(
|
||||||
|
calendar.ownerEmails?.map((e) => e.toLowerCase())
|
||||||
|
);
|
||||||
|
const delegated = calendar.delegated;
|
||||||
|
|
||||||
|
// Determine owner attendee
|
||||||
|
const ownerAttendee = getOwnerAttendee(attendees, ownerEmails);
|
||||||
|
|
||||||
|
// Handle non-owner attendee case
|
||||||
|
if (attendees.length && !delegated && !ownerAttendee) {
|
||||||
|
return <SimpleEventChip title={event.title} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color and contrast logic
|
||||||
|
const bestColor = getBestColor(
|
||||||
|
(calendar.color as { light: string; dark: string }) ?? {
|
||||||
|
light: "#fff",
|
||||||
|
dark: "#000",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Icon display configuration
|
||||||
|
const IconDisplayed: IconDisplayConfig = {
|
||||||
|
declined: ownerAttendee?.partstat === "DECLINED",
|
||||||
|
tentative: ownerAttendee?.partstat === "TENTATIVE",
|
||||||
|
needAction: ownerAttendee?.partstat === "NEEDS-ACTION",
|
||||||
|
private: isPrivate,
|
||||||
|
};
|
||||||
|
|
||||||
|
// View and time calculations
|
||||||
|
const isMonthView = arg.view.type === "dayGridMonth";
|
||||||
|
const timeZone = arg.view.calendar?.getOption("timeZone") || "UTC";
|
||||||
|
const { startTime, endTime } = getEventTimes(event, timeZone);
|
||||||
|
const eventLength = getEventDuration(event);
|
||||||
|
|
||||||
|
const isMoreThan15 = eventLength > EVENT_DURATION.SHORT;
|
||||||
|
const isMoreThan30 = eventLength > EVENT_DURATION.MEDIUM;
|
||||||
|
const isMoreThan60 = eventLength > EVENT_DURATION.LONG;
|
||||||
|
|
||||||
|
// Style calculation
|
||||||
|
const titleStyle = getTitleStyle(
|
||||||
|
bestColor,
|
||||||
|
ownerAttendee?.partstat,
|
||||||
|
calendar
|
||||||
|
);
|
||||||
|
const cardStyle = getCardStyle(
|
||||||
|
bestColor,
|
||||||
|
eventLength,
|
||||||
|
ownerAttendee?.partstat,
|
||||||
|
calendar
|
||||||
|
);
|
||||||
|
|
||||||
|
// Organizer avatar
|
||||||
|
const OrganizerAvatar = event._def.extendedProps.organizer
|
||||||
|
? stringAvatar(
|
||||||
|
event._def.extendedProps.organizer?.cn ??
|
||||||
|
event._def.extendedProps.organizer?.cal_address
|
||||||
|
)
|
||||||
|
: { style: {}, children: null };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
style={
|
||||||
|
!isMoreThan15 || isMonthView
|
||||||
|
? { ...cardStyle, height: "auto" }
|
||||||
|
: { ...cardStyle }
|
||||||
|
}
|
||||||
|
ref={cardRef}
|
||||||
|
data-testid={`event-card-${event._def.extendedProps.uid}`}
|
||||||
|
>
|
||||||
|
<CardHeader
|
||||||
|
sx={{
|
||||||
|
py: "0px",
|
||||||
|
px: "0px",
|
||||||
|
"& .MuiCardHeader-content": {
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
title={
|
||||||
|
showCompact ? (
|
||||||
|
<Typography variant="body2" style={titleStyle}>
|
||||||
|
{event.title}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", alignItems: "center", minWidth: 0 }}
|
||||||
|
>
|
||||||
|
{(!isMoreThan30 || isMonthView) &&
|
||||||
|
!event._def.extendedProps.allday && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
className="compactStartTime"
|
||||||
|
style={{
|
||||||
|
...titleStyle,
|
||||||
|
textDecoration: "none",
|
||||||
|
overflow: "visible",
|
||||||
|
opacity: "70%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
fontSize: "10px",
|
||||||
|
marginRight: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{startTime}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{DisplayedIcons(IconDisplayed)}
|
||||||
|
<Typography variant="body2" noWrap style={titleStyle}>
|
||||||
|
{event.title}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
subheader={
|
||||||
|
isMoreThan30 &&
|
||||||
|
!isMonthView &&
|
||||||
|
!event._def.extendedProps.allday && (
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
color: titleStyle.color,
|
||||||
|
opacity: "70%",
|
||||||
|
fontFamily: "Inter",
|
||||||
|
fontWeight: "500",
|
||||||
|
fontSize: "10px",
|
||||||
|
lineHeight: "16px",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{startTime} {!showCompact && ` - ${endTime}`}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{isMoreThan60 &&
|
||||||
|
!showCompact &&
|
||||||
|
!isMonthView &&
|
||||||
|
!event._def.extendedProps.allday && (
|
||||||
|
<CardContent
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
"& .MuiCardContent-content": {
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event._def.extendedProps.location && (
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
marginRight: 2,
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
fontWeight: "500",
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "16px",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
color: titleStyle.color,
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event._def.extendedProps.location}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Box sx={{ display: "flex", alignItems: "flex-start", mt: 0.5 }}>
|
||||||
|
{event._def.extendedProps.description && (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "10px",
|
||||||
|
lineHeight: "16px",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
opacity: 0.8,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
display: "-webkit-box",
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: "vertical",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: "75%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event._def.extendedProps.description}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
{(isMoreThan60 || eventLength === 60) &&
|
||||||
|
!isMonthView &&
|
||||||
|
!event._def.extendedProps.allday &&
|
||||||
|
event._def.extendedProps.organizer &&
|
||||||
|
!showCompact &&
|
||||||
|
(window as any).displayOrgAvatar && (
|
||||||
|
<Avatar
|
||||||
|
children={OrganizerAvatar.children}
|
||||||
|
style={{
|
||||||
|
...OrganizerAvatar.style,
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
margin: "8px",
|
||||||
|
position: "absolute",
|
||||||
|
border: "2px solid white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return (
|
||||||
|
<ErrorEventChip event={event} errorHandler={errorHandler} error={e} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import CancelIcon from "@mui/icons-material/Cancel";
|
||||||
|
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
||||||
|
import LockOutlineIcon from "@mui/icons-material/LockOutline";
|
||||||
|
import { Box, getContrastRatio } from "@mui/material";
|
||||||
|
import moment from "moment";
|
||||||
|
import React from "react";
|
||||||
|
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
||||||
|
import { userAttendee } from "../../../features/User/userDataTypes";
|
||||||
|
import { EventErrorHandler } from "../../Error/EventErrorHandler";
|
||||||
|
import { EVENT_DURATION } from "./EventChip";
|
||||||
|
|
||||||
|
export interface EventChipProps {
|
||||||
|
arg: any;
|
||||||
|
calendars: Record<string, Calendars>;
|
||||||
|
tempcalendars: Record<string, Calendars>;
|
||||||
|
errorHandler: EventErrorHandler;
|
||||||
|
}
|
||||||
|
export interface IconDisplayConfig {
|
||||||
|
declined: boolean;
|
||||||
|
tentative: boolean;
|
||||||
|
needAction: boolean;
|
||||||
|
private: boolean;
|
||||||
|
}
|
||||||
|
export function getEventDuration(event: any): number {
|
||||||
|
return moment(event._instance.range.end).diff(
|
||||||
|
moment(event._instance.range.start),
|
||||||
|
"minutes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function getBestColor(colors: { light: string; dark: string }): string {
|
||||||
|
const contrastToDark = getContrastRatio(colors?.dark, "#fff");
|
||||||
|
const contrastToLight = getContrastRatio(colors?.light, "#fff");
|
||||||
|
return contrastToDark > contrastToLight ? colors?.dark : colors?.light;
|
||||||
|
}
|
||||||
|
export function getEventTimes(event: any, timeZone: string) {
|
||||||
|
return {
|
||||||
|
startTime: moment.tz(event.start, timeZone).format("HH:mm"),
|
||||||
|
endTime: moment.tz(event.end, timeZone).format("HH:mm"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function getOwnerAttendee(
|
||||||
|
attendees: userAttendee[],
|
||||||
|
ownerEmails: Set<string>
|
||||||
|
): userAttendee | undefined {
|
||||||
|
return attendees.find((att) =>
|
||||||
|
ownerEmails.has(att.cal_address.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTitleStyle(
|
||||||
|
bestColor: string,
|
||||||
|
partstat?: string,
|
||||||
|
calendar?: any
|
||||||
|
): React.CSSProperties {
|
||||||
|
const baseStyle: React.CSSProperties = {
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
fontWeight: "500",
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "16px",
|
||||||
|
letterSpacing: "0.5px",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
font: "Roboto",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
color: bestColor,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (partstat) {
|
||||||
|
case "DECLINED":
|
||||||
|
return { ...baseStyle, textDecoration: "line-through" };
|
||||||
|
case "TENTATIVE":
|
||||||
|
case "ACCEPTED":
|
||||||
|
return { ...baseStyle, color: calendar?.color?.dark };
|
||||||
|
case "NEEDS-ACTION":
|
||||||
|
return baseStyle;
|
||||||
|
default:
|
||||||
|
return baseStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventVariant(duration: number) {
|
||||||
|
if (duration <= EVENT_DURATION.SHORT) return "short";
|
||||||
|
if (duration <= EVENT_DURATION.MEDIUM) return "medium";
|
||||||
|
if (duration <= EVENT_DURATION.LONG) return "long";
|
||||||
|
return "extraLong";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCardVariantStyle(
|
||||||
|
variant: string,
|
||||||
|
baseColor: string
|
||||||
|
): React.CSSProperties {
|
||||||
|
const shared: React.CSSProperties = {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
borderRadius: "8px",
|
||||||
|
boxShadow: "none",
|
||||||
|
border: `1px solid ${baseColor}`,
|
||||||
|
color: baseColor,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (variant) {
|
||||||
|
case "short":
|
||||||
|
return { ...shared, padding: "0px 6px", justifyContent: "center" };
|
||||||
|
case "medium":
|
||||||
|
return { ...shared, padding: "4px 6px", justifyContent: "center" };
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
...shared,
|
||||||
|
padding: "4px 6px",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCardStyle(
|
||||||
|
bestColor: string,
|
||||||
|
eventLength: number,
|
||||||
|
partstat?: string,
|
||||||
|
calendar?: any
|
||||||
|
): React.CSSProperties {
|
||||||
|
const baseStyle: React.CSSProperties = getCardVariantStyle(
|
||||||
|
getEventVariant(eventLength),
|
||||||
|
bestColor
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (partstat) {
|
||||||
|
case "DECLINED":
|
||||||
|
return baseStyle;
|
||||||
|
case "TENTATIVE":
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
backgroundColor: calendar?.color?.light,
|
||||||
|
color: calendar?.color?.dark,
|
||||||
|
border: "1px solid white",
|
||||||
|
};
|
||||||
|
case "NEEDS-ACTION":
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
};
|
||||||
|
case "ACCEPTED":
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
backgroundColor: calendar?.color?.light,
|
||||||
|
color: calendar?.color?.dark,
|
||||||
|
border: "1px solid white",
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return baseStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DisplayedIcons(IconDisplayed: IconDisplayConfig) {
|
||||||
|
if (!Object.values(IconDisplayed).find((b) => b === true)) return;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "1px",
|
||||||
|
marginRight: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{IconDisplayed.needAction && (
|
||||||
|
<HelpOutlineIcon style={{ fontSize: "15px" }} />
|
||||||
|
)}
|
||||||
|
{IconDisplayed.declined && (
|
||||||
|
<CancelIcon color="error" style={{ fontSize: "15px" }} />
|
||||||
|
)}
|
||||||
|
{IconDisplayed.tentative && (
|
||||||
|
<HelpOutlineIcon style={{ fontSize: "15px" }} />
|
||||||
|
)}
|
||||||
|
{IconDisplayed.private && (
|
||||||
|
<LockOutlineIcon style={{ fontSize: "15px" }} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
export function SimpleEventChip({ title }: { title: string }) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
borderRadius: "4px",
|
||||||
|
px: 0.5,
|
||||||
|
py: 0.2,
|
||||||
|
boxShadow: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ type InfoRowProps = {
|
|||||||
error?: boolean;
|
error?: boolean;
|
||||||
data?: string; // optional link target
|
data?: string; // optional link target
|
||||||
content?: React.ReactNode; // if provided, overrides text rendering
|
content?: React.ReactNode; // if provided, overrides text rendering
|
||||||
|
style?: React.CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
function detectUrls(text: string) {
|
function detectUrls(text: string) {
|
||||||
@@ -60,6 +61,7 @@ export function InfoRow({
|
|||||||
error = false,
|
error = false,
|
||||||
data,
|
data,
|
||||||
content,
|
content,
|
||||||
|
style,
|
||||||
}: InfoRowProps) {
|
}: InfoRowProps) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -83,8 +85,7 @@ export function InfoRow({
|
|||||||
maxHeight: "33vh",
|
maxHeight: "33vh",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
fontSize: "16px",
|
...style,
|
||||||
fontFamily: "'Inter', sans-serif",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data ? (
|
{data ? (
|
||||||
|
|||||||
@@ -564,6 +564,10 @@ export default function EventPreviewModal({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={event.location}
|
text={event.location}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontFamily: "'Inter', sans-serif",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
@@ -575,6 +579,10 @@ export default function EventPreviewModal({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={event.description}
|
text={event.description}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontFamily: "'Inter', sans-serif",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* ALARM */}
|
{/* ALARM */}
|
||||||
@@ -586,6 +594,10 @@ export default function EventPreviewModal({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={`${event.alarm.trigger} before by ${event.alarm.action}`}
|
text={`${event.alarm.trigger} before by ${event.alarm.action}`}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontFamily: "'Inter', sans-serif",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* Repetition */}
|
{/* Repetition */}
|
||||||
@@ -597,6 +609,10 @@ export default function EventPreviewModal({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={makeRecurrenceString(event)}
|
text={makeRecurrenceString(event)}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontFamily: "'Inter', sans-serif",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -633,6 +649,10 @@ export default function EventPreviewModal({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={event.error}
|
text={event.error}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontFamily: "'Inter', sans-serif",
|
||||||
|
}}
|
||||||
error
|
error
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ jest.mock("openid-client", () => ({
|
|||||||
}));
|
}));
|
||||||
const originalWarn = console.warn;
|
const originalWarn = console.warn;
|
||||||
|
|
||||||
|
class ResizeObserverMock {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
(global as any).ResizeObserver = ResizeObserverMock;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
console.warn = (...args: unknown[]) => {
|
console.warn = (...args: unknown[]) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
Reference in New Issue
Block a user