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.
94 lines
2.9 KiB
TypeScript
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);
|
|
}
|