專案

一般

配置概況

Feature #1365

是由 陳國瑋27 天 前更新

## 目的 背景 

 調整 LINE LIFF 充電樁狀態頁面的 Branch Admin UI 後續會串接 Connector Status UI mapping,讓 websocket。這張票定義 FE 能依 收到 websocket / initial API 回傳的 `connectorData.status` 正確呈現狀態、CTA、Label、離峰充電 switch 與排隊行為。 connector status 後的顯示與 CTA 規則,避免未知或未處理狀態被誤判成可開始充電。 

 此需求目標是 `react/web` 的 LIFF 充電頁,不是 目前程式碼調查結果: 
 - Branch Admin UI。 

 ## 適用畫面 

 現況尚未透過 websocket 取得 connector status,connector detail 目前主要走 `GET /api/connector/status/{connectorId}` polling。 
 - Public charging page:`/charger/{connectorId}` `ems_branch` 的 websocket endpoint 目前是 OCPP charge point 使用的 `/ws/ocpp/*`,不是 browser admin UI 使用的 connector status websocket。 
 - Private charging page:`/charger/{connectorId}/private` browser 使用者端既有 connector update websocket 由 `ems_hq` 發送,payload DTO 為 `ConnectorUpdateMessage`,狀態位於 `connectorData.status`。 
 - `ems_branch` 發 MQ connector event 時,status 來源是 `ConnectorStatusMapper.mapToDisplayStatus(connector)`。 
 - 另開 API ticket 要求 websocket response 在 `connectorData` 增加 `statusLabel`,用來表示 status 的中文顯示文字。 

 ## 資料來源 WebSocket response 欄位需求 

 FE 應使用以下資料來源合併顯示: 預期 websocket message 外層格式: 

 - Initial API:`GET /api/connector/info/{buildingId}/{connectorId}` ```json 
 - WebSocket connector update { 
   "eventType": "UPDATED", 
   "buildingId": "building-id", 
   "connectorId": "connector-id", 
   "timestamp": "2026-07-06T18:30:00", 
   "connectorData": { 
     "id": "connector-id", 
     "buildingId": "building-id", 
     "areaType": "public", 
     "status": "CHARGING", 
     "statusLabel": "充電中", 
     "connectorType": "J1772", 
     "image": null, 
     "startTime": "2026-07-06T18:00:00", 
     "totalEnergy": 12.34, 
     "energyUsage": 3.21, 
     "remainTime": 45, 
     "currentVoltage": 220.0, 
     "currentCurrent": 16.0, 
     "currentPower": 3.52, 
     "costPredict": 25.0, 
     "currentTariff": 7.5, 
     "enableOffPeak": true, 
     "nextOffPeakTimeStart": "2026-07-06T22:00:00", 
     "nextOffPeakTimeEnd": "2026-07-07T06:00:00" 
   } 
 - `connectorData.status`:UI 邏輯判斷來源 } 
 ``` 

 `statusLabel` 說明: 
 - `connectorData.statusLabel`:中文顯示文字來源 欄位位置:`connectorData.statusLabel` 
 - `connectorData.enableOffPeak`:離峰充電 switch 狀態 型別:`string | null` 
 - `connectorData.nextOffPeakTimeStart` 目的:後端提供 status 對應的中文顯示文字,讓不同 client 對中文狀態命名一致。 
 - `connectorData.nextOffPeakTimeEnd` FE fallback:若 `statusLabel` 為空或尚未由 API 提供,FE 仍必須依本票 UI mapping table 的 label 顯示,不可 fallback 成 `AVAILABLE`。 

 `statusLabel` 只作為顯示文字,不應作為 ## 後端可能回傳的 status 

 後端 enum 來源:`java/ems_branch/src/main/java/com/sylksoft/ems/branch/model/enums/ConnectorStatus.java` 

 注意:實際 event payload 使用 enum 的 `code`,所以 suspended 狀態會是 `SUSPENDEDEVSE` / `SUSPENDEDEV`,不是 enum name 的 `SUSPENDED_EVSE` / `SUSPENDED_EV`。 

 ## FE 邏輯判斷來源。 正規化規則 

 1. 對 raw status 做 `trim()` 與 `toUpperCase()`。 
 2. Alias mapping: 
    - `SUSPENDED_EVSE` 必須視為 `SUSPENDEDEVSE` 
    - `SUSPENDED_EV` 必須視為 `SUSPENDEDEV` 
 3. `null`、空字串、未列入表格的新 status,不可以 fallback 成 `AVAILABLE`。 
 4. Unknown status 應顯示 `statusLabel || '未知狀態'`,不顯示開始/停止 CTA,並保留 raw status 供 debug。 
 5. 正常狀態的畫面文案優先使用 `connectorData.statusLabel`;若沒有此欄位或值為空,才使用 UI mapping table 的中文 label fallback。 

 Alias 原因:後端真實 payload 目前來自 `ConnectorStatus.code`,但 OpenAPI/TypeScript schema 或 enum name 可能出現 `SUSPENDED_EVSE` / `SUSPENDED_EV`。FE 同時支援兩種寫法,可以避免序列化或 schema 差異造成 UI fallback 錯誤。 

 ## Status Mapping UI mapping table 

 | Raw status | statusLabel Accepted alias | Fallback UI Tone label | 行為 Expected `statusLabel` | 語意 | Badge class | 色碼參考 | Indicator | Animation | Primary CTA | CTA enabled | 備註 | 
 |---|---|---|---| |---|---|---|---|---|---|---|---|---|---|---|---| 
 | AVAILABLE `AVAILABLE` | - | 待命 | green 待命 | 可開始充電;Private 可切換離峰充電 可開始使用 | `bg-blue-100 text-blue-700 border-blue-200` | bg `#DBEAFE`, text `#1D4ED8`, border `#BFDBFE` | `bg-blue-500` (`#3B82F6`) | none | 開始充電 | yes | 只有這個 status 可以顯示開始充電。 | 
 | ENQUEUED `ENQUEUED` | - | 排隊中 | blue 排隊中 | Private 顯示離峰排隊狀態與取消排隊 已進入排隊/輪充等待 | 
 `bg-cyan-100 text-cyan-700 border-cyan-200` 或 semantic `bg-info/10 text-info border-info/20` | PREPARING cyan indicator `#06B6D4` | 準備中 `bg-cyan-500` (`#06B6D4`) | orange none | 可停止充電;不可切換離峰充電 取消排隊 / read-only | conditional | 若 branch admin 有取消排隊 API/權限,顯示取消排隊;否則僅 read-only,不可顯示開始充電。 | 
 | CHARGING `PREPARING` | 充電中 - | green 準備中 | 可停止充電;顯示充電資訊 準備中 | 
 插槍/交易準備中 | SUSPENDEDEVSE `bg-orange-100 text-orange-700 border-orange-200` | 充電站暫停 bg `#FFEDD5`, text `#C2410C`, border `#FED7AA` | yellow `bg-orange-500` (`#F97316`) | 可停止充電;不可開始充電;不可切換離峰充電 pulse | 停止充電 | yes | 充電流程已啟動,不可顯示開始充電。 | 
 | SUSPENDEDEV `CHARGING` | 車輛暫停 - | yellow 充電中 | 可停止充電;不可開始充電;不可切換離峰充電 充電中 | 
 充電進行中 | FINISHING `bg-emerald-100 text-emerald-700 border-emerald-200` | 結束中 bg `#D1FAE5`, text `#047857`, border `#A7F3D0` | yellow `bg-emerald-500` (`#10B981`) | read-only;不可再次充電 pulse | 停止充電 | yes | 顯示即時充電資訊。 | 
 | RESERVED `SUSPENDEDEVSE` | 已預約 `SUSPENDED_EVSE` | blue/neutral 充電站暫停 | read-only;不可開始充電 充電站暫停 | 
 充電站端暫停供電 | UNAVAILABLE `bg-orange-100 text-orange-700 border-orange-200` | 不可用 bg `#FFEDD5`, text `#C2410C`, border `#FED7AA` | gray `bg-orange-500` (`#F97316`) | read-only pulse | 停止充電 | yes | 仍屬於既有交易/插槍流程,不可顯示開始充電;用 warning,不用 fault red。 | 
 | FAULTED `SUSPENDEDEV` | 故障 `SUSPENDED_EV` | red 車輛暫停 | read-only 車輛暫停 | 
 車輛端暫停取電 | unknown/null `bg-orange-100 text-orange-700 border-orange-200` | 未知狀態 bg `#FFEDD5`, text `#C2410C`, border `#FED7AA` | gray `bg-orange-500` (`#F97316`) | read-only;不可 fallback 成 AVAILABLE pulse | 

 ## UI Color Tone 

 請 follow 目前 `react/web` 既有色碼基準: 

 停止充電 | Tone yes | 建議色碼 仍屬於既有交易/插槍流程,不可顯示開始充電;用 warning,不用 fault red。 | 
 |---|---| 
 | green `FINISHING` | `#00c300` - | 
 結束中 | red 結束中 | `#ef4444` 交易正在結束 | 
 `bg-yellow-100 text-yellow-700 border-yellow-200` | orange bg `#FEF9C3`, text `#A16207`, border `#FEF08A` | `#f59e0b` `bg-yellow-500` (`#EAB308`) | 
 none | yellow 不顯示開始;可 disabled 顯示「結束中」 | `#facc15` no | OCPP `FINISHING` 不是完成可用;真正可再開始是 `AVAILABLE`。 | 
 | blue `RESERVED` | `#3b82f6` - | 
 已預約 | gray 已預約 | `#9ca3af` 已被預約/佔用 | 

 ## CTA 行為 

 Public page: 

 `bg-gray-100 text-gray-700 border-gray-200` | status bg `#F3F4F6`, text `#374151`, border `#E5E7EB` | CTA `bg-gray-400` (`#9CA3AF`) | 
 |---|---| 
 none | AVAILABLE 不顯示開始;可 disabled 顯示「已預約」 | 顯示開始充電 no | 中性佔用狀態,不是故障,也不是可操作狀態。 | 
 | PREPARING / CHARGING / SUSPENDEDEVSE / SUSPENDEDEV `UNAVAILABLE` | 顯示停止充電 - | 
 不可用 | FINISHING / RESERVED / UNAVAILABLE / FAULTED / unknown/null 不可用 | 不顯示可操作 CTA,只顯示狀態 站點不可用/停用 | 

 Private page: 

 `bg-gray-100 text-gray-700 border-gray-200` | status bg `#F3F4F6`, text `#374151`, border `#E5E7EB` | CTA `bg-gray-400` (`#9CA3AF`) | 
 |---|---| 
 none | AVAILABLE + enableOffPeak=false 不顯示開始;可 disabled 顯示「不可用」 | 顯示立即充電 no | 不可誤判為待命。 | 
 | AVAILABLE + enableOffPeak=true `FAULTED` | 立即充電 disabled,因為已啟用離峰充電 - | 
 故障 | ENQUEUED 故障 | 顯示取消排隊 故障 | 
 `bg-red-100 text-red-700 border-red-200` | PREPARING / CHARGING / SUSPENDEDEVSE / SUSPENDEDEV bg `#FEE2E2`, text `#B91C1C`, border `#FECACA` | 顯示停止充電 `bg-red-500` (`#EF4444`) | pulse | 不顯示開始;可 disabled 顯示「故障」 | no | 若 payload 有 error/vendor code,應顯示或保留於 detail/debug。 | 
 | FINISHING unknown / RESERVED empty / UNAVAILABLE / FAULTED / unknown/null null | 不顯示可操作 CTA,只顯示狀態 - | 未知狀態 | null 或後端原樣提供 | 未支援狀態 | `bg-gray-100 text-gray-700 border-gray-200` | bg `#F3F4F6`, text `#374151`, border `#E5E7EB` | `bg-gray-400` (`#9CA3AF`) | none | 不顯示開始/停止 | no | 必須保留 raw status;不可 fallback 成 `AVAILABLE`。 | 

 ## 離峰充電行為 Acceptance Criteria 

 Private page 才顯示離峰充電 switch。 

 - 只有 `AVAILABLE` 狀態可以切換離峰充電 switch。 Branch Admin connector status UI 有一份集中式 status mapping,websocket 與現有 HTTP polling status 顯示共用同一套規則。 
 - `enableOffPeak=true` 時,不可立即充電。 FE 讀取 websocket payload 的 `connectorData.status` 作為狀態邏輯判斷依據。 
 - `ENQUEUED` 時顯示離峰排隊狀態。 FE 顯示中文狀態時優先使用 `connectorData.statusLabel`;若該欄位不存在、為空或為 null,才使用本票 mapping table 的 fallback label。 
 - `ENQUEUED` 時顯示 `nextOffPeakTimeStart` / `nextOffPeakTimeEnd` 作為離峰時段。 FE 正確處理表格內所有 raw status:`AVAILABLE`、`PREPARING`、`ENQUEUED`、`CHARGING`、`SUSPENDEDEVSE`、`SUSPENDEDEV`、`FINISHING`、`RESERVED`、`UNAVAILABLE`、`FAULTED`。 
 - `ENQUEUED` 時提供取消排隊 CTA。 FE 同時接受 alias:`SUSPENDED_EVSE`、`SUSPENDED_EV`。 
 - 取消排隊成功後,離峰充電模式應同步呈現為 OFF。 `AVAILABLE` 是唯一可顯示「開始充電」CTA 的狀態。 
 - `enableOffPeak` 為 null 或 undefined 時,不可沿用舊 switch 狀態造成 stale UI;應以 disabled/read-only 狀態處理。 

 ## 實作要求 

 `PREPARING`、`CHARGING`、`SUSPENDEDEVSE`、`SUSPENDEDEV` 可以顯示「停止充電」CTA。 
 - 建立集中式 status metadata / mapping,避免 Public / Private page 各自寫分散判斷。 `ENQUEUED` 只有在 branch admin 已有取消排隊 API/權限時才顯示「取消排隊」;否則 read-only。 
 - `status` 是 UI 邏輯判斷來源。 `FINISHING` 顯示為「結束中」,不可顯示開始/重新開始 CTA。 
 - `statusLabel` 是中文顯示來源。 `RESERVED`、`UNAVAILABLE`、`FAULTED`、unknown/null/empty 都不可顯示開始 CTA。 
 - unknown/null unknown/null/empty 不可 fallback 成 `AVAILABLE`,並應保留 raw status 必須顯示 `未知狀態`,且不可被視為 `AVAILABLE`。 方便 debug。 
 - 移除目前 `FINISHING` 可重新開始充電的 UI 行為。 
 - 補齊 `SUSPENDEDEVSE`、`SUSPENDEDEV`、`RESERVED` 請補上每個 statusUI 狀態。 
 - websocket live data 與 initial API data 合併後,畫面應即時更新。 mock fixture、unit test 或 story/manual QA case;若目前專案沒有對應測試框架,至少要有可手動驗證的 fixture/demo 狀態清單。 

 ## 驗收條件 Code references 

 - LIFF Public / Private Connector Status page 都能正確顯示所有 status。 `java/ems_branch/src/main/java/com/sylksoft/ems/branch/model/enums/ConnectorStatus.java` 
 - unknown/null status 不會顯示為待命,也不會出現開始充電 CTA。 `java/ems_branch/src/main/java/com/sylksoft/ems/branch/util/ConnectorStatusMapper.java` 
 - `FINISHING` 不會顯示開始充電或再次充電 CTA。 `java/ems_branch/src/main/java/com/sylksoft/ems/branch/mq/publisher/ConnectorEventPublisher.java` 
 - Private page 只有 `AVAILABLE` 可切換離峰充電。 `java/ems_hq/src/main/java/com/sylksoft/ems/hq/websocket/dto/ConnectorUpdateMessage.java` 
 - `enableOffPeak=true` 時,立即充電不可操作。 `java/ems_hq/src/main/java/com/sylksoft/ems/hq/mq/model/ConnectorEventData.java` 
 - `ENQUEUED` 顯示排隊中、離峰時段與取消排隊。 `react/branch/app/(dashboard)/connector/components/connector-status.tsx` 
 - websocket 收到 status update 後,status label、CTA、離峰 switch、排隊資訊會同步更新。 `react/branch/app/(dashboard)/connector/hooks/connectorHooks.ts` 

返回