import type { SiteUserDataPoint } from "@umt/contracts"; import { useState } from "react"; import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import { Button } from "~/components/ui/button.tsx"; import { formatBytes } from "~/lib/utils.ts"; import { ratioText } from "./site-fields.ts"; // 用户数据 30 天折线。主色无彩(interface-materials §1):系列靠明暗 + 实线 / 虚线区分,不用色相; // 字节与分享率量纲不同,分两张图,不做双轴。 const INK = "var(--foreground)"; const INK_2 = "var(--muted-foreground)"; const GRID = "var(--border)"; type Row = { day: string; upload: number; download: number; ratio: number | null }; const dayLabel = (d: string) => d.slice(5).replace("-", "/"); function Legend({ items }: { items: { label: string; dashed?: boolean; muted?: boolean }[] }) { return ( ); } function TipBox({ active, payload, label, kind, }: { active?: boolean; payload?: { payload: Row }[]; label?: string; kind: "bytes" | "ratio"; }) { const row = payload?.[0]?.payload; if (!active || !row) return null; return (

{label}

{kind === "bytes" ? ( <>

上传 {formatBytes(row.upload)}

下载 {formatBytes(row.download)}

) : (

分享率 {ratioText(row)}

)}
); } const axisProps = { stroke: GRID, tick: { fill: "var(--muted-foreground)", fontSize: 11 }, tickLine: false, axisLine: false, } as const; export function UserDataChart({ points }: { points: SiteUserDataPoint[] }) { const [table, setTable] = useState(false); const rows: Row[] = [...points] .sort((a, b) => a.day.localeCompare(b.day)) .map((p) => ({ day: dayLabel(p.day), upload: p.upload, download: p.download, ratio: p.ratio })); if (rows.length < 2) return (

{rows.length ? "只有 1 天的数据,满 2 天后显示趋势" : "还没有历史数据。刷新登录成功后每天记一次"}

); return (
上传 / 下载
formatBytes(v, 0)} /> } />
分享率
v.toFixed(1)} /> } />
{table ? (
{[...rows].reverse().map((r) => ( ))}
日期 上传 下载 分享率
{r.day} {formatBytes(r.upload)} {formatBytes(r.download)} {ratioText(r)}
) : null}
); }