feat(sidebar): 4-section nav with AI center active style #d97757
This commit is contained in:
@@ -28,14 +28,12 @@ import { usePathname } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
ShieldCheck,
|
||||
Zap,
|
||||
BookOpen,
|
||||
Settings,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Bug,
|
||||
LayoutDashboard, ShieldCheck, Bell, Monitor, Activity,
|
||||
Bug, GitBranch, Shield, ClipboardCheck,
|
||||
Wrench, Package, Ticket, DollarSign, Zap, FileText,
|
||||
BookOpen, Terminal, AppWindow, Server,
|
||||
Users, BellRing, CreditCard, HelpCircle, Settings,
|
||||
ChevronLeft, ChevronRight,
|
||||
} from 'lucide-react'
|
||||
// Phase 8.0 #15: 改用 approval store SSE (移除 polling)
|
||||
import { useApprovalStore } from '@/stores/approval.store'
|
||||
@@ -59,17 +57,68 @@ type NavItemConfig = {
|
||||
badge?: boolean // 是否顯示動態徽章
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Phase 7.0: 5 大核心樞紐
|
||||
// =============================================================================
|
||||
type NavSection = {
|
||||
sectionKey: string
|
||||
sectionLabel: string
|
||||
items: NavItemConfig[]
|
||||
}
|
||||
|
||||
const NAV_ITEMS: NavItemConfig[] = [
|
||||
{ id: 'warroom', href: '/', labelKey: 'dashboard', Icon: LayoutDashboard },
|
||||
{ id: 'authorizations', href: '/authorizations', labelKey: 'approvals', Icon: ShieldCheck, badge: true },
|
||||
{ id: 'errors', href: '/errors', labelKey: 'errors', Icon: Bug },
|
||||
{ id: 'action-logs', href: '/action-logs', labelKey: 'actions', Icon: Zap },
|
||||
{ id: 'knowledge-base', href: '/knowledge-base', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'settings', href: '/settings', labelKey: 'settings', Icon: Settings },
|
||||
// ============================================================
|
||||
// AI中心 v6 — 4分區 Sidebar(統帥批准 2026-04-01)
|
||||
// ============================================================
|
||||
|
||||
const NAV_SECTIONS: NavSection[] = [
|
||||
{
|
||||
sectionKey: 'ai-core',
|
||||
sectionLabel: 'AI 核心',
|
||||
items: [
|
||||
{ id: 'ai-center', href: '/', labelKey: 'dashboard', Icon: LayoutDashboard },
|
||||
{ id: 'authorizations', href: '/authorizations', labelKey: 'approvals', Icon: ShieldCheck, badge: true },
|
||||
{ id: 'alerts', href: '/alerts', labelKey: 'alerts', Icon: Bell },
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionKey: 'monitoring',
|
||||
sectionLabel: '監控與安全',
|
||||
items: [
|
||||
{ id: 'monitoring', href: '/monitoring', labelKey: 'monitoring', Icon: Monitor },
|
||||
{ id: 'apm', href: '/apm', labelKey: 'apm', Icon: Activity },
|
||||
{ id: 'errors', href: '/errors', labelKey: 'errors', Icon: Bug },
|
||||
{ id: 'topology', href: '/topology', labelKey: 'topology', Icon: GitBranch },
|
||||
{ id: 'security', href: '/security', labelKey: 'security', Icon: Shield },
|
||||
{ id: 'compliance', href: '/compliance', labelKey: 'compliance', Icon: ClipboardCheck },
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionKey: 'ops',
|
||||
sectionLabel: '運維管理',
|
||||
items: [
|
||||
{ id: 'auto-repair', href: '/auto-repair', labelKey: 'autoRepair', Icon: Wrench },
|
||||
{ id: 'deployments', href: '/deployments', labelKey: 'deployments', Icon: Package },
|
||||
{ id: 'tickets', href: '/tickets', labelKey: 'tickets', Icon: Ticket },
|
||||
{ id: 'cost', href: '/cost', labelKey: 'cost', Icon: DollarSign },
|
||||
{ id: 'action-logs', href: '/action-logs', labelKey: 'actions', Icon: Zap },
|
||||
{ id: 'reports', href: '/reports', labelKey: 'reports', Icon: FileText },
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionKey: 'knowledge',
|
||||
sectionLabel: '知識與工具',
|
||||
items: [
|
||||
{ id: 'knowledge-base', href: '/knowledge-base', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'terminal', href: '/terminal', labelKey: 'terminal', Icon: Terminal },
|
||||
{ id: 'apps', href: '/apps', labelKey: 'apps', Icon: AppWindow },
|
||||
{ id: 'services', href: '/services', labelKey: 'services', Icon: Server },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const BOTTOM_NAV_ITEMS: NavItemConfig[] = [
|
||||
{ id: 'users', href: '/users', labelKey: 'users', Icon: Users },
|
||||
{ id: 'notifications', href: '/notifications', labelKey: 'notifications', Icon: BellRing },
|
||||
{ id: 'billing', href: '/billing', labelKey: 'billing', Icon: CreditCard },
|
||||
{ id: 'help', href: '/help', labelKey: 'help', Icon: HelpCircle },
|
||||
{ id: 'settings', href: '/settings', labelKey: 'settings', Icon: Settings },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
@@ -136,54 +185,98 @@ export function Sidebar({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 導航列表 - 5 大核心樞紐 */}
|
||||
<nav className="flex-1 py-4 overflow-y-auto">
|
||||
<ul className="space-y-0.5 px-2">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const active = isActive(item.href)
|
||||
return (
|
||||
<li key={item.id}>
|
||||
{/* 導航列表 - AI中心 v6 4分區 */}
|
||||
<nav className="flex-1 py-2 overflow-y-auto">
|
||||
{/* 4 分區菜單 */}
|
||||
{NAV_SECTIONS.map(section => (
|
||||
<div key={section.sectionKey} style={{ marginBottom: 4 }}>
|
||||
{/* 分區標題(collapsed 時隱藏)*/}
|
||||
{!collapsed && (
|
||||
<div style={{
|
||||
fontSize: 8,
|
||||
color: '#b0ad9f',
|
||||
letterSpacing: '1.5px',
|
||||
textTransform: 'uppercase' as const,
|
||||
padding: '8px 12px 3px',
|
||||
fontFamily: 'monospace',
|
||||
}}>
|
||||
{section.sectionLabel}
|
||||
</div>
|
||||
)}
|
||||
{section.items.map(item => {
|
||||
const active = isActive(item.href)
|
||||
const count = item.badge && mounted ? pendingCount : 0
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-sm',
|
||||
'transition-all duration-150',
|
||||
'relative',
|
||||
// Phase 7.0 視覺規範
|
||||
active
|
||||
? 'font-bold text-black bg-neutral-100'
|
||||
: 'text-neutral-500 hover:bg-neutral-50 hover:text-black',
|
||||
collapsed && 'justify-center px-2'
|
||||
)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: collapsed ? 0 : 8,
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
padding: collapsed ? '8px 0' : '6px 12px',
|
||||
fontSize: 11,
|
||||
color: active ? '#141413' : '#87867f',
|
||||
fontWeight: active ? 600 : 400,
|
||||
borderRight: active ? '2px solid #d97757' : '2px solid transparent',
|
||||
background: active ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
position: 'relative' as const,
|
||||
}}
|
||||
>
|
||||
<item.Icon className={cn(
|
||||
'w-[18px] h-[18px] flex-shrink-0',
|
||||
active ? 'text-black' : 'text-neutral-400'
|
||||
)} />
|
||||
|
||||
{!collapsed && (
|
||||
<span className="text-[13px] tracking-wide truncate">
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* 動態徽章 - 授權中心 (Phase 8.0 #15: mounted check 防止 hydration mismatch) */}
|
||||
{item.badge && mounted && pendingCount > 0 && (
|
||||
<span className={cn(
|
||||
'flex items-center justify-center',
|
||||
'min-w-[18px] h-[18px] px-1',
|
||||
'text-[10px] font-mono font-bold',
|
||||
'bg-neutral-900 text-white rounded-sm',
|
||||
collapsed ? 'absolute top-1 right-1' : 'ml-auto'
|
||||
)}>
|
||||
{pendingCount > 99 ? '99+' : pendingCount}
|
||||
<item.Icon size={14} />
|
||||
{!collapsed && <span>{t(item.labelKey)}</span>}
|
||||
{item.badge && count > 0 && (
|
||||
<span style={{
|
||||
marginLeft: 'auto',
|
||||
fontSize: 8,
|
||||
background: '#d97757',
|
||||
color: 'white',
|
||||
borderRadius: 10,
|
||||
padding: '1px 5px',
|
||||
fontWeight: 700,
|
||||
}}>
|
||||
{count > 99 ? '99+' : count}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Separator */}
|
||||
<div style={{ borderTop: '0.5px solid #e0ddd4', margin: '6px 0' }} />
|
||||
|
||||
{/* 底部菜單 */}
|
||||
{BOTTOM_NAV_ITEMS.map(item => {
|
||||
const active = isActive(item.href)
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: collapsed ? 0 : 8,
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
padding: collapsed ? '7px 0' : '5px 12px',
|
||||
fontSize: 10,
|
||||
color: active ? '#141413' : '#87867f',
|
||||
fontWeight: active ? 600 : 400,
|
||||
borderRight: active ? '2px solid #d97757' : '2px solid transparent',
|
||||
background: active ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
}}
|
||||
>
|
||||
<item.Icon size={12} />
|
||||
{!collapsed && <span>{t(item.labelKey)}</span>}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* 底部區域 - 極簡版本號 */}
|
||||
|
||||
Reference in New Issue
Block a user