161 lines
5.0 KiB
TypeScript
161 lines
5.0 KiB
TypeScript
"use client";
|
||
|
||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback, useRef } from "react";
|
||
import { apiClient } from "@/lib/api";
|
||
import wsManager from "@/lib/websocketManager";
|
||
import { calendarService } from "@/services/calendarService";
|
||
import { useUser } from "@/contexts/UserContext";
|
||
import type { Calendar } from "@/types/api";
|
||
|
||
export type CalendarScope = "accessible" | "all";
|
||
|
||
type CalendarContextType = {
|
||
calendars: Calendar[];
|
||
selectedCalendarId: number | null;
|
||
setSelectedCalendarId: (id: number | null) => void;
|
||
calendarScope: CalendarScope;
|
||
setCalendarScope: (scope: CalendarScope) => void;
|
||
canUseAllCalendarsMode: boolean;
|
||
loading: boolean;
|
||
error: string | null;
|
||
reloadCalendars: () => Promise<void>;
|
||
};
|
||
|
||
const CalendarContext = createContext<CalendarContextType | undefined>(undefined);
|
||
|
||
export function CalendarProvider({ children }: { children: ReactNode }) {
|
||
const { user } = useUser();
|
||
const [calendars, setCalendars] = useState<Calendar[]>([]);
|
||
const [selectedCalendarId, setSelectedCalendarId] = useState<number | null>(null);
|
||
const [calendarScope, setCalendarScope] = useState<CalendarScope>("accessible");
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const initialLoadDone = useRef(false);
|
||
const calendarsRequestIdRef = useRef(0);
|
||
const realtimeSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const shouldReloadCalendarsRef = useRef(false);
|
||
const canUseAllCalendarsMode = Boolean(
|
||
user?.auth?.is_staff || user?.auth?.is_superuser
|
||
);
|
||
|
||
const loadCalendars = useCallback(async () => {
|
||
const requestId = ++calendarsRequestIdRef.current;
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
const result = await apiClient.getCalendars({
|
||
scope:
|
||
canUseAllCalendarsMode && calendarScope === "all"
|
||
? "all"
|
||
: undefined,
|
||
});
|
||
// API возвращает пагинированный ответ: {count, results}
|
||
const cals = Array.isArray(result) ? result : (result?.results || []);
|
||
if (requestId !== calendarsRequestIdRef.current) return;
|
||
setCalendars(cals);
|
||
setSelectedCalendarId((current) => (
|
||
current !== null && !cals.some((calendar) => calendar.id === current)
|
||
? null
|
||
: current
|
||
));
|
||
|
||
initialLoadDone.current = true;
|
||
} catch (err) {
|
||
if (requestId !== calendarsRequestIdRef.current) return;
|
||
console.error("Ошибка загрузки календарей:", err);
|
||
setError("Не удалось загрузить календари");
|
||
} finally {
|
||
if (requestId === calendarsRequestIdRef.current) setLoading(false);
|
||
}
|
||
}, [
|
||
calendarScope,
|
||
canUseAllCalendarsMode,
|
||
]);
|
||
|
||
useEffect(() => {
|
||
if (user) {
|
||
loadCalendars();
|
||
} else {
|
||
calendarsRequestIdRef.current += 1;
|
||
setCalendars([]);
|
||
setSelectedCalendarId(null);
|
||
setCalendarScope("accessible");
|
||
initialLoadDone.current = false;
|
||
}
|
||
}, [user, loadCalendars]);
|
||
|
||
useEffect(() => {
|
||
if (!user) return undefined;
|
||
|
||
let hasConnected = false;
|
||
let wasConnected = false;
|
||
const unsubscribeMessages = wsManager.subscribe((event) => {
|
||
if (event.type !== "calendar_update") return;
|
||
|
||
const data = event.data as {
|
||
entity?: string;
|
||
action?: string;
|
||
} | undefined;
|
||
if (!data) return;
|
||
|
||
calendarService.clearCache();
|
||
if (data.entity !== "calendar") return;
|
||
shouldReloadCalendarsRef.current = true;
|
||
|
||
if (realtimeSyncTimerRef.current) return;
|
||
realtimeSyncTimerRef.current = setTimeout(() => {
|
||
realtimeSyncTimerRef.current = null;
|
||
if (shouldReloadCalendarsRef.current) {
|
||
shouldReloadCalendarsRef.current = false;
|
||
void loadCalendars();
|
||
}
|
||
}, 100);
|
||
});
|
||
const unsubscribeStatus = wsManager.subscribeStatus((status) => {
|
||
if (status.isConnected && hasConnected && !wasConnected) {
|
||
calendarService.clearCache();
|
||
void loadCalendars();
|
||
}
|
||
if (status.isConnected) hasConnected = true;
|
||
wasConnected = status.isConnected;
|
||
});
|
||
|
||
return () => {
|
||
unsubscribeMessages();
|
||
unsubscribeStatus();
|
||
if (realtimeSyncTimerRef.current) {
|
||
clearTimeout(realtimeSyncTimerRef.current);
|
||
realtimeSyncTimerRef.current = null;
|
||
}
|
||
shouldReloadCalendarsRef.current = false;
|
||
};
|
||
}, [loadCalendars, user]);
|
||
|
||
return (
|
||
<CalendarContext.Provider
|
||
value={{
|
||
calendars,
|
||
selectedCalendarId,
|
||
setSelectedCalendarId,
|
||
calendarScope,
|
||
setCalendarScope,
|
||
canUseAllCalendarsMode,
|
||
loading,
|
||
error,
|
||
reloadCalendars: loadCalendars,
|
||
}}
|
||
>
|
||
{children}
|
||
</CalendarContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useCalendar() {
|
||
const context = useContext(CalendarContext);
|
||
if (context === undefined) {
|
||
throw new Error("useCalendar must be used within CalendarProvider");
|
||
}
|
||
return context;
|
||
}
|