# Sprint 5 — 元件抽取計畫

> 哪些頁面需要抽取為獨立元件，以便在 Tab 容器中複用

---

## 抽取原則

1. **只移動，不修改邏輯** — 確保功能零損失
2. **保留原始 page.tsx 做 redirect** — 舊連結不失效
3. **元件接收 props 而非自行 fetch** — 父層 (Tab 容器) 負責資料載入
4. **i18n 不變** — 繼續使用原有的 `useTranslations()` namespace

---

## 抽取清單

### 高優先 (AI 指令中心需要)

| # | 原始頁面 | 抽取為 | 放置位置 | 行數 | 用於 |
|---|---------|--------|---------|------|------|
| 1 | `/alerts/page.tsx` | `AlertsPanel.tsx` | `components/panels/` | 183 | AI 中心 Tab 2 左側 |
| 2 | `/reports/page.tsx` | `DispositionPanel.tsx` | `components/panels/` | 317 | AI 中心 Tab 4 |

### 中優先 (其他整合頁面需要)

| # | 原始頁面 | 抽取為 | 放置位置 | 行數 | 用於 |
|---|---------|--------|---------|------|------|
| 3 | `/monitoring/page.tsx` | `MonitoringPanel.tsx` | `components/panels/` | 269 | 可觀測性 Tab 1 |
| 4 | `/apm/page.tsx` | `APMPanel.tsx` | `components/panels/` | 128 | 可觀測性 Tab 2 |
| 5 | `/errors/page.tsx` | `ErrorsPanel.tsx` | `components/panels/` | 164 | 可觀測性 Tab 3 |
| 6 | `/apps/page.tsx` | `AppsPanel.tsx` | `components/panels/` | 103 | 可觀測性 Tab 4 |
| 7 | `/services/page.tsx` | `ServicesPanel.tsx` | `components/panels/` | 120 | 可觀測性 Tab 5 |
| 8 | `/auto-repair/page.tsx` | `AutoRepairPanel.tsx` | `components/panels/` | 460 | 自動化 Tab 1 |
| 9 | `/neural-command/page.tsx` | `NeuralCommandPanel.tsx` | `components/panels/` | 209 | 自動化 Tab 2 |
| 10 | `/drift/page.tsx` | `DriftPanel.tsx` | `components/panels/` | 324 | 自動化 Tab 3 |
| 11 | `/deployments/page.tsx` | `DeploymentsPanel.tsx` | `components/panels/` | 113 | 營運 Tab 1 |
| 12 | `/tickets/page.tsx` | `TicketsPanel.tsx` | `components/panels/` | 120 | 營運 Tab 2 |
| 13 | `/cost/page.tsx` | `CostPanel.tsx` | `components/panels/` | 95 | 營運 Tab 3 |
| 14 | `/action-logs/page.tsx` | `ActionLogsPanel.tsx` | `components/panels/` | 551 | 營運 Tab 4 |
| 15 | `/billing/page.tsx` | `BillingPanel.tsx` | `components/panels/` | 113 | 營運 Tab 5 |
| 16 | `/security/page.tsx` | `SecurityPanel.tsx` | `components/panels/` | 137 | 安全合規 Tab 1 |
| 17 | `/compliance/page.tsx` | `CompliancePanel.tsx` | `components/panels/` | 124 | 安全合規 Tab 2 |

---

## 抽取步驟 (以 AlertsPanel 為例)

### Step 1: 建立 Panel 元件

```typescript
// apps/web/src/components/panels/AlertsPanel.tsx

'use client'

// 從原始 /alerts/page.tsx 移入完整內容
// 唯一差異: 移除 AppLayout wrapper (由 Tab 容器提供)

import { useTranslations } from 'next-intl'
// ... 原始 import 保留 ...

// 移除 export default function AlertsPage({ params })
// 改為:
export function AlertsPanel() {
  const t = useTranslations('alerts')
  
  // ... 原始邏輯完全不動 ...
  
  return (
    // 移除 <AppLayout> wrapper
    // 只保留內部內容
    <div className="space-y-4">
      {/* 原始告警列表內容 */}
    </div>
  )
}
```

### Step 2: 原始頁面改為 redirect

```typescript
// apps/web/src/app/[locale]/alerts/page.tsx
import { redirect } from 'next/navigation'

export default function AlertsPage() {
  redirect('/?tab=alerts')
}
```

### Step 3: 在 Tab 容器中使用

```typescript
// apps/web/src/app/[locale]/page.tsx (AI 指令中心)
import { lazy, Suspense } from 'react'

const AlertsPanel = lazy(() => import('@/components/panels/AlertsPanel').then(m => ({ default: m.AlertsPanel })))

// Tab 2 內容:
<Suspense fallback={<Skeleton />}>
  <AlertsPanel />
</Suspense>
```

---

## 新建目錄結構

```
apps/web/src/components/panels/
├── index.ts                    # 匯出所有 Panel
├── AlertsPanel.tsx             # 從 /alerts 抽取
├── DispositionPanel.tsx        # 從 /reports 抽取
├── MonitoringPanel.tsx         # 從 /monitoring 抽取
├── APMPanel.tsx                # 從 /apm 抽取
├── ErrorsPanel.tsx             # 從 /errors 抽取
├── AppsPanel.tsx               # 從 /apps 抽取
├── ServicesPanel.tsx           # 從 /services 抽取
├── AutoRepairPanel.tsx         # 從 /auto-repair 抽取
├── NeuralCommandPanel.tsx      # 從 /neural-command 抽取
├── DriftPanel.tsx              # 從 /drift 抽取
├── DeploymentsPanel.tsx        # 從 /deployments 抽取
├── TicketsPanel.tsx            # 從 /tickets 抽取
├── CostPanel.tsx               # 從 /cost 抽取
├── ActionLogsPanel.tsx         # 從 /action-logs 抽取
├── BillingPanel.tsx            # 從 /billing 抽取
├── SecurityPanel.tsx           # 從 /security 抽取
└── CompliancePanel.tsx         # 從 /compliance 抽取
```

**共 17 個 Panel 元件**
