Files
new-api-analytics/lib/time-range-context.tsx
shangzy 9bb36432ba feat: global time range context with custom date picker
Lift time range state into a shared React context so the selected
range persists across page navigation and browser refreshes
(localStorage). Add a "Custom" option with a popover date picker
that lets users specify arbitrary start/end dates. All preset end
times now use endOf("day") (23:59:59) instead of the current moment.
2026-04-07 14:49:58 +08:00

94 lines
2.9 KiB
TypeScript

"use client";
import { createContext, useContext, useState, useCallback, 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: () => ({}),
});
function loadSaved(): { range: TimeRange; customStart: string; customEnd: string } | null {
if (typeof window === "undefined") return null;
try {
const raw = localStorage.getItem("time-range");
if (!raw) return null;
const parsed = JSON.parse(raw);
return {
range: parsed.range || "30d",
customStart: parsed.customStart || "",
customEnd: parsed.customEnd || "",
};
} catch {
return null;
}
}
function persist(range: TimeRange, customStart: string, customEnd: string) {
localStorage.setItem("time-range", JSON.stringify({ range, customStart, customEnd }));
}
export function TimeRangeProvider({ children }: { children: ReactNode }) {
const [range, setRangeState] = useState<TimeRange>(() => loadSaved()?.range ?? "30d");
const [customStart, setCustomStartState] = useState(() => loadSaved()?.customStart || dayjs().subtract(7, "day").format("YYYY-MM-DD"));
const [customEnd, setCustomEndState] = useState(() => loadSaved()?.customEnd || dayjs().format("YYYY-MM-DD"));
const setRange = useCallback((r: TimeRange) => {
setRangeState(r);
setCustomStartState(prev => { persist(r, prev, customEnd); return prev; });
}, [customEnd]);
const setCustomStart = useCallback((s: string) => {
setCustomStartState(s);
persist(range, s, customEnd);
}, [range, customEnd]);
const setCustomEnd = useCallback((e: string) => {
setCustomEndState(e);
persist(range, customStart, e);
}, [range, customStart]);
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);
}