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:
@@ -1,7 +1,7 @@
|
|||||||
import DashboardLayout from "@/components/DashboardLayout";
|
import DashboardLayout from "@/components/DashboardLayout";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { trpc } from "@/lib/trpc";
|
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 { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts";
|
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
@@ -26,6 +26,28 @@ export default function Admin() {
|
|||||||
].filter(item => item.value > 0);
|
].filter(item => item.value > 0);
|
||||||
}, [apprenants]);
|
}, [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 = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: "Formations actives",
|
title: "Formations actives",
|
||||||
@@ -83,34 +105,36 @@ export default function Admin() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Graphique de répartition par fonction */}
|
{/* Graphiques de répartition */}
|
||||||
{statsFonction.length > 0 && (
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<Card>
|
{/* Graphique de répartition par fonction */}
|
||||||
<CardHeader>
|
{statsFonction.length > 0 && (
|
||||||
<CardTitle className="flex items-center gap-2">
|
<Card>
|
||||||
<PieChartIcon className="w-5 h-5" />
|
<CardHeader>
|
||||||
Répartition des apprenants par fonction
|
<CardTitle className="flex items-center gap-2">
|
||||||
</CardTitle>
|
<PieChartIcon className="w-5 h-5" />
|
||||||
<CardDescription>
|
Répartition des apprenants par fonction
|
||||||
Distribution des {apprenants?.length || 0} apprenants selon leur fonction
|
</CardTitle>
|
||||||
</CardDescription>
|
<CardDescription>
|
||||||
</CardHeader>
|
Distribution des {apprenants?.length || 0} apprenants selon leur fonction
|
||||||
<CardContent>
|
</CardDescription>
|
||||||
{loadingApprenants ? (
|
</CardHeader>
|
||||||
<div className="h-80 flex items-center justify-center">
|
<CardContent>
|
||||||
<Skeleton className="h-64 w-64 rounded-full" />
|
{loadingApprenants ? (
|
||||||
</div>
|
<div className="h-80 flex items-center justify-center">
|
||||||
) : (
|
<Skeleton className="h-64 w-64 rounded-full" />
|
||||||
<div className="h-80">
|
</div>
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
) : (
|
||||||
<PieChart>
|
<div className="h-80">
|
||||||
<Pie
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
data={statsFonction}
|
<PieChart>
|
||||||
cx="50%"
|
<Pie
|
||||||
cy="50%"
|
data={statsFonction}
|
||||||
labelLine={false}
|
cx="50%"
|
||||||
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
|
cy="50%"
|
||||||
outerRadius={100}
|
labelLine={false}
|
||||||
|
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
|
||||||
|
outerRadius={100}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
@@ -128,6 +152,52 @@ export default function Admin() {
|
|||||||
</Card>
|
</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>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Bienvenue dans l'espace d'administration</CardTitle>
|
<CardTitle>Bienvenue dans l'espace d'administration</CardTitle>
|
||||||
|
|||||||
5
todo.md
5
todo.md
@@ -535,3 +535,8 @@
|
|||||||
- [x] Identifier la source de l'incohérence entre tableau de bord principal et analytique
|
- [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] Uniformiser les calculs de répartition par fonction
|
||||||
- [x] Vérifier la cohérence des données affichées
|
- [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
|
||||||
|
|||||||
Reference in New Issue
Block a user