Auto-deploy: 2026-01-13 13:35:50 | 1 arquivo(s) alterado(s)
This commit is contained in:
parent
c10d7b23e6
commit
16833feea0
|
|
@ -1,146 +1,220 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Search,
|
||||||
|
ChevronDown,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
ArrowDownRight,
|
ArrowDownRight,
|
||||||
ArrowRightLeft,
|
ArrowRightLeft,
|
||||||
Settings,
|
Settings,
|
||||||
ChevronLeft,
|
ShieldCheck,
|
||||||
ChevronRight,
|
|
||||||
Search,
|
|
||||||
Zap,
|
Zap,
|
||||||
User,
|
Lock,
|
||||||
LogOut,
|
LogOut
|
||||||
HelpCircle
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import logo from '@/assets/Img/Util/iT_Guys/logo1.png';
|
||||||
|
|
||||||
|
// Reusando os estilos do Sidebar original (Reproduced)
|
||||||
|
import '@/features/layout/components/Sidebar/Sidebar.css';
|
||||||
|
|
||||||
|
const MENU_ITEMS = [
|
||||||
|
{
|
||||||
|
id: 'dashboard',
|
||||||
|
label: 'Dashboard',
|
||||||
|
icon: LayoutDashboard,
|
||||||
|
category: 'Geral'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'financeiro',
|
||||||
|
label: 'Financeiro',
|
||||||
|
icon: ShieldCheck,
|
||||||
|
category: 'Financeiro',
|
||||||
|
children: [
|
||||||
|
{ id: 'entradas', label: 'Receitas (Entradas)', icon: ArrowUpRight, pathId: 'entradas' },
|
||||||
|
{ id: 'saidas', label: 'Despesas (Saídas)', icon: ArrowDownRight, pathId: 'saidas' },
|
||||||
|
{ id: 'conciliacao', label: 'Conciliação Bancária', icon: ArrowRightLeft, pathId: 'conciliacao' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'configuracoes',
|
||||||
|
label: 'Ajustes',
|
||||||
|
icon: Settings,
|
||||||
|
category: 'Sistema',
|
||||||
|
pathId: 'config'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WorkspaceSidebar - Versão "Reproduced" com alta fidelidade visual (Angular Style)
|
||||||
|
* Implementa a logo oficial iTGUYS e acentos em Ouro (#f1c232).
|
||||||
|
*/
|
||||||
export const WorkspaceSidebar = ({ activeScreen, onScreenChange }) => {
|
export const WorkspaceSidebar = ({ activeScreen, onScreenChange }) => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [expandedItems, setExpandedItems] = useState({ 'financeiro': true });
|
||||||
|
|
||||||
const menuItems = [
|
const toggleSidebar = () => setIsCollapsed(!isCollapsed);
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, category: 'Geral' },
|
|
||||||
{ id: 'entradas', label: 'Receitas', icon: ArrowUpRight, category: 'Financeiro' },
|
|
||||||
{ id: 'saidas', label: 'Despesas', icon: ArrowDownRight, category: 'Financeiro' },
|
|
||||||
{ id: 'conciliacao', label: 'Conciliação', icon: ArrowRightLeft, category: 'Financeiro' },
|
|
||||||
{ id: 'config', label: 'Ajustes', icon: Settings, category: 'Sistema' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const filteredItems = menuItems.filter(item =>
|
const toggleExpand = (id) => {
|
||||||
item.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
setExpandedItems(prev => ({
|
||||||
item.category.toLowerCase().includes(searchTerm.toLowerCase())
|
...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 MenuItem = ({ item, isSub = false }) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
const isExpanded = expandedItems[item.id];
|
||||||
|
|
||||||
|
// Identificar se está ativo
|
||||||
|
const isActive = activeScreen === (item.pathId || item.id) ||
|
||||||
|
(hasChildren && item.children.some(c => activeScreen === (c.pathId || c.id)));
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
isSub ? "sb-sublink" : "sb-link",
|
||||||
|
isActive && !hasChildren && "active",
|
||||||
|
item.disabled && "sb-locked"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.disabled) return;
|
||||||
|
if (hasChildren) {
|
||||||
|
toggleExpand(item.id);
|
||||||
|
} else {
|
||||||
|
onScreenChange(item.pathId || item.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon size={isSub ? 16 : 20} className="sb-icon" />
|
||||||
|
{(!isCollapsed || isSub) && <span className="sb-label">{item.label}</span>}
|
||||||
|
|
||||||
|
{hasChildren && !isCollapsed && (
|
||||||
|
<ChevronDown
|
||||||
|
size={14}
|
||||||
|
className={cn("sb-chevron", isExpanded && "expanded")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const categories = [...new Set(menuItems.map(item => item.category))];
|
return (
|
||||||
|
<div className="sb-item">
|
||||||
|
{content}
|
||||||
|
|
||||||
|
{hasChildren && isExpanded && !isCollapsed && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
className="sb-submenu"
|
||||||
|
>
|
||||||
|
{item.children.map(child => (
|
||||||
|
<MenuItem key={child.id} item={child} isSub />
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn(
|
<aside
|
||||||
"bg-[var(--workspace-sec-5-light)] dark:bg-[#0d253a] text-white flex flex-col transition-all duration-500 ease-in-out z-50 shadow-2xl relative",
|
className={cn("sb-container relative h-full top-auto left-auto border-none rounded-none bg-zinc-950", isCollapsed && "collapsed")}
|
||||||
collapsed ? "w-20" : "w-72"
|
style={{ boxShadow: '10px 0 30px rgba(0,0,0,0.5)' }}
|
||||||
)}>
|
|
||||||
{/* Botão de Toggle Flutuante */}
|
|
||||||
<button
|
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
|
||||||
className="absolute -right-3 top-10 w-6 h-6 bg-[var(--workspace-sec-2-light)] rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform active:scale-95"
|
|
||||||
>
|
>
|
||||||
{collapsed ? <ChevronRight size={14} className="text-white" /> : <ChevronLeft size={14} className="text-white" />}
|
{/* Subtle Top Glow (Angular Style) */}
|
||||||
</button>
|
<div className="absolute top-0 left-0 right-0 h-[2px] bg-gradient-to-r from-transparent via-[#f1c232] to-transparent opacity-40" />
|
||||||
|
|
||||||
{/* Brand / Logo */}
|
{/* Header com Toggle */}
|
||||||
<div className="p-8 flex items-center gap-3">
|
<div className="sb-toggle-container">
|
||||||
<div className="w-10 h-10 bg-gradient-to-br from-[#22bb6c] to-[#22c0a3] rounded-2xl flex items-center justify-center shadow-lg shadow-emerald-500/20 group-hover:rotate-12 transition-transform">
|
<button className="sb-toggle-btn" onClick={toggleSidebar}>
|
||||||
<Zap size={24} className="text-white fill-white" />
|
{isCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
|
||||||
<div className="flex flex-col">
|
{/* Logo Area */}
|
||||||
<span className="text-xl font-black tracking-tighter leading-none italic">iTGUYS</span>
|
<div className={cn("px-6 mb-8 mt-2 transition-all", isCollapsed ? "flex justify-center" : "")}>
|
||||||
<span className="text-[10px] uppercase tracking-[0.3em] font-bold text-white/40">Workspace</span>
|
{!isCollapsed ? (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<img src={logo} alt="iTGUYS" className="h-12 w-auto mb-2 drop-shadow-[0_0_15px_rgba(241,194,50,0.2)]" />
|
||||||
|
<div className="h-[1px] w-12 bg-[#f1c232]/30 mt-1" />
|
||||||
|
<span className="text-[10px] uppercase font-black tracking-[0.4em] text-[#f1c232]/40 mt-2 italic">Workspace</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-10 h-10 bg-[#f1c232] rounded-xl flex items-center justify-center shadow-lg shadow-[#f1c232]/20">
|
||||||
|
<Zap size={22} className="text-[#1a1a1a] fill-[#1a1a1a]" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Busca */}
|
{/* Search */}
|
||||||
{!collapsed && (
|
<div className="sb-search-wrapper">
|
||||||
<div className="px-6 mb-6">
|
<div className="relative">
|
||||||
<div className="relative group">
|
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-white/30 group-focus-within:text-[var(--workspace-sec-2-light)] transition-colors" size={16} />
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar função..."
|
className="sb-search-input"
|
||||||
|
placeholder="Buscar..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl py-2.5 pl-10 pr-4 text-xs focus:ring-2 focus:ring-[var(--workspace-sec-2-light)] focus:bg-white/10 outline-none transition-all placeholder:text-white/20"
|
|
||||||
/>
|
/>
|
||||||
|
<Search size={14} className="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-600" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Menu scrollable */}
|
{/* Menu scrollable */}
|
||||||
<nav className="flex-1 overflow-y-auto px-4 space-y-8 custom-scrollbar pb-8">
|
<nav className="sb-menu-content custom-scrollbar">
|
||||||
{categories.map(category => {
|
{filteredItems.map(item => (
|
||||||
const items = filteredItems.filter(i => i.category === category);
|
<MenuItem key={item.id} item={item} />
|
||||||
if (items.length === 0) return null;
|
))}
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={category} className="space-y-2">
|
|
||||||
{!collapsed && (
|
|
||||||
<h3 className="px-4 text-[10px] font-black uppercase tracking-[0.2em] text-white/20">
|
|
||||||
{category}
|
|
||||||
</h3>
|
|
||||||
)}
|
|
||||||
<div className="space-y-1">
|
|
||||||
{items.map(item => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = activeScreen === item.id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => onScreenChange(item.id)}
|
|
||||||
className={cn(
|
|
||||||
"w-full flex items-center gap-4 px-4 py-3.5 rounded-2xl transition-all relative group",
|
|
||||||
isActive
|
|
||||||
? "bg-white/10 text-white shadow-inner"
|
|
||||||
: "text-white/50 hover:text-white hover:bg-white/5"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon size={20} className={cn(
|
|
||||||
"transition-transform group-hover:scale-110",
|
|
||||||
isActive ? "text-[var(--workspace-sec-2-light)]" : ""
|
|
||||||
)} />
|
|
||||||
{!collapsed && <span className="font-bold text-sm tracking-tight">{item.label}</span>}
|
|
||||||
|
|
||||||
{isActive && (
|
|
||||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-[var(--workspace-sec-2-light)] rounded-r-full shadow-[0_0_15px_var(--workspace-sec-2-light)]" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Footer / User Profile */}
|
{/* Footer / User Profile */}
|
||||||
<div className="p-4 border-t border-white/5 bg-black/10">
|
<footer className="sb-footer border-t border-white/5 bg-zinc-900/50">
|
||||||
<div className={cn(
|
<div className={cn("sb-brand", isCollapsed && "justify-center mb-0")}>
|
||||||
"flex items-center gap-3 p-3 rounded-2xl bg-white/5 hover:bg-white/10 transition-colors cursor-pointer group",
|
<div className="sb-brand-logo bg-[#f1c232]">
|
||||||
collapsed && "justify-center"
|
<Zap size={20} className="text-[#1a1a1a]" />
|
||||||
)}>
|
|
||||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-slate-400 to-slate-600 flex items-center justify-center text-sm font-black text-white shadow-lg group-hover:scale-105 transition-transform">
|
|
||||||
DA
|
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
{!isCollapsed && (
|
||||||
<div className="flex-1 min-w-0">
|
<div className="sb-brand-info">
|
||||||
<p className="text-xs font-bold text-white truncate">Daivid Alves</p>
|
<span className="sb-brand-name" style={{ color: '#f1c232' }}>iTGUYS</span>
|
||||||
<p className="text-[10px] text-white/40 truncate font-medium uppercase tracking-widest">Master Admin</p>
|
<span className="sb-app-name text-zinc-500">Master Core v4</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!collapsed && <LogOut size={16} className="text-white/20 hover:text-red-400 cursor-pointer" />}
|
</div>
|
||||||
|
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="sb-legal border-zinc-800">
|
||||||
|
<div className="sb-legal-item">
|
||||||
|
<ShieldCheck size={12} className="text-[#f1c232]" />
|
||||||
|
<span className="text-zinc-600 font-bold uppercase tracking-tighter">Conexão Segura</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={cn("sb-version text-zinc-700", isCollapsed && "justify-center")}>
|
||||||
|
<Lock size={12} className="text-zinc-800" />
|
||||||
|
{!isCollapsed && <span>Licença Corporativa</span>}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue