Files
formation-manager-itinova/client/src/pages/formateur/FormateurDashboard.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

129 lines
5.0 KiB
TypeScript

import { useAuth } from "@/_core/hooks/useAuth";
import DashboardLayout from "@/components/DashboardLayout";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { trpc } from "@/lib/trpc";
import { Calendar, Clock, MapPin, Users } from "lucide-react";
import { useState } from "react";
import { Link } from "wouter";
export default function FormateurDashboard() {
const { user } = useAuth();
const [dateDebut] = useState<Date>(new Date());
const [dateFin] = useState<Date>(() => {
const date = new Date();
date.setMonth(date.getMonth() + 3); // 3 mois à l'avance
return date;
});
// Récupérer l'ID du formateur associé à l'utilisateur
// Pour l'instant, on utilise un ID fixe, mais il faudrait le lier à l'utilisateur
const formateurId = 1; // TODO: Récupérer depuis la base de données
const { data: interventions, isLoading } = trpc.formateur.calendrier.useQuery({
formateurId,
dateDebut,
dateFin,
});
const formatDate = (date: Date) => {
return new Date(date).toLocaleDateString("fr-FR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
};
const formatTime = (date: Date) => {
return new Date(date).toLocaleTimeString("fr-FR", {
hour: "2-digit",
minute: "2-digit",
});
};
// Grouper les interventions par date
const interventionsParDate = interventions?.reduce((acc, intervention) => {
const dateKey = new Date(intervention.dateDebut).toDateString();
if (!acc[dateKey]) {
acc[dateKey] = [];
}
acc[dateKey].push(intervention);
return acc;
}, {} as Record<string, typeof interventions>);
return (
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="page-title">Mon Calendrier</h1>
<p className="text-muted-foreground">
Vos interventions à venir
</p>
</div>
{isLoading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
) : !interventions || interventions.length === 0 ? (
<Card>
<CardContent className="py-12">
<div className="text-center text-muted-foreground">
<Calendar className="mx-auto h-12 w-12 mb-4 opacity-50" />
<p>Aucune intervention prévue pour les 3 prochains mois</p>
</div>
</CardContent>
</Card>
) : (
<div className="space-y-6">
{Object.entries(interventionsParDate || {}).map(([dateKey, dayInterventions]) => (
<div key={dateKey} className="space-y-3">
<h2 className="text-xl font-semibold flex items-center gap-2">
<Calendar className="h-5 w-5" />
{formatDate(new Date(dateKey))}
</h2>
<div className="grid gap-4 md:grid-cols-2">
{dayInterventions.map((intervention) => (
<Card key={intervention.dateId} className="hover:shadow-md transition-shadow">
<CardHeader>
<CardTitle className="text-lg">{intervention.formationNom}</CardTitle>
<CardDescription>{intervention.sequenceNom}</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span>
{formatTime(intervention.dateDebut)} - {formatTime(intervention.dateFin)}
</span>
</div>
{intervention.lieu && (
<div className="flex items-center gap-2 text-sm">
<MapPin className="h-4 w-4 text-muted-foreground" />
<span>{intervention.lieu}</span>
</div>
)}
<div className="flex items-center gap-2 text-sm">
<Users className="h-4 w-4 text-muted-foreground" />
<span>{intervention.nbInscrits} apprenant(s) inscrit(s)</span>
</div>
<div className="pt-2">
<Link href={`/formateur/sequence/${intervention.sequenceId}`}>
<Button variant="outline" size="sm" className="w-full">
Voir les détails
</Button>
</Link>
</div>
</CardContent>
</Card>
))}
</div>
</div>
))}
</div>
)}
</div>
</DashboardLayout>
);
}