✨(front) integrate mobile views into Scheduler component
Wire mobile components into the main Scheduler, adapt useSchedulerInit for mobile viewport, update EventModal and MiniCalendar for responsive behavior.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import {
|
import {
|
||||||
addMonths,
|
addMonths,
|
||||||
eachDayOfInterval,
|
eachDayOfInterval,
|
||||||
@@ -21,6 +22,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useCalendarContext } from "../../contexts";
|
import { useCalendarContext } from "../../contexts";
|
||||||
import { useCalendarLocale } from "../../hooks/useCalendarLocale";
|
import { useCalendarLocale } from "../../hooks/useCalendarLocale";
|
||||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||||
|
import { useLeftPanel } from "@/features/layouts/contexts/LeftPanelContext";
|
||||||
|
|
||||||
interface MiniCalendarProps {
|
interface MiniCalendarProps {
|
||||||
selectedDate: Date;
|
selectedDate: Date;
|
||||||
@@ -42,6 +44,9 @@ export const MiniCalendar = ({
|
|||||||
}: MiniCalendarProps) => {
|
}: MiniCalendarProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { goToDate, currentDate } = useCalendarContext();
|
const { goToDate, currentDate } = useCalendarContext();
|
||||||
|
const leftPanel = useLeftPanel();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const { dateFnsLocale, firstDayOfWeek } = useCalendarLocale();
|
const { dateFnsLocale, firstDayOfWeek } = useCalendarLocale();
|
||||||
const [viewDate, setViewDate] = useState(selectedDate);
|
const [viewDate, setViewDate] = useState(selectedDate);
|
||||||
|
|
||||||
@@ -98,6 +103,9 @@ export const MiniCalendar = ({
|
|||||||
const handleDayClick = (day: Date) => {
|
const handleDayClick = (day: Date) => {
|
||||||
onDateSelect(day);
|
onDateSelect(day);
|
||||||
goToDate(day);
|
goToDate(day);
|
||||||
|
if (isMobile) {
|
||||||
|
leftPanel.setIsLeftPanelOpen(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { InvitationResponseSection } from "./event-modal-sections/InvitationResp
|
|||||||
import { FreeBusySection } from "./event-modal-sections/FreeBusySection";
|
import { FreeBusySection } from "./event-modal-sections/FreeBusySection";
|
||||||
import { SectionPills } from "./event-modal-sections/SectionPills";
|
import { SectionPills } from "./event-modal-sections/SectionPills";
|
||||||
import { useResourcePrincipals } from "@/features/resources/api/useResourcePrincipals";
|
import { useResourcePrincipals } from "@/features/resources/api/useResourcePrincipals";
|
||||||
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import type { EventModalProps, RecurringDeleteOption } from "./types";
|
import type { EventModalProps, RecurringDeleteOption } from "./types";
|
||||||
import { SectionRow } from "./event-modal-sections/SectionRow";
|
import { SectionRow } from "./event-modal-sections/SectionRow";
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ export const EventModal = ({
|
|||||||
}: EventModalProps) => {
|
}: EventModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ export const EventModal = ({
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
size={ModalSize.MEDIUM}
|
size={isMobile ? ModalSize.FULL : ModalSize.MEDIUM}
|
||||||
title={
|
title={
|
||||||
mode === "create"
|
mode === "create"
|
||||||
? t("calendar.event.createTitle")
|
? t("calendar.event.createTitle")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
* - Click to create (dateClick)
|
* - Click to create (dateClick)
|
||||||
* - Select range to create (select)
|
* - Select range to create (select)
|
||||||
* - Custom toolbar with navigation and view selection
|
* - Custom toolbar with navigation and view selection
|
||||||
|
* - Mobile-optimized views (1 day, 2 days, list)
|
||||||
*
|
*
|
||||||
* Next.js consideration: This component must be client-side only
|
* Next.js consideration: This component must be client-side only
|
||||||
* due to DOM manipulation. Use dynamic import with ssr: false if needed.
|
* due to DOM manipulation. Use dynamic import with ssr: false if needed.
|
||||||
@@ -16,19 +17,57 @@
|
|||||||
|
|
||||||
import "@event-calendar/core/index.css";
|
import "@event-calendar/core/index.css";
|
||||||
|
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { useCalendarContext } from "../../contexts/CalendarContext";
|
import { useCalendarContext } from "../../contexts/CalendarContext";
|
||||||
|
import { useCalendarLocale } from "../../hooks/useCalendarLocale";
|
||||||
import type { CalDavCalendar } from "../../services/dav/types/caldav-service";
|
import type { CalDavCalendar } from "../../services/dav/types/caldav-service";
|
||||||
|
import type { CalDavExtendedProps } from "../../services/dav/EventCalendarAdapter";
|
||||||
|
import type { EventCalendarEvent } from "../../services/dav/types/event-calendar";
|
||||||
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
|
|
||||||
import { EventModal } from "./EventModal";
|
import { EventModal } from "./EventModal";
|
||||||
import { SchedulerToolbar } from "./SchedulerToolbar";
|
import { SchedulerToolbar } from "./SchedulerToolbar";
|
||||||
import type { SchedulerProps, EventModalState } from "./types";
|
import type { SchedulerProps, EventModalState, MobileListEvent } from "./types";
|
||||||
import { useSchedulerHandlers } from "./hooks/useSchedulerHandlers";
|
import { useSchedulerHandlers } from "./hooks/useSchedulerHandlers";
|
||||||
import {
|
import {
|
||||||
useSchedulerInit,
|
useSchedulerInit,
|
||||||
useSchedulingCapabilitiesCheck,
|
useSchedulingCapabilitiesCheck,
|
||||||
} from "./hooks/useSchedulerInit";
|
} from "./hooks/useSchedulerInit";
|
||||||
|
import { useMobileNavigation } from "./hooks/useMobileNavigation";
|
||||||
|
|
||||||
|
const MobileToolbar = dynamic(
|
||||||
|
() =>
|
||||||
|
import("./mobile/MobileToolbar").then((m) => ({
|
||||||
|
default: m.MobileToolbar,
|
||||||
|
})),
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const WeekDayBar = dynamic(
|
||||||
|
() =>
|
||||||
|
import("./mobile/WeekDayBar").then((m) => ({ default: m.WeekDayBar })),
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const FloatingActionButton = dynamic(
|
||||||
|
() =>
|
||||||
|
import("./mobile/FloatingActionButton").then((m) => ({
|
||||||
|
default: m.FloatingActionButton,
|
||||||
|
})),
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const MobileListView = dynamic(
|
||||||
|
() =>
|
||||||
|
import("./mobile/MobileListView").then((m) => ({
|
||||||
|
default: m.MobileListView,
|
||||||
|
})),
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const BROWSER_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
||||||
const {
|
const {
|
||||||
@@ -38,9 +77,13 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
visibleCalendarUrls,
|
visibleCalendarUrls,
|
||||||
isConnected,
|
isConnected,
|
||||||
calendarRef: contextCalendarRef,
|
calendarRef: contextCalendarRef,
|
||||||
|
currentDate,
|
||||||
setCurrentDate,
|
setCurrentDate,
|
||||||
} = useCalendarContext();
|
} = useCalendarContext();
|
||||||
|
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const { firstDayOfWeek, intlLocale } = useCalendarLocale();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const calendarRef = contextCalendarRef;
|
const calendarRef = contextCalendarRef;
|
||||||
const [calendarUrl, setCalendarUrl] = useState(defaultCalendarUrl || "");
|
const [calendarUrl, setCalendarUrl] = useState(defaultCalendarUrl || "");
|
||||||
@@ -103,8 +146,15 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
view?: { type: string; title: string };
|
view?: { type: string; title: string };
|
||||||
}) => {
|
}) => {
|
||||||
// Update current date for MiniCalendar sync
|
// Update current date for MiniCalendar sync
|
||||||
const midTime = (info.start.getTime() + info.end.getTime()) / 2;
|
// Use start for short views (day, 2-day) to avoid mid-point drift,
|
||||||
setCurrentDate(new Date(midTime));
|
// use midpoint for longer views (week, month) for better centering
|
||||||
|
const durationMs = info.end.getTime() - info.start.getTime();
|
||||||
|
const threeDaysMs = 3 * 24 * 60 * 60 * 1000;
|
||||||
|
const syncDate =
|
||||||
|
durationMs <= threeDaysMs
|
||||||
|
? info.start
|
||||||
|
: new Date((info.start.getTime() + info.end.getTime()) / 2);
|
||||||
|
setCurrentDate(syncDate);
|
||||||
|
|
||||||
// Update toolbar state
|
// Update toolbar state
|
||||||
if (calendarRef.current) {
|
if (calendarRef.current) {
|
||||||
@@ -118,6 +168,12 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
[setCurrentDate, calendarRef],
|
[setCurrentDate, calendarRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Counter incremented each time the calendar finishes loading events
|
||||||
|
const [eventsLoadedCounter, setEventsLoadedCounter] = useState(0);
|
||||||
|
const handleEventsLoaded = useCallback(() => {
|
||||||
|
setEventsLoadedCounter((c) => c + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Initialize calendar
|
// Initialize calendar
|
||||||
// Cast handlers to bypass library type differences between specific event types and unknown
|
// Cast handlers to bypass library type differences between specific event types and unknown
|
||||||
useSchedulerInit({
|
useSchedulerInit({
|
||||||
@@ -129,12 +185,14 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
adapter,
|
adapter,
|
||||||
visibleCalendarUrlsRef,
|
visibleCalendarUrlsRef,
|
||||||
davCalendarsRef,
|
davCalendarsRef,
|
||||||
|
initialView: isMobile ? "timeGridDay" : "timeGridWeek",
|
||||||
setCurrentDate: handleDatesSet,
|
setCurrentDate: handleDatesSet,
|
||||||
handleEventClick: handleEventClick as (info: unknown) => void,
|
handleEventClick: handleEventClick as (info: unknown) => void,
|
||||||
handleEventDrop: handleEventDrop as unknown as (info: unknown) => void,
|
handleEventDrop: handleEventDrop as unknown as (info: unknown) => void,
|
||||||
handleEventResize: handleEventResize as unknown as (info: unknown) => void,
|
handleEventResize: handleEventResize as unknown as (info: unknown) => void,
|
||||||
handleDateClick: handleDateClick as (info: unknown) => void,
|
handleDateClick: handleDateClick as (info: unknown) => void,
|
||||||
handleSelect: handleSelect as (info: unknown) => void,
|
handleSelect: handleSelect as (info: unknown) => void,
|
||||||
|
onEventsLoaded: handleEventsLoaded,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update toolbar title on initial render
|
// Update toolbar title on initial render
|
||||||
@@ -151,32 +209,173 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
// Update eventFilter when visible calendars change
|
// Update eventFilter when visible calendars change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (calendarRef.current) {
|
if (calendarRef.current) {
|
||||||
// The refs are already updated (synchronously during render)
|
|
||||||
// Now trigger a re-evaluation of eventFilter by calling refetchEvents
|
|
||||||
calendarRef.current.refetchEvents();
|
calendarRef.current.refetchEvents();
|
||||||
}
|
}
|
||||||
}, [visibleCalendarUrls, davCalendars]);
|
}, [visibleCalendarUrls, davCalendars]);
|
||||||
|
|
||||||
const handleViewChange = useCallback((view: string) => {
|
const handleViewChange = useCallback(
|
||||||
setCurrentView(view);
|
(view: string) => {
|
||||||
}, []);
|
calendarRef.current?.setOption("view", view);
|
||||||
|
setCurrentView(view);
|
||||||
|
},
|
||||||
|
[calendarRef],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Auto-switch view when crossing mobile/desktop breakpoint
|
||||||
|
const prevIsMobileRef = useRef(isMobile);
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevIsMobileRef.current === isMobile) return;
|
||||||
|
prevIsMobileRef.current = isMobile;
|
||||||
|
const targetView = isMobile ? "timeGridDay" : "timeGridWeek";
|
||||||
|
handleViewChange(targetView);
|
||||||
|
}, [isMobile, handleViewChange]);
|
||||||
|
|
||||||
|
// Mobile list view events (extracted from ref via effect to avoid ref access during render)
|
||||||
|
const [listEvents, setListEvents] = useState<MobileListEvent[]>([]);
|
||||||
|
const isListView = isMobile && currentView === "listWeek";
|
||||||
|
const currentDateMs = currentDate.getTime();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isListView || !calendarRef.current) {
|
||||||
|
setListEvents([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractTitle = (
|
||||||
|
title: string | { html: string } | { domNodes: Node[] } | undefined,
|
||||||
|
): string => {
|
||||||
|
if (!title) return "";
|
||||||
|
if (typeof title === "string") return title;
|
||||||
|
if ("html" in title) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = title.html;
|
||||||
|
return div.textContent || "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawEvents = calendarRef.current.getEvents();
|
||||||
|
const parsed: MobileListEvent[] = rawEvents.map((e) => ({
|
||||||
|
id: String(e.id),
|
||||||
|
title: extractTitle(e.title),
|
||||||
|
start: new Date(e.start),
|
||||||
|
end: e.end ? new Date(e.end) : new Date(e.start),
|
||||||
|
allDay: e.allDay ?? false,
|
||||||
|
backgroundColor: e.backgroundColor || "#2563eb",
|
||||||
|
extendedProps: e.extendedProps ?? {},
|
||||||
|
}));
|
||||||
|
setListEvents(parsed);
|
||||||
|
}, [isListView, currentDateMs, visibleCalendarUrls, eventsLoadedCounter]);
|
||||||
|
|
||||||
|
// Mobile navigation
|
||||||
|
const {
|
||||||
|
weekDays,
|
||||||
|
handleWeekPrev,
|
||||||
|
handleWeekNext,
|
||||||
|
handleDayClick,
|
||||||
|
handleTodayClick,
|
||||||
|
} = useMobileNavigation({
|
||||||
|
currentDate,
|
||||||
|
firstDayOfWeek,
|
||||||
|
calendarRef,
|
||||||
|
});
|
||||||
|
|
||||||
|
// FAB click: open create modal with current date/time
|
||||||
|
const handleFabClick = useCallback(() => {
|
||||||
|
const now = new Date();
|
||||||
|
const startDate = new Date(currentDate);
|
||||||
|
startDate.setHours(now.getHours(), 0, 0, 0);
|
||||||
|
const endDate = new Date(startDate);
|
||||||
|
endDate.setHours(startDate.getHours() + 1);
|
||||||
|
|
||||||
|
const defaultUrl = calendarUrl || davCalendars[0]?.url || "";
|
||||||
|
|
||||||
|
setModalState({
|
||||||
|
isOpen: true,
|
||||||
|
mode: "create",
|
||||||
|
event: {
|
||||||
|
uid: crypto.randomUUID(),
|
||||||
|
stamp: { date: new Date() },
|
||||||
|
start: { date: startDate, type: "DATE-TIME" },
|
||||||
|
end: { date: endDate, type: "DATE-TIME" },
|
||||||
|
},
|
||||||
|
calendarUrl: defaultUrl,
|
||||||
|
});
|
||||||
|
}, [currentDate, calendarUrl, davCalendars]);
|
||||||
|
|
||||||
|
// Mobile list view event click
|
||||||
|
const handleMobileEventClick = useCallback(
|
||||||
|
(eventId: string, extendedProps: Record<string, unknown>) => {
|
||||||
|
const events = calendarRef.current?.getEvents() ?? [];
|
||||||
|
const event = events.find((e) => String(e.id) === eventId);
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
const extProps = extendedProps as CalDavExtendedProps;
|
||||||
|
const icsEvent = adapter.toIcsEvent(event as EventCalendarEvent, {
|
||||||
|
defaultTimezone: extProps?.timezone || BROWSER_TIMEZONE,
|
||||||
|
});
|
||||||
|
|
||||||
|
setModalState({
|
||||||
|
isOpen: true,
|
||||||
|
mode: "edit",
|
||||||
|
event: icsEvent,
|
||||||
|
calendarUrl: extProps?.calendarUrl || calendarUrl,
|
||||||
|
eventUrl: extProps?.eventUrl,
|
||||||
|
etag: extProps?.etag,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[adapter, calendarUrl, calendarRef],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scheduler">
|
<div className="scheduler">
|
||||||
<SchedulerToolbar
|
{isMobile ? (
|
||||||
calendarRef={calendarRef}
|
<>
|
||||||
currentView={currentView}
|
<MobileToolbar
|
||||||
viewTitle={viewTitle}
|
calendarRef={calendarRef}
|
||||||
onViewChange={handleViewChange}
|
currentView={currentView}
|
||||||
/>
|
currentDate={currentDate}
|
||||||
|
onViewChange={handleViewChange}
|
||||||
|
onWeekPrev={handleWeekPrev}
|
||||||
|
onWeekNext={handleWeekNext}
|
||||||
|
onTodayClick={handleTodayClick}
|
||||||
|
/>
|
||||||
|
<WeekDayBar
|
||||||
|
currentDate={currentDate}
|
||||||
|
currentView={currentView}
|
||||||
|
intlLocale={intlLocale}
|
||||||
|
weekDays={weekDays}
|
||||||
|
onDayClick={handleDayClick}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<SchedulerToolbar
|
||||||
|
calendarRef={calendarRef}
|
||||||
|
currentView={currentView}
|
||||||
|
viewTitle={viewTitle}
|
||||||
|
onViewChange={handleViewChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isListView && (
|
||||||
|
<MobileListView
|
||||||
|
weekDays={weekDays}
|
||||||
|
events={listEvents}
|
||||||
|
intlLocale={intlLocale}
|
||||||
|
onEventClick={handleMobileEventClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
id="event-calendar"
|
id="event-calendar"
|
||||||
className="scheduler__calendar"
|
className="scheduler__calendar"
|
||||||
style={{ height: "calc(100vh - 52px - 90px)" }}
|
style={{
|
||||||
|
display: isListView ? "none" : undefined,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isMobile && <FloatingActionButton onClick={handleFabClick} />}
|
||||||
|
|
||||||
<EventModal
|
<EventModal
|
||||||
isOpen={modalState.isOpen}
|
isOpen={modalState.isOpen}
|
||||||
mode={modalState.mode}
|
mode={modalState.mode}
|
||||||
|
|||||||
@@ -33,12 +33,14 @@ interface UseSchedulerInitProps {
|
|||||||
adapter: EventCalendarAdapter;
|
adapter: EventCalendarAdapter;
|
||||||
visibleCalendarUrlsRef: MutableRefObject<Set<string>>;
|
visibleCalendarUrlsRef: MutableRefObject<Set<string>>;
|
||||||
davCalendarsRef: MutableRefObject<CalDavCalendar[]>;
|
davCalendarsRef: MutableRefObject<CalDavCalendar[]>;
|
||||||
|
initialView?: string;
|
||||||
setCurrentDate: (info: { start: Date; end: Date }) => void;
|
setCurrentDate: (info: { start: Date; end: Date }) => void;
|
||||||
handleEventClick: (info: unknown) => void;
|
handleEventClick: (info: unknown) => void;
|
||||||
handleEventDrop: (info: unknown) => void;
|
handleEventDrop: (info: unknown) => void;
|
||||||
handleEventResize: (info: unknown) => void;
|
handleEventResize: (info: unknown) => void;
|
||||||
handleDateClick: (info: unknown) => void;
|
handleDateClick: (info: unknown) => void;
|
||||||
handleSelect: (info: unknown) => void;
|
handleSelect: (info: unknown) => void;
|
||||||
|
onEventsLoaded?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get current time as HH:MM string
|
// Helper to get current time as HH:MM string
|
||||||
@@ -56,12 +58,14 @@ export const useSchedulerInit = ({
|
|||||||
adapter,
|
adapter,
|
||||||
visibleCalendarUrlsRef,
|
visibleCalendarUrlsRef,
|
||||||
davCalendarsRef,
|
davCalendarsRef,
|
||||||
|
initialView = "timeGridWeek",
|
||||||
setCurrentDate,
|
setCurrentDate,
|
||||||
handleEventClick,
|
handleEventClick,
|
||||||
handleEventDrop,
|
handleEventDrop,
|
||||||
handleEventResize,
|
handleEventResize,
|
||||||
handleDateClick,
|
handleDateClick,
|
||||||
handleSelect,
|
handleSelect,
|
||||||
|
onEventsLoaded,
|
||||||
}: UseSchedulerInitProps) => {
|
}: UseSchedulerInitProps) => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { calendarLocale, firstDayOfWeek, formatDayHeader } = useCalendarLocale();
|
const { calendarLocale, firstDayOfWeek, formatDayHeader } = useCalendarLocale();
|
||||||
@@ -78,6 +82,7 @@ export const useSchedulerInit = ({
|
|||||||
handleDateClick,
|
handleDateClick,
|
||||||
handleSelect,
|
handleSelect,
|
||||||
setCurrentDate,
|
setCurrentDate,
|
||||||
|
onEventsLoaded,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update refs when handlers change (no effect dependencies = no calendar recreation)
|
// Update refs when handlers change (no effect dependencies = no calendar recreation)
|
||||||
@@ -89,6 +94,7 @@ export const useSchedulerInit = ({
|
|||||||
handleDateClick,
|
handleDateClick,
|
||||||
handleSelect,
|
handleSelect,
|
||||||
setCurrentDate,
|
setCurrentDate,
|
||||||
|
onEventsLoaded,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,10 +106,18 @@ export const useSchedulerInit = ({
|
|||||||
[TimeGrid, DayGrid, List, Interaction],
|
[TimeGrid, DayGrid, List, Interaction],
|
||||||
{
|
{
|
||||||
// View configuration
|
// View configuration
|
||||||
view: "timeGridWeek",
|
view: initialView,
|
||||||
// Native toolbar disabled - using custom React toolbar (SchedulerToolbar)
|
// Native toolbar disabled - using custom React toolbar (SchedulerToolbar)
|
||||||
headerToolbar: false,
|
headerToolbar: false,
|
||||||
|
|
||||||
|
// Custom views
|
||||||
|
views: {
|
||||||
|
timeGridTwoDays: {
|
||||||
|
type: "timeGridDay",
|
||||||
|
duration: { days: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Locale & time settings
|
// Locale & time settings
|
||||||
locale: calendarLocale,
|
locale: calendarLocale,
|
||||||
firstDay: firstDayOfWeek,
|
firstDay: firstDayOfWeek,
|
||||||
@@ -246,7 +260,12 @@ export const useSchedulerInit = ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// Loading state is handled internally by the calendar
|
// Notify when events finish loading (used by mobile list view)
|
||||||
|
loading: (isLoading: boolean) => {
|
||||||
|
if (!isLoading) {
|
||||||
|
handlersRef.current.onEventsLoaded?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
@use "./../features/calendar/components/scheduler/event-modal-sections/InvitationResponseSection";
|
@use "./../features/calendar/components/scheduler/event-modal-sections/InvitationResponseSection";
|
||||||
@use "./../features/calendar/components/scheduler/event-modal-sections/SectionPill";
|
@use "./../features/calendar/components/scheduler/event-modal-sections/SectionPill";
|
||||||
@use "./../features/calendar/components/scheduler/FreeBusyTimeline";
|
@use "./../features/calendar/components/scheduler/FreeBusyTimeline";
|
||||||
|
@use "./../features/calendar/components/scheduler/mobile/WeekDayBar";
|
||||||
|
@use "./../features/calendar/components/scheduler/mobile/MobileToolbar";
|
||||||
|
@use "./../features/calendar/components/scheduler/mobile/FloatingActionButton";
|
||||||
|
@use "./../features/calendar/components/scheduler/mobile/MobileListView";
|
||||||
@use "./../features/resources/components/Resources";
|
@use "./../features/resources/components/Resources";
|
||||||
@use "./../features/settings/components/WorkingHoursSettings";
|
@use "./../features/settings/components/WorkingHoursSettings";
|
||||||
@use "./../pages/index" as *;
|
@use "./../pages/index" as *;
|
||||||
@@ -103,6 +107,13 @@ body {
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scoped to full-screen modals (mobile event modal uses FULL size)
|
||||||
|
.c__modal--full .c__modal__close button {
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c__modal__scroller {
|
.c__modal__scroller {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user