Spaces:
Sleeping
Sleeping
File size: 4,761 Bytes
149698e e85d815 149698e efc415a 149698e e85d815 efc415a 149698e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import { useLocation, Link, useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import {
LayoutDashboard,
ArrowLeftRight,
BarChart3,
Store,
ScrollText,
Settings,
LogOut,
Wallet,
Mail,
} from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { cn } from '@/lib/utils';
import { useAuthStore } from '@/stores/authStore';
interface NavItem {
icon: React.ElementType;
labelKey: string;
href: string;
}
const navItems: NavItem[] = [
{ icon: LayoutDashboard, labelKey: 'nav.dashboard', href: '/dashboard' },
{ icon: ArrowLeftRight, labelKey: 'nav.transactions', href: '/transactions' },
{ icon: BarChart3, labelKey: 'nav.reports', href: '/reports' },
{ icon: Store, labelKey: 'nav.branches', href: '/branches' },
{ icon: ScrollText, labelKey: 'nav.journal', href: '/journal' },
{ icon: Mail, labelKey: 'nav.emailSenders', href: '/email-senders' },
{ icon: Settings, labelKey: 'nav.settings', href: '/settings' },
];
function getInitials(name?: string | null): string {
if (!name) return '?';
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
return name.slice(0, 2).toUpperCase();
}
function getDisplayName(name?: string | null): string {
if (!name) return 'Utilisateur';
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) return `${parts[0]} ${parts[parts.length - 1][0]}.`;
return name;
}
const ROLE_LABELS: Record<string, string> = {
admin: 'Administrateur',
editor: 'Éditeur',
viewer: 'Lecteur',
};
export default function Sidebar() {
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
const user = useAuthStore((s) => s.user);
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
} catch {
// Ignore errors — clear cookies and redirect regardless
}
useAuthStore.getState().logout();
navigate('/login');
};
return (
<aside className="flex w-72 flex-col border-r border-border bg-card transition-all duration-300">
{/* Logo */}
<div className="flex items-center gap-3 px-6 py-8">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-white shadow-sm">
<Wallet className="h-5 w-5" />
</div>
<div className="flex flex-col">
<h1 className="text-slate-900 text-base font-bold leading-tight tracking-tight">
ICC Interac
</h1>
<p className="text-slate-400 text-xs font-medium">Manager v2.4</p>
</div>
</div>
{/* Navigation */}
<nav className="flex flex-1 flex-col gap-1 px-4 py-4">
{navItems.map((item) => {
const isActive = location.pathname === item.href;
return (
<Link
key={item.href}
to={item.href}
className={cn(
'flex items-center gap-3 rounded-lg px-4 py-3 transition-colors',
isActive
? 'bg-blue-50 text-primary font-semibold'
: 'text-slate-500 hover:bg-slate-50 hover:text-slate-900'
)}
>
<item.icon className="h-5 w-5" />
<span className={cn('text-sm', isActive ? 'font-semibold' : 'font-medium')}>
{t(item.labelKey)}
</span>
</Link>
);
})}
</nav>
{/* Footer */}
<div className="p-4 border-t border-border/50">
<button
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-lg px-4 py-2.5 text-slate-500 hover:text-slate-900 transition-colors group mb-4"
>
<LogOut className="h-5 w-5 group-hover:text-red-500 transition-colors" />
<span className="text-sm font-medium">{t('nav.logout')}</span>
</button>
<div className="flex items-center gap-3 rounded-xl border border-border p-3 bg-white">
<Avatar className="h-10 w-10">
<AvatarImage src={user?.avatarUrl ?? ''} alt={user?.name ?? 'User'} />
<AvatarFallback className="bg-slate-100 text-sm font-semibold text-slate-600">
{getInitials(user?.name)}
</AvatarFallback>
</Avatar>
<div className="flex flex-col min-w-0">
<span className="text-sm font-semibold text-slate-900">{getDisplayName(user?.name)}</span>
<span className="text-xs text-slate-500 truncate">{user?.email ?? ROLE_LABELS[user?.role ?? ''] ?? ''}</span>
</div>
</div>
</div>
</aside>
);
}
|