Checkpoint: Menu latéral moderne avec couleurs pastel (version douce et élégante) + correction du bug 'Cannot convert undefined or null to object' sur la page émargement formateur

This commit is contained in:
Manus
2026-01-20 08:31:21 -05:00
parent b7b2134289
commit a7a5ebdfe8
3 changed files with 89 additions and 22 deletions

View File

@@ -27,16 +27,41 @@ import { useLocation } from "wouter";
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton'; import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
import { Button } from "./ui/button"; import { Button } from "./ui/button";
// Couleurs par catégorie de menu // Couleurs et styles par catégorie de menu
const getCategoryColor = (sectionTitle: string) => { const getCategoryColor = (sectionTitle: string) => {
const colors: Record<string, string> = { const colors: Record<string, string> = {
"Tableau de bord": "text-blue-600", "Tableau de bord": "text-blue-500",
"Gestion": "text-green-600", "Gestion": "text-green-500",
"Configuration": "text-orange-600", "Configuration": "text-orange-500",
"Traçabilité": "text-purple-600", "Traçabilité": "text-purple-500",
"Analyses": "text-pink-600", "Analyses": "text-pink-500",
"Gestion technique": "text-red-500",
}; };
return colors[sectionTitle] || ""; return colors[sectionTitle] || "text-gray-500";
};
const getCategoryGradient = (sectionTitle: string) => {
const gradients: Record<string, string> = {
"Tableau de bord": "from-blue-300 to-blue-400",
"Gestion": "from-green-300 to-emerald-400",
"Configuration": "from-orange-300 to-orange-400",
"Traçabilité": "from-purple-300 to-purple-400",
"Analyses": "from-pink-300 to-pink-400",
"Gestion technique": "from-red-300 to-red-400",
};
return gradients[sectionTitle] || "from-gray-300 to-gray-400";
};
const getCategoryBgColor = (sectionTitle: string) => {
const bgColors: Record<string, string> = {
"Tableau de bord": "bg-blue-50/80 hover:bg-blue-100/90",
"Gestion": "bg-green-50/80 hover:bg-green-100/90",
"Configuration": "bg-orange-50/80 hover:bg-orange-100/90",
"Traçabilité": "bg-purple-50/80 hover:bg-purple-100/90",
"Analyses": "bg-pink-50/80 hover:bg-pink-100/90",
"Gestion technique": "bg-red-50/80 hover:bg-red-100/90",
};
return bgColors[sectionTitle] || "bg-gray-50/80 hover:bg-gray-100/90";
}; };
const menuSections = [ const menuSections = [
@@ -369,19 +394,29 @@ function DashboardLayoutContent({
)} )}
<button <button
onClick={() => toggleSection(section.title)} onClick={() => toggleSection(section.title)}
className="w-full px-4 py-2 flex items-center justify-between hover:bg-accent/50 transition-colors rounded-md mx-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring" className={`w-full px-3 py-3 flex items-center justify-between ${getCategoryBgColor(section.title)} transition-all duration-300 rounded-lg mx-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring border-l-4 ${isOpen ? 'border-l-' + section.title.toLowerCase().replace(/ /g, '-') : 'border-l-transparent'} shadow-sm hover:shadow-md hover:translate-x-1`}
style={{
borderLeftColor: isOpen ? `var(--${section.title.toLowerCase().replace(/ /g, '-')}-color, currentColor)` : 'transparent'
}}
> >
<h3 className={`text-xs font-semibold uppercase tracking-wider ${getCategoryColor(section.title)}`}> <div className="flex items-center gap-3">
<div className={`p-1.5 rounded-lg bg-gradient-to-br ${getCategoryGradient(section.title)} shadow-md transition-transform duration-300 ${isOpen ? 'scale-110 rotate-6' : ''}`}>
<div className="w-3 h-3 bg-white/30 rounded-sm" />
</div>
<h3 className={`text-xs font-bold uppercase tracking-wider ${getCategoryColor(section.title)}`}>
{section.title} {section.title}
</h3> </h3>
</div>
<ChevronDown <ChevronDown
className={`h-3 w-3 transition-transform duration-200 ${getCategoryColor(section.title)} ${ className={`h-4 w-4 transition-all duration-300 ${getCategoryColor(section.title)} ${
isOpen ? 'rotate-180' : '' isOpen ? 'rotate-180 scale-110' : ''
}`} }`}
/> />
</button> </button>
{isOpen && ( <div className={`overflow-hidden transition-all duration-300 ease-in-out ${
<SidebarMenu className="px-2 py-1"> isOpen ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'
}`}>
<SidebarMenu className="px-2 py-1 animate-in slide-in-from-top-2">
{section.items.map(item => { {section.items.map(item => {
const isActive = location === item.path; const isActive = location === item.path;
const hasSubItems = 'subItems' in item && item.subItems && Array.isArray(item.subItems) && item.subItems.length > 0; const hasSubItems = 'subItems' in item && item.subItems && Array.isArray(item.subItems) && item.subItems.length > 0;
@@ -393,11 +428,21 @@ function DashboardLayoutContent({
isActive={isActive || Boolean(isSubItemActive)} isActive={isActive || Boolean(isSubItemActive)}
onClick={() => setLocation(item.path)} onClick={() => setLocation(item.path)}
tooltip={item.label} tooltip={item.label}
className={`h-10 transition-all font-normal`} className={`h-11 transition-all duration-300 font-normal hover:translate-x-2 hover:shadow-md ${
isActive || isSubItemActive
? `bg-gradient-to-r ${getCategoryGradient(section.title)} text-white shadow-lg scale-105 border-l-4 border-white/30`
: 'hover:bg-accent/70'
}`}
> >
<div className={`p-1.5 rounded-md transition-all duration-300 ${
isActive || isSubItemActive
? 'bg-white/20 scale-110'
: `bg-gradient-to-br ${getCategoryGradient(section.title)}`
}`}>
<item.icon <item.icon
className={`h-4 w-4 ${isActive || isSubItemActive ? "text-primary" : getCategoryColor(section.title)}`} className={`h-4 w-4 ${isActive || isSubItemActive ? "text-white" : "text-white"}`}
/> />
</div>
<span>{item.label}</span> <span>{item.label}</span>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
@@ -424,7 +469,7 @@ function DashboardLayoutContent({
); );
})} })}
</SidebarMenu> </SidebarMenu>
)} </div>
</div> </div>
); );
})} })}

View File

@@ -221,7 +221,8 @@ export default function FormateurEmargement() {
{/* Dates de formation */} {/* Dates de formation */}
<div className="space-y-2"> <div className="space-y-2">
{inscrit.dates?.map((date: any) => { {inscrit.dates && Array.isArray(inscrit.dates) && inscrit.dates.length > 0 ? inscrit.dates.map((date: any) => {
if (!date || !date.id) return null;
const present = isPresent(inscrit.inscription.id, date.id); const present = isPresent(inscrit.inscription.id, date.id);
return ( return (
<div <div
@@ -264,7 +265,11 @@ export default function FormateurEmargement() {
)} )}
</div> </div>
); );
})} }) : (
<p className="text-sm text-muted-foreground py-2">
Aucune date de formation disponible
</p>
)}
</div> </div>
</div> </div>
))} ))}

17
todo.md
View File

@@ -1003,4 +1003,21 @@
- [x] Implémenter animations au survol et au chargement - [x] Implémenter animations au survol et au chargement
- [x] Améliorer la palette de couleurs - [x] Améliorer la palette de couleurs
- [x] Tester sur sandbox - [x] Tester sur sandbox
- [x] Déployer après validation utilisateur
## Refonte ergonomie du menu latéral accordéon
- [x] Analyser le menu latéral actuel (DashboardLayout.tsx)
- [x] Créer nouvelle version avec icônes colorées et animations
- [x] Ajouter effets hover et transitions fluides
- [x] Implémenter indicateur de page active
- [x] Améliorer les couleurs et gradients
- [x] Tester sur sandbox
- [ ] Déployer après validation utilisateur - [ ] Déployer après validation utilisateur
## Correction bug page émargement formateur
- [x] Analyser l'erreur "Cannot convert undefined or null to object"
- [x] Identifier la requête tRPC qui échoue
- [x] Corriger le bug
- [x] Tester la correction