feat: improve admin dashboard design and add new charts

- Add gradient backgrounds and modern styling to all admin pages
- Add Statut des Tickets donut chart
- Add Types d'Utilisateurs donut chart
- Update headers and card containers with consistent design

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-12-03 14:34:29 +01:00
parent c578b81645
commit b7b08b1961
6 changed files with 202 additions and 77 deletions

View File

@ -172,6 +172,21 @@ export default function AdminDashboardAdvanced() {
(city) => city.city && city.city.trim() !== "" && !city.city.toLowerCase().includes("non spécifié") && city.count > 0
);
// Données pour le graphique des statuts de tickets
const ticketStatusData = [
{ name: "Distribués", value: stats.tickets.distributed || 0, color: "#10b981" },
{ name: "En attente", value: stats.tickets.pending || 0, color: "#f59e0b" },
{ name: "Réclamés", value: stats.tickets.claimed || 0, color: "#3b82f6" },
{ name: "Rejetés", value: stats.tickets.rejected || 0, color: "#ef4444" },
].filter(item => item.value > 0);
// Données pour le graphique des utilisateurs par type
const userTypeData = [
{ name: "Clients", value: stats.users.clients || 0, color: "#10b981" },
{ name: "Employés", value: stats.users.employees || 0, color: "#8b5cf6" },
{ name: "Admins", value: stats.users.admins || 0, color: "#3b82f6" },
].filter(item => item.value > 0);
const ticketDistributedPercent = stats.tickets.total > 0
? ((stats.tickets.distributed / stats.tickets.total) * 100).toFixed(1)
: 0;
@ -180,33 +195,35 @@ export default function AdminDashboardAdvanced() {
: 0;
return (
<div className="p-8 bg-gray-50 min-h-screen">
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* Header avec contrôles */}
<div className="mb-8">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<div>
<h1 className="text-3xl font-bold text-gray-900">Dashboard Administrateur Avancé</h1>
<p className="text-gray-600 mt-2">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Dashboard Administrateur
</h1>
<p className="text-gray-600 text-lg">
Statistiques complètes et analyses en temps réel
</p>
</div>
<div className="flex flex-wrap items-center gap-3">
{/* Auto-refresh toggle */}
<label className="flex items-center gap-2 bg-white px-4 py-2 rounded-lg border border-gray-200">
<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">
<input
type="checkbox"
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
className="rounded"
className="rounded accent-blue-600"
/>
<span className="text-sm">Auto-refresh ({refreshInterval}s)</span>
<span className="text-sm font-medium text-gray-700">Auto-refresh ({refreshInterval}s)</span>
</label>
{/* Export button */}
<button
onClick={exportToCSV}
className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition"
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"
>
<Download className="w-4 h-4" />
Export CSV
@ -215,7 +232,7 @@ export default function AdminDashboardAdvanced() {
{/* Refresh button */}
<button
onClick={loadStatistics}
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition"
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"
>
<RefreshCw className="w-4 h-4" />
Rafraîchir
@ -224,25 +241,29 @@ export default function AdminDashboardAdvanced() {
</div>
{/* Filter period */}
<div className="mt-4 flex items-center gap-2">
<Filter className="w-4 h-4 text-gray-600" />
<span className="text-sm text-gray-600">Période:</span>
{["all", "week", "month", "year"].map((period) => (
<button
key={period}
onClick={() => setSelectedPeriod(period)}
className={`px-3 py-1 rounded text-sm ${
selectedPeriod === period
? "bg-blue-600 text-white"
: "bg-white text-gray-700 border border-gray-200 hover:bg-gray-50"
}`}
>
{period === "all" && "Tout"}
{period === "week" && "Semaine"}
{period === "month" && "Mois"}
{period === "year" && "Année"}
</button>
))}
<div className="mt-6 flex items-center gap-3">
<div className="flex items-center gap-2 text-gray-600">
<Filter className="w-4 h-4" />
<span className="text-sm font-medium">Période:</span>
</div>
<div className="flex gap-2">
{["all", "week", "month", "year"].map((period) => (
<button
key={period}
onClick={() => setSelectedPeriod(period)}
className={`px-4 py-2 rounded-xl text-sm font-semibold transition-all ${
selectedPeriod === period
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-md"
: "bg-white text-gray-600 border border-gray-200 hover:bg-gray-50 hover:shadow-sm"
}`}
>
{period === "all" && "Tout"}
{period === "week" && "Semaine"}
{period === "month" && "Mois"}
{period === "year" && "Année"}
</button>
))}
</div>
</div>
</div>
@ -288,9 +309,11 @@ export default function AdminDashboardAdvanced() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Graphique répartition des lots */}
{prizeChartData.length > 0 && (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
<Gift className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-3">
<div className="w-10 h-10 bg-yellow-100 rounded-xl flex items-center justify-center">
<Gift className="w-5 h-5 text-yellow-600" />
</div>
Répartition des Lots
</h2>
<ResponsiveContainer width="100%" height={300}>
@ -315,11 +338,82 @@ export default function AdminDashboardAdvanced() {
</div>
)}
{/* Graphique Statut des Tickets */}
{ticketStatusData.length > 0 && (
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-3">
<div className="w-10 h-10 bg-green-100 rounded-xl flex items-center justify-center">
<Ticket className="w-5 h-5 text-green-600" />
</div>
Statut des Tickets
</h2>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={ticketStatusData}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, value }) => `${name}: ${value}`}
outerRadius={80}
innerRadius={40}
fill="#8884d8"
dataKey="value"
>
{ticketStatusData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
)}
</div>
{/* Deuxième ligne de graphiques */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Graphique Types d'Utilisateurs */}
{userTypeData.length > 0 && (
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center">
<Users className="w-5 h-5 text-blue-600" />
</div>
Types d'Utilisateurs
</h2>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={userTypeData}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, value }) => `${name}: ${value}`}
outerRadius={80}
innerRadius={40}
fill="#8884d8"
dataKey="value"
>
{userTypeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
)}
{/* Graphique répartition par genre */}
{genderChartData.length > 0 && (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
<UserIcon className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-3">
<div className="w-10 h-10 bg-pink-100 rounded-xl flex items-center justify-center">
<UserIcon className="w-5 h-5 text-pink-600" />
</div>
Répartition par Genre
</h2>
<ResponsiveContainer width="100%" height={300}>
@ -331,6 +425,7 @@ export default function AdminDashboardAdvanced() {
labelLine={false}
label={({ name, value }) => `${name}: ${value}`}
outerRadius={80}
innerRadius={40}
fill="#8884d8"
dataKey="value"
>
@ -339,6 +434,7 @@ export default function AdminDashboardAdvanced() {
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
@ -347,9 +443,11 @@ export default function AdminDashboardAdvanced() {
{/* Graphique tranches d'âge */}
{ageChartData.length > 0 && (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
<Calendar className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 mb-8 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-3">
<div className="w-10 h-10 bg-purple-100 rounded-xl flex items-center justify-center">
<Calendar className="w-5 h-5 text-purple-600" />
</div>
Répartition par Âge
</h2>
<ResponsiveContainer width="100%" height={300}>
@ -368,15 +466,17 @@ export default function AdminDashboardAdvanced() {
{/* Section détaillée existante */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Statistiques Tickets détaillées */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-900 flex items-center gap-2">
<Ticket className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-3">
<div className="w-10 h-10 bg-green-100 rounded-xl flex items-center justify-center">
<Ticket className="w-5 h-5 text-green-600" />
</div>
Statistiques des Tickets
</h2>
<Link
href="/admin/tickets"
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
className="text-blue-600 hover:text-blue-700 text-sm font-semibold bg-blue-50 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition-colors"
>
Voir tout
</Link>
@ -402,15 +502,17 @@ export default function AdminDashboardAdvanced() {
</div>
{/* Utilisateurs */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-900 flex items-center gap-2">
<Users className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center">
<Users className="w-5 h-5 text-blue-600" />
</div>
Utilisateurs
</h2>
<Link
href="/admin/utilisateurs"
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
className="text-blue-600 hover:text-blue-700 text-sm font-semibold bg-blue-50 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition-colors"
>
Voir tout
</Link>
@ -426,9 +528,11 @@ export default function AdminDashboardAdvanced() {
{/* Top Villes - affiché uniquement s'il y a des vraies données de villes */}
{topCitiesData.length > 0 && (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<MapPin className="w-5 h-5" />
<div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-lg transition-shadow">
<h3 className="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<div className="w-10 h-10 bg-orange-100 rounded-xl flex items-center justify-center">
<MapPin className="w-5 h-5 text-orange-600" />
</div>
Top 10 Villes des Participants
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
@ -460,7 +564,7 @@ interface StatCardProps {
}
function StatCard({ title, value, subtitle, icon, color, link, trend }: StatCardProps) {
const colors = {
const iconColors = {
blue: "bg-blue-100 text-blue-600",
green: "bg-green-100 text-green-600",
yellow: "bg-yellow-100 text-yellow-600",
@ -468,23 +572,31 @@ function StatCard({ title, value, subtitle, icon, color, link, trend }: StatCard
red: "bg-red-100 text-red-600",
};
const valueColors = {
blue: "text-blue-600",
green: "text-green-600",
yellow: "text-yellow-500",
purple: "text-purple-600",
red: "text-red-600",
};
return (
<Link
href={link}
className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition"
className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 hover:shadow-xl transition-all hover:scale-[1.02] group"
>
<div className="flex items-center justify-between mb-4">
<div className={`p-3 rounded-lg ${colors[color]}`}>{icon}</div>
<div className={`p-3 rounded-xl ${iconColors[color]} group-hover:scale-110 transition-transform`}>{icon}</div>
{trend && (
<div className="flex items-center gap-1 text-green-600 text-sm font-medium">
<TrendingUp className="w-4 h-4" />
<div className="flex items-center gap-1 text-green-600 text-sm font-semibold bg-green-50 px-2 py-1 rounded-lg">
<TrendingUp className="w-3 h-3" />
{trend}
</div>
)}
</div>
<h3 className="text-sm font-medium text-gray-600 mb-1">{title}</h3>
<p className="text-3xl font-bold text-gray-900">{(value || 0).toLocaleString("fr-FR")}</p>
{subtitle && <p className="text-xs text-gray-500 mt-1">{subtitle}</p>}
<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>
);
}

View File

@ -3,5 +3,19 @@
import PrizeManagement from '@/components/admin/PrizeManagement';
export default function LotsPage() {
return <PrizeManagement />;
return (
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Gestion des Lots & Prix
</h1>
<p className="text-gray-600 text-lg">
Gérez les lots et prix du jeu-concours
</p>
</div>
<div className="bg-white rounded-2xl shadow-md border border-gray-100">
<PrizeManagement />
</div>
</div>
);
}

View File

@ -225,14 +225,13 @@ export default function MarketingPage() {
const hasDemographicData = filteredGenderData.length > 0 || filteredAgeData.length > 0 || filteredCityData.length > 0;
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* En-tête */}
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2 flex items-center gap-3">
<Mail className="w-10 h-10 text-blue-600" />
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Données Marketing
</h1>
<p className="text-gray-600">
<p className="text-gray-600 text-lg">
Statistiques et export des données pour vos campagnes d'emailing
</p>
</div>

View File

@ -4,16 +4,16 @@ import TicketManagement from "@/components/admin/TicketManagement";
export default function AdminTicketsPage() {
return (
<div className="p-8">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900">
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Gestion des tickets
</h1>
<p className="text-gray-600 mt-2">
<p className="text-gray-600 text-lg">
Consultez et gérez tous les tickets du jeu-concours
</p>
</div>
<div className="bg-white rounded-lg shadow-sm">
<div className="bg-white rounded-2xl shadow-md border border-gray-100">
<TicketManagement />
</div>
</div>

View File

@ -364,11 +364,10 @@ ${report.draw.notifiedAt ? `📧 Gagnant notifié le: ${new Date(report.draw.not
};
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* En-tête avec titre du prix */}
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2 flex items-center gap-3">
<Trophy className="w-10 h-10 text-yellow-600" />
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Tirage au Sort - {prizeName}
</h1>
<p className="text-gray-600 text-lg">

View File

@ -1,19 +1,20 @@
"use client";
import UserManagement from "@/components/admin/UserManagement";
import { Users } from "lucide-react";
export default function AdminUtilisateursPage() {
return (
<div className="p-8">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900">
<div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#1e3a5f] mb-2">
Gestion des utilisateurs
</h1>
<p className="text-gray-600 mt-2">
<p className="text-gray-600 text-lg">
Gérez tous les comptes utilisateurs de la plateforme
</p>
</div>
<div className="bg-white rounded-lg shadow-sm">
<div className="bg-white rounded-2xl shadow-md border border-gray-100">
<UserManagement />
</div>
</div>