@@ -1,5 +1,4 @@
|
||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||
import { getCalendarDetailAsync } from "@/features/Calendars/services";
|
||||
import EventPreviewModal from "@/features/Events/EventDisplayPreview";
|
||||
import EventPopover from "@/features/Events/EventModal";
|
||||
import { CalendarEvent } from "@/features/Events/EventsTypes";
|
||||
@@ -7,10 +6,6 @@ import ImportAlert from "@/features/Events/ImportAlert";
|
||||
import SearchResultsPage from "@/features/Search/SearchResultsPage";
|
||||
import { setTimeZone } from "@/features/Settings/SettingsSlice";
|
||||
import { setDisplayedDateAndRange } from "@/utils/CalendarRangeManager";
|
||||
import {
|
||||
formatDateToYYYYMMDDTHHMMSS,
|
||||
getCalendarRange,
|
||||
} from "@/utils/dateUtils";
|
||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||
import { setSelectedCalendars as setSelectedCalendarsToStorage } from "@/utils/storage/setSelectedCalendars";
|
||||
import { useSelectedCalendars } from "@/utils/storage/useSelectedCalendars";
|
||||
@@ -42,6 +37,7 @@ import { useCalendarViewHandlers } from "./hooks/useCalendarViewHandlers";
|
||||
import { MiniCalendar } from "./MiniCalendar";
|
||||
import { TempCalendarsInput } from "./TempCalendarsInput";
|
||||
import { TimezoneSelector } from "./TimezoneSelector";
|
||||
import { useCalendarDataLoader } from "../../features/Calendars/useCalendarLoader";
|
||||
import { updateDarkColor } from "./utils/calendarColorsUtils";
|
||||
import {
|
||||
eventToFullCalendarFormat,
|
||||
@@ -70,6 +66,11 @@ export default function CalendarApp({
|
||||
menubarProps,
|
||||
}: CalendarAppProps) {
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const [debouncedDate, setDebouncedDate] = useState(new Date());
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => setDebouncedDate(selectedDate), 300);
|
||||
return () => clearTimeout(t);
|
||||
}, [selectedDate]);
|
||||
const [selectedMiniDate, setSelectedMiniDate] = useState(new Date());
|
||||
const userId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
@@ -114,21 +115,11 @@ export default function CalendarApp({
|
||||
useAppSelector((state) => state.settings.timeZone) ??
|
||||
browserDefaultTimeZone;
|
||||
|
||||
const fetchedRangesRef = useRef<Record<string, string>>({});
|
||||
|
||||
// Auto-select personal calendars when first loaded
|
||||
const initialLoadRef = useRef(true);
|
||||
const [eventErrors, setEventErrors] = useState<string[]>([]);
|
||||
const errorHandler = useRef(new EventErrorHandler());
|
||||
|
||||
// useEffect(() => {
|
||||
// if (view === "search") {
|
||||
// document.body.classList.add("dialog-expanded");
|
||||
// } else {
|
||||
// document.body.classList.remove("dialog-expanded");
|
||||
// }
|
||||
// }, [view]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = errorHandler.current;
|
||||
handler.setErrorCallback(setEventErrors);
|
||||
@@ -195,29 +186,32 @@ export default function CalendarApp({
|
||||
});
|
||||
}, [calendarIds]);
|
||||
|
||||
const calendarRange = useMemo(
|
||||
() => getCalendarRange(selectedDate),
|
||||
[selectedDate]
|
||||
const sortedSelectedCalendars = useMemo(
|
||||
() => [...selectedCalendars].sort(),
|
||||
[selectedCalendars]
|
||||
);
|
||||
|
||||
const calendarRangeStart = calendarRange.start.getTime();
|
||||
const calendarRangeEnd = calendarRange.end.getTime();
|
||||
|
||||
const rangeStart = useMemo(
|
||||
() => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeStart)),
|
||||
[calendarRangeStart]
|
||||
const tempCalendarIdsString = useMemo(
|
||||
() =>
|
||||
Object.keys(tempcalendars || {})
|
||||
.sort()
|
||||
.join(","),
|
||||
[tempcalendars]
|
||||
);
|
||||
const tempCalendarIds = useMemo(
|
||||
() => (tempCalendarIdsString ? tempCalendarIdsString.split(",") : []),
|
||||
[tempCalendarIdsString]
|
||||
);
|
||||
|
||||
const rangeEnd = useMemo(
|
||||
() => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeEnd)),
|
||||
[calendarRangeEnd]
|
||||
);
|
||||
|
||||
// Create a stable string key for the range
|
||||
const rangeKey = useMemo(
|
||||
() => `${rangeStart}_${rangeEnd}`,
|
||||
[rangeStart, rangeEnd]
|
||||
);
|
||||
useCalendarDataLoader({
|
||||
selectedDate: debouncedDate,
|
||||
currentView,
|
||||
selectedCalendars,
|
||||
sortedSelectedCalendars,
|
||||
calendarIds,
|
||||
calendarIdsString,
|
||||
tempCalendarIds,
|
||||
});
|
||||
|
||||
const filteredEvents: CalendarEvent[] = extractEvents(
|
||||
selectedCalendars,
|
||||
@@ -226,11 +220,6 @@ export default function CalendarApp({
|
||||
hideDeclinedEvents
|
||||
);
|
||||
|
||||
const tempCalendarIds = useMemo(
|
||||
() => Object.keys(tempcalendars || {}).sort(),
|
||||
[tempcalendars]
|
||||
);
|
||||
|
||||
const filteredTempEvents: CalendarEvent[] = extractEvents(
|
||||
tempCalendarIds,
|
||||
tempcalendars || {},
|
||||
@@ -238,242 +227,6 @@ export default function CalendarApp({
|
||||
hideDeclinedEvents
|
||||
);
|
||||
|
||||
const sortedSelectedCalendars = useMemo(
|
||||
() => [...selectedCalendars].sort(),
|
||||
[selectedCalendars]
|
||||
);
|
||||
|
||||
const prefetchedCalendarsRef = useRef<Record<string, string>>({});
|
||||
const tempFetchedRangesRef = useRef<Record<string, string>>({});
|
||||
const activeLoadCompletedRef = useRef<boolean>(false);
|
||||
const [activeLoadCompleted, setActiveLoadCompleted] =
|
||||
useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
activeLoadCompletedRef.current = false;
|
||||
setActiveLoadCompleted(false);
|
||||
}, [rangeKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPending) return;
|
||||
|
||||
if (!rangeKey || sortedSelectedCalendars.length === 0) {
|
||||
activeLoadCompletedRef.current = true;
|
||||
setActiveLoadCompleted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const ACTIVE_BATCH_SIZE = 5;
|
||||
|
||||
const loadCalendars = async () => {
|
||||
try {
|
||||
const pendingIds = sortedSelectedCalendars.filter(
|
||||
(id) => fetchedRangesRef.current[id] !== rangeKey
|
||||
);
|
||||
|
||||
if (pendingIds.length === 0) {
|
||||
activeLoadCompletedRef.current = true;
|
||||
setActiveLoadCompleted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < pendingIds.length && !cancelled;
|
||||
i += ACTIVE_BATCH_SIZE
|
||||
) {
|
||||
const chunk = pendingIds.slice(i, i + ACTIVE_BATCH_SIZE);
|
||||
|
||||
chunk.forEach((id) => {
|
||||
fetchedRangesRef.current[id] = rangeKey;
|
||||
prefetchedCalendarsRef.current[id] = "active";
|
||||
});
|
||||
|
||||
const requests = chunk.map(async (id) => {
|
||||
try {
|
||||
await dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: id,
|
||||
match: {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
},
|
||||
})
|
||||
).unwrap();
|
||||
} catch {
|
||||
fetchedRangesRef.current[id] = "";
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(requests);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
activeLoadCompletedRef.current = true;
|
||||
setActiveLoadCompleted(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadCalendars();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch, rangeKey, sortedSelectedCalendars, rangeStart, rangeEnd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rangeKey || !activeLoadCompleted || isPending) return;
|
||||
const hiddenCalendars = calendarIds
|
||||
.filter((id) => !selectedCalendars.includes(id))
|
||||
.filter((id) => {
|
||||
const prefetched = prefetchedCalendarsRef.current[id];
|
||||
return prefetched !== rangeKey && prefetched !== "active";
|
||||
});
|
||||
|
||||
if (hiddenCalendars.length === 0) return;
|
||||
|
||||
hiddenCalendars.forEach((id) => {
|
||||
prefetchedCalendarsRef.current[id] = rangeKey;
|
||||
dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: id,
|
||||
match: {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
},
|
||||
})
|
||||
)
|
||||
.unwrap()
|
||||
.catch(() => {
|
||||
prefetchedCalendarsRef.current[id] = "";
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
calendarIdsString,
|
||||
selectedCalendars,
|
||||
rangeKey,
|
||||
dispatch,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
activeLoadCompleted,
|
||||
isPending,
|
||||
]);
|
||||
|
||||
const calendarsWithClearedCache = useMemo(() => {
|
||||
return selectedCalendars
|
||||
.map((id) => {
|
||||
const cleared = calendars[id]?.lastCacheCleared;
|
||||
return cleared ? { id, cleared } : null;
|
||||
})
|
||||
.filter(Boolean) as { id: string; cleared: number }[];
|
||||
}, [selectedCalendars, calendars]);
|
||||
|
||||
const processedCacheClearRef = useRef<Record<string, number>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (isPending) return;
|
||||
calendarsWithClearedCache.forEach(({ id, cleared }) => {
|
||||
if (processedCacheClearRef.current[id] === cleared) {
|
||||
return;
|
||||
}
|
||||
|
||||
processedCacheClearRef.current[id] = cleared;
|
||||
delete fetchedRangesRef.current[id];
|
||||
prefetchedCalendarsRef.current[id] = "";
|
||||
|
||||
dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: id,
|
||||
match: {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
},
|
||||
})
|
||||
)
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
fetchedRangesRef.current[id] = rangeKey;
|
||||
prefetchedCalendarsRef.current[id] = rangeKey;
|
||||
})
|
||||
.catch(() => {
|
||||
fetchedRangesRef.current[id] = "";
|
||||
prefetchedCalendarsRef.current[id] = "";
|
||||
});
|
||||
});
|
||||
}, [
|
||||
calendarsWithClearedCache,
|
||||
dispatch,
|
||||
rangeKey,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
isPending,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentIds = new Set(tempCalendarIds);
|
||||
Object.keys(tempFetchedRangesRef.current).forEach((id) => {
|
||||
if (!currentIds.has(id)) {
|
||||
delete tempFetchedRangesRef.current[id];
|
||||
}
|
||||
});
|
||||
}, [tempCalendarIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rangeKey || tempCalendarIds.length === 0) return;
|
||||
|
||||
let cancelled = false;
|
||||
const TEMP_BATCH_SIZE = 5;
|
||||
const loadTempCalendars = async () => {
|
||||
const pendingIds = tempCalendarIds.filter(
|
||||
(id) => tempFetchedRangesRef.current[id] !== rangeKey
|
||||
);
|
||||
|
||||
if (pendingIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < pendingIds.length && !cancelled;
|
||||
i += TEMP_BATCH_SIZE
|
||||
) {
|
||||
const chunk = pendingIds.slice(i, i + TEMP_BATCH_SIZE);
|
||||
chunk.forEach((id) => {
|
||||
tempFetchedRangesRef.current[id] = rangeKey;
|
||||
});
|
||||
|
||||
const requests = chunk.map(async (id) => {
|
||||
try {
|
||||
await dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: id,
|
||||
match: {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
},
|
||||
calType: "temp",
|
||||
})
|
||||
).unwrap();
|
||||
} catch {
|
||||
tempFetchedRangesRef.current[id] = "";
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(requests);
|
||||
}
|
||||
};
|
||||
|
||||
loadTempCalendars();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [dispatch, rangeKey, tempCalendarIds, rangeStart, rangeEnd]);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const [openEventDisplay, setOpenEventDisplay] = useState(false);
|
||||
const [eventDisplayedId, setEventDisplayedId] = useState("");
|
||||
|
||||
Reference in New Issue
Block a user