arbritage/layouts/Sidebar.tsx

199 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2026-01-27 18:44:44 +00:00
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';
2026-01-26 14:20:25 +00:00
import { useCRM } from '../context/CRMContext';
import clsx from 'clsx';
interface SidebarProps {
isOpen?: boolean;
onClose?: () => void;
}
2026-01-27 18:44:44 +00:00
type NavItem = {
to?: string;
icon: React.ElementType;
label: string;
children?: { to: string; icon: React.ElementType; label: string }[];
};
2026-01-26 14:20:25 +00:00
const Sidebar: React.FC<SidebarProps> = ({ isOpen = false, onClose }) => {
const { user, isAdmin, signOut } = useCRM();
const currentUser = user;
2026-01-27 18:44:44 +00:00
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] }));
};
2026-01-26 14:20:25 +00:00
2026-01-27 18:44:44 +00:00
const navItems: NavItem[] = [
2026-01-26 14:20:25 +00:00
{ to: '/', icon: LayoutDashboard, label: 'Dashboard' },
2026-01-27 18:44:44 +00:00
// 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' },
]
},
2026-01-26 14:20:25 +00:00
{ 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 */}
2026-01-27 18:44:44 +00:00
<nav className="flex-grow px-4 space-y-1 overflow-y-auto pt-4 scrollbar-thin scrollbar-thumb-muted-foreground/20">
2026-01-26 14:20:25 +00:00
<div className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest px-4 mb-3 opacity-50">Menu</div>
2026-01-27 18:44:44 +00:00
{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>
);
})}
2026-01-26 14:20:25 +00:00
</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;