Checkpoint: Ajout du graphique de répartition par établissement sur le tableau de bord principal, affiché côte à côte avec le graphique de répartition par fonction. Le graphique affiche le top 10 des établissements avec le plus d'apprenants inscrits, utilisant des couleurs distinctes pour faciliter la lecture. Les deux graphiques sont maintenant visibles en grille responsive (2 colonnes sur desktop, 1 colonne sur mobile).

This commit is contained in:
Manus Sandbox
2025-11-23 16:48:29 -05:00
parent a807183e2b
commit 3ec648f8cf
2 changed files with 104 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
import DashboardLayout from "@/components/DashboardLayout";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { trpc } from "@/lib/trpc";
import { GraduationCap, Calendar, Users, CheckCircle, PieChart as PieChartIcon } from "lucide-react";
import { GraduationCap, Calendar, Users, CheckCircle, PieChart as PieChartIcon, Building2 } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts";
import { useMemo } from "react";
@@ -26,6 +26,28 @@ export default function Admin() {
].filter(item => item.value > 0);
}, [apprenants]);
// Statistiques par établissement
const statsEtablissement = useMemo(() => {
if (!apprenants) return [];
const etablissementCounts: Record<string, number> = {};
apprenants.forEach(a => {
const code = a.codeEtablissement || "Non renseigné";
etablissementCounts[code] = (etablissementCounts[code] || 0) + 1;
});
const colors = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16", "#f97316", "#6366f1"];
return Object.entries(etablissementCounts)
.map(([name, value], index) => ({
name,
value,
color: colors[index % colors.length],
}))
.sort((a, b) => b.value - a.value)
.slice(0, 10); // Top 10 établissements
}, [apprenants]);
const stats = [
{
title: "Formations actives",
@@ -83,34 +105,36 @@ export default function Admin() {
))}
</div>
{/* Graphique de répartition par fonction */}
{statsFonction.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PieChartIcon className="w-5 h-5" />
Répartition des apprenants par fonction
</CardTitle>
<CardDescription>
Distribution des {apprenants?.length || 0} apprenants selon leur fonction
</CardDescription>
</CardHeader>
<CardContent>
{loadingApprenants ? (
<div className="h-80 flex items-center justify-center">
<Skeleton className="h-64 w-64 rounded-full" />
</div>
) : (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={statsFonction}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={100}
{/* Graphiques de répartition */}
<div className="grid gap-4 md:grid-cols-2">
{/* Graphique de répartition par fonction */}
{statsFonction.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PieChartIcon className="w-5 h-5" />
Répartition des apprenants par fonction
</CardTitle>
<CardDescription>
Distribution des {apprenants?.length || 0} apprenants selon leur fonction
</CardDescription>
</CardHeader>
<CardContent>
{loadingApprenants ? (
<div className="h-80 flex items-center justify-center">
<Skeleton className="h-64 w-64 rounded-full" />
</div>
) : (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={statsFonction}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={100}
fill="#8884d8"
dataKey="value"
>
@@ -128,6 +152,52 @@ export default function Admin() {
</Card>
)}
{/* Graphique de répartition par établissement */}
{statsEtablissement.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Building2 className="w-5 h-5" />
Répartition des apprenants par établissement
</CardTitle>
<CardDescription>
Top 10 des établissements avec le plus d'apprenants
</CardDescription>
</CardHeader>
<CardContent>
{loadingApprenants ? (
<div className="h-80 flex items-center justify-center">
<Skeleton className="h-64 w-64 rounded-full" />
</div>
) : (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={statsEtablissement}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={100}
fill="#8884d8"
dataKey="value"
>
{statsEtablissement.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
)}
</CardContent>
</Card>
)}
</div>
<Card>
<CardHeader>
<CardTitle>Bienvenue dans l'espace d'administration</CardTitle>

View File

@@ -535,3 +535,8 @@
- [x] Identifier la source de l'incohérence entre tableau de bord principal et analytique
- [x] Uniformiser les calculs de répartition par fonction
- [x] Vérifier la cohérence des données affichées
## Ajout graphique répartition par établissement
- [x] Ajouter le graphique de répartition par établissement sur le tableau de bord principal
- [x] Placer le graphique à côté du graphique par fonction