239 lines
6.8 KiB
JavaScript
239 lines
6.8 KiB
JavaScript
import React, { useState, useMemo } from 'react';
|
|
import { Link, useLocation } from 'react-router-dom';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import {
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Search,
|
|
ChevronDown,
|
|
LayoutDashboard,
|
|
Car,
|
|
Users,
|
|
Wrench,
|
|
Activity,
|
|
Package,
|
|
ShoppingCart,
|
|
Settings,
|
|
ClipboardList,
|
|
LogOut,
|
|
Building2,
|
|
Lock,
|
|
Box
|
|
} from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { useAuthContext } from '@/components/shared/AuthProvider';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import './AutoLabSidebar.css';
|
|
|
|
const MENU_ITEMS = [
|
|
{
|
|
id: 'cadastro',
|
|
label: 'Cadastro',
|
|
icon: ClipboardList,
|
|
path: '/plataforma/autolab/cadastro'
|
|
},
|
|
{
|
|
id: 'estoque',
|
|
label: 'Estoque',
|
|
icon: Box,
|
|
path: '/plataforma/autolab/estoque'
|
|
},
|
|
{
|
|
id: 'vendas',
|
|
label: 'Vendas',
|
|
icon: ShoppingCart,
|
|
path: '/plataforma/autolab/vendas'
|
|
},
|
|
{
|
|
id: 'clientes',
|
|
label: 'Clientes',
|
|
icon: Users,
|
|
path: '/plataforma/autolab/clientes'
|
|
},
|
|
{
|
|
id: 'configuracao',
|
|
label: 'Configuração',
|
|
icon: Settings,
|
|
path: '/plataforma/autolab/configuracao'
|
|
}
|
|
];
|
|
|
|
const MenuItem = React.memo(({ item, isSub = false, isCollapsed, expandedItems, toggleExpand, pathname, searchTerm }) => {
|
|
const Icon = item.icon;
|
|
const hasChildren = item.children && item.children.length > 0;
|
|
const isExpanded = expandedItems[item.id];
|
|
const isActive = pathname.startsWith(item.path) || (hasChildren && item.children.some(c => pathname.startsWith(c.path)));
|
|
const isLocked = item.disabled;
|
|
|
|
const subItems = item.children?.filter(child =>
|
|
!searchTerm || child.label.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
const content = (
|
|
<div
|
|
className={cn(
|
|
isSub ? "als-sublink" : "als-link",
|
|
isActive && !hasChildren && "active",
|
|
isLocked && "als-locked"
|
|
)}
|
|
onClick={() => {
|
|
if (isLocked) return;
|
|
if (hasChildren) toggleExpand(item.id);
|
|
}}
|
|
title={isLocked ? item.disabledReason : (isCollapsed ? item.label : '')}
|
|
>
|
|
<Icon size={isSub ? 16 : 20} className="als-icon" />
|
|
{(!isCollapsed || isSub) && <span className="als-label">{item.label}</span>}
|
|
|
|
{hasChildren && !isCollapsed && (
|
|
<ChevronDown
|
|
size={14}
|
|
className={cn("als-chevron", isExpanded && "expanded")}
|
|
/>
|
|
)}
|
|
|
|
{isLocked && !isCollapsed && (
|
|
<Lock size={12} className="als-lock-icon" />
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="als-item-wrapper">
|
|
{item.path && !hasChildren && !isLocked ? (
|
|
<Link to={item.path} style={{ textDecoration: 'none' }}>
|
|
{content}
|
|
</Link>
|
|
) : content}
|
|
|
|
<AnimatePresence>
|
|
{hasChildren && isExpanded && !isCollapsed && (
|
|
<motion.div
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: 'auto' }}
|
|
exit={{ opacity: 0, height: 0 }}
|
|
className="als-submenu"
|
|
>
|
|
{subItems?.map(child => (
|
|
<MenuItem
|
|
key={child.id}
|
|
item={child}
|
|
isSub
|
|
isCollapsed={isCollapsed}
|
|
expandedItems={expandedItems}
|
|
toggleExpand={toggleExpand}
|
|
pathname={pathname}
|
|
searchTerm={searchTerm}
|
|
/>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
MenuItem.displayName = 'MenuItem';
|
|
|
|
export const AutoLabSidebar = ({ isCollapsed, onToggle }) => {
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [expandedItems, setExpandedItems] = useState({});
|
|
const location = useLocation();
|
|
const { user, logout } = useAuthContext();
|
|
|
|
const toggleExpand = React.useCallback((id) => {
|
|
setExpandedItems(prev => ({
|
|
...prev,
|
|
[id]: !prev[id]
|
|
}));
|
|
}, []);
|
|
|
|
const filteredItems = useMemo(() => {
|
|
if (!searchTerm) return MENU_ITEMS;
|
|
|
|
return MENU_ITEMS.filter(item => {
|
|
const matchParent = item.label.toLowerCase().includes(searchTerm.toLowerCase());
|
|
const matchChildren = item.children?.some(child =>
|
|
child.label.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
return matchParent || matchChildren;
|
|
});
|
|
}, [searchTerm]);
|
|
|
|
const handleLogout = () => {
|
|
logout();
|
|
window.location.href = '/plataforma/autolab/login';
|
|
};
|
|
|
|
return (
|
|
<aside className={cn("als-container", isCollapsed && "collapsed")}>
|
|
<div className="als-toggle-container">
|
|
<button className="als-toggle-btn" onClick={onToggle}>
|
|
{isCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="als-search-wrapper">
|
|
<div className="relative">
|
|
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--als-text-muted)]" />
|
|
<input
|
|
type="text"
|
|
className="als-search-input"
|
|
placeholder="Buscar..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<nav className="als-nav-content custom-scrollbar">
|
|
{filteredItems.map(item => (
|
|
<MenuItem
|
|
key={item.id}
|
|
item={item}
|
|
isCollapsed={isCollapsed}
|
|
expandedItems={expandedItems}
|
|
toggleExpand={toggleExpand}
|
|
pathname={location.pathname}
|
|
searchTerm={searchTerm}
|
|
/>
|
|
))}
|
|
</nav>
|
|
|
|
<footer className="als-footer">
|
|
<div className="als-brand">
|
|
<div className="als-brand-logo">
|
|
AL
|
|
</div>
|
|
{!isCollapsed && (
|
|
<div className="als-brand-info">
|
|
<span className="als-brand-name">AUTO<span>LAB</span></span>
|
|
<span className="als-app-sub">Workshop Management</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="als-user-section">
|
|
<div className={cn("als-user-card", isCollapsed && "justify-center")}>
|
|
<Avatar className="h-8 w-8 border border-[var(--als-border)]">
|
|
<AvatarImage src={`https://ui-avatars.com/api/?name=${user?.name || 'User'}&background=1b4332&color=ffffff`} />
|
|
<AvatarFallback className="bg-[#1b4332] text-white font-bold text-xs">
|
|
{(user?.name || 'U').charAt(0)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
{!isCollapsed && (
|
|
<div className="als-user-data">
|
|
<span className="als-user-name">{user?.name || 'Usuário'}</span>
|
|
<button onClick={handleLogout} className="als-logout-link">
|
|
<LogOut size={10} /> Sair
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</footer>
|
|
</aside>
|
|
);
|
|
};
|