diff --git a/client/src/pages/FormateurDashboard.tsx b/client/src/pages/FormateurDashboard.tsx index 69a2cf2..0795865 100644 --- a/client/src/pages/FormateurDashboard.tsx +++ b/client/src/pages/FormateurDashboard.tsx @@ -2,7 +2,7 @@ import { trpc } from "@/lib/trpc"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; -import { Calendar, Users, GraduationCap, Clock, MapPin, AlertCircle } from "lucide-react"; +import { Calendar, Users, GraduationCap, Clock, MapPin, AlertCircle, TrendingUp } from "lucide-react"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; import { Skeleton } from "@/components/ui/skeleton"; @@ -12,196 +12,240 @@ export default function FormateurDashboard() { const { data: stats, isLoading: statsLoading } = trpc.formateurs.dashboardStats.useQuery(); const { data: prochainesSequences, isLoading: sequencesLoading } = trpc.formateurs.prochainesSequences.useQuery({ limit: 10 }); - if (statsLoading) { - return ( - -
-
- - -
-
- - - -
- -
-
- ); - } + const statsCards = [ + { + title: "Total séquences", + value: stats?.totalSequences || 0, + icon: GraduationCap, + gradient: "from-blue-300 to-blue-400", + borderColor: "border-l-blue-400", + bgColor: "bg-blue-50/80", + iconBg: "bg-gradient-to-br from-blue-300 to-blue-400", + description: "Toutes vos séquences", + loading: statsLoading, + }, + { + title: "Séquences à venir", + value: stats?.sequencesAvenir || 0, + icon: Calendar, + gradient: "from-green-300 to-emerald-400", + borderColor: "border-l-green-400", + bgColor: "bg-green-50/80", + iconBg: "bg-gradient-to-br from-green-300 to-emerald-400", + description: "Avec dates futures", + loading: statsLoading, + }, + { + title: "Total apprenants", + value: stats?.totalInscrits || 0, + icon: Users, + gradient: "from-purple-300 to-purple-400", + borderColor: "border-l-purple-400", + bgColor: "bg-purple-50/80", + iconBg: "bg-gradient-to-br from-purple-300 to-purple-400", + description: "Inscrits confirmés", + loading: statsLoading, + }, + ]; return ( -
- {/* En-tête */} -
-

Tableau de bord formateur

-

- Vue d'ensemble de vos formations et prochaines interventions -

-
- - {/* Cartes de statistiques */} -
- - - Total séquences - - - -
{stats?.totalSequences || 0}
-

- Toutes vos séquences de formation +

+ {/* En-tête */} +
+
+

+ Tableau de bord formateur +

+

+ Vue d'ensemble de vos formations et prochaines interventions

- - +
+
+ + Données en temps réel +
+
- - - Séquences à venir - + {/* Cartes de statistiques avec animations */} +
+ {statsCards.map((stat, index) => { + const Icon = stat.icon; + return ( + + + + {stat.title} + +
+ +
+
+ + {stat.loading ? ( + + ) : ( +
+
+ {stat.value} +
+
+ + {stat.description} +
+
+ )} +
+ {/* Effet de brillance au survol */} +
+ + ); + })} +
+ + {/* Liste des prochaines séquences avec design amélioré */} + + + +
+ +
+ + Prochaines séquences + +
+ + Vos interventions à venir triées par date +
- -
{stats?.sequencesAvenir || 0}
-

- Avec au moins une date future -

-
-
+ + {sequencesLoading ? ( +
+ {[1, 2, 3].map((i) => ( + + ))} +
+ ) : prochainesSequences && prochainesSequences.length > 0 ? ( +
+ {prochainesSequences.map((seq: any, index: number) => { + const pourcentageRemplissage = seq.capaciteMax > 0 + ? Math.round((seq.nbInscrits / seq.capaciteMax) * 100) + : 0; + const estComplete = pourcentageRemplissage >= 100; + const estPresqueComplete = pourcentageRemplissage >= 80 && pourcentageRemplissage < 100; - - - Total apprenants - - - -
{stats?.totalInscrits || 0}
-

- Inscrits confirmés à vos séquences -

-
-
-
- - {/* Liste des prochaines séquences */} - - - Prochaines séquences - - Vos interventions à venir triées par date - - - - {sequencesLoading ? ( -
- {[1, 2, 3].map((i) => ( - - ))} -
- ) : prochainesSequences && prochainesSequences.length > 0 ? ( -
- {prochainesSequences.map((seq: any) => { - const pourcentageRemplissage = seq.capaciteMax > 0 - ? Math.round((seq.nbInscrits / seq.capaciteMax) * 100) - : 0; - const estComplete = pourcentageRemplissage >= 100; - const estPresqueComplete = pourcentageRemplissage >= 80 && pourcentageRemplissage < 100; - - return ( - - -
-
- {seq.nom} - - - {seq.formation?.nom || "Formation inconnue"} - -
- - {seq.statut === 'ouverte' ? 'Ouverte' : seq.statut === 'bloquee' ? 'Bloquée' : 'Terminée'} - -
-
- - {/* Lieu */} -
- - {seq.lieu} -
- - {/* Dates */} -
-
- - Dates de formation -
-
- {seq.dates.map((date: any) => { - const dateDebut = new Date(date.dateDebut); - const dateFin = new Date(date.dateFin); - const estFuture = dateDebut >= new Date(); - - return ( - - {format(dateDebut, "dd MMM", { locale: fr })} - {dateDebut.toDateString() !== dateFin.toDateString() && - ` - ${format(dateFin, "dd MMM", { locale: fr })}` - } - - ); - })} -
-
- - {/* Capacité */} -
-
-
- - Inscriptions + return ( + + +
+
+ {seq.nom} + + + {seq.formation?.nom || "Formation inconnue"} +
- - {seq.nbInscrits} / {seq.capaciteMax} - + + {seq.statut === 'ouverte' ? 'Ouverte' : seq.statut === 'bloquee' ? 'Bloquée' : 'Terminée'} +
-
- - {estComplete && ( -
- - Capacité maximale atteinte -
- )} - {estPresqueComplete && ( -
- - Presque complète ({pourcentageRemplissage}%) -
- )} + + + {/* Lieu */} +
+ + {seq.lieu}
-
- -
- ); - })} -
- ) : ( -
- -

Aucune séquence à venir pour le moment

-
- )} - - + + {/* Dates */} +
+
+ + Dates de formation +
+
+ {seq.dates.map((date: any) => { + const dateDebut = new Date(date.dateDebut); + const dateFin = new Date(date.dateFin); + const estFuture = dateDebut >= new Date(); + + return ( + + {format(dateDebut, "dd MMM", { locale: fr })} + {dateDebut.toDateString() !== dateFin.toDateString() && + ` - ${format(dateFin, "dd MMM", { locale: fr })}` + } + + ); + })} +
+
+ + {/* Capacité */} +
+
+
+ + Inscriptions +
+ + {seq.nbInscrits} / {seq.capaciteMax} + +
+
+ + {estComplete && ( +
+ + Capacité maximale atteinte +
+ )} + {estPresqueComplete && ( +
+ + Presque complète ({pourcentageRemplissage}%) +
+ )} +
+
+ + + ); + })} +
+ ) : ( +
+
+ +
+

Aucune séquence à venir pour le moment

+

Vos prochaines interventions apparaîtront ici

+
+ )} + +
); diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index cf1e339..35898e3 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -11,8 +11,12 @@ export default function Home() { const [, setLocation] = useLocation(); useEffect(() => { - if (!loading && isAuthenticated && user?.role === 'admin') { - setLocation('/admin'); + if (!loading && isAuthenticated) { + if (user?.role === 'admin') { + setLocation('/admin'); + } else if (user?.role === 'formateur') { + setLocation('/formateur'); + } } }, [loading, isAuthenticated, user?.role, setLocation]); @@ -24,7 +28,7 @@ export default function Home() { ); } - if (isAuthenticated && user?.role === 'admin') { + if (isAuthenticated && (user?.role === 'admin' || user?.role === 'formateur')) { return null; } diff --git a/todo.md b/todo.md index 5aede42..77b8019 100644 --- a/todo.md +++ b/todo.md @@ -1233,5 +1233,14 @@ - [x] Identifier le code de modification de séquence qui désactive les rappels - [x] Corriger pour ne désactiver que lors de changement de dates -- [ ] Tester la modification +- [x] Tester la modification +- [x] Déployer sur le VPS + +## Créer un tableau de bord spécifique pour les formateurs + +- [x] Analyser le tableau de bord actuel (Admin.tsx) +- [x] Vérifier les procédures backend existantes (déjà présentes) +- [x] Réécrire FormateurDashboard.tsx avec le même design moderne +- [x] Vérifier la route dans App.tsx (déjà présente) +- [x] Ajouter la redirection automatique des formateurs vers /formateur - [ ] Déployer sur le VPS