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.
This commit is contained in:
2026-04-07 14:49:58 +08:00
parent 004fd37622
commit 9bb36432ba
10 changed files with 280 additions and 55 deletions

View File

@@ -5,7 +5,8 @@ import Link from "next/link";
import { motion } from "motion/react";
import { Trophy, Users, Cpu, Radio, ArrowUpDown, ArrowDown, ArrowUp } from "lucide-react";
import { TimeRangeSelector } from "@/components/TimeRangeSelector";
import { type TimeRange, getTimeRange, buildQuery, formatNumber, formatTokens } from "@/lib/utils";
import { buildQuery, formatNumber, formatTokens } from "@/lib/utils";
import { useTimeRange } from "@/lib/time-range-context";
import { useI18n } from "@/lib/i18n";
type Tab = "user" | "model" | "channel";
@@ -18,7 +19,7 @@ interface RankItem {
export default function RankingsPage() {
const { t } = useI18n();
const [range, setRange] = useState<TimeRange>("30d");
const { getEffectiveRange } = useTimeRange();
const [tab, setTab] = useState<Tab>("user");
const [data, setData] = useState<RankItem[]>([]);
const [loading, setLoading] = useState(true);
@@ -33,11 +34,11 @@ export default function RankingsPage() {
const fetchData = useCallback(async () => {
setLoading(true);
const { start, end } = getTimeRange(range);
const { start, end } = getEffectiveRange();
const res = await fetch(buildQuery("/api/rankings", { start, end, type: tab, limit: 100 }));
setData(await res.json());
setLoading(false);
}, [range, tab]);
}, [tab, getEffectiveRange]);
useEffect(() => { fetchData(); }, [fetchData]);
@@ -80,7 +81,7 @@ export default function RankingsPage() {
<Trophy className="h-5 w-5" style={{ color: "var(--accent)", opacity: 0.6 }} />
<h1 className="text-2xl font-bold gradient-text">{t("rank.title")}</h1>
</motion.div>
<TimeRangeSelector value={range} onChange={setRange} />
<TimeRangeSelector />
</div>
<div className="flex gap-1 rounded-lg p-1 w-fit" style={{ background: "var(--row-hover)", border: "1px solid var(--surface-border)" }}>