# 羽毛球記分板

這是一個使用 `Vite + React + TypeScript + Node.js` 製作的羽毛球記分板專案，提供手機優先的記分介面、歷史戰績、房間觀戰、語音播報、PWA 安裝，以及 Docker / NAS 部署方式。

## 功能特色

- 選隊伍頁面
  - 可依指定日期從資料庫讀取分組資料。
  - 若當天沒有資料，可手動輸入 A、B 區名單建立配對。
  - 點進記分板時會直接帶入該組對戰。
- 記分板
  - 兩隊隊員可自由交換上下、左右位置。
  - 畫面編號固定為左上 `1`、右上 `2`、右下 `3`、左下 `4`。
  - 先攻只能在開局設定一次，之後不會跟著發球權改變。
  - 點擊分數直接加分，沒有加一減一按鈕。
  - 第一分開始後，`設定隊伍` 會改成 `上一步`。
  - `比賽結算` 需要長按 `1` 秒才會觸發，避免誤觸。
  - 達標分數後有獲勝動畫與結算流程。
  - 手機上會盡量壓縮成單頁滿版，避免上下滑動。
- 羽球規則
  - 預設 `21` 分制，可在設定隊伍時調整目標分數。
  - 支援 Deuce：`20:20` 後需領先 `2` 分才獲勝。
  - `29:29` 時第 `30` 分直接獲勝。
  - 發球方依羽球規則處理，`0` 分在右發球區。
  - 畫面以下方隊伍為我方、上方隊伍為對方。
  - 上方隊伍採鏡像顯示，所以我方 `0:0` 在右邊發球時，對方會在左邊接發。
- 語音播報
  - 只在按下加分當下播報，不會因復原或其他操作重複報分。
  - 可選擇是否播報得分與發球者。
  - 同隊連續得分才會播報 `換邊發球`。
  - 支援調整語速，最高可到 `10x`。
  - `RURU` 會做大小寫無關判斷並以指定發音播報。
- 歷史戰績
  - 可從資料庫讀取歷史列表。
  - 點開單筆可查看得分過程。
  - 每筆資料可刪除，刪除前會顯示確認提示。
- 房間觀戰
  - 記分板帶入隊伍後會自動建立房間。
  - 房間列表可查看目前直播中的比賽。
  - 觀戰者只能看，不能操作記分。
  - 分數、房間狀態、比賽結算會即時同步給觀戰者。
  - 房間失效、重整、重選隊伍後也會通知觀戰者。
  - 房間列表有 `重新取得列表`，並帶有 `5` 秒冷卻。
- PWA
  - 可安裝到 iPhone / iPad / Android 主畫面。
  - 支援 Web App 模式啟動。
  - 新版本部署後會提示重新整理或重新安裝。

## 本機開發

### Port

- Client: `3501`
- Server: `8788`

### 安裝

```bash
npm install
```

### 啟動開發模式

```bash
npm run dev
```

啟動後：

- 前端：`http://localhost:3501`
- API：`http://localhost:8788`

### 檢查

```bash
npm run lint
npm run build
```

## 記分板滿版模式

- 記分板頁面會套用 `100dvh` 高度。
- 手機進入記分板時會關閉頁面捲動與 overscroll。
- `viewport` 已加上 `viewport-fit=cover`，較能貼合 iPhone / iPad 安全區。
- 若手機高度較矮，會再縮小字級、按鈕與分數區，盡量維持整頁顯示。

## 環境變數

請先建立 `.env`：

```env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_DATABASE=badminton
DB_TABLE=badminton
DB_HISTORY_TABLE=history
PORT=8788
```

## Docker / NAS 部署

對外服務配置：

- 容器內 Node / API：`8788`
- 對外 HTTPS 網址：`3501`

部署指令：

```bash
sudo docker compose up -d --build
```

部署完成後可用：

```text
https://你的網域或 NAS IP:3501
```

每次執行 `sudo docker compose up -d --build` 都會重新建置前後端與 PWA 靜態資產。

## SSL 憑證

Docker Compose 會掛載以下目錄：

```text
/volume1/docker/certs/
```

需包含：

- `cert.pem`
- `chain.pem`
- `privkey.pem`

nginx 容器會監看這個目錄，更新憑證檔案後會自動重新載入，不需要重啟容器。

### 憑證到期偵測

- 後端會讀取 `cert.pem` 的到期日，透過 `/api/version` 與 `/api/health` 回報給前端。
- 前端會把到期日存在 localStorage；當 API 出現 `Failed to fetch` 時，若存下來的到期日已過，會明確顯示「SSL 憑證已於某日過期」而不是通用錯誤。
- 憑證到期前 `7` 天，頁面會跳出提醒更新憑證的通知。

## 資料表格式

### `history`

- `id`
- `time`
- `dayOfWeek`
- `score`
- `winScore`
- `type`
  - `0`: 雙打
  - `1`: 單打
- `players`
  - 依照 `1 ~ 4` 固定編號順序儲存玩家名單。
- `team`
  - `1` 跟 `2` 一隊
  - `3` 跟 `4` 一隊
- `scoreList`
  - 格式：`[round, 發球者編號, 連勝數, 得分隊伍(0 或 1)]`

## PWA Icon

目前使用：

- `public/favicon.png`
- `public/apple-touch-icon.png`
- `public/pwa-192.png`
- `public/pwa-512.png`

## Git 中文顯示

若要讓 git log / commit 顯示中文，建議設定：

```bash
git config i18n.commitEncoding utf-8
git config i18n.logOutputEncoding utf-8
git config core.quotepath false
```
