import { useState, useMemo } from "react"; import { trpc } from "@/lib/trpc"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "sonner"; import DashboardLayout from "@/components/DashboardLayout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { BarChart3, TrendingUp, Users, Building2, Calendar, PieChart, Download, FileText, FileSpreadsheet } from "lucide-react"; import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart as RePieChart, Pie, Cell, } from "recharts"; const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']; export default function AdminAnalytics() { const [periodFilter, setPeriodFilter] = useState("all"); // Calculer les dates de début et fin selon le filtre const { startDate, endDate } = useMemo(() => { const now = new Date(); let start: Date | undefined; let end: Date | undefined = now; switch (periodFilter) { case "last_month": start = new Date(now.getFullYear(), now.getMonth() - 1, 1); break; case "last_quarter": start = new Date(now.getFullYear(), now.getMonth() - 3, 1); break; case "last_year": start = new Date(now.getFullYear() - 1, now.getMonth(), 1); break; case "all": default: start = undefined; end = undefined; } return { startDate: start?.toISOString(), endDate: end?.toISOString(), }; }, [periodFilter]); const { data: globalStats, isLoading: loadingGlobal } = trpc.analytics.globalStats.useQuery(); const { data: inscriptionsByMonth, isLoading: loadingInscriptions } = trpc.analytics.inscriptionsByMonth.useQuery({ startDate, endDate, }); const { data: tauxRemplissage, isLoading: loadingTaux } = trpc.analytics.tauxRemplissageByMonth.useQuery(); const { data: participationEtablissement, isLoading: loadingEtablissement } = trpc.analytics.participationByEtablissement.useQuery(); const { data: participationFonction, isLoading: loadingFonction } = trpc.analytics.participationByFonction.useQuery(); // Formater les données pour les graphiques const inscriptionsData = inscriptionsByMonth?.map(item => ({ mois: item.mois, Confirmées: Number(item.confirmees), "Liste d'attente": Number(item.listeAttente), Annulées: Number(item.annulees), Total: Number(item.total), })) || []; const tauxRemplissageData = tauxRemplissage?.map(item => ({ mois: item.mois, "Taux de remplissage (%)": Number(item.tauxRemplissage) || 0, "Capacité totale": Number(item.capaciteTotale), "Inscrits confirmés": Number(item.inscritsConfirmes), })) || []; const etablissementData = participationEtablissement?.slice(0, 10).map(item => ({ etablissement: item.codeEtablissement, "Total inscriptions": Number(item.totalInscriptions), Confirmées: Number(item.inscriptionsConfirmees), "Liste d'attente": Number(item.inscriptionsListeAttente), })) || []; const fonctionData = participationFonction?.map(item => ({ name: item.fonction === 'directeur' ? 'Directeurs' : item.fonction === 'chef_service' ? 'Chefs de service' : 'Autres', value: Number(item.totalApprenants), confirmees: Number(item.inscriptionsConfirmees), })) || []; const exportExcelMutation = trpc.analytics.exportExcel.useMutation(); const exportPDFMutation = trpc.analytics.exportPDF.useMutation(); const handleExportExcel = async () => { try { toast.info("Génération du rapport Excel en cours..."); const result = await exportExcelMutation.mutateAsync({ startDate, endDate }); // Convertir base64 en blob et télécharger const blob = new Blob( [Uint8Array.from(atob(result.data), c => c.charCodeAt(0))], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = result.filename; a.click(); URL.revokeObjectURL(url); toast.success("Rapport Excel téléchargé avec succès"); } catch (error: any) { toast.error("Erreur lors de l'export Excel: " + error.message); } }; const handleExportPDF = async () => { try { toast.info("Génération du rapport PDF en cours..."); const result = await exportPDFMutation.mutateAsync({ startDate, endDate }); // Convertir base64 en blob et télécharger const blob = new Blob( [Uint8Array.from(atob(result.data), c => c.charCodeAt(0))], { type: 'application/pdf' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = result.filename; a.click(); URL.revokeObjectURL(url); toast.success("Rapport PDF téléchargé avec succès"); } catch (error: any) { toast.error("L'export PDF sera implémenté dans une prochaine version"); } }; return (

Tableau de bord analytique

Statistiques et indicateurs de performance des formations

{/* Filtres de période */}
{/* Statistiques globales */}
Total Formations
{globalStats?.totalFormations || 0}

{globalStats?.totalSequences || 0} séquences au total

Séquences ouvertes
{globalStats?.sequencesOuvertes || 0}

Disponibles pour inscription

Total Apprenants
{globalStats?.totalApprenants || 0}

{globalStats?.totalInscriptions || 0} inscriptions

Taux de remplissage
{globalStats?.tauxRemplissageMoyen || 0}%

Moyenne globale

{/* Graphiques */}
{/* Évolution des inscriptions */} Évolution des inscriptions par mois Répartition des inscriptions confirmées, en liste d'attente et annulées {loadingInscriptions ? (

Chargement...

) : inscriptionsData.length > 0 ? ( ) : (

Aucune donnée disponible

)}
{/* Taux de remplissage */} Taux de remplissage par mois Pourcentage de places occupées par rapport à la capacité totale {loadingTaux ? (

Chargement...

) : tauxRemplissageData.length > 0 ? ( ) : (

Aucune donnée disponible

)}
{/* Participation par établissement et fonction */}
{/* Top 10 établissements */} Top 10 établissements Établissements avec le plus d'inscriptions {loadingEtablissement ? (

Chargement...

) : etablissementData.length > 0 ? ( ) : (

Aucune donnée disponible

)}
{/* Répartition par fonction */} Répartition par fonction Distribution des inscriptions par type de fonction {loadingFonction ? (

Chargement...

) : fonctionData.length > 0 ? ( `${name}: ${(percent * 100).toFixed(0)}%`} outerRadius={80} fill="#8884d8" dataKey="value" > {fonctionData.map((entry, index) => ( ))} ) : (

Aucune donnée disponible

)}
); }