Refine scoreboard flow and update ports

This commit is contained in:
2026-04-15 22:56:50 +08:00
parent 8f4411d97e
commit 7fc8e2698b
17 changed files with 4368 additions and 294 deletions

View File

@@ -0,0 +1,196 @@
import { useNavigate } from 'react-router-dom'
import { getTeamDisplayName } from '../lib/match'
import type { LoadStatus, RoundGroup } from '../types'
type TeamSelectionPageProps = {
areaAInput: string
areaBInput: string
groups: RoundGroup[]
groupSource: 'idle' | 'db' | 'manual'
loadMessage: string
loadStatus: LoadStatus
parsedAreaA: string[]
parsedAreaB: string[]
selectedGroupId: number | null
targetDate: string
onAreaAInputChange: (value: string) => void
onAreaBInputChange: (value: string) => void
onGenerateManualGroups: () => void
onLoadGroupsFromDb: () => void
onSelectGroup: (groupId: number) => void
onTargetDateChange: (value: string) => void
onUseGroup: (groupId: number) => void
}
export function TeamSelectionPage({
areaAInput,
areaBInput,
groups,
groupSource,
loadMessage,
loadStatus,
parsedAreaA,
parsedAreaB,
selectedGroupId,
targetDate,
onAreaAInputChange,
onAreaBInputChange,
onGenerateManualGroups,
onLoadGroupsFromDb,
onSelectGroup,
onTargetDateChange,
onUseGroup,
}: TeamSelectionPageProps) {
const navigate = useNavigate()
return (
<section className="selection-shell">
<article className="panel panel-hero selection-hero">
<div>
<p className="panel-kicker"> 1</p>
<h2></h2>
<p className="panel-copy">
DB A B
</p>
</div>
<div className="summary-grid">
<article className="mini-stat">
<span>A </span>
<strong>{parsedAreaA.length}</strong>
</article>
<article className="mini-stat">
<span>B </span>
<strong>{parsedAreaB.length}</strong>
</article>
<article className="mini-stat">
<span></span>
<strong>{groups.length}</strong>
</article>
</div>
{loadMessage ? (
<p className={`status-banner status-banner-${loadStatus}`}>{loadMessage}</p>
) : null}
</article>
<article className="panel">
<div className="selection-form">
<div className="selection-toolbar">
<label className="field">
<span></span>
<input
type="date"
value={targetDate}
onChange={(event) => onTargetDateChange(event.target.value)}
/>
</label>
<div className="button-stack">
<button className="primary-button" type="button" onClick={onLoadGroupsFromDb}>
</button>
<button className="secondary-button" type="button" onClick={onGenerateManualGroups}>
</button>
</div>
</div>
<div className="double-grid">
<label className="field">
<span>A </span>
<textarea
rows={8}
value={areaAInput}
onChange={(event) => onAreaAInputChange(event.target.value)}
placeholder={'每行一隊,例如:\n柏威'}
/>
</label>
<label className="field">
<span>B </span>
<textarea
rows={8}
value={areaBInput}
onChange={(event) => onAreaBInputChange(event.target.value)}
placeholder={'每行一隊,例如:\nRURU'}
/>
</label>
</div>
<div className="selection-hint">
<span>
{groupSource === 'db' ? 'DB' : groupSource === 'manual' ? '手動配對' : '尚未載入'}
</span>
<span></span>
</div>
</div>
</article>
<article className="panel full-span">
<div className="panel-heading">
<div>
<p className="panel-kicker"> 2</p>
<h2></h2>
</div>
</div>
{groups.length === 0 ? (
<div className="empty-state">
<h3></h3>
<p></p>
</div>
) : (
<div className="group-board">
{groups.map((group) => (
<article
className={
group.id === selectedGroupId
? 'group-card group-card-active group-card-stage'
: 'group-card group-card-stage'
}
key={group.id}
>
<div className="group-head">
<div>
<p className="panel-kicker"> {group.id} </p>
<h3>{group.teams.length} </h3>
</div>
<div className="group-actions">
<button
className="secondary-button"
type="button"
onClick={() => onSelectGroup(group.id)}
>
</button>
<button
className="primary-button"
type="button"
onClick={() => {
onUseGroup(group.id)
navigate('/scoreboard')
}}
>
</button>
</div>
</div>
<div className="team-stage-grid">
{group.teams.map((team) => (
<article className="team-stage-card" key={`${group.id}-${team.id}`}>
<span className="team-index"> {team.id} </span>
<p className="team-name">{getTeamDisplayName(team)}</p>
</article>
))}
</div>
</article>
))}
</div>
)}
</article>
</section>
)
}