功能:首頁新增其他區名單,可在記分板臨時選上場但不參與配對

根本原因:
現場常有臨時來打球的人,不屬於 A、B 區的正規輪替名單;原本要讓他們上場只能把名字塞進 A 或 B 區,但這樣會被排進對戰組合,打亂正規配對。

影響:
首頁 A、B 區下方新增跨整行的「其他區名單」輸入框,內容存 localStorage 且不會被資料庫載入蓋掉;分組配對完全不使用其他區。記分板選隊伍時,其他區的人排在清單最後、名字旁掛「其他區」徽章,可手動勾選上場(含與正規名單混搭),今日場次照計;「自動選擇」不會選到他們,自動輪替仍以正規名單為準。

修法:
App 新增 areaOtherInput 狀態與 localStorage 持久化,parseRoster 後以 extraPlayers 傳入 ScoreboardPage;selectablePlayers 併入其他區人選並用 extraPlayerSet 標記,autoPickDraftPlayers 過濾掉其他區,TeamPickerModal 依標記顯示徽章;TeamSelectionPage 新增第三個輸入欄位與說明文字。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 15:22:17 +08:00
co-authored by Claude Opus 4.8
parent 710d04bffe
commit 37fa1446af
4 changed files with 91 additions and 3 deletions
+15
View File
@@ -47,6 +47,7 @@ import type {
const STORAGE_KEYS = {
areaA: 'badminton-scoreboard::area-a',
areaB: 'badminton-scoreboard::area-b',
areaOther: 'badminton-scoreboard::area-other',
history: 'badminton-scoreboard::history',
playCounts: 'badminton-scoreboard::play-counts',
} as const
@@ -123,6 +124,10 @@ function App() {
const [areaBInput, setAreaBInput] = useState(() =>
loadStoredText(STORAGE_KEYS.areaB, defaultAreaB.join('\n')),
)
// 其他區:不參與分組配對,但會帶進記分板讓臨時上場的人可以被選到。
const [areaOtherInput, setAreaOtherInput] = useState(() =>
loadStoredText(STORAGE_KEYS.areaOther, ''),
)
const [groups, setGroups] = useState<RoundGroup[]>([])
const [groupSource, setGroupSource] = useState<'idle' | 'db' | 'manual'>('idle')
const [loadStatus, setLoadStatus] = useState<LoadStatus>('idle')
@@ -163,6 +168,7 @@ function App() {
const parsedAreaA = useMemo(() => parseRoster(areaAInput), [areaAInput])
const parsedAreaB = useMemo(() => parseRoster(areaBInput), [areaBInput])
const parsedAreaOther = useMemo(() => parseRoster(areaOtherInput), [areaOtherInput])
const selectedGroup = groups.find((group) => group.id === selectedGroupId) ?? null
const leftTeam = activeMatchup.leftTeam
const rightTeam = activeMatchup.rightTeam
@@ -177,6 +183,10 @@ function App() {
window.localStorage.setItem(STORAGE_KEYS.areaB, areaBInput)
}, [areaBInput])
useEffect(() => {
window.localStorage.setItem(STORAGE_KEYS.areaOther, areaOtherInput)
}, [areaOtherInput])
useEffect(() => {
window.localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(history))
}, [history])
@@ -1068,6 +1078,7 @@ function App() {
<TeamSelectionPage
areaAInput={areaAInput}
areaBInput={areaBInput}
areaOtherInput={areaOtherInput}
groups={groups}
groupSource={groupSource}
loadMessage={loadMessage}
@@ -1075,6 +1086,7 @@ function App() {
targetDate={targetDate}
onAreaAInputChange={setAreaAInput}
onAreaBInputChange={setAreaBInput}
onAreaOtherInputChange={setAreaOtherInput}
onGenerateManualGroups={generateManualGroups}
onLoadGroupsFromDb={() => void loadGroupsFromDb()}
onTargetDateChange={setTargetDate}
@@ -1088,6 +1100,7 @@ function App() {
<TeamSelectionPage
areaAInput={areaAInput}
areaBInput={areaBInput}
areaOtherInput={areaOtherInput}
groups={groups}
groupSource={groupSource}
loadMessage={loadMessage}
@@ -1095,6 +1108,7 @@ function App() {
targetDate={targetDate}
onAreaAInputChange={setAreaAInput}
onAreaBInputChange={setAreaBInput}
onAreaOtherInputChange={setAreaOtherInput}
onGenerateManualGroups={generateManualGroups}
onLoadGroupsFromDb={() => void loadGroupsFromDb()}
onTargetDateChange={setTargetDate}
@@ -1107,6 +1121,7 @@ function App() {
element={
<ScoreboardPage
currentSelectionOrder={getSelectionOrder(leftTeam, rightTeam)}
extraPlayers={parsedAreaOther}
finishDialogError={settlement.error}
finishDialogOpen={settlement.open}
finishDialogUploading={settlement.uploading}