105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
import { fetchApi } from "@/lib/api";
|
|
import { Users, Award, Camera, Clock } from "lucide-react";
|
|
|
|
interface Contributor {
|
|
wallet_hash: string;
|
|
photo_count: number;
|
|
last_upload: string;
|
|
}
|
|
|
|
export default async function ContributorsPage() {
|
|
let contributors: Contributor[] = [];
|
|
try {
|
|
const response = await fetchApi('/api/contributors');
|
|
contributors = response.contributors;
|
|
} catch (error) {
|
|
console.error("Error fetching contributors:", error);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<div className="flex flex-col gap-2">
|
|
<h2 className="text-3xl font-bold tracking-tight">Top Contributeurs</h2>
|
|
<p className="text-slate-400">Classement des utilisateurs selon leur contribution à l'entraînement de l'IA.</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
|
{/* Stats Summary */}
|
|
<div className="lg:col-span-1 space-y-6">
|
|
<StatCard
|
|
title="Total Contributeurs"
|
|
value={contributors.length}
|
|
icon={<Users className="text-indigo-400" />}
|
|
/>
|
|
<StatCard
|
|
title="Total Photos"
|
|
value={contributors.reduce((acc, c) => acc + c.photo_count, 0)}
|
|
icon={<Camera className="text-emerald-400" />}
|
|
/>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<div className="lg:col-span-3 bg-slate-900 border border-slate-800 rounded-2xl overflow-hidden">
|
|
<table className="w-full text-left">
|
|
<thead className="bg-slate-950/50 border-b border-slate-800">
|
|
<tr>
|
|
<th className="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Rang</th>
|
|
<th className="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Wallet Hash</th>
|
|
<th className="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest text-center">Photos</th>
|
|
<th className="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest text-right">Dernier Upload</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-800">
|
|
{contributors.map((c, index) => (
|
|
<tr key={c.wallet_hash} className="hover:bg-slate-800/30 transition-colors">
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-center gap-3">
|
|
{index < 3 ? (
|
|
<Award className={index === 0 ? "text-yellow-500" : index === 1 ? "text-slate-400" : "text-amber-700"} />
|
|
) : (
|
|
<span className="text-slate-500 font-mono w-6 text-center">#{index + 1}</span>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className="font-mono text-xs text-slate-300">{c.wallet_hash}</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-center">
|
|
<span className="px-2 py-1 bg-indigo-500/10 text-indigo-400 rounded-md text-xs font-bold border border-indigo-500/20">
|
|
{c.photo_count}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-right text-xs text-slate-400 flex items-center justify-end gap-2">
|
|
<Clock size={14} />
|
|
{new Date(c.last_upload).toLocaleString()}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
{contributors.length === 0 && (
|
|
<div className="py-24 text-center text-slate-500">
|
|
Aucun contributeur enregistré pour le moment.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StatCard({ title, value, icon }: any) {
|
|
return (
|
|
<div className="bg-slate-900 border border-slate-800 p-6 rounded-2xl flex items-center justify-between">
|
|
<div className="space-y-1">
|
|
<p className="text-xs text-slate-500 font-bold uppercase tracking-widest">{title}</p>
|
|
<p className="text-3xl font-bold">{value}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-slate-800 rounded-xl flex items-center justify-center shadow-inner">
|
|
{icon}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|