Files
new-api-analytics/lib/time-range-context.tsx
shangzy c45e026ab3 fix: add explicit type annotations to setState callbacks in time-range-context
Fixes TypeScript strict mode error where callback parameters implicitly had 'any' type.
2026-04-07 15:26:15 +08:00

119 lines
3.4 KiB
TypeScript

"use client";
import { createContext, useContext, useState, useCallback, useSyncExternalStore, type ReactNode } from "react";
import dayjs from "dayjs";
import { type TimeRange, getTimeRange } from "@/lib/utils";
interface TimeRangeContextType {
range: TimeRange;
setRange: (r: TimeRange) => void;
customStart: string;
customEnd: string;
setCustomStart: (s: string) => void;
setCustomEnd: (s: string) => void;
/** Returns { start?, end? } unix timestamps (seconds) ready for API calls */
getEffectiveRange: () => { start?: number; end?: number };
}
const TimeRangeContext = createContext<TimeRangeContextType>({
range: "30d",
setRange: () => {},
customStart: "",
customEnd: "",
setCustomStart: () => {},
setCustomEnd: () => {},
getEffectiveRange: () => ({}),
});
// Use useSyncExternalStore to safely read localStorage without hydration mismatch
const STORAGE_KEY = "time-range";
const DEFAULT_RANGE = "30d";
let listeners: Array<() => void> = [];
function subscribe(cb: () => void) {
listeners = [...listeners, cb];
return () => { listeners = listeners.filter(l => l !== cb); };
}
function emitChange() {
for (const l of listeners) l();
}
function getSnapshot(): string {
return localStorage.getItem(STORAGE_KEY) ?? "";
}
function getServerSnapshot(): string {
return "";
}
function persist(range: TimeRange, customStart: string, customEnd: string) {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ range, customStart, customEnd }));
emitChange();
}
export function TimeRangeProvider({ children }: { children: ReactNode }) {
const raw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
const saved = (() => {
if (!raw) return null;
try { return JSON.parse(raw); } catch { return null; }
})();
const [range, setRangeState] = useState<TimeRange>(saved?.range ?? DEFAULT_RANGE);
const [customStart, setCustomStartState] = useState(
saved?.customStart || dayjs().subtract(7, "day").format("YYYY-MM-DD")
);
const [customEnd, setCustomEndState] = useState(
saved?.customEnd || dayjs().format("YYYY-MM-DD")
);
const setRange = useCallback((r: TimeRange) => {
setRangeState(r);
setCustomStartState((prev: string) => {
setCustomEndState((end: string) => { persist(r, prev, end); return end; });
return prev;
});
}, []);
const setCustomStart = useCallback((s: string) => {
setCustomStartState(s);
setRangeState((r: TimeRange) => {
setCustomEndState((end: string) => { persist(r, s, end); return end; });
return r;
});
}, []);
const setCustomEnd = useCallback((e: string) => {
setCustomEndState(e);
setRangeState((r: TimeRange) => {
setCustomStartState((start: string) => { persist(r, start, e); return start; });
return r;
});
}, []);
const getEffectiveRange = useCallback(() => {
if (range === "custom") {
const result: { start?: number; end?: number } = {};
if (customStart) result.start = dayjs(customStart).startOf("day").unix();
if (customEnd) result.end = dayjs(customEnd).endOf("day").unix();
return result;
}
return getTimeRange(range);
}, [range, customStart, customEnd]);
return (
<TimeRangeContext.Provider value={{
range, setRange,
customStart, customEnd,
setCustomStart,
setCustomEnd,
getEffectiveRange,
}}>
{children}
</TimeRangeContext.Provider>
);
}
export function useTimeRange() {
return useContext(TimeRangeContext);
}