refactor: sidebar modules
This commit is contained in:
parent
100b397ede
commit
d90d1d61d3
1 changed files with 114 additions and 29 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { LayoutDashboard, Search, History, Boxes, Store, Users, TrendingUp, LogOut, Wallet, ShoppingCart, Package, BarChart3, Settings, Printer, Scroll } from 'lucide-react';
|
||||
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';
|
||||
|
|
@ -10,15 +10,41 @@ interface SidebarProps {
|
|||
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();
|
||||
|
||||
const navItems = [
|
||||
// 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' },
|
||||
{ to: '/printing/calculator', icon: Printer, label: 'Calc. 3D' },
|
||||
{ to: '/printing/filaments', icon: Scroll, label: 'Filamentos' },
|
||||
{ to: '/printing/printers', icon: Settings, label: 'Impressoras 3D' },
|
||||
|
||||
// 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' },
|
||||
|
|
@ -67,29 +93,88 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen = false, onClose }) => {
|
|||
</div>
|
||||
|
||||
{/* NAV */}
|
||||
<nav className="flex-grow px-4 space-y-1 overflow-y-auto pt-4">
|
||||
<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) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
onClick={onClose} // Auto close on mobile nav click
|
||||
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>
|
||||
))}
|
||||
|
||||
{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 */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue