feat: redesign admin dashboard with modern UI
- Add gradient header with dark blue theme - Improve stat cards with gradient backgrounds and decorative circles - Update period filter with modern button styling - Improve city cards with gradient rank badges - Better hover effects and transitions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ea67bf4137
commit
04119b69cc
|
|
@ -198,51 +198,53 @@ export default function AdminDashboardAdvanced() {
|
||||||
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
|
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
|
||||||
{/* Header avec contrôles */}
|
{/* Header avec contrôles */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
<div className="bg-gradient-to-r from-[#1e3a5f] to-[#2d5a8f] rounded-2xl p-6 mb-6">
|
||||||
<div>
|
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||||
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
|
<div>
|
||||||
Dashboard Administrateur
|
<h1 className="text-3xl font-bold text-white mb-1">
|
||||||
</h1>
|
Dashboard Administrateur
|
||||||
<p className="text-gray-600 text-lg">
|
</h1>
|
||||||
Statistiques complètes et analyses en temps réel
|
<p className="text-blue-200">
|
||||||
</p>
|
Statistiques complètes et analyses en temps réel
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
{/* Auto-refresh toggle */}
|
{/* Auto-refresh toggle */}
|
||||||
<label className="flex items-center gap-2 bg-white px-4 py-2.5 rounded-xl border border-gray-200 shadow-sm cursor-pointer hover:shadow-md transition-all">
|
<label className="flex items-center gap-2 bg-white/10 backdrop-blur-sm px-4 py-2.5 rounded-xl border border-white/20 cursor-pointer hover:bg-white/20 transition-all">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={autoRefresh}
|
checked={autoRefresh}
|
||||||
onChange={(e) => setAutoRefresh(e.target.checked)}
|
onChange={(e) => setAutoRefresh(e.target.checked)}
|
||||||
className="rounded accent-blue-600"
|
className="rounded accent-white"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium text-gray-700">Auto-refresh ({refreshInterval}s)</span>
|
<span className="text-sm font-medium text-white">Auto-refresh ({refreshInterval}s)</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Export button */}
|
{/* Export button */}
|
||||||
<button
|
<button
|
||||||
onClick={exportToCSV}
|
onClick={exportToCSV}
|
||||||
className="flex items-center gap-2 bg-gradient-to-r from-green-600 to-green-700 text-white px-5 py-2.5 rounded-xl hover:from-green-700 hover:to-green-800 transition-all shadow-md hover:shadow-lg font-medium"
|
className="flex items-center gap-2 bg-emerald-500 text-white px-5 py-2.5 rounded-xl hover:bg-emerald-600 transition-all shadow-lg font-medium"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
Export CSV
|
Export CSV
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Refresh button */}
|
{/* Refresh button */}
|
||||||
<button
|
<button
|
||||||
onClick={loadStatistics}
|
onClick={loadStatistics}
|
||||||
className="flex items-center gap-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white px-5 py-2.5 rounded-xl hover:from-blue-700 hover:to-blue-800 transition-all shadow-md hover:shadow-lg font-medium"
|
className="flex items-center gap-2 bg-white text-[#1e3a5f] px-5 py-2.5 rounded-xl hover:bg-gray-100 transition-all shadow-lg font-semibold"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4" />
|
<RefreshCw className="w-4 h-4" />
|
||||||
Rafraîchir
|
Rafraîchir
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter period */}
|
{/* Filter period */}
|
||||||
<div className="mt-6 flex items-center gap-3">
|
<div className="flex items-center gap-3 bg-white rounded-xl p-3 shadow-sm border border-gray-100">
|
||||||
<div className="flex items-center gap-2 text-gray-600">
|
<div className="flex items-center gap-2 text-gray-600 px-2">
|
||||||
<Filter className="w-4 h-4" />
|
<Filter className="w-4 h-4" />
|
||||||
<span className="text-sm font-medium">Période:</span>
|
<span className="text-sm font-medium">Période:</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -251,10 +253,10 @@ export default function AdminDashboardAdvanced() {
|
||||||
<button
|
<button
|
||||||
key={period}
|
key={period}
|
||||||
onClick={() => setSelectedPeriod(period)}
|
onClick={() => setSelectedPeriod(period)}
|
||||||
className={`px-4 py-2 rounded-xl text-sm font-semibold transition-all ${
|
className={`px-5 py-2 rounded-lg text-sm font-semibold transition-all ${
|
||||||
selectedPeriod === period
|
selectedPeriod === period
|
||||||
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-md"
|
? "bg-gradient-to-r from-[#1e3a5f] to-[#2d5a8f] text-white shadow-md"
|
||||||
: "bg-white text-gray-600 border border-gray-200 hover:bg-gray-50 hover:shadow-sm"
|
: "text-gray-600 hover:bg-gray-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{period === "all" && "Tout"}
|
{period === "all" && "Tout"}
|
||||||
|
|
@ -564,39 +566,46 @@ interface StatCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatCard({ title, value, subtitle, icon, color, link, trend }: StatCardProps) {
|
function StatCard({ title, value, subtitle, icon, color, link, trend }: StatCardProps) {
|
||||||
const iconColors = {
|
const gradients = {
|
||||||
blue: "bg-blue-100 text-blue-600",
|
blue: "from-blue-500 to-blue-600",
|
||||||
green: "bg-green-100 text-green-600",
|
green: "from-emerald-500 to-emerald-600",
|
||||||
yellow: "bg-yellow-100 text-yellow-600",
|
yellow: "from-amber-500 to-orange-500",
|
||||||
purple: "bg-purple-100 text-purple-600",
|
purple: "from-purple-500 to-purple-600",
|
||||||
red: "bg-red-100 text-red-600",
|
red: "from-red-500 to-red-600",
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueColors = {
|
const bgColors = {
|
||||||
blue: "text-blue-600",
|
blue: "from-blue-50 to-blue-100 border-blue-200",
|
||||||
green: "text-green-600",
|
green: "from-emerald-50 to-emerald-100 border-emerald-200",
|
||||||
yellow: "text-yellow-500",
|
yellow: "from-amber-50 to-orange-100 border-amber-200",
|
||||||
purple: "text-purple-600",
|
purple: "from-purple-50 to-purple-100 border-purple-200",
|
||||||
red: "text-red-600",
|
red: "from-red-50 to-red-100 border-red-200",
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={link}
|
href={link}
|
||||||
className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-xl transition-all hover:scale-[1.02] group"
|
className={`bg-gradient-to-br ${bgColors[color]} rounded-2xl border p-6 hover:shadow-xl transition-all hover:scale-[1.02] group relative overflow-hidden`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="absolute top-0 right-0 w-32 h-32 transform translate-x-8 -translate-y-8">
|
||||||
<div className={`p-3 rounded-xl ${iconColors[color]} group-hover:scale-110 transition-transform`}>{icon}</div>
|
<div className={`w-full h-full bg-gradient-to-br ${gradients[color]} rounded-full opacity-10`}></div>
|
||||||
{trend && (
|
</div>
|
||||||
<div className="flex items-center gap-1 text-green-600 text-sm font-semibold bg-green-50 px-2 py-1 rounded-lg">
|
<div className="relative">
|
||||||
<TrendingUp className="w-3 h-3" />
|
<div className="flex items-center justify-between mb-4">
|
||||||
{trend}
|
<div className={`p-3 rounded-xl bg-gradient-to-br ${gradients[color]} text-white shadow-lg group-hover:scale-110 transition-transform`}>
|
||||||
</div>
|
{icon}
|
||||||
)}
|
</div>
|
||||||
|
{trend && (
|
||||||
|
<div className="flex items-center gap-1 text-emerald-700 text-sm font-semibold bg-emerald-100 px-2.5 py-1 rounded-full">
|
||||||
|
<TrendingUp className="w-3 h-3" />
|
||||||
|
{trend}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-semibold text-gray-600 mb-1">{title}</h3>
|
||||||
|
<p className="text-4xl font-bold text-gray-900">{(value || 0).toLocaleString("fr-FR")}</p>
|
||||||
|
{subtitle && <p className="text-xs text-gray-500 mt-2">{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-medium text-gray-500 mb-2">{title}</h3>
|
|
||||||
<p className={`text-4xl font-bold ${valueColors[color]}`}>{(value || 0).toLocaleString("fr-FR")}</p>
|
|
||||||
{subtitle && <p className="text-xs text-gray-500 mt-2">{subtitle}</p>}
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -638,27 +647,28 @@ interface CityCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function CityCard({ rank, city, count, percentage }: CityCardProps) {
|
function CityCard({ rank, city, count, percentage }: CityCardProps) {
|
||||||
const rankColors = {
|
const rankStyles = {
|
||||||
1: "bg-yellow-100 text-yellow-800 border-yellow-300",
|
1: { bg: "from-amber-400 to-yellow-500", badge: "bg-amber-500" },
|
||||||
2: "bg-gray-100 text-gray-800 border-gray-300",
|
2: { bg: "from-gray-300 to-gray-400", badge: "bg-gray-400" },
|
||||||
3: "bg-orange-100 text-orange-800 border-orange-300",
|
3: { bg: "from-orange-400 to-amber-500", badge: "bg-orange-500" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const rankColor =
|
const style = rankStyles[rank as keyof typeof rankStyles] || { bg: "from-blue-400 to-blue-500", badge: "bg-blue-500" };
|
||||||
rankColors[rank as keyof typeof rankColors] || "bg-blue-50 text-blue-800 border-blue-200";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`rounded-lg border-2 p-4 ${rankColor} hover:shadow-md transition`}>
|
<div className="bg-white rounded-xl border border-gray-100 p-4 hover:shadow-lg transition-all group">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-lg font-bold">#{rank}</span>
|
<span className={`w-8 h-8 rounded-lg bg-gradient-to-br ${style.bg} flex items-center justify-center text-white font-bold text-sm shadow-md`}>
|
||||||
<MapPin className="w-4 h-4" />
|
{rank}
|
||||||
|
</span>
|
||||||
|
<MapPin className="w-4 h-4 text-gray-400 group-hover:text-orange-500 transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold text-sm mb-1 truncate" title={city}>
|
<h4 className="font-bold text-gray-900 mb-1 truncate" title={city}>
|
||||||
{city}
|
{city}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-xl font-bold">{count}</span>
|
<span className="text-2xl font-bold text-gray-900">{count.toLocaleString('fr-FR')}</span>
|
||||||
<span className="text-xs">({percentage.toFixed(1)}%)</span>
|
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full">{percentage.toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ ${report.draw.notifiedAt ? `📧 Gagnant notifié le: ${new Date(report.draw.not
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-white text-blue-600 hover:bg-blue-50 rounded-xl"
|
className="bg-blue-600 text-white hover:bg-blue-700 rounded-xl"
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||||
Actualiser
|
Actualiser
|
||||||
|
|
@ -563,25 +563,31 @@ ${report.draw.notifiedAt ? `📧 Gagnant notifié le: ${new Date(report.draw.not
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bouton de tirage au sort */}
|
{/* Bouton de tirage au sort */}
|
||||||
<div className="mt-6 p-6 bg-gradient-to-r from-amber-50 to-orange-50 rounded-xl border-2 border-amber-200">
|
<div className="mt-6 bg-gradient-to-r from-amber-500 via-orange-500 to-red-500 rounded-2xl p-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="bg-white rounded-xl p-6">
|
||||||
<div>
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-1">
|
<div className="flex items-center gap-4">
|
||||||
Prêt à lancer le tirage au sort ?
|
<div className="w-14 h-14 bg-gradient-to-br from-amber-400 to-orange-500 rounded-xl flex items-center justify-center shadow-lg">
|
||||||
</h3>
|
<Trophy className="w-7 h-7 text-white" />
|
||||||
<p className="text-sm text-gray-600">
|
</div>
|
||||||
{participants.length} participant{participants.length > 1 ? 's ont' : ' a'} une chance égale de gagner {prizeName} ({prizeValue}€)
|
<div>
|
||||||
</p>
|
<h3 className="text-xl font-bold text-gray-900">
|
||||||
|
Prêt à lancer le tirage au sort ?
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
<span className="font-semibold text-orange-600">{participants.length}</span> participant{participants.length > 1 ? 's ont' : ' a'} une chance égale de gagner <span className="font-semibold text-purple-600">{prizeName}</span> ({prizeValue}€)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={conductDraw}
|
||||||
|
disabled={loading}
|
||||||
|
className="flex items-center gap-2 px-8 py-4 bg-gradient-to-r from-amber-500 via-orange-500 to-red-500 text-white font-bold text-lg rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
|
||||||
|
>
|
||||||
|
<Trophy className="w-6 h-6" />
|
||||||
|
Lancer le Tirage
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
onClick={conductDraw}
|
|
||||||
disabled={loading}
|
|
||||||
size="lg"
|
|
||||||
className="bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 rounded-xl shadow-lg"
|
|
||||||
>
|
|
||||||
<Trophy className="w-5 h-5 mr-2" />
|
|
||||||
Lancer le Tirage
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user