Checkpoint: Espace Formateur - Fonctionnalités complètes
✅ **Nouvelles fonctionnalités** : **Base de données** : - Nouvelle table `supportsFormation` pour stocker les documents uploadés par les formateurs - Champs : sequenceId, formateurId, nomFichier, typeFichier, tailleFichier, urlFichier, s3Key, description - Migration effectuée avec succès **Backend (server/formateurDb.ts)** : - `getCalendrierFormateur()` : Récupère les interventions à venir avec dates, lieux et nombre d'inscrits - `getApprenantsSequence()` : Liste des apprenants inscrits à une séquence - `getSupportsSequence()` : Liste des supports de formation d'une séquence - `ajouterSupport()` : Upload d'un nouveau support - `supprimerSupport()` : Suppression d'un support avec vérification de propriété - `validerPresence()` : Validation de la présence d'un apprenant - `getHistoriqueFormateur()` : Historique des formations passées avec statistiques - `getDetailSequence()` : Détails complets d'une séquence avec dates **API tRPC (server/routers.ts)** : - `formateur.calendrier` : Calendrier des interventions avec filtres de dates - `formateur.apprenants` : Liste des apprenants d'une séquence - `formateur.supports` : Gestion des supports (liste, ajout, suppression) - `formateur.validerPresence` : Validation des présences en ligne - `formateur.historique` : Historique complet des formations - `formateur.detailSequence` : Détails d'une séquence **Pages frontend** : - `/formateur` : **FormateurDashboard** - Calendrier des interventions à venir groupées par date - `/formateur/sequence/:id` : **FormateurSequence** - Détails séquence avec onglets Apprenants et Supports - `/formateur/historique` : **FormateurHistorique** - Historique des formations avec taux de présence **Fonctionnalités implémentées** : - 📅 Calendrier des interventions avec dates, heures, lieux et nombre d'inscrits - 👥 Liste des apprenants avec informations complètes (nom, email, fonction, établissement) - ✅ Validation des présences en ligne avec checkboxes interactives - 📎 Gestion des supports de formation (liste, téléchargement) - 📊 Historique des formations avec statistiques (inscrits, présents, taux) - 🎨 Interface moderne avec cartes, tableaux et onglets **Notes** : - L'ID du formateur est actuellement fixé à 1 (TODO: lier à l'utilisateur connecté) - La fonctionnalité d'upload de supports est préparée mais nécessite l'implémentation du composant d'upload - Les présences sont gérées via le statut de l'inscription (confirmée/annulée)
This commit is contained in:
128
client/src/pages/formateur/FormateurDashboard.tsx
Normal file
128
client/src/pages/formateur/FormateurDashboard.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
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="text-3xl font-bold">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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user