import DashboardLayout from "@/components/DashboardLayout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; import { BarChart3, TrendingUp, AlertCircle } from "lucide-react"; import { useMemo } from "react"; export default function AdminRapportPublicCible() { const { data: sequences, isLoading: loadingSequences } = trpc.sequences.list.useQuery(); const { data: inscriptions, isLoading: loadingInscriptions } = trpc.inscriptions.listAll.useQuery(); // Calculer les statistiques d'écart entre public cible et fonction des apprenants const stats = useMemo(() => { if (!sequences || !inscriptions) return null; const sequencesWithMismatch = sequences.map((sequence) => { const sequenceInscriptions = inscriptions.filter( (insc: any) => insc.inscription.sequenceId === sequence.id && insc.inscription.statut === "confirmee" ); const totalInscrits = sequenceInscriptions.length; const mismatchCount = sequenceInscriptions.filter((insc: any) => { const apprenantFonction = insc.apprenant?.fonction; const sequencePublicCible = sequence.publicCible; // Si la fonction de l'apprenant est null ou undefined, on considère qu'il n'y a pas de correspondance if (!apprenantFonction) return true; // Si le public cible est "Tous", il y a toujours correspondance if (sequencePublicCible === "tous") return false; // Mapper les fonctions aux publics cibles const fonctionToPublicCible: Record = { directeur: "directeur", chef_service: "chef_service", autre: "autre", }; return fonctionToPublicCible[apprenantFonction] !== sequencePublicCible; }).length; const matchRate = totalInscrits > 0 ? ((totalInscrits - mismatchCount) / totalInscrits) * 100 : 100; return { sequence, totalInscrits, mismatchCount, matchCount: totalInscrits - mismatchCount, matchRate, }; }); // Statistiques globales const totalInscrits = sequencesWithMismatch.reduce((sum, s) => sum + s.totalInscrits, 0); const totalMismatches = sequencesWithMismatch.reduce((sum, s) => sum + s.mismatchCount, 0); const globalMatchRate = totalInscrits > 0 ? ((totalInscrits - totalMismatches) / totalInscrits) * 100 : 100; return { sequencesWithMismatch: sequencesWithMismatch.filter((s) => s.totalInscrits > 0), totalInscrits, totalMismatches, totalMatches: totalInscrits - totalMismatches, globalMatchRate, }; }, [sequences, inscriptions]); const getPublicCibleLabel = (publicCible: string) => { const labels = { directeur: "Directeur", chef_service: "Chef de service", autre: "Autre", }; return labels[publicCible as keyof typeof labels] || "Autre"; }; const getPublicCibleBadge = (publicCible: string) => { const colors = { directeur: "bg-blue-100 text-blue-800", chef_service: "bg-green-100 text-green-800", autre: "bg-gray-100 text-gray-800", }; return colors[publicCible as keyof typeof colors] || colors.autre; }; const getMatchRateBadge = (matchRate: number) => { if (matchRate >= 80) return "bg-green-100 text-green-800"; if (matchRate >= 50) return "bg-orange-100 text-orange-800"; return "bg-red-100 text-red-800"; }; if (loadingSequences || loadingInscriptions) { return (
Chargement...
); } if (!stats) { return (
Aucune donnée disponible
); } return (

Rapport d'analyse Public Cible

Comparaison entre le public cible des séquences et la fonction des apprenants inscrits

{/* Statistiques globales */}
Total Inscrits
{stats.totalInscrits}

Inscrits confirmés

Correspondances
{stats.totalMatches}

{stats.globalMatchRate.toFixed(1)}% de correspondance

Écarts
{stats.totalMismatches}

{(100 - stats.globalMatchRate).toFixed(1)}% d'écart

Séquences Analysées
{stats.sequencesWithMismatch.length}

Avec inscrits confirmés

{/* Tableau détaillé par séquence */} Analyse détaillée par séquence Taux de correspondance entre le public cible et la fonction des apprenants inscrits {stats.sequencesWithMismatch.length === 0 ? (
Aucune séquence avec des inscrits confirmés
) : (
Séquence Public cible Total Inscrits Correspondances Écarts Taux de correspondance {stats.sequencesWithMismatch .sort((a, b) => a.matchRate - b.matchRate) .map((stat) => ( {stat.sequence.nom} {getPublicCibleLabel(stat.sequence.publicCible)} {stat.totalInscrits} {stat.matchCount} {stat.mismatchCount} {stat.matchRate.toFixed(1)}% ))}
)}
{/* Recommandations */} Recommandations Actions suggérées pour optimiser la planification des formations
{stats.globalMatchRate < 70 && (

Taux de correspondance faible

Le taux de correspondance global est inférieur à 70%. Considérez la création de séquences spécifiques pour chaque public cible.

)} {stats.sequencesWithMismatch.some((s) => s.matchRate < 50) && (

Séquences à revoir

Certaines séquences ont un taux de correspondance inférieur à 50%. Vérifiez si le public cible est correctement défini ou si les apprenants sont inscrits aux bonnes séquences.

)} {stats.globalMatchRate >= 80 && (

Excellente correspondance

Le taux de correspondance global est excellent (≥ 80%). La planification des formations est bien alignée avec les publics cibles.

)}
); }