♻️(front) reorganize calendar components into subfolders
- Move scheduler components (AttendeesInput, RecurrenceEditor, EventModal styles) to scheduler/ - Move left panel components (LeftPanel, MiniCalendar) to left-panel/ - Move CalendarList.scss to calendar-list/ - Simplify SchedulerToolbar component - Update imports, exports and page layout Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* LeftPanel component - Calendar sidebar with mini calendar and calendar list.
|
||||
*/
|
||||
|
||||
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
|
||||
import { Calendar } from "../api";
|
||||
import { CalendarList } from "./calendar-list";
|
||||
import { MiniCalendar } from "./MiniCalendar";
|
||||
import { useCalendarContext } from "../contexts";
|
||||
|
||||
interface LeftPanelProps {
|
||||
calendars: Calendar[];
|
||||
selectedDate: Date;
|
||||
onDateSelect: (date: Date) => void;
|
||||
onCreateEvent: () => void;
|
||||
}
|
||||
|
||||
export const LeftPanel = ({
|
||||
calendars,
|
||||
selectedDate,
|
||||
onDateSelect,
|
||||
onCreateEvent,
|
||||
}: LeftPanelProps) => {
|
||||
useCalendarContext();
|
||||
return (
|
||||
<div className="calendar-left-panel">
|
||||
<div className="calendar-left-panel__create">
|
||||
<Button onClick={onCreateEvent} icon={<span className="material-icons">add</span>}>
|
||||
Créer
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<MiniCalendar selectedDate={selectedDate} onDateSelect={onDateSelect} />
|
||||
|
||||
<div className="calendar-left-panel__divider" />
|
||||
|
||||
<CalendarList calendars={calendars} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,68 +1,61 @@
|
||||
/**
|
||||
* CalendarItemMenu component.
|
||||
* Context menu for calendar item actions (edit, delete).
|
||||
* Context menu for calendar item actions (edit, delete, subscription).
|
||||
*/
|
||||
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DropdownMenu, DropdownMenuOption } from "@gouvfr-lasuite/ui-kit";
|
||||
|
||||
import type { CalendarItemMenuProps } from "./types";
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
|
||||
export const CalendarItemMenu = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onSubscription,
|
||||
onClose,
|
||||
}: CalendarItemMenuProps) => {
|
||||
const { t } = useTranslation();
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, [onClose]);
|
||||
const options: DropdownMenuOption[] = useMemo(() => {
|
||||
const items: DropdownMenuOption[] = [
|
||||
{
|
||||
label: t("calendar.list.edit"),
|
||||
icon: <span className="material-icons">edit</span>,
|
||||
callback: onEdit,
|
||||
},
|
||||
];
|
||||
|
||||
const handleEdit = () => {
|
||||
onEdit();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleSubscription = () => {
|
||||
if (onSubscription) {
|
||||
onSubscription();
|
||||
onClose();
|
||||
items.push({
|
||||
label: t("calendar.list.subscription"),
|
||||
icon: <span className="material-icons">link</span>,
|
||||
callback: onSubscription,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
items.push({
|
||||
label: t("calendar.list.delete"),
|
||||
icon: <span className="material-icons">delete</span>,
|
||||
callback: onDelete,
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [t, onEdit, onDelete, onSubscription]);
|
||||
|
||||
return (
|
||||
<div ref={menuRef} className="calendar-list__menu">
|
||||
<button className="calendar-list__menu-item" onClick={handleEdit}>
|
||||
<span className="material-icons">edit</span>
|
||||
{t('calendar.list.edit')}
|
||||
</button>
|
||||
{onSubscription && (
|
||||
<button className="calendar-list__menu-item" onClick={handleSubscription}>
|
||||
<span className="material-icons">link</span>
|
||||
{t('calendar.list.subscription')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="calendar-list__menu-item calendar-list__menu-item--danger"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<span className="material-icons">delete</span>
|
||||
{t('calendar.list.delete')}
|
||||
</button>
|
||||
</div>
|
||||
<DropdownMenu options={options} isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<Button
|
||||
className="calendar-list__options-btn"
|
||||
aria-label={t("calendar.list.options")}
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
onClick={() => onOpenChange(!isOpen)}
|
||||
icon={<span className="material-icons">more_vert</span>}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,7 +57,9 @@
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
@@ -72,14 +74,13 @@
|
||||
&__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
padding: 0.1rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
@@ -106,6 +107,12 @@
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
// Override Cunningham checkbox colors with calendar color
|
||||
input:checked {
|
||||
background-color: var(--calendar-color) !important;
|
||||
border-color: var(--calendar-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__color {
|
||||
@@ -132,27 +139,11 @@
|
||||
}
|
||||
|
||||
&__options-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, background-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-200);
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
transition:
|
||||
opacity 0.15s,
|
||||
background-color 0.15s,
|
||||
color 0.15s;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
@@ -272,7 +263,9 @@
|
||||
border: 3px solid transparent;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
transition:
|
||||
transform 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
@@ -280,7 +273,9 @@
|
||||
|
||||
&--selected {
|
||||
border-color: #1f2937;
|
||||
box-shadow: 0 0 0 2px white, 0 0 0 4px #9ca3af;
|
||||
box-shadow:
|
||||
0 0 0 2px white,
|
||||
0 0 0 4px #9ca3af;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { Checkbox } from "@gouvfr-lasuite/cunningham-react";
|
||||
|
||||
import { CalendarItemMenu } from "./CalendarItemMenu";
|
||||
import type { CalendarListItemProps, SharedCalendarListItemProps } from "./types";
|
||||
import type {
|
||||
CalendarListItemProps,
|
||||
SharedCalendarListItemProps,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* CalendarListItem - Displays a user-owned calendar.
|
||||
@@ -27,40 +30,35 @@ export const CalendarListItem = ({
|
||||
|
||||
return (
|
||||
<div className="calendar-list__item">
|
||||
<div className="calendar-list__item-checkbox">
|
||||
<div
|
||||
className="calendar-list__item-checkbox"
|
||||
style={{ "--calendar-color": calendar.color } as React.CSSProperties}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isVisible}
|
||||
onChange={() => onToggleVisibility(calendar.url)}
|
||||
label=""
|
||||
aria-label={`${t('calendar.list.showCalendar')} ${calendar.displayName || ''}`}
|
||||
/>
|
||||
<span
|
||||
className="calendar-list__color"
|
||||
style={{ backgroundColor: calendar.color }}
|
||||
aria-label={`${t("calendar.list.showCalendar")} ${calendar.displayName || ""}`}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="calendar-list__name"
|
||||
title={calendar.displayName || undefined}
|
||||
>
|
||||
{calendar.displayName || 'Sans nom'}
|
||||
{calendar.displayName || "Sans nom"}
|
||||
</span>
|
||||
<div className="calendar-list__item-actions">
|
||||
<button
|
||||
className="calendar-list__options-btn"
|
||||
onClick={(e) => onMenuToggle(calendar.url, e)}
|
||||
aria-label="Options"
|
||||
>
|
||||
<span className="material-icons">more_horiz</span>
|
||||
</button>
|
||||
{isMenuOpen && (
|
||||
<CalendarItemMenu
|
||||
onEdit={() => onEdit(calendar)}
|
||||
onDelete={() => onDelete(calendar)}
|
||||
onSubscription={onSubscription ? () => onSubscription(calendar) : undefined}
|
||||
onClose={onCloseMenu}
|
||||
/>
|
||||
)}
|
||||
<CalendarItemMenu
|
||||
isOpen={isMenuOpen}
|
||||
onOpenChange={(open) =>
|
||||
open ? onMenuToggle(calendar.url) : onCloseMenu()
|
||||
}
|
||||
onEdit={() => onEdit(calendar)}
|
||||
onDelete={() => onDelete(calendar)}
|
||||
onSubscription={
|
||||
onSubscription ? () => onSubscription(calendar) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -78,12 +76,15 @@ export const SharedCalendarListItem = ({
|
||||
|
||||
return (
|
||||
<div className="calendar-list__item">
|
||||
<div className="calendar-list__item-checkbox">
|
||||
<div
|
||||
className="calendar-list__item-checkbox"
|
||||
style={{ "--calendar-color": calendar.color } as React.CSSProperties}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isVisible}
|
||||
onChange={() => onToggleVisibility(String(calendar.id))}
|
||||
label=""
|
||||
aria-label={`${t('calendar.list.showCalendar')} ${calendar.name}`}
|
||||
aria-label={`${t("calendar.list.showCalendar")} ${calendar.name}`}
|
||||
/>
|
||||
<span
|
||||
className="calendar-list__color"
|
||||
|
||||
@@ -147,8 +147,7 @@ export const useCalendarListState = ({
|
||||
|
||||
// Menu handlers
|
||||
const handleMenuToggle = useCallback(
|
||||
(calendarUrl: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
(calendarUrl: string) => {
|
||||
setOpenMenuUrl(openMenuUrl === calendarUrl ? null : calendarUrl);
|
||||
},
|
||||
[openMenuUrl]
|
||||
|
||||
@@ -21,10 +21,11 @@ export interface CalendarModalProps {
|
||||
* Props for the CalendarItemMenu component.
|
||||
*/
|
||||
export interface CalendarItemMenuProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
onSubscription?: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,7 @@ export interface CalendarListItemProps {
|
||||
isVisible: boolean;
|
||||
isMenuOpen: boolean;
|
||||
onToggleVisibility: (url: string) => void;
|
||||
onMenuToggle: (url: string, e: React.MouseEvent) => void;
|
||||
onMenuToggle: (url: string) => void;
|
||||
onEdit: (calendar: CalDavCalendar) => void;
|
||||
onDelete: (calendar: CalDavCalendar) => void;
|
||||
onSubscription?: (calendar: CalDavCalendar) => void;
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { LeftPanel } from "./LeftPanel";
|
||||
export { MiniCalendar } from "./MiniCalendar";
|
||||
export { AttendeesInput } from "./AttendeesInput";
|
||||
export { LeftPanel, MiniCalendar } from "./left-panel";
|
||||
export { AttendeesInput } from "./scheduler";
|
||||
|
||||
@@ -3,16 +3,22 @@
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: var(--c--theme--colors--greyscale-000);
|
||||
border-right: 1px solid
|
||||
var(--c--contextuals--border--semantic--neutral--tertiary);
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
&__create {
|
||||
padding: 1rem 0.75rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
|
||||
.c__button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mini-calendar {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* LeftPanel component - Calendar sidebar with mini calendar and calendar list.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, useModal } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { IcsEvent } from "ts-ics";
|
||||
|
||||
import { CalendarList } from "../calendar-list";
|
||||
import { MiniCalendar } from "./MiniCalendar";
|
||||
import { EventModal } from "../scheduler/EventModal";
|
||||
import { useCalendarContext } from "../../contexts";
|
||||
import { useCalendars } from "../../hooks/useCalendars";
|
||||
|
||||
const BROWSER_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
/**
|
||||
* Get rounded start and end times for a new event.
|
||||
* Rounds down to the current hour, end is 1 hour later.
|
||||
* Example: 14:30 -> start: 14:00, end: 15:00
|
||||
*/
|
||||
const getDefaultEventTimes = () => {
|
||||
const now = new Date();
|
||||
const start = new Date(now);
|
||||
start.setMinutes(0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setHours(end.getHours() + 1);
|
||||
|
||||
return { start, end };
|
||||
};
|
||||
|
||||
export const LeftPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const modal = useModal();
|
||||
|
||||
const {
|
||||
selectedDate,
|
||||
setSelectedDate,
|
||||
davCalendars,
|
||||
caldavService,
|
||||
adapter,
|
||||
calendarRef,
|
||||
} = useCalendarContext();
|
||||
|
||||
const { data: calendars = [] } = useCalendars();
|
||||
|
||||
// Get default calendar URL
|
||||
const defaultCalendarUrl = davCalendars[0]?.url || "";
|
||||
|
||||
// Create default event with rounded times
|
||||
const defaultEvent = useMemo(() => {
|
||||
const { start, end } = getDefaultEventTimes();
|
||||
|
||||
// Create "fake UTC" dates for the adapter
|
||||
const fakeUtcStart = new Date(
|
||||
Date.UTC(
|
||||
start.getFullYear(),
|
||||
start.getMonth(),
|
||||
start.getDate(),
|
||||
start.getHours(),
|
||||
start.getMinutes(),
|
||||
0
|
||||
)
|
||||
);
|
||||
const fakeUtcEnd = new Date(
|
||||
Date.UTC(
|
||||
end.getFullYear(),
|
||||
end.getMonth(),
|
||||
end.getDate(),
|
||||
end.getHours(),
|
||||
end.getMinutes(),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
start: {
|
||||
date: fakeUtcStart,
|
||||
type: "DATE-TIME" as const,
|
||||
local: {
|
||||
date: fakeUtcStart,
|
||||
timezone: BROWSER_TIMEZONE,
|
||||
tzoffset: adapter.getTimezoneOffset(start, BROWSER_TIMEZONE),
|
||||
},
|
||||
},
|
||||
end: {
|
||||
date: fakeUtcEnd,
|
||||
type: "DATE-TIME" as const,
|
||||
local: {
|
||||
date: fakeUtcEnd,
|
||||
timezone: BROWSER_TIMEZONE,
|
||||
tzoffset: adapter.getTimezoneOffset(end, BROWSER_TIMEZONE),
|
||||
},
|
||||
},
|
||||
};
|
||||
}, [adapter]);
|
||||
|
||||
// Handle save event
|
||||
const handleSave = useCallback(
|
||||
async (event: IcsEvent, calendarUrl: string) => {
|
||||
const result = await caldavService.createEvent({
|
||||
calendarUrl,
|
||||
event,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Failed to create event");
|
||||
}
|
||||
|
||||
// Refresh the calendar view
|
||||
if (calendarRef.current) {
|
||||
calendarRef.current.refetchEvents();
|
||||
}
|
||||
},
|
||||
[caldavService, calendarRef]
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
modal.close();
|
||||
}, [modal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="calendar-left-panel">
|
||||
<div className="calendar-left-panel__create">
|
||||
<Button
|
||||
onClick={modal.open}
|
||||
icon={<span className="material-icons">add</span>}
|
||||
>
|
||||
{t("calendar.leftPanel.newEvent")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<MiniCalendar
|
||||
selectedDate={selectedDate}
|
||||
onDateSelect={setSelectedDate}
|
||||
/>
|
||||
|
||||
<div className="calendar-left-panel__divider" />
|
||||
|
||||
<CalendarList calendars={calendars} />
|
||||
</div>
|
||||
|
||||
{modal.isOpen && (
|
||||
<EventModal
|
||||
isOpen={modal.isOpen}
|
||||
mode="create"
|
||||
event={defaultEvent}
|
||||
calendarUrl={defaultCalendarUrl}
|
||||
calendars={davCalendars}
|
||||
adapter={adapter}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
&__month-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
}
|
||||
|
||||
&__nav {
|
||||
@@ -23,29 +23,6 @@
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
&__nav-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -63,15 +40,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
font-size: var(--c--globals--font--sizes--s);
|
||||
font-weight: 700;
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
text-transform: lowercase;
|
||||
|
||||
&--week-num {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
font-weight: 500;
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +68,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
font-size: 0.6rem;
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@ -107,32 +84,53 @@
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
font-weight: 500;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
|
||||
&--outside {
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
font-weight: 400;
|
||||
|
||||
&.mini-calendar__day--today {
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.mini-calendar__day--selected {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&--today {
|
||||
color: var(--c--theme--colors--primary-600);
|
||||
&--today:not(.mini-calendar__day--outside) {
|
||||
color: var(--c--contextuals--content--semantic--brand--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// &:not(&--outside):--today {
|
||||
// color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
// font-weight: 600;
|
||||
// }
|
||||
|
||||
&:hover {
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--neutral--tertiary
|
||||
);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: #8b4513;
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--brand--primary
|
||||
);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: #7a3d11;
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--brand--primary-hover
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,9 @@ import {
|
||||
subMonths,
|
||||
} from "date-fns";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useCalendarContext } from "../contexts";
|
||||
import { useCalendarLocale } from "../hooks/useCalendarLocale";
|
||||
import { useCalendarContext } from "../../contexts";
|
||||
import { useCalendarLocale } from "../../hooks/useCalendarLocale";
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
|
||||
interface MiniCalendarProps {
|
||||
selectedDate: Date;
|
||||
@@ -71,13 +72,13 @@ export const MiniCalendar = ({
|
||||
// Generate weekday labels based on locale and first day of week
|
||||
const weekDays = useMemo(() => {
|
||||
const days = [
|
||||
t('calendar.recurrence.weekdays.mo'),
|
||||
t('calendar.recurrence.weekdays.tu'),
|
||||
t('calendar.recurrence.weekdays.we'),
|
||||
t('calendar.recurrence.weekdays.th'),
|
||||
t('calendar.recurrence.weekdays.fr'),
|
||||
t('calendar.recurrence.weekdays.sa'),
|
||||
t('calendar.recurrence.weekdays.su'),
|
||||
t("calendar.recurrence.weekdays.mo"),
|
||||
t("calendar.recurrence.weekdays.tu"),
|
||||
t("calendar.recurrence.weekdays.we"),
|
||||
t("calendar.recurrence.weekdays.th"),
|
||||
t("calendar.recurrence.weekdays.fr"),
|
||||
t("calendar.recurrence.weekdays.sa"),
|
||||
t("calendar.recurrence.weekdays.su"),
|
||||
];
|
||||
// Rotate array based on firstDayOfWeek (0 = Sunday, 1 = Monday)
|
||||
if (firstDayOfWeek === 0) {
|
||||
@@ -106,20 +107,23 @@ export const MiniCalendar = ({
|
||||
{format(viewDate, "MMMM yyyy", { locale: dateFnsLocale })}
|
||||
</span>
|
||||
<div className="mini-calendar__nav">
|
||||
<button
|
||||
className="mini-calendar__nav-btn"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
color="neutral"
|
||||
onClick={handlePrevMonth}
|
||||
icon={<span className="material-icons">chevron_left</span>}
|
||||
aria-label={t("calendar.miniCalendar.previousMonth")}
|
||||
>
|
||||
<span className="material-icons">chevron_left</span>
|
||||
</button>
|
||||
<button
|
||||
className="mini-calendar__nav-btn"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
color="neutral"
|
||||
onClick={handleNextMonth}
|
||||
icon={<span className="material-icons">chevron_right</span>}
|
||||
aria-label={t("calendar.miniCalendar.nextMonth")}
|
||||
>
|
||||
<span className="material-icons">chevron_right</span>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -155,7 +159,9 @@ export const MiniCalendar = ({
|
||||
className={`mini-calendar__day ${
|
||||
!isCurrentMonth ? "mini-calendar__day--outside" : ""
|
||||
} ${isSelected ? "mini-calendar__day--selected" : ""} ${
|
||||
isToday && !isSelected ? "mini-calendar__day--today" : ""
|
||||
isToday && !isSelected
|
||||
? "mini-calendar__day--today"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => handleDayClick(day)}
|
||||
>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { LeftPanel } from "./LeftPanel";
|
||||
export { MiniCalendar } from "./MiniCalendar";
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
} from "@gouvfr-lasuite/cunningham-react";
|
||||
|
||||
import { useAuth } from "@/features/auth/Auth";
|
||||
import { AttendeesInput } from "../AttendeesInput";
|
||||
import { RecurrenceEditor } from "../RecurrenceEditor";
|
||||
import { AttendeesInput } from "./AttendeesInput";
|
||||
import { RecurrenceEditor } from "./RecurrenceEditor";
|
||||
import { DeleteEventModal } from "./DeleteEventModal";
|
||||
import type { EventModalProps, RecurringDeleteOption } from "./types";
|
||||
import {
|
||||
|
||||
@@ -98,7 +98,11 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
||||
|
||||
// Callback to update toolbar state when calendar dates/view changes
|
||||
const handleDatesSet = useCallback(
|
||||
(info: { start: Date; end: Date; view?: { type: string; title: string } }) => {
|
||||
(info: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
view?: { type: string; title: string };
|
||||
}) => {
|
||||
// Update current date for MiniCalendar sync
|
||||
const midTime = (info.start.getTime() + info.end.getTime()) / 2;
|
||||
setCurrentDate(new Date(midTime));
|
||||
@@ -112,7 +116,7 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[setCurrentDate, calendarRef]
|
||||
[setCurrentDate, calendarRef],
|
||||
);
|
||||
|
||||
// Initialize calendar
|
||||
@@ -171,7 +175,7 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
||||
ref={containerRef}
|
||||
id="event-calendar"
|
||||
className="scheduler__calendar"
|
||||
style={{ height: "calc(100vh - 160px)" }}
|
||||
style={{ height: "calc(100vh - 52px - 90px)" }}
|
||||
/>
|
||||
|
||||
<EventModal
|
||||
|
||||
@@ -121,54 +121,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__view-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
min-width: 140px;
|
||||
background: white;
|
||||
border: 1px solid var(--c--globals--colors--gray-200);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 0.25rem 0;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
&__view-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
color: var(--c--globals--colors--gray-700);
|
||||
transition: background-color 0.15s;
|
||||
text-align: left;
|
||||
|
||||
&:hover,
|
||||
&--focused {
|
||||
background-color: var(--c--globals--colors--gray-50);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--c--globals--colors--brand-500);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
color: var(--c--globals--colors--brand-500);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__view-check {
|
||||
font-size: 1rem;
|
||||
color: var(--c--globals--colors--brand-500);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -3,18 +3,14 @@
|
||||
* Replaces the native toolbar with React components using Cunningham design system.
|
||||
*/
|
||||
|
||||
import { useMemo, useState, useRef, useEffect, useCallback } from "react";
|
||||
import { useMemo, useState, useCallback } from "react";
|
||||
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { DropdownMenu, type DropdownMenuOption } from "@gouvfr-lasuite/ui-kit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { SchedulerToolbarProps } from "./types";
|
||||
|
||||
type ViewOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const SchedulerToolbar = ({
|
||||
calendarRef,
|
||||
currentView,
|
||||
@@ -23,22 +19,40 @@ export const SchedulerToolbar = ({
|
||||
}: SchedulerToolbarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [isViewDropdownOpen, setIsViewDropdownOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const isOpenRef = useRef(isViewDropdownOpen);
|
||||
|
||||
// Keep ref in sync with state to avoid stale closures
|
||||
isOpenRef.current = isViewDropdownOpen;
|
||||
const handleViewChange = useCallback(
|
||||
(value: string) => {
|
||||
calendarRef.current?.setOption("view", value);
|
||||
onViewChange?.(value);
|
||||
setIsViewDropdownOpen(false);
|
||||
},
|
||||
[calendarRef, onViewChange],
|
||||
);
|
||||
|
||||
const viewOptions: ViewOption[] = useMemo(
|
||||
const viewOptions: DropdownMenuOption[] = useMemo(
|
||||
() => [
|
||||
{ value: "timeGridDay", label: t("calendar.views.day") },
|
||||
{ value: "timeGridWeek", label: t("calendar.views.week") },
|
||||
{ value: "dayGridMonth", label: t("calendar.views.month") },
|
||||
{ value: "listWeek", label: t("calendar.views.listWeek") },
|
||||
{
|
||||
value: "timeGridDay",
|
||||
label: t("calendar.views.day"),
|
||||
callback: () => handleViewChange("timeGridDay"),
|
||||
},
|
||||
{
|
||||
value: "timeGridWeek",
|
||||
label: t("calendar.views.week"),
|
||||
callback: () => handleViewChange("timeGridWeek"),
|
||||
},
|
||||
{
|
||||
value: "dayGridMonth",
|
||||
label: t("calendar.views.month"),
|
||||
callback: () => handleViewChange("dayGridMonth"),
|
||||
},
|
||||
{
|
||||
value: "listWeek",
|
||||
label: t("calendar.views.listWeek"),
|
||||
callback: () => handleViewChange("listWeek"),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
[t, handleViewChange],
|
||||
);
|
||||
|
||||
const currentViewLabel = useMemo(() => {
|
||||
@@ -46,55 +60,6 @@ export const SchedulerToolbar = ({
|
||||
return option?.label || t("calendar.views.week");
|
||||
}, [currentView, viewOptions, t]);
|
||||
|
||||
// Handle click outside to close dropdown (uses ref to avoid stale closure)
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
isOpenRef.current &&
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsViewDropdownOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Handle keyboard events for accessibility
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isOpenRef.current) return;
|
||||
|
||||
switch (event.key) {
|
||||
case "Escape":
|
||||
setIsViewDropdownOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
triggerRef.current?.focus();
|
||||
break;
|
||||
case "ArrowDown":
|
||||
event.preventDefault();
|
||||
setFocusedIndex((prev) =>
|
||||
prev < viewOptions.length - 1 ? prev + 1 : 0,
|
||||
);
|
||||
break;
|
||||
case "ArrowUp":
|
||||
event.preventDefault();
|
||||
setFocusedIndex((prev) =>
|
||||
prev > 0 ? prev - 1 : viewOptions.length - 1,
|
||||
);
|
||||
break;
|
||||
case "Tab":
|
||||
setIsViewDropdownOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [viewOptions.length]);
|
||||
|
||||
const handleToday = useCallback(() => {
|
||||
calendarRef.current?.setOption("date", new Date());
|
||||
}, [calendarRef]);
|
||||
@@ -107,55 +72,6 @@ export const SchedulerToolbar = ({
|
||||
calendarRef.current?.next();
|
||||
}, [calendarRef]);
|
||||
|
||||
const handleViewChange = useCallback(
|
||||
(value: string) => {
|
||||
calendarRef.current?.setOption("view", value);
|
||||
onViewChange?.(value);
|
||||
setIsViewDropdownOpen(false);
|
||||
},
|
||||
[calendarRef, onViewChange],
|
||||
);
|
||||
|
||||
const toggleViewDropdown = useCallback(() => {
|
||||
setIsViewDropdownOpen((prev) => {
|
||||
if (!prev) {
|
||||
// Opening: set focus to current view
|
||||
const currentIndex = viewOptions.findIndex(
|
||||
(opt) => opt.value === currentView,
|
||||
);
|
||||
setFocusedIndex(currentIndex >= 0 ? currentIndex : 0);
|
||||
} else {
|
||||
setFocusedIndex(-1);
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
}, [viewOptions, currentView]);
|
||||
|
||||
const handleKeyDownOnTrigger = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === "ArrowDown" && !isViewDropdownOpen) {
|
||||
event.preventDefault();
|
||||
setIsViewDropdownOpen(true);
|
||||
const currentIndex = viewOptions.findIndex(
|
||||
(opt) => opt.value === currentView,
|
||||
);
|
||||
setFocusedIndex(currentIndex >= 0 ? currentIndex : 0);
|
||||
}
|
||||
},
|
||||
[isViewDropdownOpen, viewOptions, currentView],
|
||||
);
|
||||
|
||||
const handleKeyDownOnOption = useCallback(
|
||||
(event: React.KeyboardEvent, value: string) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
handleViewChange(value);
|
||||
triggerRef.current?.focus();
|
||||
}
|
||||
},
|
||||
[handleViewChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="scheduler-toolbar">
|
||||
<div className="scheduler-toolbar__left">
|
||||
@@ -186,51 +102,31 @@ export const SchedulerToolbar = ({
|
||||
<h2 className="scheduler-toolbar__title">{viewTitle}</h2>
|
||||
</div>
|
||||
|
||||
<div className="scheduler-toolbar__right" ref={dropdownRef}>
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
className="scheduler-toolbar__view-trigger"
|
||||
onClick={toggleViewDropdown}
|
||||
onKeyDown={handleKeyDownOnTrigger}
|
||||
aria-expanded={isViewDropdownOpen}
|
||||
aria-haspopup="listbox"
|
||||
<div className="scheduler-toolbar__right">
|
||||
<DropdownMenu
|
||||
options={viewOptions}
|
||||
isOpen={isViewDropdownOpen}
|
||||
onOpenChange={setIsViewDropdownOpen}
|
||||
selectedValues={[currentView]}
|
||||
>
|
||||
<span>{currentViewLabel}</span>
|
||||
<span
|
||||
className={`material-icons scheduler-toolbar__view-arrow ${isViewDropdownOpen ? "scheduler-toolbar__view-arrow--open" : ""}`}
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{isViewDropdownOpen && (
|
||||
<div className="scheduler-toolbar__view-dropdown" role="listbox">
|
||||
{viewOptions.map((option, index) => (
|
||||
<button
|
||||
key={option.value}
|
||||
className={`scheduler-toolbar__view-option ${currentView === option.value ? "scheduler-toolbar__view-option--selected" : ""} ${focusedIndex === index ? "scheduler-toolbar__view-option--focused" : ""}`}
|
||||
onClick={() => handleViewChange(option.value)}
|
||||
onKeyDown={(e) => handleKeyDownOnOption(e, option.value)}
|
||||
role="option"
|
||||
aria-selected={currentView === option.value}
|
||||
tabIndex={focusedIndex === index ? 0 : -1}
|
||||
ref={(el) => {
|
||||
if (focusedIndex === index && el) {
|
||||
el.focus();
|
||||
}
|
||||
}}
|
||||
<Button
|
||||
iconPosition="right"
|
||||
color="neutral"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsViewDropdownOpen(!isViewDropdownOpen)}
|
||||
aria-expanded={isViewDropdownOpen}
|
||||
icon={
|
||||
<span
|
||||
className={`material-icons scheduler-toolbar__view-arrow ${isViewDropdownOpen ? "scheduler-toolbar__view-arrow--open" : ""}`}
|
||||
>
|
||||
{option.label}
|
||||
{currentView === option.value && (
|
||||
<span className="material-icons scheduler-toolbar__view-check">
|
||||
check
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
expand_more
|
||||
</span>
|
||||
}
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
<span>{currentViewLabel}</span>
|
||||
</Button>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
export { Scheduler } from "./Scheduler";
|
||||
export { EventModal } from "./EventModal";
|
||||
export { DeleteEventModal } from "./DeleteEventModal";
|
||||
export { AttendeesInput } from "./AttendeesInput";
|
||||
export { RecurrenceEditor } from "./RecurrenceEditor";
|
||||
export { useSchedulerHandlers } from "./hooks/useSchedulerHandlers";
|
||||
export { useSchedulerInit, useSchedulingCapabilitiesCheck } from "./hooks/useSchedulerInit";
|
||||
export * from "./types";
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
"errorServer": "Server error. Please try again later."
|
||||
},
|
||||
"leftPanel": {
|
||||
"create": "Create"
|
||||
"newEvent": "New event"
|
||||
},
|
||||
"miniCalendar": {
|
||||
"previousMonth": "Previous month",
|
||||
@@ -731,7 +731,7 @@
|
||||
"errorServer": "Erreur serveur. Veuillez réessayer plus tard."
|
||||
},
|
||||
"leftPanel": {
|
||||
"create": "Créer"
|
||||
"newEvent": "Nouvel événement"
|
||||
},
|
||||
"miniCalendar": {
|
||||
"previousMonth": "Mois précédent",
|
||||
@@ -1047,7 +1047,7 @@
|
||||
"errorServer": "Serverfout. Probeer het later opnieuw."
|
||||
},
|
||||
"leftPanel": {
|
||||
"create": "Aanmaken"
|
||||
"newEvent": "Nieuw evenement"
|
||||
},
|
||||
"miniCalendar": {
|
||||
"previousMonth": "Vorige maand",
|
||||
|
||||
@@ -2,43 +2,23 @@
|
||||
* Calendar page - Main calendar view with sidebar.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { MainLayout } from "@gouvfr-lasuite/ui-kit";
|
||||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import { login, useAuth } from "@/features/auth/Auth";
|
||||
import { LeftPanel } from "@/features/calendar/components";
|
||||
import { useCalendars } from "@/features/calendar/hooks/useCalendars";
|
||||
import { GlobalLayout } from "@/features/layouts/components/global/GlobalLayout";
|
||||
import { HeaderRight } from "@/features/layouts/components/header/Header";
|
||||
import { SpinnerPage } from "@/features/ui/components/spinner/SpinnerPage";
|
||||
import { Toaster } from "@/features/ui/components/toaster/Toaster";
|
||||
import { Scheduler } from "@/features/calendar/components/scheduler/Scheduler";
|
||||
import { CalendarContextProvider, useCalendarContext } from "@/features/calendar/contexts";
|
||||
import { CalendarContextProvider } from "@/features/calendar/contexts";
|
||||
|
||||
export default function CalendarPage() {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Use selectedDate from context (the specific day user has clicked/selected)
|
||||
// Note: currentDate (for view sync) is used directly by MiniCalendar
|
||||
const { selectedDate, setSelectedDate } = useCalendarContext();
|
||||
|
||||
// Fetch calendars for the sidebar
|
||||
const { data: calendars = [] } = useCalendars();
|
||||
|
||||
|
||||
// Handlers
|
||||
const handleDateSelect = useCallback((date: Date) => {
|
||||
setSelectedDate(date);
|
||||
}, [setSelectedDate]);
|
||||
|
||||
const handleCreateEvent = useCallback(() => {
|
||||
console.log("handleCreateEvent");
|
||||
}, []);
|
||||
|
||||
// Redirect to login if not authenticated
|
||||
if (!user) {
|
||||
if (typeof window !== "undefined") {
|
||||
@@ -56,24 +36,12 @@ export default function CalendarPage() {
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
</Head>
|
||||
|
||||
|
||||
<div className="calendar-page">
|
||||
<div className="calendar-page__sidebar">
|
||||
<LeftPanel
|
||||
calendars={calendars}
|
||||
selectedDate={selectedDate}
|
||||
onDateSelect={handleDateSelect}
|
||||
onCreateEvent={handleCreateEvent}
|
||||
/>
|
||||
</div>
|
||||
<div className="calendar-page__main">
|
||||
<Scheduler />
|
||||
|
||||
</div>
|
||||
<div className="calendar-page">
|
||||
<div className="calendar-page__main">
|
||||
<Scheduler />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
@@ -82,23 +50,22 @@ export default function CalendarPage() {
|
||||
CalendarPage.getLayout = function getLayout(page: React.ReactElement) {
|
||||
return (
|
||||
<CalendarContextProvider>
|
||||
<div className="calendars__calendar">
|
||||
<GlobalLayout>
|
||||
<MainLayout
|
||||
enableResize
|
||||
hideLeftPanelOnDesktop={true}
|
||||
leftPanelContent={null}
|
||||
icon={
|
||||
<div className="calendars__header__left">
|
||||
<div className="calendars__header__logo" />
|
||||
</div>
|
||||
}
|
||||
rightHeaderContent={<HeaderRight />}
|
||||
>
|
||||
{page}
|
||||
</MainLayout>
|
||||
</GlobalLayout>
|
||||
</div>
|
||||
<div className="calendars__calendar">
|
||||
<GlobalLayout>
|
||||
<MainLayout
|
||||
enableResize={false}
|
||||
leftPanelContent={<LeftPanel />}
|
||||
icon={
|
||||
<div className="calendars__header__left">
|
||||
<div className="calendars__header__logo" />
|
||||
</div>
|
||||
}
|
||||
rightHeaderContent={<HeaderRight />}
|
||||
>
|
||||
{page}
|
||||
</MainLayout>
|
||||
</GlobalLayout>
|
||||
</div>
|
||||
</CalendarContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
@use "./../features/ui/components/generic-disclaimer/GenericDisclaimer.scss";
|
||||
@use "./../features/ui/components/spinner/SpinnerPage.scss";
|
||||
@use "./../features/layouts/components/left-panel/LeftPanelMobile.scss";
|
||||
@use "./../features/calendar/components/MiniCalendar.scss";
|
||||
@use "./../features/calendar/components/CalendarList.scss";
|
||||
@use "./../features/calendar/components/LeftPanel.scss";
|
||||
@use "./../features/calendar/components/EventModal.scss";
|
||||
@use "./../features/calendar/components/RecurrenceEditor.scss";
|
||||
@use "./../features/calendar/components/AttendeesInput.scss";
|
||||
@use "./../features/calendar/components/left-panel/MiniCalendar.scss";
|
||||
@use "./../features/calendar/components/calendar-list/CalendarList.scss";
|
||||
@use "./../features/calendar/components/left-panel/LeftPanel.scss";
|
||||
@use "./../features/calendar/components/scheduler/EventModal.scss";
|
||||
@use "./../features/calendar/components/scheduler/RecurrenceEditor.scss";
|
||||
@use "./../features/calendar/components/scheduler/AttendeesInput.scss";
|
||||
@use "./../features/calendar/components/scheduler/Scheduler.scss";
|
||||
@use "./../features/calendar/components/scheduler/scheduler-theme.scss";
|
||||
@use "./../features/calendar/components/scheduler/SchedulerToolbar.scss";
|
||||
|
||||
Reference in New Issue
Block a user