Files
AIRegulation-DocAnalysis/frontend/src/components/layout/Sidebar.tsx

118 lines
3.8 KiB
TypeScript
Raw Normal View History

import { NavLink } from 'react-router-dom';
import {
LayoutDashboard, Radio, Monitor, FileText,
Shield, MessageSquare, Sun, Moon, LogOut
} from 'lucide-react';
import { useTheme } from '../../contexts/ThemeContext';
import { useAuth } from '../../contexts/AuthContext';
2026-06-10 11:10:36 +08:00
import { useLanguage } from '../../contexts/LanguageContext';
interface NavItem {
to: string;
icon: React.ReactNode;
label: string;
badge?: number;
}
function NavGroup({ title, items }: { title: string; items: NavItem[] }) {
return (
<div className="nav-group">
<div className="nav-group-label">{title}</div>
{items.map(item => (
<NavLink
key={item.to}
to={item.to}
end={item.to === '/'}
className={({ isActive }) => `nav-item${isActive ? ' active' : ''}`}
>
<span className="nav-icon">{item.icon}</span>
<span className="nav-label">{item.label}</span>
{item.badge !== undefined && item.badge > 0 && (
<span className="nav-badge">{item.badge}</span>
)}
</NavLink>
))}
</div>
);
}
/** Avatar initials from username (up to 2 chars). */
function initials(name: string): string {
const parts = name.trim().split(/[\s_-]+/);
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
return name.slice(0, 2).toUpperCase();
}
export function Sidebar() {
const { theme, toggleTheme } = useTheme();
const { user, logout } = useAuth();
2026-06-10 11:10:36 +08:00
const { lang, t, toggleLang } = useLanguage();
const mainNav: NavItem[] = [
{ to: '/', icon: <LayoutDashboard size={16} />, label: t.nav.overview },
{ to: '/signals', icon: <Radio size={16} />, label: t.nav.signals },
{ to: '/status', icon: <Monitor size={16} />, label: t.nav.status },
];
const workbenchNav: NavItem[] = [
{ to: '/documents', icon: <FileText size={16} />, label: t.nav.documents },
{ to: '/compliance', icon: <Shield size={16} />, label: t.nav.compliance },
];
const chatNav: NavItem[] = [
{ to: '/chat', icon: <MessageSquare size={16} />, label: t.nav.chat },
];
return (
<aside className="sidebar">
<div className="sidebar-brand">
2026-06-04 15:43:44 +08:00
<img src="/company-logo.ico" alt="T-Systems" className="brand-logo" />
<div className="brand-text">
2026-06-04 15:43:44 +08:00
<div className="brand-name">T-Systems</div>
<div className="brand-sub">Regulation Hub</div>
</div>
</div>
<nav className="sidebar-nav">
2026-06-10 11:10:36 +08:00
<NavGroup title={t.nav.groupMain} items={mainNav} />
<NavGroup title={t.nav.groupWorkbench} items={workbenchNav} />
<NavGroup title={t.nav.groupChat} items={chatNav} />
</nav>
<div className="sidebar-footer">
<div className="sidebar-user">
<div className="user-avatar">{user ? initials(user.username) : 'TS'}</div>
<div className="user-info">
<div className="user-name">{user?.username ?? 'Analyst'}</div>
<div className="user-role">
{user ? (
<span className="user-badge">{user.role}</span>
) : (
'T-Systems'
)}
</div>
</div>
</div>
<div style={{ display: 'flex', gap: 4 }}>
2026-06-10 11:10:36 +08:00
<button
className="theme-btn"
onClick={toggleLang}
title={t.sidebar.toggleLang}
style={{ fontSize: 12, fontWeight: 600 }}
>
{lang === 'en' ? 'EN' : '中'}
</button>
<button className="theme-btn" onClick={toggleTheme} title={t.sidebar.toggleTheme}>
{theme === 'dark' ? <Sun size={14} /> : <Moon size={14} />}
</button>
{user && (
2026-06-10 11:10:36 +08:00
<button className="logout-btn" onClick={logout} title={t.sidebar.signOut}>
<LogOut size={14} />
</button>
)}
</div>
</div>
</aside>
);
}