Checkpoint: Ajout d'un système plier/déplier (accordéon) pour les catégories du menu de navigation. Les utilisateurs peuvent maintenant cliquer sur les titres de sections (TABLEAU DE BORD, GESTION, CONFIGURATION, ANALYSES) pour afficher ou masquer les items, rendant l'interface moins surchargée. Les icônes chevron indiquent l'état ouvert/fermé avec une animation de rotation. La section contenant la page active reste automatiquement ouverte.

This commit is contained in:
Manus Sandbox
2025-12-10 03:43:37 -05:00
parent e484f45467
commit ebaa2a5fff
2 changed files with 110 additions and 55 deletions

View File

@@ -21,7 +21,7 @@ import {
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const"; import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const";
import { useIsMobile } from "@/hooks/useMobile"; import { useIsMobile } from "@/hooks/useMobile";
import { LayoutDashboard, LogOut, PanelLeft, Users, GraduationCap, Calendar, CalendarDays, Bell, BarChart3, UserCog, Mail, Settings, TrendingUp, Building2, FileSpreadsheet, ClipboardList } from "lucide-react"; import { LayoutDashboard, LogOut, PanelLeft, Users, GraduationCap, Calendar, CalendarDays, Bell, BarChart3, UserCog, Mail, Settings, TrendingUp, Building2, FileSpreadsheet, ClipboardList, ChevronDown } from "lucide-react";
import { CSSProperties, useEffect, useRef, useState } from "react"; import { CSSProperties, useEffect, useRef, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton'; import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
@@ -160,6 +160,40 @@ function DashboardLayoutContent({
const activeMenuItem = menuSections.flatMap(section => section.items).find(item => item.path === location); const activeMenuItem = menuSections.flatMap(section => section.items).find(item => item.path === location);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
// État pour gérer les sections ouvertes/fermées (accordéon)
const [openSections, setOpenSections] = useState<Record<string, boolean>>(() => {
// Par défaut, ouvrir toutes les sections
const initial: Record<string, boolean> = {};
menuSections.forEach(section => {
initial[section.title] = true;
});
return initial;
});
// Fonction pour toggle une section
const toggleSection = (sectionTitle: string) => {
setOpenSections(prev => ({
...prev,
[sectionTitle]: !prev[sectionTitle]
}));
};
// S'assurer que la section contenant l'item actif est ouverte
useEffect(() => {
const activeSection = menuSections.find(section =>
section.items.some(item =>
item.path === location ||
('subItems' in item && item.subItems?.some((sub: any) => sub.path === location))
)
);
if (activeSection && !openSections[activeSection.title]) {
setOpenSections(prev => ({
...prev,
[activeSection.title]: true
}));
}
}, [location]);
useEffect(() => { useEffect(() => {
if (isCollapsed) { if (isCollapsed) {
setIsResizing(false); setIsResizing(false);
@@ -244,16 +278,27 @@ function DashboardLayoutContent({
</SidebarHeader> </SidebarHeader>
<SidebarContent className="gap-0"> <SidebarContent className="gap-0">
{menuSections.map((section, sectionIndex) => ( {menuSections.map((section, sectionIndex) => {
const isOpen = openSections[section.title] ?? true;
return (
<div key={section.title}> <div key={section.title}>
{sectionIndex > 0 && ( {sectionIndex > 0 && (
<div className="border-t border-border my-2" /> <div className="border-t border-border my-2" />
)} )}
<div className="px-4 py-2"> <button
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"
>
<h3 className="text-xs font-semibold text-blue-600 uppercase tracking-wider"> <h3 className="text-xs font-semibold text-blue-600 uppercase tracking-wider">
{section.title} {section.title}
</h3> </h3>
</div> <ChevronDown
className={`h-3 w-3 text-blue-600 transition-transform duration-200 ${
isOpen ? 'rotate-180' : ''
}`}
/>
</button>
{isOpen && (
<SidebarMenu className="px-2 py-1"> <SidebarMenu className="px-2 py-1">
{section.items.map(item => { {section.items.map(item => {
const isActive = location === item.path; const isActive = location === item.path;
@@ -297,8 +342,10 @@ function DashboardLayoutContent({
); );
})} })}
</SidebarMenu> </SidebarMenu>
)}
</div> </div>
))} );
})}
</SidebarContent> </SidebarContent>
<SidebarFooter className="p-3"> <SidebarFooter className="p-3">

View File

@@ -139,3 +139,11 @@
- [x] Vérifier que les liens sont bien dans le menu DashboardLayout - [x] Vérifier que les liens sont bien dans le menu DashboardLayout
- [x] Ajouter la gestion des sous-menus dans DashboardLayout - [x] Ajouter la gestion des sous-menus dans DashboardLayout
- [x] Tester l'accès aux pages /admin/rappels/historique et /admin/rappels/statistiques - [x] Tester l'accès aux pages /admin/rappels/historique et /admin/rappels/statistiques
## Système plier/déplier pour le menu de navigation
- [x] Ajouter un état pour gérer les sections ouvertes/fermées
- [x] Ajouter des icônes chevron pour indiquer l'état
- [x] Implémenter la logique de toggle pour chaque section
- [x] Garder la section active automatiquement ouverte
- [x] Tester le comportement sur toutes les sections