Fixed Codex
This commit is contained in:
1 parent
f2025cc776
commit
adf57aa0c9
18 files changed
+1419
-1627
No files matched your search
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import Card from "@/shared/components/Card";
|
||||
|
||||
const PERIODS = [
|
||||
{ value: "24h", label: "24h" },
|
||||
{ value: "7d", label: "7D" },
|
||||
{ value: "30d", label: "30D" },
|
||||
{ value: "60d", label: "60D" },
|
||||
];
|
||||
|
||||
const fmtTokens = (n) => {
|
||||
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
|
||||
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
|
||||
return String(n || 0);
|
||||
};
|
||||
|
||||
const fmtCost = (n) => `$${(n || 0).toFixed(4)}`;
|
||||
|
||||
export default function UsageChart() {
|
||||
const [period, setPeriod] = useState("7d");
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [viewMode, setViewMode] = useState("tokens");
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/usage/chart?period=${period}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
setData(json);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch chart data:", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [period]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const hasData = data.some((d) => d.tokens > 0 || d.cost > 0);
|
||||
|
||||
return (
|
||||
<Card className="p-4 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex items-center gap-1 bg-bg-subtle rounded-lg p-1 border border-border">
|
||||
<button
|
||||
onClick={() => setViewMode("tokens")}
|
||||
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "tokens" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
||||
>
|
||||
Tokens
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("cost")}
|
||||
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "cost" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
||||
>
|
||||
Cost
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 bg-bg-subtle rounded-lg p-1 border border-border">
|
||||
{PERIODS.map((p) => (
|
||||
<button
|
||||
key={p.value}
|
||||
onClick={() => setPeriod(p.value)}
|
||||
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${period === p.value ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
||||
>
|
||||
{p.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="h-48 flex items-center justify-center text-text-muted text-sm">Loading...</div>
|
||||
) : !hasData ? (
|
||||
<div className="h-48 flex items-center justify-center text-text-muted text-sm">No data for this period</div>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<AreaChart data={data} margin={{ top: 4, right: 8, left: 0, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="gradTokens" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#6366f1" stopOpacity={0.25} />
|
||||
<stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradCost" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.25} />
|
||||
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" strokeOpacity={0.1} />
|
||||
<XAxis
|
||||
dataKey="label"
|
||||
tick={{ fontSize: 10, fill: "currentColor", fillOpacity: 0.5 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
tick={{ fontSize: 10, fill: "currentColor", fillOpacity: 0.5 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={viewMode === "tokens" ? fmtTokens : fmtCost}
|
||||
width={50}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: "var(--color-bg)",
|
||||
border: "1px solid var(--color-border)",
|
||||
borderRadius: "8px",
|
||||
fontSize: "12px",
|
||||
}}
|
||||
formatter={(value, name) =>
|
||||
name === "tokens" ? [fmtTokens(value), "Tokens"] : [fmtCost(value), "Cost"]
|
||||
}
|
||||
/>
|
||||
{viewMode === "tokens" ? (
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="tokens"
|
||||
stroke="#6366f1"
|
||||
strokeWidth={2}
|
||||
fill="url(#gradTokens)"
|
||||
dot={false}
|
||||
activeDot={{ r: 4 }}
|
||||
/>
|
||||
) : (
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cost"
|
||||
stroke="#f59e0b"
|
||||
strokeWidth={2}
|
||||
fill="url(#gradCost)"
|
||||
dot={false}
|
||||
activeDot={{ r: 4 }}
|
||||
/>
|
||||
)}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
UsageChart.propTypes = {};
|
||||
Reference in new issue
Block a user