feat(web): refine operator navigation IA
This commit is contained in:
@@ -17,7 +17,21 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useRouter, usePathname } from 'next/navigation'
|
||||
import { useLocale } from 'next-intl'
|
||||
import { Search, Terminal, Home, Activity, Wrench, BookOpen, Settings, Zap, GitBranch, Radar } from 'lucide-react'
|
||||
import {
|
||||
Activity,
|
||||
BookOpen,
|
||||
BrainCircuit,
|
||||
ClipboardList,
|
||||
GitBranch,
|
||||
Home,
|
||||
Radar,
|
||||
Search,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
Terminal,
|
||||
Wrench,
|
||||
Zap,
|
||||
} from 'lucide-react'
|
||||
import { useTerminalStore } from '@/stores/terminal.store'
|
||||
import { Z_INDEX } from '@/lib/constants/z-index'
|
||||
|
||||
@@ -83,6 +97,38 @@ export function CommandPalette() {
|
||||
action: () => nav('/observability'),
|
||||
keywords: ['observability', '可觀測性', 'monitor', '監控'],
|
||||
},
|
||||
{
|
||||
id: 'awooop-home',
|
||||
label: t('actionGoAwoooP'),
|
||||
group: t('groupNav'),
|
||||
icon: <BrainCircuit size={14} />,
|
||||
action: () => nav('/awooop'),
|
||||
keywords: ['awooop', 'operator', 'console', '操作控制台', '總覽'],
|
||||
},
|
||||
{
|
||||
id: 'awooop-work-items',
|
||||
label: t('actionGoWorkItems'),
|
||||
group: t('groupNav'),
|
||||
icon: <ClipboardList size={14} />,
|
||||
action: () => nav('/awooop/work-items'),
|
||||
keywords: ['work items', '工作鏈路', '工作項', 'recurrence'],
|
||||
},
|
||||
{
|
||||
id: 'awooop-runs',
|
||||
label: t('actionGoRuns'),
|
||||
group: t('groupNav'),
|
||||
icon: <Activity size={14} />,
|
||||
action: () => nav('/awooop/runs'),
|
||||
keywords: ['runs', 'run monitor', '執行監控', 'run 監控'],
|
||||
},
|
||||
{
|
||||
id: 'awooop-approvals',
|
||||
label: t('actionGoApprovalQueue'),
|
||||
group: t('groupNav'),
|
||||
icon: <ShieldCheck size={14} />,
|
||||
action: () => nav('/awooop/approvals'),
|
||||
keywords: ['approval queue', '審批佇列', '人工閘門', 'approve'],
|
||||
},
|
||||
{
|
||||
id: 'automation',
|
||||
label: t('actionGoAutomation'),
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Sidebar - Phase 7.0 極簡五柱導航
|
||||
* =================================
|
||||
* Sidebar - Operator workflow navigation
|
||||
* ======================================
|
||||
* Nothing.tech 視覺憲法:
|
||||
* - 純白背景 (bg-white)
|
||||
* - 極細右邊框 (border-r-[0.5px] border-neutral-200)
|
||||
* - 無陰影
|
||||
* - 單色圖示
|
||||
*
|
||||
* 5 大核心樞紐:
|
||||
* 1. 全局戰情室 (/)
|
||||
* 2. 授權中心 (/authorizations) - 含動態徽章
|
||||
* 3. 行動日誌 (/action-logs)
|
||||
* 4. 知識殿堂 (/knowledge-base)
|
||||
* 5. 系統設定 (/settings)
|
||||
* IA v2 (2026-06-04):
|
||||
* - 頂層按照 operator 工作流排序,不按照技術模組排序。
|
||||
* - AwoooP / Runs / Work Items / Approvals / Alerts 直接成為全域入口。
|
||||
* - 頁內 tabs 只保留同一頁的視角切換,避免把任務入口藏在第二層。
|
||||
*
|
||||
* Phase 19: 使用 Z_INDEX.SIDEBAR (40)
|
||||
* @see lib/constants/z-index.ts
|
||||
@@ -28,12 +26,26 @@ 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, Radar,
|
||||
Activity,
|
||||
Bell,
|
||||
BookOpen,
|
||||
BrainCircuit,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ClipboardList,
|
||||
DollarSign,
|
||||
GitBranch,
|
||||
HelpCircle,
|
||||
LayoutDashboard,
|
||||
Monitor,
|
||||
Package,
|
||||
Radar,
|
||||
Route,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
Terminal,
|
||||
Ticket,
|
||||
Wrench,
|
||||
} from 'lucide-react'
|
||||
// Phase 8.0 #15: 改用 approval store SSE (移除 polling)
|
||||
import { useApprovalStore } from '@/stores/approval.store'
|
||||
@@ -56,55 +68,67 @@ type NavItemConfig = {
|
||||
labelKey: string
|
||||
Icon: typeof LayoutDashboard
|
||||
aliases?: string[]
|
||||
exact?: boolean
|
||||
badge?: boolean // 是否顯示動態徽章
|
||||
}
|
||||
|
||||
type NavSection = {
|
||||
sectionKey: string
|
||||
sectionLabel: string
|
||||
items: NavItemConfig[]
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sprint 5: 精簡導航 6+2+經典(統帥批准 2026-04-08)
|
||||
// 2026-06-04: Operator-first IA
|
||||
// ============================================================
|
||||
// 整合對照:
|
||||
// 指令中心 / → 整合: 儀表板 + 授權 + 告警 + 報表 (4 Tab)
|
||||
// 可觀測性 /observability → 整合: 監控 + APM + 錯誤 + 應用 + 服務 (5 Tab) [暫指 /monitoring]
|
||||
// 自動化 /automation → 整合: 自動修復 + 神經指揮 + Drift (3 Tab) [暫指 /auto-repair]
|
||||
// 營運 /operations → 整合: 部署 + 工單 + 成本 + 行動日誌 + 計費 (5 Tab) [暫指 /deployments]
|
||||
// IwoooS /iwooos → 單一資安主入口;/security-compliance 保留相容高亮
|
||||
// 知識 /knowledge → 知識庫 [暫指 /knowledge-base]
|
||||
// 參考 Material / Atlassian / Carbon 的 app shell 模式:
|
||||
// 側欄放高頻任務與產品導航,Header 留給搜尋與全域工具。
|
||||
// ============================================================
|
||||
|
||||
const NAV_SECTIONS: NavSection[] = [
|
||||
{
|
||||
sectionKey: 'main',
|
||||
sectionLabel: '',
|
||||
sectionKey: 'queues',
|
||||
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: 'iwooos-security', href: '/iwooos', labelKey: 'iwooos', Icon: Radar, aliases: ['/security-compliance'] },
|
||||
{ id: 'knowledge', href: '/knowledge', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'governance', href: '/governance', labelKey: 'governance', Icon: ShieldCheck },
|
||||
{ id: 'awooop', href: '/awooop/work-items', labelKey: 'awooop', Icon: BrainCircuit },
|
||||
{ id: 'command-center', href: '/', labelKey: 'commandCenter', Icon: LayoutDashboard },
|
||||
{ id: 'awooop-home', href: '/awooop', labelKey: 'awooopHome', Icon: BrainCircuit, exact: true },
|
||||
{ id: 'awooop-work-items', href: '/awooop/work-items', labelKey: 'workItems', Icon: ClipboardList },
|
||||
{ id: 'awooop-runs', href: '/awooop/runs', labelKey: 'runMonitor', Icon: Activity },
|
||||
{ id: 'awooop-approvals', href: '/awooop/approvals', labelKey: 'approvalQueue', Icon: ShieldCheck, badge: true },
|
||||
{ id: 'alerts', href: '/alerts', labelKey: 'alerts', Icon: Bell },
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionKey: 'truth',
|
||||
items: [
|
||||
{ id: 'observability', href: '/observability', labelKey: 'observability', Icon: Monitor },
|
||||
{ id: 'automation', href: '/automation', labelKey: 'automation', Icon: Wrench },
|
||||
{ id: 'governance', href: '/governance', labelKey: 'governance', Icon: ShieldCheck },
|
||||
{ id: 'knowledge', href: '/knowledge', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'iwooos-security', href: '/iwooos', labelKey: 'iwooos', Icon: Radar, aliases: ['/security-compliance'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionKey: 'ops',
|
||||
items: [
|
||||
{ id: 'operations', href: '/operations', labelKey: 'operationsOverview', Icon: Package },
|
||||
{ id: 'topology', href: '/topology', labelKey: 'topology', Icon: Route },
|
||||
{ id: 'deployments', href: '/deployments', labelKey: 'deployments', Icon: GitBranch },
|
||||
{ id: 'tickets', href: '/tickets', labelKey: 'tickets', Icon: Ticket },
|
||||
{ id: 'cost', href: '/cost', labelKey: 'cost', Icon: DollarSign },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Legacy: 經典 AI 中心 (統帥指示保留)
|
||||
sectionKey: 'legacy',
|
||||
sectionLabel: 'legacy',
|
||||
items: [
|
||||
{ id: 'classic', href: '/classic', labelKey: 'classicAICenter', Icon: LayoutDashboard },
|
||||
{ id: 'terminal', href: '/terminal', labelKey: 'terminal', Icon: Terminal },
|
||||
{ id: 'settings', href: '/settings', labelKey: 'settings', Icon: Settings },
|
||||
{ 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 },
|
||||
{ id: 'help', href: '/help', labelKey: 'help', Icon: HelpCircle },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
@@ -119,7 +143,6 @@ export function Sidebar({
|
||||
className,
|
||||
}: SidebarProps) {
|
||||
const t = useTranslations('nav')
|
||||
const tBrand = useTranslations('brand')
|
||||
const tSection = useTranslations('navSection')
|
||||
const tSidebar = useTranslations('sidebar')
|
||||
const pathname = usePathname()
|
||||
@@ -134,16 +157,19 @@ export function Sidebar({
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const isRouteActive = (href: string) => {
|
||||
const isRouteActive = (href: string, exact = false) => {
|
||||
const fullHref = `/${locale}${href === '/' ? '' : href}`
|
||||
if (href === '/') {
|
||||
return pathname === `/${locale}` || pathname === `/${locale}/`
|
||||
}
|
||||
if (exact) {
|
||||
return pathname === fullHref || pathname === `${fullHref}/`
|
||||
}
|
||||
return pathname === fullHref || pathname.startsWith(fullHref + '/')
|
||||
}
|
||||
|
||||
const isActive = (item: NavItemConfig) => (
|
||||
isRouteActive(item.href) || item.aliases?.some(alias => isRouteActive(alias)) === true
|
||||
isRouteActive(item.href, item.exact) || item.aliases?.some(alias => isRouteActive(alias)) === true
|
||||
)
|
||||
const sidebarWidth = compact && collapsed ? 48 : collapsed ? 64 : 224
|
||||
|
||||
@@ -164,13 +190,11 @@ export function Sidebar({
|
||||
}}
|
||||
>
|
||||
|
||||
{/* 導航列表 - AI中心 v6 4分區 */}
|
||||
{/* 導航列表 - Operator workflow sections */}
|
||||
<nav className="flex-1 py-2 overflow-y-auto">
|
||||
{/* 4 分區菜單 */}
|
||||
{NAV_SECTIONS.map(section => (
|
||||
<div key={section.sectionKey} style={{ marginBottom: 4 }}>
|
||||
{/* 分區標題 — main 不顯示,legacy 顯示分隔線+標題 */}
|
||||
{!collapsed && section.sectionLabel && (
|
||||
{!collapsed && (
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
color: '#b0ad9f',
|
||||
@@ -181,7 +205,7 @@ export function Sidebar({
|
||||
borderTop: '0.5px solid #e0ddd4',
|
||||
marginTop: 4,
|
||||
}}>
|
||||
{section.sectionLabel}
|
||||
{tSection(section.sectionKey)}
|
||||
</div>
|
||||
)}
|
||||
{section.items.map(item => {
|
||||
@@ -191,6 +215,7 @@ export function Sidebar({
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
title={collapsed ? t(item.labelKey) : undefined}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -205,10 +230,15 @@ export function Sidebar({
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
position: 'relative' as const,
|
||||
minHeight: collapsed ? 36 : 32,
|
||||
}}
|
||||
>
|
||||
<item.Icon size={15} />
|
||||
{!collapsed && <span>{t(item.labelKey)}</span>}
|
||||
<item.Icon size={15} aria-hidden="true" />
|
||||
{!collapsed && (
|
||||
<span style={{ minWidth: 0, overflowWrap: 'anywhere' }}>
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
{item.badge && count > 0 && (
|
||||
<span style={{
|
||||
marginLeft: 'auto',
|
||||
@@ -238,6 +268,7 @@ export function Sidebar({
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
title={collapsed ? t(item.labelKey) : undefined}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -251,10 +282,15 @@ export function Sidebar({
|
||||
background: active ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
minHeight: collapsed ? 34 : 30,
|
||||
}}
|
||||
>
|
||||
<item.Icon size={15} />
|
||||
{!collapsed && <span>{t(item.labelKey)}</span>}
|
||||
<item.Icon size={15} aria-hidden="true" />
|
||||
{!collapsed && (
|
||||
<span style={{ minWidth: 0, overflowWrap: 'anywhere' }}>
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user