# C5 — Implementation Plan

> 對應 design：[design-C5.md](design-C5.md)
> 級別：中
> 預估工時：FE 4~5 天

---

## Pre-flight 驗證

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe

# 1. C2.1 endpoints 已 ship？
# 確認以下 API 在 dev 環境可用：
# GET /projects/<uid>/current-ssp-uid
# GET /ssp/<uid>/system-characteristic
# GET /ssp/<uid>/parties
# GET /ssp/<uid>/ssp-resources
# PUT /ssp/<uid>/parties/<party_uid>
# POST /ssp/<uid>/ssp-resources/items

# 2. 找專案規劃頁 root component
find src/views -name "*Planning*" -o -name "*ProjectDetail*" | head -5

# 3. 找既有 MF panel 元件（可複用）
ls src/components/module_frame/

# 4. 確認既有 composable
ls src/composables/ | grep -E "ModuleFrame|Ssp"

# 5. 確認 PrimeVue 版本 + 已有 Accordion 元件
grep -n "primevue" package.json
```

---

## 任務清單

### T1 — 建立 composables（FE）

#### T1.1 `useSspContext.js`

**新檔**：`src/composables/useSspContext.js`

**內容**：對齊 design-C5.md §4.3

**Unit test**：（Vitest if 有）
- loadCurrent 呼叫 `/projects/<uid>/current-ssp-uid` 並設定 ref
- 404 處理

**Commit**：`feat(composables): C5 useSspContext`

#### T1.2 `useSspParties.js` / `useSspResources.js` / `useSspSystemCharacteristic.js` / `useSspLeveraged.js`

**新檔**：對應 4 個 composable

**每個 composable** 包含：
- fetch / create / update / delete actions
- reactive state (loading / error / data)
- Module-scope cache（仿 `useModuleFrameParties` 模式）+ `invalidate(sspUid)` 清快取

**Commit**：`feat(composables): C5 useSspParties / useSspResources / useSspSystemCharacteristic / useSspLeveraged`

---

### T2 — Service 層 wrapper（FE）

**新檔**：
- `src/service/SspService.js`（仿 ModuleFrameService 模式，但 entry 改 ssp_uid）

**內容**：封裝 axios call for 所有 SSP-scoped endpoint

**Commit**：`feat(service): C5 SspService`

---

### T3 — `SspTabPanel.vue` root 元件（FE）

**新檔**：`src/views/project/planning/SspTabPanel.vue`

**Props**：
- `projectUid: String`

**內容**：
- AP selector dropdown
- 五個 accordion + sections（lazy load）
- 權限 + AP 狀態判斷邏輯
- 控制項 / 程序書 link 區塊

**State**：
```javascript
const sspContext = useSspContext(toRef(props, 'projectUid'))
const selectedApUid = ref(null)
const expandedSections = ref(['basic'])  // 預設展開

const isEditable = computed(() =>
  sspContext.isEditable.value && currentUserRole.value === 'manager'
)

// AP selector
const apMenu = ref([])
async function loadApMenu() {
  apMenu.value = await GrcProjectService.getApMenu(props.projectUid)
}

// Deep link
onMounted(() => {
  const { section } = route.query
  if (section) {
    expandedSections.value = [section === 'scope' ? ['scope-devices', 'scope-info-systems'] : section]
  }
})
```

**Commit**：`feat(project-planning): C5 SspTabPanel root component`

---

### T4 — `SspBasicSection.vue` 基本資料區塊（FE）

**新檔**：`src/components/project/ssp/SspBasicSection.vue`

**內容**：
- 顯示 + 編輯 system_characteristic（name / system_identifier / sensitivity / scope_description / status / system_owner）
- 用 `useSspSystemCharacteristic` composable
- editable 模式：表單；readonly：純顯示
- 儲存按鈕 → PUT API + 顯示 toast

**Commit**：`feat(project-planning): C5 SspBasicSection`

---

### T5 — `SspScopeDevicesSection.vue` + `SspScopeInfoSystemsSection.vue`（FE）

**新檔**：
- `src/components/project/ssp/SspScopeDevicesSection.vue`
- `src/components/project/ssp/SspScopeInfoSystemsSection.vue`

**仿 MF 版本**：
- `src/components/module_frame/ModuleFrameSspDevicesPanel.vue`
- `src/components/module_frame/ModuleFrameSspInfoSystemsPanel.vue`

**改動**：
- props 改吃 `sspUid`
- API call 改 `/ssp/<sspUid>/ssp-resources/...`
- 用 `useSspResources` composable

**Commit**：`feat(project-planning): C5 SspScopeDevicesSection + SspScopeInfoSystemsSection`

---

### T6 — `SspPartiesSection.vue` 參與人員區塊（FE）

**新檔**：`src/components/project/ssp/SspPartiesSection.vue`

**仿 MF 版本**：`src/components/module_frame/ModuleFramePartiesPanel.vue`

**改動**：
- props 改吃 `sspUid`
- API call 改 `/ssp/<sspUid>/parties/...`
- **role dropdown** 改吃 `GET /system/menu/ssp_party_role` (C1)
- role 顯示用 `$t('lang.oscal_role.ssp_party_role.<key>.label')` (C1 i18n)
- 用 `useSspParties` composable

**Commit**：`feat(project-planning): C5 SspPartiesSection`

---

### T7 — `SspLeveragedSection.vue` 外部利用服務（FE，待 C2.2 後可實作）

**新檔**：`src/components/project/ssp/SspLeveragedSection.vue`

**Phase 1**（C5 first ship）：placeholder 顯示「尚未開放，待 C2.2」

**Phase 2**（C2.2 ship 後補）：
- DataTable: service_name / provider / purpose
- 仿 SspScopeDevicesSection pattern，API 改 `/ssp/<uid>/leveraged`

**Commit**：`feat(project-planning): C5 SspLeveragedSection placeholder`（C5 階段）/ `feat(project-planning): C5.2 SspLeveragedSection full impl`（C2.2 後）

---

### T8 — 控制項 / 程序書 link 區塊（FE）

**內容**：
- 在 SspTabPanel 底部加兩個 link button
- 「→ 前往控制項實作」→ router.push 到既有 SSP control-implementation 頁
- 「→ 前往程序書管理」→ router.push 到既有 ref-document 頁

**Commit**：`feat(project-planning): C5 控制項/程序書 link 區塊`

---

### T9 — Tab 整合 + AP selector（FE）

**檔案**：專案規劃頁 root component（pre-flight 確認檔名）

**動作**：
- 加新 tab「SSP」進入既有 TabView
- 加 AP selector dropdown
- AP 切換邏輯：清空各 section state，重 fetch
- Deep link `?tab=ssp&section=...` 處理

**Commit**：`feat(project-planning): C5 SSP tab + AP selector 整合`

---

### T10 — i18n 文案（FE）

**檔案**：
- `src/config/locales/i18n/zh-tw/project-planning.json`
- `src/config/locales/i18n/en/project-planning.json`

**新增 key**：
```json
{
  "project_planning": {
    "tab_ssp": "SSP",
    "ssp_no_active_ap": "請先啟動 AP",
    "ssp_ap_closed_hint": "此 AP 已關閉，僅可檢視",
    "ssp_historical_ap_hint": "歷史 AP，僅可檢視",
    "ssp_no_manager_permission": "需 manager 權限",
    "section": {
      "basic": "基本資料",
      "scope_devices": "受評範圍 — 設備",
      "scope_info_systems": "受評範圍 — 資訊系統",
      "leveraged": "外部利用服務",
      "parties": "參與人員",
      "controls_link": "前往控制項實作",
      "ref_docs_link": "前往程序書管理"
    }
  }
}
```

**Commit**：`feat(i18n): C5 project-planning SSP tab 文案`

---

### T11 — Manual smoke（FE）

**步驟**：
1. 建測試專案 + 啟動 AP（C2 + C3 PR1 已 ship 後）
2. 進專案規劃頁 → 點「SSP」tab
3. 預期：看到 AP selector + 五個 accordion（basic 預展開）
4. 編輯基本資料 → 儲存 → 重 fetch 看到新值
5. 展開受評範圍-設備 → 新增 device → table 內看到
6. 展開參與人員 → 新增 party + 選 OSCAL role → table 內看到
7. 切到非 active AP → 全 section readonly + hint
8. 用非 manager 帳號進來 → 編輯按鈕 disabled + tooltip
9. 從專案詳情頁點「前往 SSP 編輯」link → 跳到 SSP tab + 自動展開對應 section

---

### T12 — Changelog (FE)

**檔案**：FE repo `docs/changelog/YYYY-MM-DD-feat-project-planning-ssp-tab.md`

```markdown
---
type: feat
breaking: false
modules: [project-planning]
---

## 需求說明
C5：專案規劃頁加 SSP tab，讓 manager 在專案內直接編輯 current AP 對應的 SSP 內容。

## 變更範圍
### FE
- 5 個新 composable: useSspContext / useSspParties / useSspResources / useSspSystemCharacteristic / useSspLeveraged
- 1 個 service: SspService
- SspTabPanel root + 5 個 section components
- AP selector + 權限 / AP 狀態雙判斷 readonly mode
- Deep link `?tab=ssp&section=...` 支援
- i18n 文案

## UX
- Tab 切到 SSP 才 fetch（lazy load）
- 預設展開「基本資料」accordion
- 從專案詳情頁的範圍 card 點「前往 SSP 編輯」可直跳 + 自動展開 scope section
- 控制項實作 / 程序書連結既有頁面

## 後續
- Leveraged section 待 C2.2 後補
- Excel import 入口待 C2.2 後補
```

---

## 測試規格

| 測試 | 內容 |
|------|------|
| Manual smoke | 上面 T11 全 case |
| Manual smoke | Deep link 跳轉行為（從詳情頁 + 直接貼 URL）|
| Manual smoke | AP 切換 + lazy load 行為 |
| Manual smoke | 權限：manager / reviewer / auditor / viewer 四角色 UI 差異 |
| Vitest (if exists) | composable cache invalidation |

---

## 完成標準（DoD）

- [ ] 5 個 composable + service 完成
- [ ] SspTabPanel + 5 個 section components 完成
- [ ] AP selector + deep link 功能正常
- [ ] 權限 / AP closed readonly 模式運作正確
- [ ] i18n zh-tw + en 文案完成
- [ ] Manual smoke 全 case 通過
- [ ] Leveraged section placeholder 已放（待 C2.2 後補實作）
- [ ] Changelog 完成
- [ ] README tracker (C5) 狀態更新

---

## 風險 / Rollback

| 風險 | 緩解 |
|------|------|
| C2.1 endpoint 還沒 ship FE 撞 404 | 開工前 verify C2.1 全綠 |
| 既有 MF 元件複用後改不乾淨造成 MF 頁面 break | 複用採 **複製整檔再改 ssp 版本**，而非抽 base，MF 完全不動 |
| 大型 component 一次性 ship 撞 review block | 拆 commit by section（每個 section 一個 commit） |
| Deep link 與 router 行為衝突 | 用 `beforeEach` 或 onMounted 處理，加 unit test |

**Rollback**：FE-only，新增檔案，`git revert` 即可。專案規劃頁原本的 tab 邏輯不動。
