Files
advx/src/components/layouts/MainLayout.tsx
T
2025-04-11 21:06:00 -03:00

383 lines
15 KiB
TypeScript

import React, { useState, Fragment, useEffect, useRef } from "react";
import { Outlet, Link, useLocation } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext";
import {
BellIcon,
ChevronDownIcon,
MoonIcon,
SunIcon,
HomeIcon,
ScaleIcon,
UserGroupIcon,
UserIcon,
ClipboardDocumentCheckIcon,
DocumentTextIcon,
DocumentCheckIcon,
ArrowUpTrayIcon,
ShieldCheckIcon,
CurrencyDollarIcon,
CalculatorIcon,
} from "@heroicons/react/24/outline";
import { Menu, Transition } from "@headlessui/react";
import api from "../../services/api";
import { useTheme } from "../../contexts/ThemeContext";
// Importando o ícone para cálculos judiciais
import CalculateIcon from '@mui/icons-material/Calculate';
// Importar ícones específicos para subcategorias de cálculos
import MoneyIcon from '@mui/icons-material/Money';
import ChildCareIcon from '@mui/icons-material/ChildCare';
export const MainLayout: React.FC = () => {
const [isSidebarOpen, setSidebarOpen] = useState(true);
const location = useLocation();
const { user, signOut } = useAuth();
const [userProfile, setUserProfile] = useState<any>(null);
const { isDarkMode, toggleDarkMode } = useTheme();
const [activeSubmenu, setActiveSubmenu] = useState<string | null>(null);
const submenuTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Adicionar efeito para buscar perfil do usuário
useEffect(() => {
const getUserProfile = async () => {
try {
const response = await api.get("/auth/me"); // Removido o /api pois já está no baseURL
console.log("Response data:", response.data); // Debug log
setUserProfile(response.data);
} catch (error) {
console.error("Erro ao buscar perfil:", error);
}
};
getUserProfile();
}, []);
// Console log para debug
useEffect(() => {
console.log("Perfil do usuário:", userProfile);
}, [userProfile]);
const menuItems = [
{ path: "/dashboard", label: "Dashboard", icon: HomeIcon },
{ path: "/processos", label: "Processos", icon: ScaleIcon },
{ path: "/processos-administrativos", label: "Proc.Adm", icon: ScaleIcon },
{
path: null,
label: "Provas",
icon: DocumentCheckIcon,
subitems: [
{
path: '/provas/upload',
label: 'Upload',
roles: [],
icon: ArrowUpTrayIcon
},
{
path: '/provas/admin',
label: 'Admin',
roles: ['ADVOGADO_ADMIN'],
icon: ShieldCheckIcon
}
]
},
{ path: "/clientes", label: "Clientes", icon: UserGroupIcon },
{ path: "/atendimentos", label: "Atendimentos", icon: UserIcon },
{ path: "/tarefas", label: "Tarefas", icon: ClipboardDocumentCheckIcon },
{ path: "/documentos", label: "Documentos", icon: DocumentTextIcon },
{
path: null,
label: "Cálculos",
icon: CalculateIcon,
roles: ['ADVOGADO_ADMIN', 'ADVOGADO', 'ASSISTENTE'],
subitems: [
{
path: '/calculos',
label: 'Dashboard',
roles: ['ADVOGADO_ADMIN', 'ADVOGADO', 'ASSISTENTE'],
icon: HomeIcon
},
{
path: '/calculos/pensao-alimenticia',
label: 'Pensão Alimentícia',
roles: ['ADVOGADO_ADMIN', 'ADVOGADO', 'ASSISTENTE'],
icon: ChildCareIcon
},
{
path: '/calculos/trabalhistas',
label: 'Trabalhistas',
roles: ['ADVOGADO_ADMIN', 'ADVOGADO', 'ASSISTENTE'],
icon: MoneyIcon
}
]
}
];
// Função para verificar se o caminho atual corresponde ao item do menu
const isCurrentPath = (path: string) => {
return location.pathname.startsWith(path);
};
// Função para lidar com hover no menu
const handleMenuMouseEnter = (menuLabel: string) => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
submenuTimeoutRef.current = null;
}
setActiveSubmenu(menuLabel);
// Atualiza a posição vertical do submenu
const menuItem = document.querySelector(`[data-menu-label="${menuLabel}"]`);
if (menuItem) {
const rect = menuItem.getBoundingClientRect();
document.documentElement.style.setProperty('--menu-item-top', `${rect.top}px`);
}
};
// Função para lidar com mouseleave do menu
const handleMenuMouseLeave = () => {
submenuTimeoutRef.current = setTimeout(() => {
setActiveSubmenu(null);
}, 300); // Atraso de 300ms antes de fechar o submenu
};
// Função para lidar com mouseenter no submenu
const handleSubmenuMouseEnter = () => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
submenuTimeoutRef.current = null;
}
};
// Limpar timeout quando o componente é desmontado
useEffect(() => {
return () => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
}
};
}, []);
return (
<div className="flex h-screen bg-gray-100 dark:bg-gray-900">
{/* Sidebar com barra de rolagem */}
<div className="w-20 flex flex-col bg-gradient-to-b from-blue-900 via-blue-800 to-blue-900 dark:from-blue-950 dark:via-blue-900 dark:to-blue-950 text-white">
{/* Logo - fixo no topo */}
<div className="h-16 flex-shrink-0 flex items-center justify-center border-b border-white/10">
<span className="text-xl font-bold">A</span>
</div>
{/* Menu de navegação vertical com scroll */}
<nav className="flex-1 overflow-y-auto">
<div className="py-4">
{menuItems.map((item, index) => (
<div
key={item.path || item.label}
className="px-2 mb-2"
>
{!item.subitems ? (
<Link
to={item.path}
className={`flex flex-col items-center justify-center p-3 rounded-lg transition-colors ${
isCurrentPath(item.path)
? "bg-white/20"
: "hover:bg-white/10"
}`}
title={item.label}
>
<item.icon className="h-6 w-6 text-white opacity-80" />
<span className="text-xs mt-1 text-white/80">{item.label}</span>
</Link>
) : (
<div
className="relative"
data-menu-label={item.label}
onMouseEnter={() => handleMenuMouseEnter(item.label)}
onMouseLeave={handleMenuMouseLeave}
>
<div
className={`flex flex-col items-center justify-center p-3 rounded-lg transition-colors hover:bg-white/10 cursor-pointer ${
item.subitems.some(subitem => isCurrentPath(subitem.path))
? "bg-white/20"
: ""
}`}
title={item.label}
>
<item.icon className="h-6 w-6 text-white opacity-80" />
<span className="text-xs mt-1 text-white/80">{item.label}</span>
</div>
</div>
)}
</div>
))}
</div>
</nav>
{/* Submenus renderizados fora da estrutura do menu principal */}
{menuItems
.filter(item => item.subitems)
.map(item => (
<div
key={`submenu-${item.label}`}
className={`fixed left-20 z-50 ${activeSubmenu === item.label ? 'block' : 'hidden'}`}
style={{ top: 'var(--menu-item-top, 0)' }}
onMouseEnter={handleSubmenuMouseEnter}
onMouseLeave={handleMenuMouseLeave}
>
<div
className="transition-all duration-200 ml-2"
style={{ transformOrigin: 'left center' }}
>
<div className="bg-blue-800/95 backdrop-blur-sm rounded-lg py-2 px-1 shadow-xl w-[140px] border border-blue-700/30">
{item.subitems
.filter(subitem =>
!subitem.roles?.length ||
(userProfile?.perfil && subitem.roles.includes(userProfile.perfil))
)
.map((subitem) => (
<Link
key={subitem.path}
to={subitem.path}
className={`block px-3 py-2 hover:bg-blue-700/50 rounded-lg transition-colors ${
isCurrentPath(subitem.path) ? "bg-blue-700/70" : ""
}`}
>
<div className="flex items-center space-x-2">
<subitem.icon className="h-5 w-5 text-white opacity-80" />
<span className="text-sm text-white/90">{subitem.label}</span>
</div>
</Link>
))}
</div>
</div>
</div>
))}
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="h-16 bg-white dark:bg-gray-800 shadow-sm">
<div className="h-full px-4 flex items-center justify-between">
<h1 className="text-xl font-semibold text-gray-800 dark:text-gray-200">
{menuItems.find((item) => item.path === location.pathname)
?.label || "Dashboard"}
</h1>
<div className="flex items-center space-x-4">
{/* Adicionar botão de toggle do tema antes do menu do usuário */}
<button
onClick={toggleDarkMode}
className="p-2 rounded-lg text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-gray-400"
title={isDarkMode ? "Modo claro" : "Modo escuro"}
>
{isDarkMode ? (
<SunIcon className="h-5 w-5" />
) : (
<MoonIcon className="h-5 w-5" />
)}
</button>
{/* Notificações */}
<button className="text-gray-600 hover:text-gray-800 relative">
<BellIcon className="h-6 w-6" />
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center">
3
</span>
</button>
{/* Menu do usuário */}
<Menu as="div" className="relative">
<Menu.Button className="flex items-center space-x-3 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg p-2 transition-colors">
<img
src={`https://ui-avatars.com/api/?name=${user?.nome || "User"}&background=0D8ABC&color=fff`}
alt={user?.nome}
className="w-8 h-8 rounded-full"
/>
<div className="text-left hidden sm:block">
<div className="text-sm font-medium text-gray-700 dark:text-gray-200">
{user?.nome}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">{user?.cargo}</div>
</div>
<ChevronDownIcon className="w-4 h-4 text-gray-500 dark:text-gray-400" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-2 w-48 rounded-md bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<Link
to="/perfil"
className={`${
active ? "bg-gray-100 dark:bg-gray-700" : ""
} block px-4 py-2 text-sm text-gray-700 dark:text-gray-200`}
>
Meu Perfil
</Link>
)}
</Menu.Item>
{/* Novo item - visível apenas para ADMIN */}
{userProfile?.perfil === "ADVOGADO_ADMIN" && (
<Menu.Item>
{({ active }) => (
<Link
to="/usuarios"
className={`${
active ? "bg-gray-100 dark:bg-gray-700" : ""
} block px-4 py-2 text-sm text-gray-700 dark:text-gray-200`}
>
Gerenciar Usuários
</Link>
)}
</Menu.Item>
)}
<Menu.Item>
{({ active }) => (
<a
href="#configuracoes"
className={`${
active ? "bg-gray-100 dark:bg-gray-700" : ""
} block px-4 py-2 text-sm text-gray-700 dark:text-gray-200`}
>
Configurações
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
onClick={signOut}
className={`${
active ? "bg-gray-100 dark:bg-gray-700" : ""
} block w-full text-left px-4 py-2 text-sm text-red-600 dark:text-red-400`}
>
Sair
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</header>
{/* Content */}
<main className="flex-1 overflow-auto p-6 bg-gray-100 dark:bg-gray-900">
<Outlet />
</main>
</div>
</div>
);
};