init
This commit is contained in:
91
components/dashboard/region-ranking.tsx
Normal file
91
components/dashboard/region-ranking.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user