"use client"; import { useState, useEffect, useCallback } from "react"; import { adminService } from "@/services/admin.service"; import { AdminStatistics } from "@/types"; import Link from "next/link"; import { Users, Ticket, TrendingUp, AlertCircle, Gift, BarChart3, RefreshCw, MapPin, User as UserIcon, Calendar, Download, Filter, } from "lucide-react"; import { PieChart, Pie, Cell, BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from "recharts"; // Couleurs pour les graphiques const COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"]; const GENDER_COLORS = { male: "#3b82f6", female: "#ec4899", other: "#8b5cf6", notSpecified: "#6b7280", }; export default function AdminDashboardAdvanced() { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [autoRefresh, setAutoRefresh] = useState(false); const [refreshInterval, setRefreshInterval] = useState(30); // secondes const [selectedPeriod, setSelectedPeriod] = useState("all"); // all, week, month, year useEffect(() => { loadStatistics(); }, []); // Auto-refresh useEffect(() => { if (autoRefresh) { const interval = setInterval(() => { loadStatistics(); }, refreshInterval * 1000); return () => clearInterval(interval); } }, [autoRefresh, refreshInterval]); const loadStatistics = async () => { try { setLoading(true); setError(null); const data = await adminService.getStatistics(); setStats(data); } catch (err: any) { setError(err.message || "Erreur lors du chargement des statistiques"); setStats(null); } finally { setLoading(false); } }; // Export CSV const exportToCSV = useCallback(() => { if (!stats) return; const csv = [ ["Type", "Métrique", "Valeur"], ["Utilisateurs", "Total", stats.users.total], ["Utilisateurs", "Clients", stats.users.clients], ["Utilisateurs", "Employés", stats.users.employees], ["Utilisateurs", "Admins", stats.users.admins], ["Tickets", "Total", stats.tickets.total], ["Tickets", "Distribués", stats.tickets.distributed], ["Tickets", "Utilisés", stats.tickets.used], ["Tickets", "En attente", stats.tickets.pending], ["Lots", "Total distribué", stats.prizes.distributed], ]; // Ajouter les données démographiques if (stats.demographics?.gender) { csv.push(["Démographie", "Hommes", stats.demographics.gender.male]); csv.push(["Démographie", "Femmes", stats.demographics.gender.female]); } const csvContent = csv.map((row) => row.join(",")).join("\n"); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const link = document.createElement("a"); const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", `statistiques_${new Date().toISOString()}.csv`); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, [stats]); if (loading) { return (
{[...Array(4)].map((_, i) => (
))}
); } if (error || !stats) { return (
{error || "Erreur lors du chargement des statistiques"}
); } // Préparer les données pour les graphiques const prizeChartData = stats.prizes.byCategory?.map((prize) => ({ name: prize.prizeName, value: prize.count, percentage: prize.percentage, })) || []; // Ne montrer le graphique genre que s'il y a des données autres que "Non spécifié" const genderChartData = stats.demographics?.gender ? [ { name: "Hommes", value: stats.demographics.gender.male, color: GENDER_COLORS.male }, { name: "Femmes", value: stats.demographics.gender.female, color: GENDER_COLORS.female }, { name: "Autre", value: stats.demographics.gender.other, color: GENDER_COLORS.other }, ].filter((item) => item.value > 0) : []; // Ne montrer le graphique âge que s'il y a des données avec des vraies tranches d'âge const ageChartData = (stats.demographics?.ageRanges || []).filter( (item) => item.range && !item.range.toLowerCase().includes("non spécifié") && item.count > 0 ); // Ne montrer les villes que s'il y a des vraies villes (pas vides ou "non spécifié") const topCitiesData = (stats.demographics?.topCities || []).filter( (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); // Données pour le graphique des clients actifs/inactifs const clientStatusData = [ { name: "Actifs", value: stats.users.activeClients || 0, color: "#10b981" }, { name: "Inactifs", value: stats.users.inactiveClients || 0, color: "#ef4444" }, ].filter(item => item.value > 0); const ticketDistributedPercent = stats.tickets.total > 0 ? ((stats.tickets.distributed / stats.tickets.total) * 100).toFixed(1) : 0; const ticketUsedPercent = stats.tickets.distributed > 0 ? ((stats.tickets.used / stats.tickets.distributed) * 100).toFixed(1) : 0; return (
{/* Header avec contrôles */}

Dashboard Administrateur

Statistiques complètes et analyses en temps réel

{/* Refresh button */}
{/* Filter period */}
Période:
{["all", "week", "month", "year"].map((period) => ( ))}
{/* Stats Cards - Principales */}
} color="blue" link="/admin/utilisateurs" trend="+12%" /> } color="green" link="/admin/tickets" trend="+8%" /> } color="purple" link="/admin/tickets" trend="+15%" /> } color="yellow" link="/admin/tickets" trend="+10%" />
{/* Graphiques en ligne */}
{/* Graphique répartition des lots */} {prizeChartData.length > 0 && (

Répartition des Lots

`${entry.name}: ${entry.percentage}%`} outerRadius={80} fill="#8884d8" dataKey="value" > {prizeChartData.map((entry, index) => ( ))}
)} {/* Graphique Statut des Tickets */} {ticketStatusData.length > 0 && (

Statut des Tickets

`${name}: ${value}`} outerRadius={80} innerRadius={40} fill="#8884d8" dataKey="value" > {ticketStatusData.map((entry, index) => ( ))}
)}
{/* Deuxième ligne de graphiques */}
{/* Graphique Types d'Utilisateurs */} {userTypeData.length > 0 && (

Types d'Utilisateurs

`${name}: ${value}`} outerRadius={80} innerRadius={40} fill="#8884d8" dataKey="value" > {userTypeData.map((entry, index) => ( ))}
)} {/* Graphique Clients Actifs/Inactifs */} {clientStatusData.length > 0 && (

Statut des Clients

`${name}: ${value}`} outerRadius={80} innerRadius={40} fill="#8884d8" dataKey="value" > {clientStatusData.map((entry, index) => ( ))}
)} {/* Graphique répartition par genre */} {genderChartData.length > 0 && (

Répartition par Genre

`${name}: ${value}`} outerRadius={80} innerRadius={40} fill="#8884d8" dataKey="value" > {genderChartData.map((entry, index) => ( ))}
)}
{/* Graphique tranches d'âge */} {ageChartData.length > 0 && (

Répartition par Âge

)} {/* Section détaillée existante */}
{/* Statistiques Tickets détaillées */}

Statistiques des Tickets

Voir tout →
{/* Utilisateurs */}

Utilisateurs

Voir tout →
{/* Top Villes - affiché uniquement s'il y a des vraies données de villes */} {topCitiesData.length > 0 && (

Top 10 Villes des Participants

{topCitiesData.slice(0, 10).map((city, idx) => ( ))}
)}
); } // Composants réutilisables interface StatCardProps { title: string; value: number; subtitle?: string; icon: React.ReactNode; color: "blue" | "green" | "yellow" | "purple" | "red"; link: string; trend?: string; } function StatCard({ title, value, subtitle, icon, color, link, trend }: StatCardProps) { const gradients = { blue: "from-blue-500 to-blue-600", green: "from-emerald-500 to-emerald-600", yellow: "from-amber-500 to-orange-500", purple: "from-purple-500 to-purple-600", red: "from-red-500 to-red-600", }; const bgColors = { blue: "from-blue-50 to-blue-100 border-blue-200", green: "from-emerald-50 to-emerald-100 border-emerald-200", yellow: "from-amber-50 to-orange-100 border-amber-200", purple: "from-purple-50 to-purple-100 border-purple-200", red: "from-red-50 to-red-100 border-red-200", }; return (
{icon}
{trend && (
{trend}
)}

{title}

{(value || 0).toLocaleString("fr-FR")}

{subtitle &&

{subtitle}

}
); } interface StatRowProps { label: string; value: number; color?: "green" | "yellow" | "red" | "purple" | "blue"; percentage?: string | number; } function StatRow({ label, value, color, percentage }: StatRowProps) { const colors = { green: "text-green-600", yellow: "text-yellow-600", red: "text-red-600", purple: "text-purple-600", blue: "text-blue-600", }; return (
{label}
{(value || 0).toLocaleString("fr-FR")} {percentage !== undefined && ({percentage}%)}
); } interface CityCardProps { rank: number; city: string; count: number; percentage: number; } function CityCard({ rank, city, count, percentage }: CityCardProps) { const rankStyles = { 1: { bg: "from-amber-400 to-yellow-500", badge: "bg-amber-500" }, 2: { bg: "from-gray-300 to-gray-400", badge: "bg-gray-400" }, 3: { bg: "from-orange-400 to-amber-500", badge: "bg-orange-500" }, }; const style = rankStyles[rank as keyof typeof rankStyles] || { bg: "from-blue-400 to-blue-500", badge: "bg-blue-500" }; return (
{rank}

{city}

{count.toLocaleString('fr-FR')} {percentage.toFixed(1)}%
); }