# T515 — fe-service-profile-tab-brands

> Status: `done`  
> Milestone: `M1`  
> Kind: `feat`  
> Est. change: `~180 lines`

## Goal

完成後系統應：可完整編修工作區共用服務 Policy 並存檔；目前入口已依實際使用回饋拆到獨立 `/app/policy`，不再混在品牌／案例管理。

## Depends on

- T502、T511

## Inputs

- Spec：`../spec.md` §4.1、§5.3
- 既有：`apps/web/src/pages/BrandsPage.tsx`、`src/components/ui/`、`src/styles/radar.css`

## Outputs

### 程式變更（預期路徑）

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/web/src/pages/PolicyPage.tsx` | add | 獨立 Policy 頁，掛入服務檔案面板 |
| `apps/web/src/pages/BrandsPage.tsx` | edit | 移除內嵌面板；舊查詢參數轉址 |
| `apps/web/src/lib/nav.ts` | edit | 新增「商機政策」入口 |
| `apps/web/src/components/radar/ServiceProfileForm.tsx` | add | 服務項目＋價格區間、案例、不能說的內容、FAQ、服務地區＋可遠端、空檔、語氣備註 |
| `apps/web/src/data/live/radarRepos.ts` | edit | service-profile GET／PUT 實作 |
| `apps/web/src/styles/radar.css` | edit | 表單樣式 |

### 行為變更

- 尚未建立時顯示引導文案與必填提示；存檔成功後顯示更新時間。
- 「不能說的內容」明示用途：會硬性過濾 AI 回覆用詞。
- 驗證錯誤就地顯示後端 message，不吞錯。

## Out of scope

- 雷達訂閱管理（T516）
- 重新設計 Policy 資料模型或改成每品牌一份

## Acceptance

- [x] 填寫→存檔→重載欄位一致（mock 與 live 皆可）
- [x] 新樣式只進 `radar.css`
- [x] 指令：

```bash
cd apps/web && npm run build && npm run check:tokens && npm run test && npm run lint
```

## Notes

原先放在 `/app/brands`；2026-08-12 依使用回饋改為獨立 `/app/policy`。舊 `?tab=serviceProfile` 保留轉址，既有資料與 GET／PUT 契約不變。

### 實作落點

- `apps/web/src/components/radar/ServiceProfileForm.tsx`：服務項目（可增刪）＋價格區間、服務區域 chip＋可遠端、禁語（一行一條）、案例、FAQ、可接案時間、口吻備註。
- `apps/web/src/components/radar/serviceAreas.ts`：22 個縣市代碼表，與後端 `radar/domain/service_profile.go` 的 `serviceAreaCodes` 一致；後端會拒絕表外代碼，所以不自行擴充。
- `apps/web/src/pages/BrandsPage.tsx`：只保留品牌庫與產品；舊 `serviceProfile` 查詢參數轉到獨立 Policy 頁。
- `apps/web/src/pages/PolicyPage.tsx`：目前獨立入口；`BrandsPage` 只維護品牌／產品，舊服務檔案分頁連結自動轉址。
- `apps/web/src/styles/radar.css`：`.hb-radar-row`／`.hb-radar-area-grid`／`.hb-radar-chip.is-active`／`.hb-radar-check`／`.hb-radar-actions`。

### 決策

- **驗證錯誤顯示後端原句**：`apiErrors.ts` 把 `400100` 加進「優先顯示後端 message」的碼單。後端會回 `services[0].name required` 這種指到欄位的訊息，比通用句有用；訊息太短（<24 字元且非中文）時仍退回語言包。
- **價格留空 = 面議**：空字串轉 `undefined` 而非 `0`，否則後端會收到「免費」。
- **禁語一行一條**：禁語本身可能含逗號，用換行切比逗號安全。
- 測試 `ServiceProfileForm.test.tsx` 蓋：首次引導文案、填寫→存檔→重載一致、空價格不送 0、後端驗證訊息原樣顯示。
