diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx index fbdb05f..97f0d65 100644 --- a/client/src/components/DashboardLayout.tsx +++ b/client/src/components/DashboardLayout.tsx @@ -15,29 +15,63 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubItem, + SidebarMenuSubButton, SidebarProvider, SidebarTrigger, useSidebar, } from "@/components/ui/sidebar"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { getLoginUrl } from "@/const"; import { useIsMobile } from "@/hooks/useMobile"; -import { LayoutDashboard, LogOut, PanelLeft, Users, FileText, Upload, History, Settings, Download, List, Zap } from "lucide-react"; +import { LayoutDashboard, LogOut, PanelLeft, Users, FileText, Upload, History, Settings, Download, List, Zap, ChevronDown, Receipt, Cog, ClipboardList } from "lucide-react"; import { CSSProperties, useEffect, useRef, useState } from "react"; import { useLocation } from "wouter"; import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton'; import { Button } from "./ui/button"; -const menuItems = [ - { icon: LayoutDashboard, label: "Tableau de bord", path: "/dashboard" }, - { icon: Upload, label: "Importer", path: "/upload" }, - { icon: FileText, label: "Factures", path: "/invoices" }, - { icon: FileText, label: "Factures BAP", path: "/invoices-bap" }, - { icon: History, label: "Historique", path: "/history" }, - { icon: Settings, label: "Param\u00e8tres", path: "/settings" }, - { icon: Download, label: "Param\u00e8tres de r\u00e9ception", path: "/import-settings" }, - { icon: Zap, label: "Automatismes", path: "/automation-rules" }, - { icon: List, label: "Administration des listes", path: "/lists-admin" }, - { icon: Users, label: "Utilisateurs", path: "/users", adminOnly: true }, +type MenuItem = { + icon: any; + label: string; + path?: string; + adminOnly?: boolean; + children?: MenuItem[]; +}; + +const menuStructure: MenuItem[] = [ + { + icon: LayoutDashboard, + label: "Tableau de bord", + path: "/dashboard", + }, + { + icon: Receipt, + label: "Facturation", + children: [ + { icon: Upload, label: "Import", path: "/upload" }, + { icon: FileText, label: "Factures", path: "/invoices" }, + { icon: FileText, label: "Factures BAP", path: "/invoices-bap" }, + ], + }, + { + icon: Cog, + label: "Configuration", + children: [ + { icon: Settings, label: "Paramètres", path: "/settings" }, + { icon: Download, label: "Paramètres d'import", path: "/import-settings" }, + { icon: List, label: "Administration des listes", path: "/lists-admin" }, + { icon: Zap, label: "Automatismes", path: "/automation-rules" }, + { icon: Users, label: "Utilisateurs", path: "/users", adminOnly: true }, + ], + }, + { + icon: ClipboardList, + label: "Traçabilité", + children: [ + { icon: History, label: "Historiques", path: "/history" }, + ], + }, ]; const SIDEBAR_WIDTH_KEY = "sidebar-width"; @@ -120,8 +154,34 @@ function DashboardLayoutContent({ const isCollapsed = state === "collapsed"; const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); - const activeMenuItem = menuItems.find(item => item.path === location); const isMobile = useIsMobile(); + + // État pour les sections ouvertes (Tableau de bord ouvert par défaut) + const [openSections, setOpenSections] = useState>(new Set(["Tableau de bord"])); + + // Trouver le label actif pour le header mobile + const findActiveLabel = (): string => { + for (const section of menuStructure) { + if (section.path === location) return section.label; + if (section.children) { + const child = section.children.find(c => c.path === location); + if (child) return child.label; + } + } + return "Menu"; + }; + + const toggleSection = (label: string) => { + setOpenSections(prev => { + const newSet = new Set(prev); + if (newSet.has(label)) { + newSet.delete(label); + } else { + newSet.add(label); + } + return newSet; + }); + }; useEffect(() => { if (isCollapsed) { @@ -159,6 +219,79 @@ function DashboardLayoutContent({ }; }, [isResizing, setSidebarWidth]); + const renderMenuItem = (item: MenuItem) => { + // Filtrer les items admin + if (item.adminOnly && user?.role !== "admin") return null; + + // Item simple sans enfants + if (!item.children) { + const isActive = location === item.path; + return ( + + item.path && setLocation(item.path)} + tooltip={item.label} + className="h-10 transition-all font-normal" + > + + {item.label} + + + ); + } + + // Item avec enfants (accordéon) + const isOpen = openSections.has(item.label); + const hasActiveChild = item.children.some(child => child.path === location); + + return ( + toggleSection(item.label)} + className="group/collapsible" + > + + + + + {item.label} + + + + + + {item.children + .filter(child => !child.adminOnly || user?.role === "admin") + .map(child => { + const isActive = location === child.path; + return ( + + child.path && setLocation(child.path)} + className="h-9" + > + + {child.label} + + + ); + })} + + + + + ); + }; + return ( <>
@@ -188,24 +321,7 @@ function DashboardLayoutContent({ - {menuItems.filter(item => !item.adminOnly || user?.role === "admin").map(item => { - const isActive = location === item.path; - return ( - - setLocation(item.path)} - tooltip={item.label} - className={`h-10 transition-all font-normal`} - > - - {item.label} - - - ); - })} + {menuStructure.map(renderMenuItem)} @@ -258,7 +374,7 @@ function DashboardLayoutContent({
- {activeMenuItem?.label ?? "Menu"} + {findActiveLabel()}
diff --git a/todo.md b/todo.md index 9a33454..9cab362 100644 --- a/todo.md +++ b/todo.md @@ -405,4 +405,15 @@ - [x] Appliquer les badges de statut et animations - [x] Améliorer la typographie et les espacements - [x] Tester localement les interfaces modernisées +- [x] Déployer sur le VPS + +## Menu latéral accordéon +- [x] Modifier DashboardLayout pour ajouter un menu accordéon +- [x] Créer la structure avec 4 sections : Tableau de bord, Facturation, Configuration, Traçabilité +- [x] Implémenter l'accordéon pour les sections Facturation, Configuration, Traçabilité +- [x] Ajouter les icônes pour chaque section et sous-menu +- [x] Configurer "Tableau de bord" comme section ouverte par défaut +- [x] Créer les pages manquantes (Factures BAP, Automatismes, Utilisateurs, Historiques) +- [x] Mettre à jour App.tsx avec les nouvelles routes +- [ ] Tester la navigation et le comportement de l'accordéon - [ ] Déployer sur le VPS