[#189] added custom event chip (#262)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-10-31 11:50:22 +01:00
committed by GitHub
parent 39d8085766
commit ec7230fa94
14 changed files with 689 additions and 218 deletions
+5 -12
View File
@@ -50,19 +50,12 @@
.sidebar:hover
scrollbar-color: rgba(0, 0, 0, 0.5) transparent
.declined-event
opacity 0.7
// .declined-event
// opacity 0.7
.needs-action-event
opacity 0.7
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
// .needs-action-event
// opacity 0.7
// border dashed 1px #ffffff
[data-theme="dark"] .fc-timegrid-slot::after
border-top 1px dotted rgba(255, 255, 255, 0.2)
+58 -39
View File
@@ -251,37 +251,26 @@ tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller
/* 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
z-index 700 !important
border-radius 4px
border-radius 8px
background #fff
overflow hidden
font-family Inter
font-family Inter, sans-serif
width 244px
box-shadow 0 4px 16px rgba(0,0,0,0.1)
.fc-popover-header
background #FCFCFC
display flex
justify-content space-between
align-items center
width 244px
height 40px
padding 12px
border-bottom 1px solid #eee
.fc-popover-title
font-family Inter
font-weight 500
font-size 14px
color #243B55
@@ -290,36 +279,66 @@ tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller
.fc-popover-close
color #605D62
cursor pointer
.fc-popover-body
max-height 208px
overflow auto
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 FullCalendars extra margin block */
.fc-more-popover-misc
height 0 !important
margin 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
white-space nowrap
overflow hidden
text-overflow ellipsis
// Custom event chips
.fc-event
background-color transparent
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 { CalendarApi, NowIndicatorContentArg } from "@fullcalendar/core";
import { updateSlotLabelVisibility } from "../utils/calendarUtils";
import { createMouseHandlers } from "./mouseHandlers";
import { userAttendee } from "../../../features/User/userDataTypes";
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 { EventChip } from "../../Event/EventChip/EventChip";
export interface ViewHandlersProps {
calendarRef: React.RefObject<CalendarApi | null>;
@@ -108,79 +105,12 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
};
const handleEventContent = (arg: any) => {
const event = arg.event;
const props = event._def.extendedProps;
const {
calId,
temp,
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)
);
}
return React.createElement(EventChip, {
arg,
calendars,
tempcalendars,
errorHandler,
});
};
const handleEventDidMount = (arg: any) => {
@@ -228,65 +158,3 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
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 })))
.map((e) => {
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 };
});
};