import { useAuth } from "@/_core/hooks/useAuth"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, 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, 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"; type MenuItem = { icon: any; label: string; path?: string; adminOnly?: boolean; children?: MenuItem[]; color?: string; // Couleur de la section }; const menuStructure: MenuItem[] = [ { icon: LayoutDashboard, label: "Tableau de bord", path: "/dashboard", color: "from-blue-500 to-cyan-500", }, { icon: Receipt, label: "Facturation", color: "from-purple-500 to-pink-500", 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", color: "from-orange-500 to-amber-500", 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é", color: "from-green-500 to-emerald-500", children: [ { icon: History, label: "Historiques", path: "/history" }, ], }, ]; const SIDEBAR_WIDTH_KEY = "sidebar-width"; const DEFAULT_WIDTH = 280; const MIN_WIDTH = 200; const MAX_WIDTH = 480; export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { const [sidebarWidth, setSidebarWidth] = useState(() => { const saved = localStorage.getItem(SIDEBAR_WIDTH_KEY); return saved ? parseInt(saved, 10) : DEFAULT_WIDTH; }); const { loading, user } = useAuth(); useEffect(() => { localStorage.setItem(SIDEBAR_WIDTH_KEY, sidebarWidth.toString()); }, [sidebarWidth]); if (loading) { return } if (!user) { window.location.href = "/login"; return ; } return ( {children} ); } type DashboardLayoutContentProps = { children: React.ReactNode; setSidebarWidth: (width: number) => void; }; function DashboardLayoutContent({ children, setSidebarWidth, }: DashboardLayoutContentProps) { const { user, logout } = useAuth(); const [location, setLocation] = useLocation(); const { state, toggleSidebar } = useSidebar(); const isCollapsed = state === "collapsed"; const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); const isMobile = useIsMobile(); // État pour la section ouverte (une seule à la fois, Tableau de bord par défaut) const [openSection, setOpenSection] = useState("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) => { // Si on clique sur la section déjà ouverte, on la ferme // Sinon, on ouvre la nouvelle section (et ferme automatiquement l'ancienne) setOpenSection(prev => prev === label ? null : label); }; // Ouvrir automatiquement la section parent quand on navigue vers une page enfant useEffect(() => { for (const section of menuStructure) { if (section.children) { const hasActiveChild = section.children.some(child => child.path === location); if (hasActiveChild && openSection !== section.label) { setOpenSection(section.label); break; } } } }, [location]); useEffect(() => { if (isCollapsed) { setIsResizing(false); } }, [isCollapsed]); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isResizing) return; const sidebarLeft = sidebarRef.current?.getBoundingClientRect().left ?? 0; const newWidth = e.clientX - sidebarLeft; if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) { setSidebarWidth(newWidth); } }; const handleMouseUp = () => { setIsResizing(false); }; if (isResizing) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; } return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.cursor = ""; document.body.style.userSelect = ""; }; }, [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-12 transition-all font-medium rounded-xl ${ isActive ? `bg-gradient-to-r ${item.color} text-white shadow-lg` : "hover:bg-accent/50" }`} > {item.label} ); } // Item avec enfants (accordéon) const isOpen = openSection === item.label; const hasActiveChild = item.children.some(child => child.path === location); return ( toggleSection(item.label)} className="group/collapsible mb-2" > {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-10 rounded-lg transition-all ${ isActive ? "bg-accent text-accent-foreground font-medium shadow-sm" : "hover:bg-accent/50" }`} > {child.label} ); })} ); }; return ( <> {!isCollapsed ? ( Navigation ) : null} {menuStructure.map(renderMenuItem)} {user?.name?.charAt(0).toUpperCase()} {user?.name || "-"} {user?.email || "-"} Sign out { if (isCollapsed) return; setIsResizing(true); }} style={{ zIndex: 50 }} /> {isMobile && ( {findActiveLabel()} )} {children} > ); }
{user?.name || "-"}
{user?.email || "-"}