198 lines
11 KiB
TypeScript
198 lines
11 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { NavLink, useLocation } from 'react-router-dom';
|
|
import { LayoutDashboard, Search, History, Boxes, Store, Users, TrendingUp, LogOut, Wallet, ShoppingCart, Package, BarChart3, Settings, Printer, Scroll, ChevronDown, ChevronRight, Calculator } from 'lucide-react';
|
|
|
|
import { useCRM } from '../context/CRMContext';
|
|
import clsx from 'clsx';
|
|
|
|
interface SidebarProps {
|
|
isOpen?: boolean;
|
|
onClose?: () => void;
|
|
}
|
|
|
|
type NavItem = {
|
|
to?: string;
|
|
icon: React.ElementType;
|
|
label: string;
|
|
children?: { to: string; icon: React.ElementType; label: string }[];
|
|
};
|
|
|
|
const Sidebar: React.FC<SidebarProps> = ({ isOpen = false, onClose }) => {
|
|
const { user, isAdmin, signOut } = useCRM();
|
|
const currentUser = user;
|
|
const location = useLocation();
|
|
|
|
// State to track expanded modules
|
|
const [expandedModules, setExpandedModules] = useState<Record<string, boolean>>({
|
|
'Impressão 3D': true // Default open for visibility
|
|
});
|
|
|
|
const toggleModule = (label: string) => {
|
|
setExpandedModules(prev => ({ ...prev, [label]: !prev[label] }));
|
|
};
|
|
|
|
const navItems: NavItem[] = [
|
|
{ to: '/', icon: LayoutDashboard, label: 'Dashboard' },
|
|
|
|
// Módulo Impressão 3D
|
|
{
|
|
icon: Printer,
|
|
label: 'Impressão 3D',
|
|
children: [
|
|
{ to: '/printing/calculator', icon: Calculator, label: 'Calculadora' },
|
|
{ to: '/printing/filaments', icon: Scroll, label: 'Filamentos' },
|
|
{ to: '/printing/printers', icon: Settings, label: 'Impressoras' },
|
|
]
|
|
},
|
|
|
|
{ to: '/sales', icon: ShoppingCart, label: 'Vendas' },
|
|
{ to: '/sourcing', icon: Search, label: 'Sourcing' },
|
|
{ to: '/products', icon: Package, label: 'Produtos' },
|
|
{ to: '/orders', icon: History, label: 'Pedidos' },
|
|
{ to: '/financial', icon: Wallet, label: 'Financeiro' },
|
|
{ to: '/customers', icon: Users, label: 'Clientes' },
|
|
{ to: '/inventory', icon: Boxes, label: 'Estoque' },
|
|
{ to: '/suppliers', icon: Store, label: 'Fornecedores' },
|
|
{ to: '/reports', icon: BarChart3, label: 'Relatórios' },
|
|
{ to: '/users', icon: Users, label: 'Usuários' },
|
|
{ to: '/settings', icon: Settings, label: 'Configurações' }
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{/* Mobile Overlay */}
|
|
{isOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 md:hidden"
|
|
onClick={onClose}
|
|
/>
|
|
)}
|
|
|
|
<aside className={clsx(
|
|
"fixed inset-y-0 left-0 z-50 w-64 bg-card/95 backdrop-blur-xl border-r border-border flex flex-col justify-between transition-transform duration-300 md:translate-x-0 md:relative md:bg-card/80",
|
|
isOpen ? "translate-x-0" : "-translate-x-full"
|
|
)}>
|
|
<div>
|
|
{/* Logo Area */}
|
|
<div className="h-20 flex items-center justify-between px-6 md:justify-start md:px-8 border-b border-border">
|
|
<div className="flex items-center">
|
|
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg shadow-indigo-500/20">
|
|
<span className="text-white font-bold text-xl">P</span>
|
|
</div>
|
|
<span className="block ml-3 font-bold text-lg bg-clip-text text-transparent bg-gradient-to-r from-white to-slate-400">
|
|
Paraguai
|
|
</span>
|
|
</div>
|
|
{/* Close button for mobile only */}
|
|
<button onClick={onClose} className="md:hidden text-muted-foreground hover:text-foreground">
|
|
<span className="sr-only">Fechar</span>
|
|
{/* Using a simple X approach manually or just relying on overlay */}
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18" /><path d="m6 6 18 18" /></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* NAV */}
|
|
<nav className="flex-grow px-4 space-y-1 overflow-y-auto pt-4 scrollbar-thin scrollbar-thumb-muted-foreground/20">
|
|
<div className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest px-4 mb-3 opacity-50">Menu</div>
|
|
|
|
{navItems.filter(item => item.label !== 'Usuários' || isAdmin).map((item) => {
|
|
// Render Module with Children
|
|
if (item.children) {
|
|
const isExpanded = expandedModules[item.label];
|
|
const isActiveChild = item.children.some(child => child.to === location.pathname);
|
|
|
|
return (
|
|
<div key={item.label} className="mb-1">
|
|
<button
|
|
onClick={() => toggleModule(item.label)}
|
|
className={clsx(
|
|
"w-full group flex items-center justify-between px-4 py-3 rounded-xl text-sm font-medium transition-all duration-200 outline-none select-none",
|
|
(isExpanded || isActiveChild)
|
|
? 'text-foreground bg-secondary/30'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-secondary/50'
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<item.icon size={18} className={clsx("transition-transform group-hover:scale-110", (isExpanded || isActiveChild) ? "text-indigo-400" : "text-muted-foreground")} />
|
|
<span>{item.label}</span>
|
|
</div>
|
|
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
</button>
|
|
|
|
{/* Submenu */}
|
|
<div className={clsx(
|
|
"overflow-hidden transition-all duration-300 ease-in-out space-y-1",
|
|
isExpanded ? "max-h-48 opacity-100 mt-1" : "max-h-0 opacity-0"
|
|
)}>
|
|
{item.children.map(child => (
|
|
<NavLink
|
|
key={child.to}
|
|
to={child.to}
|
|
onClick={onClose}
|
|
className={({ isActive }) => clsx(
|
|
"flex items-center gap-3 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 outline-none ml-4 pl-9 relative",
|
|
isActive
|
|
? 'text-indigo-400 bg-indigo-500/10'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-white/5'
|
|
)}
|
|
>
|
|
{({ isActive }) => (
|
|
<>
|
|
{/* Connector Line */}
|
|
<div className="absolute left-4 top-1/2 -translate-y-1/2 w-3 h-[1px] bg-border opacity-50"></div>
|
|
<span>{child.label}</span>
|
|
{isActive && <div className="ml-auto w-1.5 h-1.5 rounded-full bg-indigo-400 shadow-[0_0_8px_rgba(99,102,241,0.5)]"></div>}
|
|
</>
|
|
)}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Render Regular Item
|
|
return (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to!}
|
|
onClick={onClose}
|
|
className={({ isActive }) => clsx(
|
|
"group flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-200 outline-none",
|
|
isActive
|
|
? 'bg-secondary text-foreground shadow-sm'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-secondary/50'
|
|
)}
|
|
>
|
|
{({ isActive }) => (
|
|
<>
|
|
<item.icon size={18} className={clsx("transition-transform group-hover:scale-110", isActive ? "text-foreground" : "text-muted-foreground")} />
|
|
<span>{item.label}</span>
|
|
{isActive && <div className="ml-auto w-1.5 h-1.5 rounded-full bg-foreground shadow-[0_0_8px_rgba(255,255,255,0.5)]"></div>}
|
|
</>
|
|
)}
|
|
</NavLink>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
{/* USER FOOTER */}
|
|
<div className="p-6 border-t border-border mt-auto bg-card/50">
|
|
<div onClick={signOut} className="flex items-center gap-3 p-3 rounded-2xl bg-secondary/30 border border-border/50 hover:bg-secondary/50 transition-colors cursor-pointer group">
|
|
<div className="w-9 h-9 rounded-full overflow-hidden border border-border ring-2 ring-transparent group-hover:ring-border/50 transition-all">
|
|
<img src={currentUser?.avatar || "https://api.dicebear.com/7.x/avataaars/svg?seed=Admin"} alt="User" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-bold text-foreground truncate">{currentUser?.name || "Admin"}</p>
|
|
<p className="text-[10px] text-muted-foreground truncate">Administrador</p>
|
|
</div>
|
|
<LogOut size={14} className="text-muted-foreground group-hover:text-destructive transition-colors" />
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Sidebar;
|