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";
|
} 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,61 +278,74 @@ function DashboardLayoutContent({
|
|||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|
||||||
<SidebarContent className="gap-0">
|
<SidebarContent className="gap-0">
|
||||||
{menuSections.map((section, sectionIndex) => (
|
{menuSections.map((section, sectionIndex) => {
|
||||||
<div key={section.title}>
|
const isOpen = openSections[section.title] ?? true;
|
||||||
{sectionIndex > 0 && (
|
return (
|
||||||
<div className="border-t border-border my-2" />
|
<div key={section.title}>
|
||||||
)}
|
{sectionIndex > 0 && (
|
||||||
<div className="px-4 py-2">
|
<div className="border-t border-border my-2" />
|
||||||
<h3 className="text-xs font-semibold text-blue-600 uppercase tracking-wider">
|
)}
|
||||||
{section.title}
|
<button
|
||||||
</h3>
|
onClick={() => toggleSection(section.title)}
|
||||||
</div>
|
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"
|
||||||
<SidebarMenu className="px-2 py-1">
|
>
|
||||||
{section.items.map(item => {
|
<h3 className="text-xs font-semibold text-blue-600 uppercase tracking-wider">
|
||||||
const isActive = location === item.path;
|
{section.title}
|
||||||
const hasSubItems = 'subItems' in item && item.subItems && item.subItems.length > 0;
|
</h3>
|
||||||
const isSubItemActive = hasSubItems && item.subItems.some((sub: any) => location === sub.path);
|
<ChevronDown
|
||||||
return (
|
className={`h-3 w-3 text-blue-600 transition-transform duration-200 ${
|
||||||
<div key={item.path}>
|
isOpen ? 'rotate-180' : ''
|
||||||
<SidebarMenuItem>
|
}`}
|
||||||
<SidebarMenuButton
|
/>
|
||||||
isActive={isActive || isSubItemActive}
|
</button>
|
||||||
onClick={() => setLocation(item.path)}
|
{isOpen && (
|
||||||
tooltip={item.label}
|
<SidebarMenu className="px-2 py-1">
|
||||||
className={`h-10 transition-all font-normal`}
|
{section.items.map(item => {
|
||||||
>
|
const isActive = location === item.path;
|
||||||
<item.icon
|
const hasSubItems = 'subItems' in item && item.subItems && item.subItems.length > 0;
|
||||||
className={`h-4 w-4 ${isActive || isSubItemActive ? "text-primary" : ""}`}
|
const isSubItemActive = hasSubItems && item.subItems.some((sub: any) => location === sub.path);
|
||||||
/>
|
return (
|
||||||
<span>{item.label}</span>
|
<div key={item.path}>
|
||||||
</SidebarMenuButton>
|
<SidebarMenuItem>
|
||||||
</SidebarMenuItem>
|
<SidebarMenuButton
|
||||||
{hasSubItems && (
|
isActive={isActive || isSubItemActive}
|
||||||
<div className="ml-6 mt-1 space-y-1">
|
onClick={() => setLocation(item.path)}
|
||||||
{item.subItems.map((subItem: any) => {
|
tooltip={item.label}
|
||||||
const isSubActive = location === subItem.path;
|
className={`h-10 transition-all font-normal`}
|
||||||
return (
|
>
|
||||||
<SidebarMenuItem key={subItem.path}>
|
<item.icon
|
||||||
<SidebarMenuButton
|
className={`h-4 w-4 ${isActive || isSubItemActive ? "text-primary" : ""}`}
|
||||||
isActive={isSubActive}
|
/>
|
||||||
onClick={() => setLocation(subItem.path)}
|
<span>{item.label}</span>
|
||||||
tooltip={subItem.label}
|
</SidebarMenuButton>
|
||||||
className="h-9 text-sm font-normal"
|
</SidebarMenuItem>
|
||||||
>
|
{hasSubItems && (
|
||||||
<span className="ml-2">{subItem.label}</span>
|
<div className="ml-6 mt-1 space-y-1">
|
||||||
</SidebarMenuButton>
|
{item.subItems.map((subItem: any) => {
|
||||||
</SidebarMenuItem>
|
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>
|
||||||
)}
|
);
|
||||||
</div>
|
})}
|
||||||
);
|
</SidebarMenu>
|
||||||
})}
|
)}
|
||||||
</SidebarMenu>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
<SidebarFooter className="p-3">
|
<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] 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
|
||||||
|
|||||||
Reference in New Issue
Block a user