diff --git a/app/rankings/page.tsx b/app/rankings/page.tsx index 1630d1c..40c02fd 100644 --- a/app/rankings/page.tsx +++ b/app/rankings/page.tsx @@ -3,12 +3,13 @@ import { useEffect, useState, useCallback } from "react"; import Link from "next/link"; import { motion } from "motion/react"; -import { Trophy, Users, Cpu, Radio } from "lucide-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 { useI18n } from "@/lib/i18n"; type Tab = "user" | "model" | "channel"; +type SortKey = "calls" | "prompt_tokens" | "completion_tokens" | "total_tokens"; interface RankItem { rank: number; name: string; id?: number; calls: number; @@ -21,6 +22,8 @@ export default function RankingsPage() { const [tab, setTab] = useState("user"); const [data, setData] = useState([]); const [loading, setLoading] = useState(true); + const [sortKey, setSortKey] = useState("total_tokens"); + const [sortAsc, setSortAsc] = useState(false); const tabConfig: Record = { user: { label: t("rank.user"), icon: Users }, @@ -44,7 +47,31 @@ export default function RankingsPage() { return `/detail/user/${encodeURIComponent(item.name)}`; } - const headers = [t("th.rank"), t("th.name"), t("th.calls"), t("th.input"), t("th.output"), t("th.totalToken")]; + function handleSort(key: SortKey) { + if (sortKey === key) setSortAsc(!sortAsc); + else { setSortKey(key); setSortAsc(false); } + } + + const sorted = [...data].sort((a, b) => { + const diff = (a[sortKey] as number) - (b[sortKey] as number); + return sortAsc ? diff : -diff; + }); + + function SortIcon({ col }: { col: SortKey }) { + if (sortKey !== col) return ; + return sortAsc + ? + : ; + } + + const columns: { key: SortKey | null; label: string; align: "left" | "right" }[] = [ + { key: null, label: t("th.rank"), align: "left" }, + { key: null, label: t("th.name"), align: "left" }, + { key: "calls", label: t("th.calls"), align: "right" }, + { key: "prompt_tokens", label: t("th.input"), align: "right" }, + { key: "completion_tokens", label: t("th.output"), align: "right" }, + { key: "total_tokens", label: t("th.totalToken"), align: "right" }, + ]; return (
@@ -80,18 +107,28 @@ export default function RankingsPage() { - {headers.map((h, i) => ( - + {columns.map((col) => ( + ))} {loading ? ( - ) : data.map((item, i) => ( - - + ) : sorted.map((item, i) => ( + +
= 2 ? "text-right" : "text-left"}`} style={{ color: "var(--text-muted)" }}>{h} handleSort(col.key!) : undefined} + > + {col.key ? ( + + {col.label} + + ) : col.label} +
{item.rank}{i + 1} {item.name}