'use client'; import React, { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react'; import { useTheme } from 'next-themes'; import { getUser, User, getToken, saveAuth } from '@/lib/auth'; import { API_ENDPOINTS } from '@/lib/api'; import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, UserCircleIcon, ArrowRightOnRectangleIcon, SunIcon, MoonIcon, Cog6ToothIcon, XMarkIcon, } from '@heroicons/react/24/outline'; export interface MenuItem { id: string; label: string; href: string; icon: any; subItems?: { label: string; href: string; }[]; } interface SidebarRailProps { isExpanded: boolean; onToggle: () => void; menuItems: MenuItem[]; } export const SidebarRail: React.FC = ({ isExpanded, onToggle, menuItems }) => { const pathname = usePathname(); const router = useRouter(); const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); const [user, setUser] = useState(null); const [openSubmenu, setOpenSubmenu] = useState(null); const sidebarRef = useRef(null); useEffect(() => { setMounted(true); const currentUser = getUser(); setUser(currentUser); // Buscar perfil da agência para atualizar logo e nome const fetchProfile = async () => { const token = getToken(); if (!token) return; try { const res = await fetch(API_ENDPOINTS.agencyProfile, { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); if (currentUser) { // Usar localStorage como fallback se API não retornar logo const cachedLogo = localStorage.getItem('agency-logo-url'); const finalLogoUrl = data.logo_url || cachedLogo; const updatedUser = { ...currentUser, company: data.name || currentUser.company, logoUrl: finalLogoUrl }; setUser(updatedUser); saveAuth(token, updatedUser); // Persistir atualização // Atualizar localStorage do logo (preservar se já existe) if (finalLogoUrl) { console.log('📝 Salvando logo no localStorage:', finalLogoUrl); localStorage.setItem('agency-logo-url', finalLogoUrl); window.dispatchEvent(new Event('auth-update')); // Notificar favicon window.dispatchEvent(new Event('branding-update')); // Notificar AgencyBranding } } } } catch (error) { console.error('Error fetching agency profile:', error); } }; fetchProfile(); // Listener para atualizar logo em tempo real após upload // REMOVIDO: Causa loop infinito com o dispatchEvent dentro do fetchProfile // O AgencyBranding já cuida de atualizar o favicon/cores // Se precisar atualizar o sidebar após upload, usar um evento específico 'logo-uploaded' /* const handleBrandingUpdate = () => { console.log('SidebarRail: branding-update event received'); fetchProfile(); // Re-buscar perfil do backend }; window.addEventListener('branding-update', handleBrandingUpdate); return () => { window.removeEventListener('branding-update', handleBrandingUpdate); }; */ }, []); // Fechar submenu ao clicar fora useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (sidebarRef.current && !sidebarRef.current.contains(event.target as Node)) { // Verifica se o submenu aberto corresponde à rota atual // Se estivermos navegando dentro do módulo (ex: CRM), o menu deve permanecer fixo const activeItem = menuItems.find(item => item.id === openSubmenu); const isRouteActive = activeItem && activeItem.subItems?.some(sub => pathname === sub.href || pathname.startsWith(sub.href)); if (!isRouteActive) { setOpenSubmenu(null); } } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [openSubmenu, pathname, menuItems]); // Auto-open submenu if active useEffect(() => { if (isExpanded && pathname) { const activeItem = menuItems.find(item => item.subItems?.some(sub => pathname === sub.href || pathname.startsWith(sub.href)) ); if (activeItem) { setOpenSubmenu(activeItem.id); } } }, [pathname, isExpanded, menuItems]); const handleLogout = () => { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = '/login'; }; const toggleTheme = () => { setTheme(theme === 'dark' ? 'light' : 'dark'); }; // Encontrar o item ativo para renderizar o submenu const activeMenuItem = menuItems.find(item => item.id === openSubmenu); // Lógica de largura do Rail: Se tiver submenu aberto, força recolhimento visual (80px) // Se não, respeita o estado isExpanded const railWidth = isExpanded && !openSubmenu ? 'w-[240px]' : 'w-[80px]'; const showLabels = isExpanded && !openSubmenu; return (
{/* Rail Principal (Ícones + Labels Opcionais) */}
{/* Toggle Button - Floating on the border */} {/* Só mostra o toggle se não tiver submenu aberto, para evitar confusão */} {!openSubmenu && ( )} {/* Header com Logo */}
{user?.logoUrl ? ( {user.company ) : ( (user?.company?.[0] || 'A').toUpperCase() )}
{/* Título com animação */}
{user?.company || 'Aggios'}
{/* Navegação */}
{menuItems.map((item) => ( { if (item.subItems) { // Se já estiver aberto, fecha e previne navegação if (openSubmenu === item.id) { e.preventDefault(); setOpenSubmenu(null); } else { // Se estiver fechado, abre o submenu setOpenSubmenu(item.id); } } else { setOpenSubmenu(null); } }} showLabel={showLabels} hasSubItems={!!item.subItems} isOpen={openSubmenu === item.id} /> ))}
{/* Separador */}
{/* User Menu */}
{mounted && (
{user?.name || 'Usuário'}
)} {!mounted && (
)}
{/* Painel Secundário (Drawer) - Abre ao lado do Rail */}
{activeMenuItem && ( <>

{activeMenuItem.label}

{activeMenuItem.subItems?.map((sub) => ( setOpenSubmenu(null)} // Removido para manter fixo className={` flex items-center gap-2 px-3 py-2.5 rounded-lg text-xs font-medium transition-colors mb-1 ${pathname === sub.href ? 'bg-brand-50 dark:bg-brand-900/10 text-brand-600 dark:text-brand-400' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-zinc-800 hover:text-gray-900 dark:hover:text-white' } `} > {sub.label} ))}
)}
); }; // Subcomponente do Botão interface RailButtonProps { label: string; icon: React.ComponentType<{ className?: string }>; href: string; active: boolean; onClick: (e?: any) => void; showLabel: boolean; hasSubItems?: boolean; isOpen?: boolean; } const RailButton: React.FC = ({ label, icon: Icon, href, active, onClick, showLabel, hasSubItems, isOpen }) => { // Determine styling based on state // Sempre usa Link se tiver href, para garantir navegação correta e prefetching const Wrapper = href ? Link : 'button'; // Desabilitar prefetch para evitar sobrecarga no middleware/backend e loops de redirecionamento const props = href ? { href, onClick, prefetch: false } : { onClick, type: 'button' }; let baseClasses = "flex items-center p-2 rounded-lg transition-all duration-300 group relative overflow-hidden "; if (showLabel) { baseClasses += "w-full justify-start "; } else { baseClasses += "w-10 h-10 justify-center mx-auto "; } // Lógica unificada de ativo const isActiveItem = active || isOpen; if (isActiveItem) { baseClasses += "bg-brand-500 text-white shadow-sm"; } else { // Inactive item baseClasses += "hover:bg-gray-100 dark:hover:bg-zinc-800 hover:text-gray-900 dark:hover:text-white text-gray-600 dark:text-gray-400"; } return ( {/* Ícone */} {/* Texto (Visível apenas se expandido) */}
{label} {hasSubItems && ( )}
{/* Indicador de Ativo (Ponto lateral) - Apenas se recolhido e NÃO tiver gradiente (redundante agora, mas mantido por segurança) */} {active && !hasSubItems && !showLabel && !isActiveItem && (
)} ); };