From bcf0705132690d79ea36b1077565ffb7932ca483 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:07:21 -0500 Subject: [PATCH] optimize calendar rendering with Set lookups and remove unnecessary remount key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old code built Date[] arrays with a TZDate object for every day in recording history (365+ timezone-aware date constructions). react-day-picker then did O(visible × history) date comparisons to match each of the displayed days against these arrays. Now we build Set from the raw keys (zero date construction), and pass matcher functions that do O(1) Set.has() lookups. react-day-picker only calls these for visible days --- .../overlay/ReviewActivityCalendar.tsx | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/web/src/components/overlay/ReviewActivityCalendar.tsx b/web/src/components/overlay/ReviewActivityCalendar.tsx index 86bc424ba..fe9256c23 100644 --- a/web/src/components/overlay/ReviewActivityCalendar.tsx +++ b/web/src/components/overlay/ReviewActivityCalendar.tsx @@ -3,7 +3,7 @@ import { Calendar } from "../ui/calendar"; import { ButtonHTMLAttributes, useEffect, useMemo, useRef } from "react"; import { FaCircle } from "react-icons/fa"; import { getUTCOffset } from "@/utils/dateUtil"; -import { type DayButtonProps, TZDate } from "react-day-picker"; +import { type DayButtonProps } from "react-day-picker"; import { LAST_24_HOURS_KEY } from "@/types/filter"; import { useUserPersistence } from "@/hooks/use-user-persistence"; import { cn } from "@/lib/utils"; @@ -38,60 +38,46 @@ export default function ReviewActivityCalendar({ }, []); const modifiers = useMemo(() => { - const recordings: Date[] = []; - const alerts: Date[] = []; - const detections: Date[] = []; + const recordingsSet = new Set(); + const alertsSet = new Set(); + const detectionsSet = new Set(); - // Handle recordings if (recordingsSummary) { - Object.keys(recordingsSummary).forEach((date) => { - if (date === LAST_24_HOURS_KEY) { - return; + for (const date of Object.keys(recordingsSummary)) { + if (date !== LAST_24_HOURS_KEY) { + recordingsSet.add(date); } - - const parts = date.split("-"); - const cal = new TZDate(date + "T00:00:00", timezone); - - cal.setFullYear( - parseInt(parts[0]), - parseInt(parts[1]) - 1, - parseInt(parts[2]), - ); - - recordings.push(cal); - }); + } } - // Handle reviews if present if (reviewSummary) { - Object.entries(reviewSummary).forEach(([date, data]) => { - if (date === LAST_24_HOURS_KEY) { - return; - } - - const parts = date.split("-"); - const cal = new TZDate(date + "T00:00:00", timezone); - - cal.setFullYear( - parseInt(parts[0]), - parseInt(parts[1]) - 1, - parseInt(parts[2]), - ); + for (const [date, data] of Object.entries(reviewSummary)) { + if (date === LAST_24_HOURS_KEY) continue; if (data.total_alert > data.reviewed_alert) { - alerts.push(cal); + alertsSet.add(date); } else if (data.total_detection > data.reviewed_detection) { - detections.push(cal); + detectionsSet.add(date); } - }); + } } - return { alerts, detections, recordings }; - }, [reviewSummary, recordingsSummary, timezone]); + const formatDay = (day: Date) => { + const y = day.getFullYear(); + const m = String(day.getMonth() + 1).padStart(2, "0"); + const d = String(day.getDate()).padStart(2, "0"); + return `${y}-${m}-${d}`; + }; + + return { + recordings: (day: Date) => recordingsSet.has(formatDay(day)), + alerts: (day: Date) => alertsSet.has(formatDay(day)), + detections: (day: Date) => detectionsSet.has(formatDay(day)), + }; + }, [reviewSummary, recordingsSummary]); return (