♻️(front) integrate custom toolbar in Scheduler
- Integrate SchedulerToolbar component above calendar container - Disable native headerToolbar in useSchedulerInit - Add toolbar state (currentView, viewTitle) to Scheduler - Use datesSet callback to sync toolbar with calendar navigation - Update CalendarContext to use CalendarApi type for better type safety - Pass calendarRef to toolbar for API method access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
* - Click to edit (eventClick)
|
* - Click to edit (eventClick)
|
||||||
* - 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
|
||||||
*
|
*
|
||||||
* 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.
|
||||||
@@ -15,13 +16,13 @@
|
|||||||
|
|
||||||
import "@event-calendar/core/index.css";
|
import "@event-calendar/core/index.css";
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { useCalendarContext } from "../../contexts/CalendarContext";
|
import { useCalendarContext } from "../../contexts/CalendarContext";
|
||||||
import type { CalDavCalendar } from "../../services/dav/types/caldav-service";
|
import type { CalDavCalendar } from "../../services/dav/types/caldav-service";
|
||||||
import type { EventCalendarEvent } from "../../services/dav/types/event-calendar";
|
|
||||||
|
|
||||||
import { EventModal } from "./EventModal";
|
import { EventModal } from "./EventModal";
|
||||||
|
import { SchedulerToolbar } from "./SchedulerToolbar";
|
||||||
import type { SchedulerProps, EventModalState } from "./types";
|
import type { SchedulerProps, EventModalState } from "./types";
|
||||||
import { useSchedulerHandlers } from "./hooks/useSchedulerHandlers";
|
import { useSchedulerHandlers } from "./hooks/useSchedulerHandlers";
|
||||||
import {
|
import {
|
||||||
@@ -29,17 +30,6 @@ import {
|
|||||||
useSchedulingCapabilitiesCheck,
|
useSchedulingCapabilitiesCheck,
|
||||||
} from "./hooks/useSchedulerInit";
|
} from "./hooks/useSchedulerInit";
|
||||||
|
|
||||||
type ECEvent = EventCalendarEvent;
|
|
||||||
|
|
||||||
// Calendar API interface
|
|
||||||
interface CalendarApi {
|
|
||||||
updateEvent: (event: ECEvent) => void;
|
|
||||||
addEvent: (event: ECEvent) => void;
|
|
||||||
unselect: () => void;
|
|
||||||
refetchEvents: () => void;
|
|
||||||
$destroy?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
||||||
const {
|
const {
|
||||||
caldavService,
|
caldavService,
|
||||||
@@ -52,9 +42,13 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
} = useCalendarContext();
|
} = useCalendarContext();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const calendarRef = contextCalendarRef as React.MutableRefObject<CalendarApi | null>;
|
const calendarRef = contextCalendarRef;
|
||||||
const [calendarUrl, setCalendarUrl] = useState(defaultCalendarUrl || "");
|
const [calendarUrl, setCalendarUrl] = useState(defaultCalendarUrl || "");
|
||||||
|
|
||||||
|
// Toolbar state
|
||||||
|
const [currentView, setCurrentView] = useState("timeGridWeek");
|
||||||
|
const [viewTitle, setViewTitle] = useState("");
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
const [modalState, setModalState] = useState<EventModalState>({
|
const [modalState, setModalState] = useState<EventModalState>({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
@@ -102,6 +96,25 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
setModalState,
|
setModalState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Callback to update toolbar state when calendar dates/view changes
|
||||||
|
const handleDatesSet = useCallback(
|
||||||
|
(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));
|
||||||
|
|
||||||
|
// Update toolbar state
|
||||||
|
if (calendarRef.current) {
|
||||||
|
const view = calendarRef.current.getView();
|
||||||
|
if (view) {
|
||||||
|
setCurrentView(view.type);
|
||||||
|
setViewTitle(view.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setCurrentDate, calendarRef]
|
||||||
|
);
|
||||||
|
|
||||||
// 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({
|
||||||
@@ -113,7 +126,7 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
adapter,
|
adapter,
|
||||||
visibleCalendarUrlsRef,
|
visibleCalendarUrlsRef,
|
||||||
davCalendarsRef,
|
davCalendarsRef,
|
||||||
setCurrentDate,
|
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,
|
||||||
@@ -121,6 +134,17 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
handleSelect: handleSelect as (info: unknown) => void,
|
handleSelect: handleSelect as (info: unknown) => void,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update toolbar title on initial render
|
||||||
|
useEffect(() => {
|
||||||
|
if (calendarRef.current) {
|
||||||
|
const view = calendarRef.current.getView();
|
||||||
|
if (view) {
|
||||||
|
setCurrentView(view.type);
|
||||||
|
setViewTitle(view.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isConnected]);
|
||||||
|
|
||||||
// Update eventFilter when visible calendars change
|
// Update eventFilter when visible calendars change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (calendarRef.current) {
|
if (calendarRef.current) {
|
||||||
@@ -130,12 +154,24 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
}
|
}
|
||||||
}, [visibleCalendarUrls, davCalendars]);
|
}, [visibleCalendarUrls, davCalendars]);
|
||||||
|
|
||||||
|
const handleViewChange = useCallback((view: string) => {
|
||||||
|
setCurrentView(view);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="scheduler">
|
||||||
|
<SchedulerToolbar
|
||||||
|
calendarRef={calendarRef}
|
||||||
|
currentView={currentView}
|
||||||
|
viewTitle={viewTitle}
|
||||||
|
onViewChange={handleViewChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
id="event-calendar"
|
id="event-calendar"
|
||||||
style={{ height: "calc(100vh - 100px)" }}
|
className="scheduler__calendar"
|
||||||
|
style={{ height: "calc(100vh - 160px)" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EventModal
|
<EventModal
|
||||||
@@ -150,7 +186,7 @@ export const Scheduler = ({ defaultCalendarUrl }: SchedulerProps) => {
|
|||||||
onRespondToInvitation={handleRespondToInvitation}
|
onRespondToInvitation={handleRespondToInvitation}
|
||||||
onClose={handleModalClose}
|
onClose={handleModalClose}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ interface UseSchedulerInitProps {
|
|||||||
adapter: EventCalendarAdapter;
|
adapter: EventCalendarAdapter;
|
||||||
visibleCalendarUrlsRef: MutableRefObject<Set<string>>;
|
visibleCalendarUrlsRef: MutableRefObject<Set<string>>;
|
||||||
davCalendarsRef: MutableRefObject<CalDavCalendar[]>;
|
davCalendarsRef: MutableRefObject<CalDavCalendar[]>;
|
||||||
setCurrentDate: (date: 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;
|
||||||
@@ -75,20 +75,8 @@ export const useSchedulerInit = ({
|
|||||||
{
|
{
|
||||||
// View configuration
|
// View configuration
|
||||||
view: "timeGridWeek",
|
view: "timeGridWeek",
|
||||||
headerToolbar: {
|
// Native toolbar disabled - using custom React toolbar (SchedulerToolbar)
|
||||||
start: "prev,next today",
|
headerToolbar: false,
|
||||||
center: "title",
|
|
||||||
end: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Button text translations
|
|
||||||
buttonText: {
|
|
||||||
today: t('calendar.views.today'),
|
|
||||||
dayGridMonth: t('calendar.views.month'),
|
|
||||||
timeGridWeek: t('calendar.views.week'),
|
|
||||||
timeGridDay: t('calendar.views.day'),
|
|
||||||
listWeek: t('calendar.views.listWeek'),
|
|
||||||
},
|
|
||||||
|
|
||||||
// Locale & time settings
|
// Locale & time settings
|
||||||
locale: calendarLocale,
|
locale: calendarLocale,
|
||||||
@@ -117,9 +105,7 @@ export const useSchedulerInit = ({
|
|||||||
|
|
||||||
// Sync current date with MiniCalendar when navigating
|
// Sync current date with MiniCalendar when navigating
|
||||||
datesSet: (info: { start: Date; end: Date }) => {
|
datesSet: (info: { start: Date; end: Date }) => {
|
||||||
// Use the middle of the visible range as the "current" date
|
setCurrentDate(info);
|
||||||
const midTime = (info.start.getTime() + info.end.getTime()) / 2;
|
|
||||||
setCurrentDate(new Date(midTime));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Event display
|
// Event display
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import { createContext, useContext, useRef, useMemo, useState, useEffect, useCallback, type ReactNode } from "react";
|
import {
|
||||||
import { Calendar } from "@event-calendar/core";
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
import { CalDavService } from "../services/dav/CalDavService";
|
import { CalDavService } from "../services/dav/CalDavService";
|
||||||
import { EventCalendarAdapter } from "../services/dav/EventCalendarAdapter";
|
import { EventCalendarAdapter } from "../services/dav/EventCalendarAdapter";
|
||||||
import { caldavServerUrl, headers, fetchOptions } from "../utils/DavClient";
|
import { caldavServerUrl, headers, fetchOptions } from "../utils/DavClient";
|
||||||
import type { CalDavCalendar, CalDavCalendarCreate } from "../services/dav/types/caldav-service";
|
import type {
|
||||||
|
CalDavCalendar,
|
||||||
|
CalDavCalendarCreate,
|
||||||
|
} from "../services/dav/types/caldav-service";
|
||||||
|
import type { CalendarApi } from "../components/scheduler/types";
|
||||||
import { createCalendarApi } from "../api";
|
import { createCalendarApi } from "../api";
|
||||||
|
|
||||||
export interface CalendarContextType {
|
export interface CalendarContextType {
|
||||||
calendarRef: React.RefObject<Calendar | null>;
|
calendarRef: React.RefObject<CalendarApi | null>;
|
||||||
caldavService: CalDavService;
|
caldavService: CalDavService;
|
||||||
adapter: EventCalendarAdapter;
|
adapter: EventCalendarAdapter;
|
||||||
davCalendars: CalDavCalendar[];
|
davCalendars: CalDavCalendar[];
|
||||||
@@ -20,19 +32,33 @@ export interface CalendarContextType {
|
|||||||
setSelectedDate: (date: Date) => void;
|
setSelectedDate: (date: Date) => void;
|
||||||
refreshCalendars: () => Promise<void>;
|
refreshCalendars: () => Promise<void>;
|
||||||
toggleCalendarVisibility: (calendarUrl: string) => void;
|
toggleCalendarVisibility: (calendarUrl: string) => void;
|
||||||
createCalendar: (params: CalDavCalendarCreate) => Promise<{ success: boolean; error?: string }>;
|
createCalendar: (
|
||||||
updateCalendar: (calendarUrl: string, params: { displayName?: string; color?: string; description?: string }) => Promise<{ success: boolean; error?: string }>;
|
params: CalDavCalendarCreate,
|
||||||
deleteCalendar: (calendarUrl: string) => Promise<{ success: boolean; error?: string }>;
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
shareCalendar: (calendarUrl: string, email: string) => Promise<{ success: boolean; error?: string }>;
|
updateCalendar: (
|
||||||
|
calendarUrl: string,
|
||||||
|
params: { displayName?: string; color?: string; description?: string },
|
||||||
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
deleteCalendar: (
|
||||||
|
calendarUrl: string,
|
||||||
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
shareCalendar: (
|
||||||
|
calendarUrl: string,
|
||||||
|
email: string,
|
||||||
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
goToDate: (date: Date) => void;
|
goToDate: (date: Date) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CalendarContext = createContext<CalendarContextType | undefined>(undefined);
|
const CalendarContext = createContext<CalendarContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
export const useCalendarContext = () => {
|
export const useCalendarContext = () => {
|
||||||
const context = useContext(CalendarContext);
|
const context = useContext(CalendarContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useCalendarContext must be used within a CalendarContextProvider");
|
throw new Error(
|
||||||
|
"useCalendarContext must be used within a CalendarContextProvider",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
@@ -41,12 +67,16 @@ interface CalendarContextProviderProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CalendarContextProvider = ({ children }: CalendarContextProviderProps) => {
|
export const CalendarContextProvider = ({
|
||||||
const calendarRef = useRef<Calendar | null>(null);
|
children,
|
||||||
|
}: CalendarContextProviderProps) => {
|
||||||
|
const calendarRef = useRef<CalendarApi | null>(null);
|
||||||
const caldavService = useMemo(() => new CalDavService(), []);
|
const caldavService = useMemo(() => new CalDavService(), []);
|
||||||
const adapter = useMemo(() => new EventCalendarAdapter(), []);
|
const adapter = useMemo(() => new EventCalendarAdapter(), []);
|
||||||
const [davCalendars, setDavCalendars] = useState<CalDavCalendar[]>([]);
|
const [davCalendars, setDavCalendars] = useState<CalDavCalendar[]>([]);
|
||||||
const [visibleCalendarUrls, setVisibleCalendarUrls] = useState<Set<string>>(new Set());
|
const [visibleCalendarUrls, setVisibleCalendarUrls] = useState<Set<string>>(
|
||||||
|
new Set(),
|
||||||
|
);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [currentDate, setCurrentDate] = useState<Date>(new Date());
|
const [currentDate, setCurrentDate] = useState<Date>(new Date());
|
||||||
@@ -59,7 +89,7 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setDavCalendars(result.data);
|
setDavCalendars(result.data);
|
||||||
// Initialize all calendars as visible
|
// Initialize all calendars as visible
|
||||||
setVisibleCalendarUrls(new Set(result.data.map(cal => cal.url)));
|
setVisibleCalendarUrls(new Set(result.data.map((cal) => cal.url)));
|
||||||
} else {
|
} else {
|
||||||
console.error("Error fetching calendars:", result.error);
|
console.error("Error fetching calendars:", result.error);
|
||||||
setDavCalendars([]);
|
setDavCalendars([]);
|
||||||
@@ -75,7 +105,7 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
}, [caldavService]);
|
}, [caldavService]);
|
||||||
|
|
||||||
const toggleCalendarVisibility = useCallback((calendarUrl: string) => {
|
const toggleCalendarVisibility = useCallback((calendarUrl: string) => {
|
||||||
setVisibleCalendarUrls(prev => {
|
setVisibleCalendarUrls((prev) => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
if (newSet.has(calendarUrl)) {
|
if (newSet.has(calendarUrl)) {
|
||||||
newSet.delete(calendarUrl);
|
newSet.delete(calendarUrl);
|
||||||
@@ -86,85 +116,124 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const createCalendar = useCallback(async (params: CalDavCalendarCreate): Promise<{ success: boolean; error?: string }> => {
|
const createCalendar = useCallback(
|
||||||
try {
|
async (
|
||||||
// Use Django API to create calendar (creates both CalDAV and Django records)
|
params: CalDavCalendarCreate,
|
||||||
await createCalendarApi({
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
name: params.displayName,
|
try {
|
||||||
color: params.color,
|
// Use Django API to create calendar (creates both CalDAV and Django records)
|
||||||
description: params.description,
|
await createCalendarApi({
|
||||||
});
|
name: params.displayName,
|
||||||
// Refresh CalDAV calendars list to show the new calendar
|
color: params.color,
|
||||||
await refreshCalendars();
|
description: params.description,
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating calendar:", error);
|
|
||||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
||||||
}
|
|
||||||
}, [refreshCalendars]);
|
|
||||||
|
|
||||||
const updateCalendar = useCallback(async (
|
|
||||||
calendarUrl: string,
|
|
||||||
params: { displayName?: string; color?: string; description?: string }
|
|
||||||
): Promise<{ success: boolean; error?: string }> => {
|
|
||||||
try {
|
|
||||||
const result = await caldavService.updateCalendar(calendarUrl, params);
|
|
||||||
if (result.success) {
|
|
||||||
await refreshCalendars();
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
return { success: false, error: result.error || 'Failed to update calendar' };
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating calendar:", error);
|
|
||||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
||||||
}
|
|
||||||
}, [caldavService, refreshCalendars]);
|
|
||||||
|
|
||||||
const deleteCalendar = useCallback(async (calendarUrl: string): Promise<{ success: boolean; error?: string }> => {
|
|
||||||
try {
|
|
||||||
const result = await caldavService.deleteCalendar(calendarUrl);
|
|
||||||
if (result.success) {
|
|
||||||
// Remove from visible calendars
|
|
||||||
setVisibleCalendarUrls(prev => {
|
|
||||||
const newSet = new Set(prev);
|
|
||||||
newSet.delete(calendarUrl);
|
|
||||||
return newSet;
|
|
||||||
});
|
});
|
||||||
|
// Refresh CalDAV calendars list to show the new calendar
|
||||||
await refreshCalendars();
|
await refreshCalendars();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating calendar:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { success: false, error: result.error || 'Failed to delete calendar' };
|
},
|
||||||
} catch (error) {
|
[refreshCalendars],
|
||||||
console.error("Error deleting calendar:", error);
|
);
|
||||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
||||||
}
|
|
||||||
}, [caldavService, refreshCalendars]);
|
|
||||||
|
|
||||||
const shareCalendar = useCallback(async (
|
const updateCalendar = useCallback(
|
||||||
calendarUrl: string,
|
async (
|
||||||
email: string
|
calendarUrl: string,
|
||||||
): Promise<{ success: boolean; error?: string }> => {
|
params: { displayName?: string; color?: string; description?: string },
|
||||||
try {
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
const result = await caldavService.shareCalendar({
|
try {
|
||||||
calendarUrl,
|
const result = await caldavService.updateCalendar(calendarUrl, params);
|
||||||
sharees: [{
|
if (result.success) {
|
||||||
href: `mailto:${email}`,
|
await refreshCalendars();
|
||||||
privilege: 'read-write', // Same rights as principal
|
return { success: true };
|
||||||
}],
|
}
|
||||||
});
|
return {
|
||||||
if (result.success) {
|
success: false,
|
||||||
return { success: true };
|
error: result.error || "Failed to update calendar",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating calendar:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { success: false, error: result.error || 'Failed to share calendar' };
|
},
|
||||||
} catch (error) {
|
[caldavService, refreshCalendars],
|
||||||
console.error("Error sharing calendar:", error);
|
);
|
||||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
||||||
}
|
const deleteCalendar = useCallback(
|
||||||
}, [caldavService]);
|
async (
|
||||||
|
calendarUrl: string,
|
||||||
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
|
try {
|
||||||
|
const result = await caldavService.deleteCalendar(calendarUrl);
|
||||||
|
if (result.success) {
|
||||||
|
// Remove from visible calendars
|
||||||
|
setVisibleCalendarUrls((prev) => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(calendarUrl);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
await refreshCalendars();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: result.error || "Failed to delete calendar",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting calendar:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[caldavService, refreshCalendars],
|
||||||
|
);
|
||||||
|
|
||||||
|
const shareCalendar = useCallback(
|
||||||
|
async (
|
||||||
|
calendarUrl: string,
|
||||||
|
email: string,
|
||||||
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
|
try {
|
||||||
|
const result = await caldavService.shareCalendar({
|
||||||
|
calendarUrl,
|
||||||
|
sharees: [
|
||||||
|
{
|
||||||
|
href: `mailto:${email}`,
|
||||||
|
privilege: "read-write", // Same rights as principal
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (result.success) {
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: result.error || "Failed to share calendar",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sharing calendar:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[caldavService],
|
||||||
|
);
|
||||||
|
|
||||||
const goToDate = useCallback((date: Date) => {
|
const goToDate = useCallback((date: Date) => {
|
||||||
if (calendarRef.current) {
|
if (calendarRef.current) {
|
||||||
calendarRef.current.setOption('date', date);
|
calendarRef.current.setOption("date", date);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -187,7 +256,9 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
const calendarsResult = await caldavService.fetchCalendars();
|
const calendarsResult = await caldavService.fetchCalendars();
|
||||||
if (isMounted && calendarsResult.success && calendarsResult.data) {
|
if (isMounted && calendarsResult.success && calendarsResult.data) {
|
||||||
setDavCalendars(calendarsResult.data);
|
setDavCalendars(calendarsResult.data);
|
||||||
setVisibleCalendarUrls(new Set(calendarsResult.data.map(cal => cal.url)));
|
setVisibleCalendarUrls(
|
||||||
|
new Set(calendarsResult.data.map((cal) => cal.url)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else if (isMounted) {
|
} else if (isMounted) {
|
||||||
@@ -210,7 +281,6 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
};
|
};
|
||||||
// Note: refreshCalendars is excluded to avoid dependency cycle
|
// Note: refreshCalendars is excluded to avoid dependency cycle
|
||||||
// The initial fetch is done inline in this effect
|
// The initial fetch is done inline in this effect
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [caldavService]);
|
}, [caldavService]);
|
||||||
|
|
||||||
const value: CalendarContextType = {
|
const value: CalendarContextType = {
|
||||||
@@ -234,5 +304,9 @@ export const CalendarContextProvider = ({ children }: CalendarContextProviderPro
|
|||||||
goToDate,
|
goToDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <CalendarContext.Provider value={value}>{children}</CalendarContext.Provider>;
|
return (
|
||||||
|
<CalendarContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</CalendarContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user