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
This commit is contained in:
Manus
2026-01-20 11:46:56 -05:00
parent 68e2cc64ef
commit fc4df8ed36
51 changed files with 2466 additions and 62 deletions

View File

@@ -148,7 +148,7 @@ export default function AdminAnalytics() {
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-3xl font-bold mb-2">Tableau de bord analytique</h1>
<h1 className="page-title">Tableau de bord analytique</h1>
<p className="text-muted-foreground">
Statistiques et indicateurs de performance des formations
</p>

View File

@@ -89,7 +89,7 @@ export default function AdminApprenantDetail() {
<ArrowLeft className="h-4 w-4 mr-2" />
Retour aux apprenants
</Button>
<h1 className="text-3xl font-bold">Détail de l'apprenant</h1>
<h1 className="page-title">Détail de l'apprenant</h1>
<p className="text-muted-foreground">
Consultez toutes les inscriptions de cet apprenant
</p>

View File

@@ -118,7 +118,7 @@ export default function AdminApprenants() {
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Apprenants</h1>
<h1 className="page-title">Apprenants</h1>
<p className="text-muted-foreground mt-2">
Gérez les apprenants et leurs informations
</p>

View File

@@ -173,7 +173,7 @@ export default function AdminCalendrier() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Calendrier des séquences</h1>
<h1 className="page-title">Calendrier des séquences</h1>
<p className="text-muted-foreground">
Vue mensuelle de toutes les séquences planifiées
</p>

View File

@@ -106,7 +106,7 @@ export default function AdminEmailConfig() {
<DashboardLayout>
<div className="container py-8 max-w-4xl">
<div className="mb-8">
<h1 className="text-3xl font-bold">Configuration des emails</h1>
<h1 className="page-title">Configuration des emails</h1>
<p className="text-muted-foreground mt-2">
Configurez l'envoi d'emails via Resend et basculez entre mode simulation et production
</p>

View File

@@ -221,7 +221,7 @@ export default function AdminEmailTemplates() {
return (
<DashboardLayout>
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2">Templates d'emails</h1>
<h1 className="page-title">Templates d'emails</h1>
<p className="text-muted-foreground">
Personnalisez l'apparence de vos emails automatiques
</p>

View File

@@ -16,7 +16,7 @@ export default function AdminEtablissements() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Établissements</h1>
<h1 className="page-title">Établissements</h1>
<p className="text-muted-foreground mt-2">
Vue d'ensemble des établissements et de leur participation
</p>

View File

@@ -121,7 +121,7 @@ export default function AdminFormations() {
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Formations</h1>
<h1 className="page-title">Formations</h1>
<p className="text-muted-foreground mt-2">
Gérez les formations et leurs liens d'inscription
</p>

View File

@@ -292,7 +292,7 @@ export default function AdminGestionAttestations() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Gestion des attestations de formation</h1>
<h1 className="page-title">Gestion des attestations de formation</h1>
<p className="text-muted-foreground">
Configurez le mode de génération et d'envoi des attestations pour chaque formation
</p>

View File

@@ -111,7 +111,7 @@ export default function AdminImportExcel() {
<DashboardLayout>
<div className="container mx-auto py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Import Excel - Formations et Séquences</h1>
<h1 className="page-title">Import Excel - Formations et Séquences</h1>
<p className="text-muted-foreground">
Importez plusieurs formations et séquences en une seule fois à partir d'un fichier Excel
</p>

View File

@@ -104,7 +104,7 @@ export default function AdminParametres() {
<div className="flex items-center gap-3 mb-6">
<Settings className="h-8 w-8 text-primary" />
<div>
<h1 className="text-3xl font-bold">Paramètres de l'application</h1>
<h1 className="page-title">Paramètres de l'application</h1>
<p className="text-muted-foreground">
Configuration globale de l'application
</p>

View File

@@ -140,7 +140,7 @@ export default function AdminQuestionnaireAnalyse() {
<div className="container mx-auto py-8">
<div className="mb-6 flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold">{questionnaire.titre}</h1>
<h1 className="page-title">{questionnaire.titre}</h1>
<p className="text-muted-foreground mt-1">Analyse des réponses</p>
</div>
<div className="flex gap-2">

View File

@@ -196,7 +196,7 @@ export default function AdminQuestionnaireEdit() {
<DashboardLayout>
<div className="container mx-auto py-8 max-w-4xl">
<div className="mb-6">
<h1 className="text-3xl font-bold">
<h1 className="page-title">
{questionnaireId ? "Modifier le questionnaire" : "Nouveau questionnaire"}
</h1>
</div>

View File

@@ -29,7 +29,7 @@ export default function AdminQuestionnaireSuivi() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Suivi des Questionnaires</h1>
<h1 className="page-title">Suivi des Questionnaires</h1>
<p className="text-muted-foreground mt-2">
Tableau de bord de suivi des questionnaires avec taux de réponse et statistiques
</p>

View File

@@ -57,7 +57,7 @@ export default function AdminQuestionnaires() {
<div className="container mx-auto py-8">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold">Questionnaires</h1>
<h1 className="page-title">Questionnaires</h1>
<p className="text-muted-foreground mt-1">
Gérez les questionnaires de satisfaction et d'évaluation
</p>

View File

@@ -318,7 +318,7 @@ export default function AdminRappels() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Rappels automatiques</h1>
<h1 className="page-title">Rappels automatiques</h1>
<p className="text-muted-foreground">
Configurez les rappels envoyés automatiquement avant les séquences
</p>

View File

@@ -20,9 +20,15 @@ export default function AdminRapportPublicCible() {
const totalInscrits = sequenceInscriptions.length;
const mismatchCount = sequenceInscriptions.filter((insc: any) => {
const apprenantFonction = insc.apprenant.fonction;
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<string, string> = {
directeur: "directeur",
@@ -106,7 +112,7 @@ export default function AdminRapportPublicCible() {
<DashboardLayout>
<div className="container mx-auto py-8 space-y-6">
<div>
<h1 className="text-3xl font-bold">Rapport d'analyse Public Cible</h1>
<h1 className="page-title">Rapport d'analyse Public Cible</h1>
<p className="text-muted-foreground mt-1">
Comparaison entre le public cible des séquences et la fonction des apprenants inscrits
</p>

View File

@@ -309,7 +309,7 @@ export default function AdminSequenceInscrits() {
<ArrowLeft className="h-4 w-4 mr-2" />
Retour aux séquences
</Button>
<h1 className="text-3xl font-bold">Inscrits à la séquence</h1>
<h1 className="page-title">Inscrits à la séquence</h1>
<p className="text-muted-foreground mt-1">
{getFormationName()} - {sequence.nom}
</p>

View File

@@ -373,7 +373,7 @@ export default function AdminSequences() {
<div className="container mx-auto py-8 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Gestion des Séquences</h1>
<h1 className="page-title">Gestion des Séquences</h1>
<p className="text-muted-foreground mt-1">
Créez et gérez les séquences de formation avec leurs dates
</p>

View File

@@ -239,7 +239,7 @@ export default function AdminUsers() {
<DashboardLayout>
<div className="mb-6 flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold mb-2">Gestion des Utilisateurs</h1>
<h1 className="page-title">Gestion des Utilisateurs</h1>
<p className="text-muted-foreground">
Gérez les comptes utilisateurs de l'application
</p>

View File

@@ -230,7 +230,7 @@ export default function ComponentsShowcase() {
<div className="min-h-screen bg-background text-foreground">
<main className="container max-w-6xl mx-auto">
<div className="space-y-2 justify-between flex">
<h2 className="text-3xl font-bold tracking-tight mb-6">
<h2 className="page-title">
Shadcn/ui Component Library
</h2>
<Button variant="outline" size="icon" onClick={toggleTheme}>

View File

@@ -36,7 +36,7 @@ export default function FormateurDashboard() {
<div className="container py-8 space-y-8">
{/* En-tête */}
<div>
<h1 className="text-3xl font-bold tracking-tight">Tableau de bord formateur</h1>
<h1 className="page-title">Tableau de bord formateur</h1>
<p className="text-muted-foreground mt-2">
Vue d'ensemble de vos formations et prochaines interventions
</p>

View File

@@ -119,7 +119,7 @@ export default function FormateurEmargement() {
<DashboardLayout>
<div className="container py-8 space-y-6">
<div>
<h1 className="text-3xl font-bold">Émargement numérique</h1>
<h1 className="page-title">Émargement numérique</h1>
<p className="text-muted-foreground mt-2">
Gérez les présences de vos apprenants avec le QR code
</p>

View File

@@ -28,7 +28,7 @@ export default function HistoriqueAttestations() {
<div className="flex items-center gap-3 mb-6">
<FileText className="h-8 w-8 text-primary" />
<div>
<h1 className="text-3xl font-bold">Historique des attestations</h1>
<h1 className="page-title">Historique des attestations</h1>
<p className="text-muted-foreground">
Suivi de tous les envois d'attestations de formation
</p>

View File

@@ -113,7 +113,7 @@ export default function Home() {
{/* Features Grid */}
<section className="py-16 px-4 bg-white">
<div className="container mx-auto max-w-6xl">
<h3 className="text-3xl font-bold text-center mb-12">Fonctionnalités principales</h3>
<h3 className="page-title">Fonctionnalités principales</h3>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{features.map((feature, index) => (
<Card key={index} className="hover:shadow-lg transition-shadow">
@@ -135,7 +135,7 @@ export default function Home() {
{/* How it works */}
<section className="py-16 px-4">
<div className="container mx-auto max-w-4xl">
<h3 className="text-3xl font-bold text-center mb-12">Comment ça fonctionne ?</h3>
<h3 className="page-title">Comment ça fonctionne ?</h3>
<div className="space-y-8">
<Card>
<CardHeader>

View File

@@ -229,7 +229,6 @@ export default function Inscription() {
<ul className="list-disc list-inside space-y-1 text-sm text-muted-foreground">
<li>Vous recevrez un email de confirmation avec tous les détails</li>
<li>Une invitation Outlook sera envoyée pour bloquer votre agenda</li>
<li>Un email de rappel vous sera envoyé 7 jours avant la formation</li>
</ul>
</div>
<p className="text-sm text-muted-foreground">

View File

@@ -47,7 +47,7 @@ export default function LoginChoice() {
<img src={APP_LOGO} alt="Logo" className="h-20 w-auto" />
</div>
<div className="text-center">
<CardTitle className="text-3xl font-bold">{APP_TITLE}</CardTitle>
<CardTitle className="page-title">{APP_TITLE}</CardTitle>
<CardDescription className="mt-3 text-base">
Choisissez votre méthode de connexion
</CardDescription>

View File

@@ -113,7 +113,7 @@ export default function QuestionnaireReponse() {
{APP_LOGO && (
<img src={APP_LOGO} alt={APP_TITLE} className="h-16 mx-auto mb-4" />
)}
<h1 className="text-3xl font-bold mb-2">{data.questionnaire?.titre || 'Questionnaire'}</h1>
<h1 className="page-title">{data.questionnaire?.titre || 'Questionnaire'}</h1>
{data.questionnaire?.description && (
<p className="text-muted-foreground">{data.questionnaire.description}</p>
)}

View File

@@ -70,7 +70,7 @@ export default function AdminAttestations() {
<div className="flex items-center gap-3 mb-6">
<FileText className="h-8 w-8 text-primary" />
<div>
<h1 className="text-3xl font-bold">Configuration des attestations</h1>
<h1 className="page-title">Configuration des attestations</h1>
<p className="text-muted-foreground">
Personnalisez le contenu et la signature des attestations de formation
</p>

View File

@@ -65,7 +65,7 @@ export default function AdminFail2Ban() {
<div className="container mx-auto py-8 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Monitoring Fail2Ban</h1>
<h1 className="page-title">Monitoring Fail2Ban</h1>
<p className="text-muted-foreground">Surveillance des tentatives d'intrusion SSH</p>
</div>
<Button onClick={handleRefresh} variant="outline">

View File

@@ -87,7 +87,7 @@ export default function AdminRappelsHistorique() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Historique des Rappels</h1>
<h1 className="page-title">Historique des Rappels</h1>
<p className="text-muted-foreground mt-1">
Consultez tous les rappels envoyés avec filtres avancés
</p>

View File

@@ -29,7 +29,7 @@ export default function AdminRappelsStats() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Statistiques des Rappels</h1>
<h1 className="page-title">Statistiques des Rappels</h1>
<p className="text-muted-foreground mt-1">
Analysez les performances du système de rappels
</p>

View File

@@ -56,7 +56,7 @@ export default function FormateurDashboard() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Mon Calendrier</h1>
<h1 className="page-title">Mon Calendrier</h1>
<p className="text-muted-foreground">
Vos interventions à venir
</p>

View File

@@ -40,7 +40,7 @@ export default function FormateurHistorique() {
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Historique de mes formations</h1>
<h1 className="page-title">Historique de mes formations</h1>
<p className="text-muted-foreground">
Toutes vos formations passées
</p>

View File

@@ -141,7 +141,7 @@ export default function FormateurSequence() {
<div className="space-y-6">
{/* En-tête */}
<div>
<h1 className="text-3xl font-bold">{sequence.nom}</h1>
<h1 className="page-title">{sequence.nom}</h1>
<p className="text-muted-foreground">{sequence.formationNom}</p>
</div>