Files
formation-manager-itinova/client/src/pages/AdminEtablissements.tsx
Manus fc4df8ed36 Checkpoint: Correction des erreurs sur la page rapport-public-cible et harmonisation de la charte graphique des titres :
**Corrections d'erreurs :**

1. **Erreurs SQL DATE_FORMAT corrigées** (analyticsDb.ts) :
   - Remplacement des sous-requêtes corrélées par des JOIN dans getTauxRemplissageByMonth()
   - Utilisation de COUNT(CASE WHEN...) au lieu de SUM(SELECT COUNT(*))
   - Ajout de leftJoin pour éviter les erreurs de sous-requêtes

2. **Erreur React "Cannot read properties of null" corrigée** (AdminRapportPublicCible.tsx) :
   - Ajout de l'opérateur null-safe `?.` pour accéder à `insc.apprenant?.fonction`
   - Vérification explicite si la fonction est null/undefined
   - Gestion du cas "public cible = tous" qui correspond toujours

3. **Tests réussis** :
   - Page rapport-public-cible : affiche correctement les statistiques et recommandations
   - Tableau de bord analytique : graphiques et statistiques fonctionnels
   - Page suivi questionnaires : affiche correctement les données

**Harmonisation charte graphique :**

- Application du style bleu clair (#6B9FE8) avec ombre portée à tous les titres des 32 pages
- Création de la classe CSS `.page-title` réutilisable dans index.css
- Cohérence visuelle sur toute l'application

**Résultat :**
 Toutes les erreurs SQL et React sont corrigées
 Les pages fonctionnent correctement sans erreur
 Charte graphique harmonisée sur toutes les pages
2026-01-20 11:46:56 -05:00

127 lines
5.5 KiB
TypeScript

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import DashboardLayout from "@/components/DashboardLayout";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { trpc } from "@/lib/trpc";
import { Building2, Users, TrendingUp, Activity } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
export default function AdminEtablissements() {
const { data: etablissements, isLoading } = trpc.etablissements.list.useQuery();
const totalApprenants = etablissements?.reduce((sum, etab) => sum + etab.nombreApprenants, 0) || 0;
const totalActifs = etablissements?.reduce((sum, etab) => sum + etab.apprenantsActifs, 0) || 0;
const tauxGlobal = totalApprenants > 0 ? Math.round((totalActifs / totalApprenants) * 100) : 0;
return (
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="page-title">Établissements</h1>
<p className="text-muted-foreground mt-2">
Vue d'ensemble des établissements et de leur participation
</p>
</div>
{/* Statistiques globales */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Établissements</CardTitle>
<Building2 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{etablissements?.length || 0}</div>
<p className="text-xs text-muted-foreground">Nombre total</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Apprenants</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalApprenants}</div>
<p className="text-xs text-muted-foreground">Tous établissements</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Apprenants actifs</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalActifs}</div>
<p className="text-xs text-muted-foreground">Avec inscriptions</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Taux de participation</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{tauxGlobal}%</div>
<p className="text-xs text-muted-foreground">Global</p>
</CardContent>
</Card>
</div>
{/* Tableau des établissements */}
<Card>
<CardHeader>
<CardTitle>Liste des établissements</CardTitle>
<CardDescription>
Statistiques de participation par établissement
</CardDescription>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="space-y-2">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>
) : etablissements && etablissements.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
<TableHead>Code établissement</TableHead>
<TableHead className="text-center">Nombre d'apprenants</TableHead>
<TableHead className="text-center">Apprenants actifs</TableHead>
<TableHead className="text-center">Taux de participation</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{etablissements.map((etab) => (
<TableRow key={etab.codeEtablissement}>
<TableCell className="font-medium">{etab.codeEtablissement}</TableCell>
<TableCell className="text-center">{etab.nombreApprenants}</TableCell>
<TableCell className="text-center">{etab.apprenantsActifs}</TableCell>
<TableCell className="text-center">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
etab.tauxParticipation >= 70 ? "bg-green-100 text-green-800" :
etab.tauxParticipation >= 40 ? "bg-orange-100 text-orange-800" :
"bg-red-100 text-red-800"
}`}>
{etab.tauxParticipation}%
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<p className="text-center text-muted-foreground py-8">
Aucun établissement trouvé
</p>
)}
</CardContent>
</Card>
</div>
</DashboardLayout>
);
}