Files
openclaw-market/components/dashboard/region-ranking.tsx
richarjiang fa4c458eda init
2026-03-13 11:00:01 +08:00

92 lines
2.9 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { motion } from "framer-motion";
interface RegionData {
name: string;
count: number;
color: string;
}
const regionColors: Record<string, string> = {
Asia: "var(--accent-cyan)",
Europe: "var(--accent-purple)",
Americas: "var(--accent-pink)",
Africa: "var(--accent-orange)",
Oceania: "var(--accent-green)",
};
export function RegionRanking() {
const [regions, setRegions] = useState<RegionData[]>([]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("/api/v1/stats");
if (res.ok) {
const data = await res.json();
const breakdown = (data.regionBreakdown ?? {}) as Record<string, number>;
const sorted = Object.entries(breakdown)
.map(([name, count]) => ({
name,
count,
color: regionColors[name] ?? "var(--accent-cyan)",
}))
.sort((a, b) => b.count - a.count);
setRegions(sorted);
}
} catch {
// retry on interval
}
};
fetchData();
const interval = setInterval(fetchData, 15000);
return () => clearInterval(interval);
}, []);
const maxCount = Math.max(...regions.map((r) => r.count), 1);
return (
<Card className="border-white/5">
<CardHeader className="pb-2">
<CardTitle>Region Ranking</CardTitle>
</CardHeader>
<CardContent className="space-y-3 p-4 pt-0">
{regions.length === 0 ? (
<p className="py-4 text-center text-xs text-[var(--text-muted)]">No data yet</p>
) : (
regions.map((region, i) => (
<div key={region.name} className="space-y-1">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-[var(--text-muted)]">
#{i + 1}
</span>
<span className="text-sm font-medium" style={{ color: region.color }}>
{region.name}
</span>
</div>
<span className="font-mono text-xs text-[var(--text-secondary)]">
{region.count}
</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-white/5">
<motion.div
className="h-full rounded-full"
style={{ backgroundColor: region.color }}
initial={{ width: 0 }}
animate={{ width: `${(region.count / maxCount) * 100}%` }}
transition={{ duration: 0.8, ease: "easeOut" }}
/>
</div>
</div>
))
)}
</CardContent>
</Card>
);
}