Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -23,8 +23,9 @@
|
||||
|
||||
.sidebar
|
||||
border-right 2px solid #f3f4f6
|
||||
width 330px
|
||||
padding 16px 25px
|
||||
width 350px
|
||||
padding-top 16px
|
||||
padding-left 5px
|
||||
height 90vh
|
||||
flex-direction column
|
||||
overflow auto
|
||||
@@ -71,32 +72,8 @@
|
||||
font-family "Segoe UI", Tahoma, Geneva, Verdana, sans-serif
|
||||
|
||||
/* mini calendar style */
|
||||
.react-calendar
|
||||
border none
|
||||
font-size 14px
|
||||
width 100%
|
||||
user-select none
|
||||
|
||||
/* selected day hover */
|
||||
.react-calendar__tile
|
||||
transition background-color 0.3s ease, color 0.3s ease
|
||||
border none
|
||||
background-color white
|
||||
width 12px
|
||||
height 37.52px
|
||||
|
||||
.react-calendar__tile--active
|
||||
background-color rgb(255, 255, 255)
|
||||
|
||||
.today
|
||||
background orange !important
|
||||
color white
|
||||
|
||||
.selectedWeek
|
||||
background-color lightgrey
|
||||
|
||||
.react-calendar__month-view__days__day--neighboringMonth
|
||||
color gray
|
||||
.MuiDayCalendar-weekContainer:has(.selectedWeek)
|
||||
background-color lightgray !important
|
||||
|
||||
/* sidebar header */
|
||||
.sidebar-calendar h2
|
||||
|
||||
@@ -41,6 +41,13 @@ import { EventErrorHandler } from "../Error/EventErrorHandler";
|
||||
import { EventErrorSnackbar } from "../Error/ErrorSnackbar";
|
||||
import momentTimezonePlugin from "@fullcalendar/moment-timezone";
|
||||
import { TimezoneSelector } from "./TimezoneSelector";
|
||||
import { DateCalendar } from "@mui/x-date-pickers/DateCalendar";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||
import moment from "moment";
|
||||
import { DayCalendarSkeleton } from "@mui/x-date-pickers/DayCalendarSkeleton";
|
||||
import { PickersDay } from "@mui/x-date-pickers/PickersDay";
|
||||
import { MiniCalendar } from "./MiniCalendar";
|
||||
|
||||
interface CalendarAppProps {
|
||||
calendarRef: React.RefObject<CalendarApi | null>;
|
||||
@@ -242,14 +249,6 @@ export default function CalendarApp({
|
||||
errorHandler: errorHandler.current,
|
||||
});
|
||||
|
||||
const handleMonthUp = () => {
|
||||
eventHandlers.handleMonthUp(selectedMiniDate, setSelectedMiniDate);
|
||||
};
|
||||
|
||||
const handleMonthDown = () => {
|
||||
eventHandlers.handleMonthDown(selectedMiniDate, setSelectedMiniDate);
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
(window as any).__calendarRef = calendarRef;
|
||||
}
|
||||
@@ -265,87 +264,13 @@ export default function CalendarApp({
|
||||
>
|
||||
<AddIcon /> <p>Create Event</p>
|
||||
</Button>
|
||||
<div className="calendar-label">
|
||||
<div className="calendar-label">
|
||||
<span title="mini calendar month">
|
||||
{selectedMiniDate.toLocaleDateString("en-us", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<button onClick={handleMonthUp}><</button>
|
||||
<button onClick={handleMonthDown}>></button>
|
||||
</div>
|
||||
<ReactCalendar
|
||||
key={selectedMiniDate.toDateString()}
|
||||
calendarType="iso8601"
|
||||
formatShortWeekday={(locale, date) =>
|
||||
date.toLocaleDateString(locale, { weekday: "narrow" })
|
||||
}
|
||||
value={selectedMiniDate}
|
||||
onClickDay={(date) => {
|
||||
setSelectedDate(date);
|
||||
setSelectedMiniDate(date);
|
||||
calendarRef.current?.gotoDate(date);
|
||||
}}
|
||||
prevLabel={null}
|
||||
showNeighboringMonth={true}
|
||||
nextLabel={null}
|
||||
showNavigation={false}
|
||||
tileClassName={({ date }) => {
|
||||
const classNames: string[] = [];
|
||||
|
||||
const today = new Date().setHours(0, 0, 0, 0);
|
||||
if (date.getTime() === today) {
|
||||
classNames.push("today");
|
||||
}
|
||||
const selected = new Date(selectedDate);
|
||||
selected.setHours(0, 0, 0, 0);
|
||||
if (
|
||||
calendarRef.current?.view.type === "timeGridWeek" ||
|
||||
calendarRef.current?.view.type === undefined
|
||||
) {
|
||||
const startOfWeek = computeStartOfTheWeek(selected);
|
||||
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
endOfWeek.setDate(startOfWeek.getDate() + 6); // Sunday
|
||||
endOfWeek.setHours(23, 59, 59, 999);
|
||||
|
||||
if (date <= endOfWeek && date >= startOfWeek) {
|
||||
classNames.push("selectedWeek");
|
||||
}
|
||||
}
|
||||
if (
|
||||
calendarRef.current?.view.type === "timeGridDay" &&
|
||||
date.getTime() === selected.getTime()
|
||||
) {
|
||||
classNames.push("selectedWeek");
|
||||
}
|
||||
return classNames;
|
||||
}}
|
||||
tileContent={({ date }) => {
|
||||
const classNames: string[] = [];
|
||||
const hasEvents = dottedEvents.some((event) => {
|
||||
const eventDate = new Date(event.start);
|
||||
return (
|
||||
eventDate.getFullYear() === date.getFullYear() &&
|
||||
eventDate.getMonth() === date.getMonth() &&
|
||||
eventDate.getDate() === date.getDate() &&
|
||||
event.status !== "CANCELLED"
|
||||
);
|
||||
});
|
||||
if (hasEvents) {
|
||||
classNames.push("event-dot");
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames.join(" ")}
|
||||
data-testid={`date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`}
|
||||
></div>
|
||||
);
|
||||
}}
|
||||
<MiniCalendar
|
||||
calendarRef={calendarRef}
|
||||
selectedDate={selectedMiniDate}
|
||||
setSelectedDate={setSelectedDate}
|
||||
setSelectedMiniDate={setSelectedMiniDate}
|
||||
dottedEvents={dottedEvents}
|
||||
/>
|
||||
<TempCalendarsInput
|
||||
setAnchorEl={setAnchorEl}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||
import { DateCalendar, PickersDay } from "@mui/x-date-pickers";
|
||||
import { DayCalendarSkeleton } from "@mui/x-date-pickers/DayCalendarSkeleton";
|
||||
import moment from "moment";
|
||||
import {
|
||||
computeStartOfTheWeek,
|
||||
formatDateToYYYYMMDDTHHMMSS,
|
||||
getCalendarRange,
|
||||
} from "../../utils/dateUtils";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { refreshCalendars } from "../Event/utils/eventUtils";
|
||||
import { getCalendarDetailAsync } from "../../features/Calendars/CalendarSlice";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function MiniCalendar({
|
||||
calendarRef,
|
||||
selectedDate,
|
||||
setSelectedDate,
|
||||
setSelectedMiniDate,
|
||||
dottedEvents,
|
||||
}: {
|
||||
calendarRef: any;
|
||||
selectedDate: Date;
|
||||
setSelectedDate: (d: Date) => void;
|
||||
setSelectedMiniDate: (d: Date) => void;
|
||||
dottedEvents: any[];
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const [visibleDate, setVisibleDate] = useState(selectedDate);
|
||||
|
||||
useEffect(() => setVisibleDate(selectedDate), [selectedDate]);
|
||||
return (
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="en-gb">
|
||||
<DateCalendar
|
||||
value={moment(visibleDate)}
|
||||
onChange={(dateMoment) => {
|
||||
if (!dateMoment) return;
|
||||
const date = dateMoment.toDate();
|
||||
setSelectedDate(date);
|
||||
setSelectedMiniDate(date);
|
||||
calendarRef.current?.gotoDate(date);
|
||||
}}
|
||||
showDaysOutsideCurrentMonth
|
||||
onMonthChange={(month) => {
|
||||
const calendarRange = getCalendarRange(month.toDate());
|
||||
setVisibleDate(month.toDate());
|
||||
Object.values(calendars.list).forEach((cal) =>
|
||||
dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: cal.id,
|
||||
match: {
|
||||
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
|
||||
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}}
|
||||
views={["month", "day"]}
|
||||
slotProps={{
|
||||
day: (ownerState) => {
|
||||
const date = ownerState.day.toDate();
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const selected = new Date(selectedDate);
|
||||
selected.setHours(0, 0, 0, 0);
|
||||
|
||||
const isToday = date.getTime() === today.getTime();
|
||||
const isSelectedDay =
|
||||
calendarRef.current?.view.type === "timeGridDay" &&
|
||||
date.getTime() === selected.getTime();
|
||||
|
||||
const isInSelectedWeek =
|
||||
calendarRef.current?.view.type === "timeGridWeek" ||
|
||||
calendarRef.current?.view.type === undefined
|
||||
? (() => {
|
||||
const startOfWeek = computeStartOfTheWeek(selected);
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
endOfWeek.setDate(startOfWeek.getDate() + 6);
|
||||
endOfWeek.setHours(23, 59, 59, 999);
|
||||
return date >= startOfWeek && date <= endOfWeek;
|
||||
})()
|
||||
: false;
|
||||
|
||||
const hasEvents = dottedEvents.some((event) => {
|
||||
const eventDate = new Date(event.start);
|
||||
return (
|
||||
eventDate.getFullYear() === date.getFullYear() &&
|
||||
eventDate.getMonth() === date.getMonth() &&
|
||||
eventDate.getDate() === date.getDate() &&
|
||||
event.status !== "CANCELLED"
|
||||
);
|
||||
});
|
||||
|
||||
const classNames = [
|
||||
isToday ? "today" : "",
|
||||
isSelectedDay ? "selectedDay" : "",
|
||||
isInSelectedWeek ? "selectedWeek" : "",
|
||||
hasEvents ? "event-dot" : "",
|
||||
].join(" ");
|
||||
|
||||
return {
|
||||
className: classNames,
|
||||
selected: classNames.includes("selectedWeek"),
|
||||
outsideCurrentMonth: ownerState.isDayOutsideMonth,
|
||||
disableMargin: false,
|
||||
onClick: () => {
|
||||
setSelectedDate(date);
|
||||
setSelectedMiniDate(date);
|
||||
calendarRef.current?.gotoDate(date);
|
||||
},
|
||||
style: {
|
||||
backgroundColor: "transparent",
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
border: 0,
|
||||
},
|
||||
sx: {
|
||||
"&.Mui-selected": {
|
||||
color: "inherit !important",
|
||||
fontWeight: "inherit !important",
|
||||
},
|
||||
"&.selectedDay": {
|
||||
backgroundColor: "lightgray !important",
|
||||
},
|
||||
"&.today": {
|
||||
background: "orange !important",
|
||||
color: "white !important",
|
||||
},
|
||||
},
|
||||
"data-testid": `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`,
|
||||
children: (
|
||||
<>
|
||||
{ownerState.day.date()}
|
||||
{hasEvents && <div className="event-dot" />}
|
||||
</>
|
||||
),
|
||||
};
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
}
|
||||
@@ -89,24 +89,6 @@ export const createEventHandlers = (props: EventHandlersProps) => {
|
||||
setOpenEventDisplay(false);
|
||||
};
|
||||
|
||||
const handleMonthUp = (
|
||||
selectedMiniDate: Date,
|
||||
setSelectedMiniDate: (date: Date) => void
|
||||
) => {
|
||||
setSelectedMiniDate(
|
||||
new Date(selectedMiniDate.getFullYear(), selectedMiniDate.getMonth() - 1)
|
||||
);
|
||||
};
|
||||
|
||||
const handleMonthDown = (
|
||||
selectedMiniDate: Date,
|
||||
setSelectedMiniDate: (date: Date) => void
|
||||
) => {
|
||||
setSelectedMiniDate(
|
||||
new Date(selectedMiniDate.getFullYear(), selectedMiniDate.getMonth() + 1)
|
||||
);
|
||||
};
|
||||
|
||||
const handleEventClick = (info: any) => {
|
||||
info.jsEvent.preventDefault();
|
||||
|
||||
@@ -255,8 +237,6 @@ export const createEventHandlers = (props: EventHandlersProps) => {
|
||||
handleDateSelect,
|
||||
handleClosePopover,
|
||||
handleCloseEventDisplay,
|
||||
handleMonthUp,
|
||||
handleMonthDown,
|
||||
handleEventClick,
|
||||
handleEventAllow,
|
||||
handleEventDrop,
|
||||
|
||||
@@ -25,8 +25,6 @@ export const useCalendarEventHandlers = (props: EventHandlersProps) => {
|
||||
eventHandlers.handleCloseEventDisplay,
|
||||
[props.setOpenEventDisplay]
|
||||
),
|
||||
handleMonthUp: useCallback(eventHandlers.handleMonthUp, []),
|
||||
handleMonthDown: useCallback(eventHandlers.handleMonthDown, []),
|
||||
handleEventClick: useCallback(eventHandlers.handleEventClick, [
|
||||
props.setOpenEventDisplay,
|
||||
props.setEventDisplayedId,
|
||||
|
||||
Reference in New Issue
Block a user