Files
finanzas/components/layout/Sidebar.tsx

153 lines
4.8 KiB
TypeScript

'use client';
import {
LayoutDashboard,
Wallet,
CreditCard,
PiggyBank,
Bell,
Lightbulb,
Settings,
TrendingUp,
X,
LogOut,
} from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { usePathname } from 'next/navigation';
import { Logo } from './Logo';
interface SidebarProps {
isOpen: boolean;
onClose: () => void;
unreadAlertsCount?: number;
}
const navigationItems = [
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
{ name: 'Ingresos', href: '/incomes', icon: TrendingUp },
{ name: 'Deudas', href: '/debts', icon: Wallet },
{ name: 'Tarjetas', href: '/cards', icon: CreditCard },
{ name: 'Presupuesto', href: '/budget', icon: PiggyBank },
{ name: 'Servicios', href: '/services', icon: Lightbulb },
{ name: 'Configuración', href: '/settings', icon: Settings },
{ name: 'Alertas', href: '/alerts', icon: Bell, hasBadge: true },
];
export function Sidebar({
isOpen,
onClose,
unreadAlertsCount = 0,
}: SidebarProps) {
const pathname = usePathname();
const router = useRouter();
const isActive = (href: string) => {
if (href === '/') {
return pathname === '/';
}
return pathname.startsWith(href);
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
// Clear local data to avoid leaking to other users
localStorage.removeItem('finanzas-storage');
router.push('/login');
router.refresh();
} catch (e) {
console.error('Logout failed', e);
}
};
return (
<>
{/* Mobile overlay */}
{isOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={onClose}
aria-hidden="true"
/>
)}
{/* Sidebar */}
<aside
className={`
fixed top-0 left-0 z-50 h-full w-64 bg-slate-900 border-r border-slate-800
transform transition-transform duration-300 ease-in-out
lg:translate-x-0 lg:static lg:h-screen
${isOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-slate-800">
<Logo size="md" showText />
<button
onClick={onClose}
className="lg:hidden p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-lg transition-colors"
aria-label="Cerrar menú"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-4 px-3">
<ul className="space-y-1">
{navigationItems.map((item) => {
const active = isActive(item.href);
const Icon = item.icon;
return (
<li key={item.name}>
<Link
href={item.href}
onClick={onClose}
className={`
flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
transition-colors relative
${active
? 'bg-slate-800 text-emerald-400 border-l-2 border-emerald-500'
: 'text-slate-300 hover:bg-slate-800 hover:text-slate-100'
}
`}
>
<Icon className="w-5 h-5 flex-shrink-0" />
<span className="flex-1">{item.name}</span>
{item.hasBadge && unreadAlertsCount > 0 && (
<span className="inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 text-xs font-semibold bg-red-500 text-white rounded-full">
{unreadAlertsCount > 99 ? '99+' : unreadAlertsCount}
</span>
)}
</Link>
</li>
);
})}
</ul>
<div className="mt-auto px-3 py-3 border-t border-slate-800">
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-slate-400 hover:bg-red-500/10 hover:text-red-400 transition-colors"
>
<LogOut className="w-5 h-5 flex-shrink-0" />
<span className="flex-1 text-left">Cerrar Sesión</span>
</button>
</div>
</nav>
{/* Footer */}
<div className="p-4 border-t border-slate-800">
<p className="text-xs text-slate-500 text-center">
Finanzas v{process.env.NEXT_PUBLIC_APP_VERSION || '1.0.0'}
</p>
</div>
</div>
</aside>
</>
);
}