Auto-deploy: 2026-01-13 12:14:29 | 1 arquivo(s) alterado(s)
This commit is contained in:
parent
4a33cbafce
commit
096dac3e0d
|
|
@ -1,139 +1,229 @@
|
||||||
import React from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
ChevronLeft,
|
||||||
Car,
|
ChevronRight,
|
||||||
Wrench,
|
Search,
|
||||||
BarChart3,
|
ChevronDown,
|
||||||
ClipboardList,
|
LayoutDashboard,
|
||||||
Activity,
|
Car,
|
||||||
Radio,
|
|
||||||
AlertTriangle,
|
|
||||||
Store,
|
|
||||||
Users,
|
Users,
|
||||||
|
Wrench,
|
||||||
|
Activity,
|
||||||
|
Radio,
|
||||||
|
AlertTriangle,
|
||||||
|
Store,
|
||||||
|
ClipboardList,
|
||||||
LogOut,
|
LogOut,
|
||||||
Menu,
|
ShieldAlert,
|
||||||
ChevronLeft
|
Building2,
|
||||||
|
Award,
|
||||||
|
GitBranch,
|
||||||
|
Lock
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAuthContext } from '@/components/shared/AuthProvider';
|
import { useAuthContext } from '@/components/shared/AuthProvider';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import './PrafrotSidebar.css';
|
import './PrafrotSidebar.css';
|
||||||
|
|
||||||
export const PrafrotSidebar = ({ isCollapsed, onToggle }) => {
|
const MENU_ITEMS = [
|
||||||
const { user, logout } = useAuthContext();
|
{
|
||||||
const location = useLocation();
|
id: 'dashboard',
|
||||||
|
label: 'Estatísticas',
|
||||||
|
icon: LayoutDashboard,
|
||||||
|
path: '/plataforma/prafrot/estatisticas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cadastros',
|
||||||
|
label: 'Cadastros',
|
||||||
|
icon: ClipboardList,
|
||||||
|
children: [
|
||||||
|
{ id: 'c-veiculos', label: 'Veículos', path: '/plataforma/prafrot/veiculos', icon: Car },
|
||||||
|
{ id: 'c-dispatcher', label: 'Dispatcher', path: '/plataforma/prafrot/dispatcher', icon: ClipboardList },
|
||||||
|
{ id: 'c-motoristas', label: 'Motoristas', path: '/plataforma/prafrot/motoristas', icon: Users, disabled: true, disabledReason: 'Funcionalidade em manutenção' },
|
||||||
|
{ id: 'c-oficinas', label: 'Oficinas', path: '/plataforma/prafrot/oficinas', icon: Store }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gerencia',
|
||||||
|
label: 'Gerência',
|
||||||
|
icon: Activity,
|
||||||
|
children: [
|
||||||
|
{ id: 'g-monitoramento', label: 'Monitoramento', path: '/plataforma/prafrot/monitoramento', icon: Radio },
|
||||||
|
{ id: 'g-manutencao', label: 'Manutenção', path: '/plataforma/prafrot/manutencao', icon: Wrench },
|
||||||
|
{ id: 'g-sinistros', label: 'Sinistros', path: '/plataforma/prafrot/sinistros', icon: AlertTriangle }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const MENU_GROUPS = [
|
export const PrafrotSidebar = ({ isCollapsed, onToggle }) => {
|
||||||
{
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
label: 'Geral',
|
const [expandedItems, setExpandedItems] = useState({ cadastros: true, gerencia: true });
|
||||||
items: [
|
const location = useLocation();
|
||||||
{ icon: LayoutDashboard, label: 'Estatísticas', path: '/plataforma/prafrot/estatisticas' },
|
const { user, logout } = useAuthContext();
|
||||||
]
|
|
||||||
},
|
const toggleExpand = (id) => {
|
||||||
{
|
setExpandedItems(prev => ({
|
||||||
label: 'Cadastros',
|
...prev,
|
||||||
items: [
|
[id]: !prev[id]
|
||||||
{ icon: Car, label: 'Veículos', path: '/plataforma/prafrot/veiculos' },
|
}));
|
||||||
{ icon: ClipboardList, label: 'Dispatcher', path: '/plataforma/prafrot/dispatcher' },
|
};
|
||||||
{ icon: Users, label: 'Motoristas', path: '/plataforma/prafrot/motoristas', disabled: true },
|
|
||||||
{ icon: Store, label: 'Oficinas', path: '/plataforma/prafrot/oficinas' },
|
const filteredItems = useMemo(() => {
|
||||||
]
|
if (!searchTerm) return MENU_ITEMS;
|
||||||
},
|
|
||||||
{
|
return MENU_ITEMS.filter(item => {
|
||||||
label: 'Gerência',
|
const matchParent = item.label.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
items: [
|
const matchChildren = item.children?.some(child =>
|
||||||
{ icon: Radio, label: 'Monitoramento', path: '/plataforma/prafrot/monitoramento' },
|
child.label.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
{ icon: Wrench, label: 'Manutenção', path: '/plataforma/prafrot/manutencao' },
|
);
|
||||||
{ icon: AlertTriangle, label: 'Sinistros', path: '/plataforma/prafrot/sinistros' },
|
return matchParent || matchChildren;
|
||||||
]
|
});
|
||||||
}
|
}, [searchTerm]);
|
||||||
];
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
window.location.href = '/plataforma/prafrot/login';
|
window.location.href = '/plataforma/prafrot/login';
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavItem = ({ item }) => {
|
const MenuItem = ({ item, isSub = false }) => {
|
||||||
const isActive = location.pathname.startsWith(item.path);
|
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
const isExpanded = expandedItems[item.id];
|
||||||
|
const isActive = location.pathname.startsWith(item.path) || (hasChildren && item.children.some(c => location.pathname.startsWith(c.path)));
|
||||||
|
const isLocked = item.disabled;
|
||||||
|
|
||||||
if (item.disabled) {
|
// Filter sub-items if searching
|
||||||
// Motoristas option is commented out/disabled as requested
|
const subItems = item.children?.filter(child =>
|
||||||
// I'll keep it in the code but won't render or will render disabled
|
!searchTerm || child.label.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
return (
|
);
|
||||||
/*
|
|
||||||
<div className="pfs-item disabled" title="Em breve">
|
const content = (
|
||||||
<Icon size={20} className="pfs-icon" />
|
<div
|
||||||
{!isCollapsed && <span className="pfs-label">{item.label}</span>}
|
className={cn(
|
||||||
</div>
|
isSub ? "pfs-sublink" : "pfs-link",
|
||||||
*/
|
isActive && !hasChildren && "active",
|
||||||
null
|
isLocked && "pfs-locked"
|
||||||
);
|
)}
|
||||||
}
|
onClick={() => {
|
||||||
|
if (isLocked) return;
|
||||||
|
if (hasChildren) toggleExpand(item.id);
|
||||||
|
}}
|
||||||
|
title={isLocked ? item.disabledReason : (isCollapsed ? item.label : '')}
|
||||||
|
>
|
||||||
|
<Icon size={isSub ? 16 : 20} className="pfs-icon" />
|
||||||
|
{(!isCollapsed || isSub) && <span className="pfs-label">{item.label}</span>}
|
||||||
|
|
||||||
|
{hasChildren && !isCollapsed && (
|
||||||
|
<ChevronDown
|
||||||
|
size={14}
|
||||||
|
className={cn("pfs-chevron", isExpanded && "expanded")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLocked && !isCollapsed && (
|
||||||
|
<Lock size={12} className="pfs-lock-icon" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div className="pfs-item-wrapper">
|
||||||
to={item.path}
|
{item.path && !hasChildren && !isLocked ? (
|
||||||
className={cn(
|
<Link to={item.path} style={{ textDecoration: 'none' }}>
|
||||||
"pfs-item",
|
{content}
|
||||||
isActive && "active"
|
</Link>
|
||||||
|
) : content}
|
||||||
|
|
||||||
|
{hasChildren && isExpanded && !isCollapsed && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
className="pfs-submenu"
|
||||||
|
>
|
||||||
|
{subItems.map(child => (
|
||||||
|
<MenuItem key={child.id} item={child} isSub />
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
>
|
</div>
|
||||||
<Icon size={20} className="pfs-icon" strokeWidth={isActive ? 2.5 : 2} />
|
|
||||||
{!isCollapsed && <span className="pfs-label">{item.label}</span>}
|
|
||||||
</Link>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn("pfs-container", isCollapsed && "collapsed")}>
|
<aside className={cn("pfs-container", isCollapsed && "collapsed")}>
|
||||||
{/* Header */}
|
<div className="pfs-toggle-container">
|
||||||
<div className="pfs-header">
|
<button className="pfs-toggle-btn" onClick={onToggle}>
|
||||||
<div className="pfs-logo-box">PF</div>
|
{isCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
||||||
{!isCollapsed && (
|
|
||||||
<div className="pfs-brand-info">
|
|
||||||
<span className="pfs-brand-name">PRA<span>FROTA</span></span>
|
|
||||||
<span className="pfs-brand-sub">Fleet Management</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<button onClick={onToggle} className="pfs-toggle-btn">
|
|
||||||
{isCollapsed ? <Menu size={18} /> : <ChevronLeft size={18} />}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
<div className="pfs-search-wrapper">
|
||||||
<nav className="pfs-nav custom-scrollbar">
|
<div className="relative">
|
||||||
{MENU_GROUPS.map((group, gIdx) => (
|
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" />
|
||||||
<React.Fragment key={gIdx}>
|
<input
|
||||||
{!isCollapsed && <div className="pfs-group-label">{group.label}</div>}
|
type="text"
|
||||||
{group.items.map((item, iIdx) => (
|
className="pfs-search-input"
|
||||||
<NavItem key={iIdx} item={item} />
|
placeholder="Buscar..."
|
||||||
))}
|
value={searchTerm}
|
||||||
</React.Fragment>
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="pfs-nav-content custom-scrollbar">
|
||||||
|
{filteredItems.map(item => (
|
||||||
|
<MenuItem key={item.id} item={item} />
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Footer */}
|
<footer className="pfs-footer">
|
||||||
<div className="pfs-footer">
|
<div className="pfs-brand">
|
||||||
<div className="pfs-user-card">
|
<div className="pfs-brand-logo">
|
||||||
<Avatar className="h-8 w-8 border border-[#2a2a2a]">
|
PF
|
||||||
<AvatarImage src={`https://ui-avatars.com/api/?name=${user?.name || 'User'}&background=10b981&color=141414`} />
|
</div>
|
||||||
<AvatarFallback className="bg-[#10b981] text-[#141414] font-bold text-xs">
|
|
||||||
{(user?.name || 'U').charAt(0)}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div className="pfs-user-info">
|
<div className="pfs-brand-info">
|
||||||
<div className="pfs-user-name truncate">{user?.name || 'Usuário'}</div>
|
<span className="pfs-brand-name">PRA<span>FROTA</span></span>
|
||||||
<button onClick={handleLogout} className="pfs-logout-btn">
|
<span className="pfs-app-sub">Fleet Management</span>
|
||||||
<LogOut size={12} /> Sair
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="pfs-user-section">
|
||||||
|
<div className={cn("pfs-user-card", isCollapsed && "justify-center")}>
|
||||||
|
<Avatar className="h-8 w-8 border border-zinc-800">
|
||||||
|
<AvatarImage src={`https://ui-avatars.com/api/?name=${user?.name || 'User'}&background=10b981&color=141414`} />
|
||||||
|
<AvatarFallback className="bg-emerald-500 text-zinc-950 font-bold text-xs">
|
||||||
|
{(user?.name || 'U').charAt(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="pfs-user-data">
|
||||||
|
<span className="pfs-user-name">{user?.name || 'Usuário'}</span>
|
||||||
|
<button onClick={handleLogout} className="pfs-logout-link">
|
||||||
|
<LogOut size={10} /> Sair
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="pfs-legal">
|
||||||
|
<div className="pfs-legal-item">
|
||||||
|
<Building2 size={10} />
|
||||||
|
<span>CNPJ: 31.882.636/0001-38</span>
|
||||||
|
</div>
|
||||||
|
<div className="pfs-version">
|
||||||
|
<GitBranch size={10} />
|
||||||
|
<span>v1.3.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue