2026-06-03 17:05:05 +08:00
|
|
|
import { NavLink } from 'react-router-dom';
|
|
|
|
|
import {
|
|
|
|
|
LayoutDashboard, Radio, Monitor, FileText,
|
2026-06-05 18:00:31 +08:00
|
|
|
Shield, MessageSquare, Sun, Moon, LogOut
|
2026-06-03 17:05:05 +08:00
|
|
|
} from 'lucide-react';
|
|
|
|
|
import { useTheme } from '../../contexts/ThemeContext';
|
2026-06-05 18:00:31 +08:00
|
|
|
import { useAuth } from '../../contexts/AuthContext';
|
2026-06-10 11:10:36 +08:00
|
|
|
import { useLanguage } from '../../contexts/LanguageContext';
|
2026-06-03 17:05:05 +08:00
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 18:00:31 +08:00
|
|
|
/** 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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 17:05:05 +08:00
|
|
|
export function Sidebar() {
|
|
|
|
|
const { theme, toggleTheme } = useTheme();
|
2026-06-05 18:00:31 +08:00
|
|
|
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 },
|
|
|
|
|
];
|
2026-06-05 18:00:31 +08:00
|
|
|
|
2026-06-03 17:05:05 +08:00
|
|
|
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" />
|
2026-06-03 17:05:05 +08:00
|
|
|
<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>
|
2026-06-03 17:05:05 +08:00
|
|
|
</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} />
|
2026-06-03 17:05:05 +08:00
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div className="sidebar-footer">
|
|
|
|
|
<div className="sidebar-user">
|
2026-06-05 18:00:31 +08:00
|
|
|
<div className="user-avatar">{user ? initials(user.username) : 'TS'}</div>
|
2026-06-03 17:05:05 +08:00
|
|
|
<div className="user-info">
|
2026-06-05 18:00:31 +08:00
|
|
|
<div className="user-name">{user?.username ?? 'Analyst'}</div>
|
|
|
|
|
<div className="user-role">
|
|
|
|
|
{user ? (
|
|
|
|
|
<span className="user-badge">{user.role}</span>
|
|
|
|
|
) : (
|
|
|
|
|
'T-Systems'
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-06-03 17:05:05 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-06-05 18:00:31 +08:00
|
|
|
<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}>
|
2026-06-05 18:00:31 +08:00
|
|
|
{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}>
|
2026-06-05 18:00:31 +08:00
|
|
|
<LogOut size={14} />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-06-03 17:05:05 +08:00
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
);
|
|
|
|
|
}
|