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:
@@ -21,7 +21,7 @@ import {
|
||||
} from "@/components/ui/sidebar";
|
||||
import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const";
|
||||
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 { useLocation } from "wouter";
|
||||
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
|
||||
@@ -160,6 +160,40 @@ function DashboardLayoutContent({
|
||||
const activeMenuItem = menuSections.flatMap(section => section.items).find(item => item.path === location);
|
||||
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(() => {
|
||||
if (isCollapsed) {
|
||||
setIsResizing(false);
|
||||
@@ -244,61 +278,74 @@ function DashboardLayoutContent({
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent className="gap-0">
|
||||
{menuSections.map((section, sectionIndex) => (
|
||||
<div key={section.title}>
|
||||
{sectionIndex > 0 && (
|
||||
<div className="border-t border-border my-2" />
|
||||
)}
|
||||
<div className="px-4 py-2">
|
||||
<h3 className="text-xs font-semibold text-blue-600 uppercase tracking-wider">
|
||||
{section.title}
|
||||
</h3>
|
||||
</div>
|
||||
<SidebarMenu className="px-2 py-1">
|
||||
{section.items.map(item => {
|
||||
const isActive = location === item.path;
|
||||
const hasSubItems = 'subItems' in item && item.subItems && item.subItems.length > 0;
|
||||
const isSubItemActive = hasSubItems && item.subItems.some((sub: any) => location === sub.path);
|
||||
return (
|
||||
<div key={item.path}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={isActive || isSubItemActive}
|
||||
onClick={() => setLocation(item.path)}
|
||||
tooltip={item.label}
|
||||
className={`h-10 transition-all font-normal`}
|
||||
>
|
||||
<item.icon
|
||||
className={`h-4 w-4 ${isActive || isSubItemActive ? "text-primary" : ""}`}
|
||||
/>
|
||||
<span>{item.label}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
{hasSubItems && (
|
||||
<div className="ml-6 mt-1 space-y-1">
|
||||
{item.subItems.map((subItem: any) => {
|
||||
const isSubActive = location === subItem.path;
|
||||
return (
|
||||
<SidebarMenuItem key={subItem.path}>
|
||||
<SidebarMenuButton
|
||||
isActive={isSubActive}
|
||||
onClick={() => setLocation(subItem.path)}
|
||||
tooltip={subItem.label}
|
||||
className="h-9 text-sm font-normal"
|
||||
>
|
||||
<span className="ml-2">{subItem.label}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
{menuSections.map((section, sectionIndex) => {
|
||||
const isOpen = openSections[section.title] ?? true;
|
||||
return (
|
||||
<div key={section.title}>
|
||||
{sectionIndex > 0 && (
|
||||
<div className="border-t border-border my-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">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ChevronDown
|
||||
className={`h-3 w-3 text-blue-600 transition-transform duration-200 ${
|
||||
isOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
{isOpen && (
|
||||
<SidebarMenu className="px-2 py-1">
|
||||
{section.items.map(item => {
|
||||
const isActive = location === item.path;
|
||||
const hasSubItems = 'subItems' in item && item.subItems && item.subItems.length > 0;
|
||||
const isSubItemActive = hasSubItems && item.subItems.some((sub: any) => location === sub.path);
|
||||
return (
|
||||
<div key={item.path}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={isActive || isSubItemActive}
|
||||
onClick={() => setLocation(item.path)}
|
||||
tooltip={item.label}
|
||||
className={`h-10 transition-all font-normal`}
|
||||
>
|
||||
<item.icon
|
||||
className={`h-4 w-4 ${isActive || isSubItemActive ? "text-primary" : ""}`}
|
||||
/>
|
||||
<span>{item.label}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
{hasSubItems && (
|
||||
<div className="ml-6 mt-1 space-y-1">
|
||||
{item.subItems.map((subItem: any) => {
|
||||
const isSubActive = location === subItem.path;
|
||||
return (
|
||||
<SidebarMenuItem key={subItem.path}>
|
||||
<SidebarMenuButton
|
||||
isActive={isSubActive}
|
||||
onClick={() => setLocation(subItem.path)}
|
||||
tooltip={subItem.label}
|
||||
className="h-9 text-sm font-normal"
|
||||
>
|
||||
<span className="ml-2">{subItem.label}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter className="p-3">
|
||||
|
||||
8
todo.md
8
todo.md
@@ -139,3 +139,11 @@
|
||||
- [x] Vérifier que les liens sont bien dans le menu DashboardLayout
|
||||
- [x] Ajouter la gestion des sous-menus dans DashboardLayout
|
||||
- [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
|
||||
|
||||
Reference in New Issue
Block a user