功能:自動選隊排除「那個」、選隊面板顯示每人今日場次

摘要:
- 記分板「自動選擇」時排除名字為「那個」的球員,手動仍可勾選
- 結算完成(上傳或不上傳)時幫上場 4 人各記一場,輪空不計
- 選球員面板每位球員名字旁顯示「今日 N 場」徽章

根本原因:
- 名單裡的「那個」是佔位用名字,自動選擇會把它當一般球員排進場
- 現場排下一場時沒有依據看誰打得多、誰打得少,容易分配不均

影響:
- src/App.tsx:新增 playCounts state 與 localStorage 持久化(跨日自動歸零)、recordPlayedMatch() 於 skipUpload 與 uploadSettledMatch 成功時計數、傳 playCounts 給記分板
- src/pages/ScoreboardPage.tsx:新增 AUTO_PICK_EXCLUDED_NAMES 常數過濾自動選擇、選隊 modal 每列加「今日 N 場」徽章
- src/App.css:徽章樣式與選項改三欄 grid(含手機版縮小字級)

修法:
- 自動選擇先以排除名單過濾 selectablePlayers 再洗牌取 4 位
- 場次以 {date, counts} 存 localStorage,載入時日期非當天即歸零
- 計數以 activeMatchup 兩隊為準,isPlaceholder 的輪空欄位跳過

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 11:18:44 +08:00
co-authored by Claude Fable 5
parent 1bc42f5bf6
commit 540050a991
3 changed files with 91 additions and 3 deletions
+61
View File
@@ -48,6 +48,7 @@ const STORAGE_KEYS = {
areaA: 'badminton-scoreboard::area-a',
areaB: 'badminton-scoreboard::area-b',
history: 'badminton-scoreboard::history',
playCounts: 'badminton-scoreboard::play-counts',
} as const
const defaultAreaA = ['柏威', '建喵', 'Yuki', '阿釧']
@@ -137,6 +138,10 @@ function App() {
const [history, setHistory] = useState<MatchHistoryItem[]>(() =>
loadStoredHistory(STORAGE_KEYS.history),
)
// 當天每位球員打過的場次(結算一次算一場),跨日自動歸零。
const [playCounts, setPlayCounts] = useState<Record<string, number>>(() =>
loadStoredPlayCounts(STORAGE_KEYS.playCounts),
)
const [settlement, setSettlement] = useState<SettlementState>({
error: '',
open: false,
@@ -176,6 +181,13 @@ function App() {
window.localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(history))
}, [history])
useEffect(() => {
window.localStorage.setItem(
STORAGE_KEYS.playCounts,
JSON.stringify({ date: formatDateInputValue(), counts: playCounts }),
)
}, [playCounts])
useEffect(() => {
if (loadStatus !== 'loaded' || !loadMessage) {
return
@@ -895,8 +907,31 @@ function App() {
}))
}
// 結算完成時幫這場上場的球員各記一場(輪空不計)。
const recordPlayedMatch = () => {
if (!leftTeam || !rightTeam) {
return
}
const playedNames = [leftTeam, rightTeam].flatMap((team) => [
team.isPlaceholderA ? null : team.playerA,
team.isPlaceholderB ? null : team.playerB,
])
setPlayCounts((current) => {
const next = { ...current }
playedNames.forEach((name) => {
if (name) {
next[name] = (next[name] ?? 0) + 1
}
})
return next
})
}
const skipUpload = () => {
void finalizeLiveRoom().finally(() => {
recordPlayedMatch()
setSettlement({
error: '',
open: false,
@@ -946,6 +981,7 @@ function App() {
}
setHistory((current) => [historyItem, ...current])
recordPlayedMatch()
await finalizeLiveRoom()
setSettlement({
error: '',
@@ -1081,6 +1117,7 @@ function App() {
nextMatchSignal={nextMatchSignal}
rightTeam={rightTeam}
scoreState={scoreState}
playCounts={playCounts}
selectedGroup={selectedGroup}
streakAnnouncement={streakAnnouncement}
victoryAnnouncement={victoryAnnouncement}
@@ -1264,6 +1301,30 @@ function loadStoredHistory(storageKey: string) {
}
}
// 讀取當天的球員場次紀錄,日期不是今天就重新歸零。
function loadStoredPlayCounts(storageKey: string): Record<string, number> {
const value = window.localStorage.getItem(storageKey)
if (!value) {
return {}
}
try {
const parsed = JSON.parse(value) as {
date?: string
counts?: Record<string, number>
}
if (parsed.date !== formatDateInputValue() || !parsed.counts) {
return {}
}
return parsed.counts
} catch {
return {}
}
}
function buildLiveRoomPayload({
groupId,
leftTeam,