'use client' /** * Sidebar - Phase 7.0 極簡五柱導航 * ================================= * Nothing.tech 視覺憲法: * - 純白背景 (bg-white) * - 極細右邊框 (border-r-[0.5px] border-neutral-200) * - 無陰影 * - 單色圖示 * * 5 大核心樞紐: * 1. 全局戰情室 (/) * 2. 授權中心 (/authorizations) - 含動態徽章 * 3. 行動日誌 (/action-logs) * 4. 知識殿堂 (/knowledge-base) * 5. 系統設定 (/settings) * * Phase 19: 使用 Z_INDEX.SIDEBAR (40) * @see lib/constants/z-index.ts */ import { useEffect, useState } from 'react' import { Z_INDEX } from '@/lib/constants/z-index' // Phase 8.0 #15: SSE 驅動的待簽核數量 import { useTranslations } from 'next-intl' import { usePathname } from 'next/navigation' import Link from 'next/link' import { cn } from '@/lib/utils' import { 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, Diff, BrainCircuit, } from 'lucide-react' // Phase 8.0 #15: 改用 approval store SSE (移除 polling) import { useApprovalStore } from '@/stores/approval.store' // ============================================================================= // Types // ============================================================================= interface SidebarProps { locale: string collapsed?: boolean onToggle?: () => void className?: string } type NavItemConfig = { id: string href: string labelKey: string Icon: typeof LayoutDashboard badge?: boolean // 是否顯示動態徽章 } type NavSection = { sectionKey: string sectionLabel: string items: NavItemConfig[] } // ============================================================ // Sprint 5: 精簡導航 6+2+經典(統帥批准 2026-04-08) // ============================================================ // 整合對照: // 指令中心 / → 整合: 儀表板 + 授權 + 告警 + 報表 (4 Tab) // 可觀測性 /observability → 整合: 監控 + APM + 錯誤 + 應用 + 服務 (5 Tab) [暫指 /monitoring] // 自動化 /automation → 整合: 自動修復 + 神經指揮 + Drift (3 Tab) [暫指 /auto-repair] // 營運 /operations → 整合: 部署 + 工單 + 成本 + 行動日誌 + 計費 (5 Tab) [暫指 /deployments] // 安全合規 /security-compliance → 整合: 安全 + 合規 (2 Tab) [暫指 /security] // 知識 /knowledge → 知識庫 [暫指 /knowledge-base] // ============================================================ const NAV_SECTIONS: NavSection[] = [ { sectionKey: 'main', sectionLabel: '', items: [ { id: 'command-center', href: '/', labelKey: 'commandCenter', Icon: LayoutDashboard }, { id: 'observability', href: '/observability', labelKey: 'observability', Icon: Monitor }, { id: 'automation', href: '/automation', labelKey: 'automation', Icon: Wrench }, { id: 'operations', href: '/operations', labelKey: 'operations', Icon: Package }, { id: 'security-compliance', href: '/security-compliance', labelKey: 'securityCompliance',Icon: Shield }, { id: 'knowledge', href: '/knowledge', labelKey: 'knowledge', Icon: BookOpen }, { id: 'governance', href: '/governance', labelKey: 'governance', Icon: ShieldCheck }, ], }, { // Legacy: 經典 AI 中心 (統帥指示保留) sectionKey: 'legacy', sectionLabel: 'legacy', items: [ { id: 'classic', href: '/classic', labelKey: 'classicAICenter', Icon: LayoutDashboard }, ], }, ] const BOTTOM_NAV_ITEMS: NavItemConfig[] = [ { id: 'terminal', href: '/terminal', labelKey: 'terminal', Icon: Terminal }, { id: 'settings', href: '/settings', labelKey: 'settings', Icon: Settings }, ] // ============================================================================= // Component // ============================================================================= export function Sidebar({ locale, collapsed = false, onToggle, className, }: SidebarProps) { const t = useTranslations('nav') const tBrand = useTranslations('brand') const tSection = useTranslations('navSection') const tSidebar = useTranslations('sidebar') const pathname = usePathname() // Phase 8.0 #15: 改用 SSE 驅動的 pending count (移除 30s polling) // 防止 SSR hydration mismatch: 只在 client 顯示 badge const [mounted, setMounted] = useState(false) const pendingApprovals = useApprovalStore(state => state.pendingApprovals) const pendingCount = pendingApprovals.length useEffect(() => { setMounted(true) }, []) const isActive = (href: string) => { const fullHref = `/${locale}${href === '/' ? '' : href}` if (href === '/') { return pathname === `/${locale}` || pathname === `/${locale}/` } return pathname === fullHref || pathname.startsWith(fullHref + '/') } return ( ) }