精簡選隊伍頁提示並更新 README
This commit is contained in:
31
src/App.css
31
src/App.css
@@ -178,6 +178,37 @@
|
||||
background: rgba(255, 214, 224, 0.94);
|
||||
}
|
||||
|
||||
.floating-status-bubble {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 60;
|
||||
transform: translate(-50%, -50%);
|
||||
min-width: min(78vw, 320px);
|
||||
max-width: min(84vw, 420px);
|
||||
padding: 14px 20px;
|
||||
border-radius: 999px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #f8fff9;
|
||||
background: linear-gradient(135deg, rgba(8, 47, 73, 0.96), rgba(10, 96, 84, 0.92));
|
||||
box-shadow: 0 18px 40px rgba(8, 47, 73, 0.28);
|
||||
pointer-events: none;
|
||||
animation: status-bubble-in 0.18s ease-out;
|
||||
}
|
||||
|
||||
@keyframes status-bubble-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, calc(-50% + 10px)) scale(0.96);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.selection-shell,
|
||||
.selection-form,
|
||||
.group-board {
|
||||
|
||||
16
src/App.tsx
16
src/App.tsx
@@ -113,6 +113,18 @@ function App() {
|
||||
window.localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(history))
|
||||
}, [history])
|
||||
|
||||
useEffect(() => {
|
||||
if (loadStatus !== 'loaded' || !loadMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
setLoadMessage('')
|
||||
}, 500)
|
||||
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [loadMessage, loadStatus])
|
||||
|
||||
const resetScoring = (nextState: ScoreState = initialScoreState) => {
|
||||
setScoreState(nextState)
|
||||
setScoreHistory([])
|
||||
@@ -450,8 +462,6 @@ function App() {
|
||||
groupSource={groupSource}
|
||||
loadMessage={loadMessage}
|
||||
loadStatus={loadStatus}
|
||||
parsedAreaA={parsedAreaA}
|
||||
parsedAreaB={parsedAreaB}
|
||||
selectedGroupId={selectedGroupId}
|
||||
targetDate={targetDate}
|
||||
onAreaAInputChange={setAreaAInput}
|
||||
@@ -474,8 +484,6 @@ function App() {
|
||||
groupSource={groupSource}
|
||||
loadMessage={loadMessage}
|
||||
loadStatus={loadStatus}
|
||||
parsedAreaA={parsedAreaA}
|
||||
parsedAreaB={parsedAreaB}
|
||||
selectedGroupId={selectedGroupId}
|
||||
targetDate={targetDate}
|
||||
onAreaAInputChange={setAreaAInput}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getTeamDisplayName } from '../lib/match'
|
||||
import type { LoadStatus, RoundGroup } from '../types'
|
||||
|
||||
@@ -9,8 +9,6 @@ type TeamSelectionPageProps = {
|
||||
groupSource: 'idle' | 'db' | 'manual'
|
||||
loadMessage: string
|
||||
loadStatus: LoadStatus
|
||||
parsedAreaA: string[]
|
||||
parsedAreaB: string[]
|
||||
selectedGroupId: number | null
|
||||
targetDate: string
|
||||
onAreaAInputChange: (value: string) => void
|
||||
@@ -29,8 +27,6 @@ export function TeamSelectionPage({
|
||||
groupSource,
|
||||
loadMessage,
|
||||
loadStatus,
|
||||
parsedAreaA,
|
||||
parsedAreaB,
|
||||
selectedGroupId,
|
||||
targetDate,
|
||||
onAreaAInputChange,
|
||||
@@ -41,41 +37,21 @@ export function TeamSelectionPage({
|
||||
onTargetDateChange,
|
||||
onUseGroup,
|
||||
}: TeamSelectionPageProps) {
|
||||
const navigate = useNavigate()
|
||||
const hasGroups = groups.length > 0
|
||||
const showInlineStatus = loadStatus !== 'idle' && loadStatus !== 'loaded' && Boolean(loadMessage)
|
||||
const sourceLabel =
|
||||
groupSource === 'db' ? '資料庫載入' : groupSource === 'manual' ? '手動產生' : '尚未建立'
|
||||
|
||||
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>
|
||||
<section className="page-grid">
|
||||
{loadStatus === 'loaded' && loadMessage ? (
|
||||
<div className="floating-status-bubble" role="status" aria-live="polite">
|
||||
{loadMessage}
|
||||
</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>
|
||||
) : null}
|
||||
|
||||
<article className="panel">
|
||||
<div className="selection-form">
|
||||
<div className="selection-shell">
|
||||
<div className="selection-toolbar">
|
||||
<label className="field">
|
||||
<span>指定日期</span>
|
||||
@@ -88,74 +64,64 @@ export function TeamSelectionPage({
|
||||
|
||||
<div className="button-stack">
|
||||
<button className="primary-button" type="button" onClick={onLoadGroupsFromDb}>
|
||||
讀取指定日期
|
||||
從資料庫讀取
|
||||
</button>
|
||||
<button className="secondary-button" type="button" onClick={onGenerateManualGroups}>
|
||||
手動產生配對
|
||||
手動產生分組
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showInlineStatus ? (
|
||||
<div className={`status-banner status-banner-${loadStatus}`}>{loadMessage}</div>
|
||||
) : null}
|
||||
|
||||
<div className="double-grid">
|
||||
<label className="field">
|
||||
<span>A 區名單</span>
|
||||
<textarea
|
||||
rows={8}
|
||||
placeholder="每行一位球員"
|
||||
value={areaAInput}
|
||||
onChange={(event) => onAreaAInputChange(event.target.value)}
|
||||
placeholder={'每行一隊,例如:\n柏威'}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
<span>B 區名單</span>
|
||||
<textarea
|
||||
rows={8}
|
||||
placeholder="每行一位球員"
|
||||
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>
|
||||
<span>分組來源:{sourceLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article className="panel full-span">
|
||||
<div className="panel-heading">
|
||||
<article className="panel">
|
||||
<div className="group-head">
|
||||
<div>
|
||||
<p className="panel-kicker">步驟 2</p>
|
||||
<h2>選擇第幾組帶進記分板</h2>
|
||||
<p className="panel-kicker">Step 2</p>
|
||||
<h2>選擇要上場的組別</h2>
|
||||
</div>
|
||||
{selectedGroupId ? <span className="winner-badge">目前第 {selectedGroupId} 組</span> : null}
|
||||
</div>
|
||||
|
||||
{groups.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<h3>目前還沒有分組</h3>
|
||||
<p>先讀取指定日期資料,或手動輸入名單後產生配對。</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="group-board">
|
||||
{groups.map((group) => (
|
||||
<div className="group-board">
|
||||
{hasGroups ? (
|
||||
groups.map((group) => (
|
||||
<article
|
||||
className={
|
||||
group.id === selectedGroupId
|
||||
? 'group-card group-card-active group-card-stage'
|
||||
: 'group-card group-card-stage'
|
||||
}
|
||||
key={group.id}
|
||||
className={`group-card ${selectedGroupId === group.id ? 'group-card-active' : ''}`}
|
||||
>
|
||||
<div className="group-head">
|
||||
<div>
|
||||
<p className="panel-kicker">第 {group.id} 組</p>
|
||||
<h3>{group.teams.length} 隊可選</h3>
|
||||
<h3>本組對戰名單</h3>
|
||||
</div>
|
||||
<div className="group-actions">
|
||||
<button
|
||||
@@ -163,33 +129,31 @@ export function TeamSelectionPage({
|
||||
type="button"
|
||||
onClick={() => onSelectGroup(group.id)}
|
||||
>
|
||||
先選這組
|
||||
</button>
|
||||
<button
|
||||
className="primary-button"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onUseGroup(group.id)
|
||||
navigate('/scoreboard')
|
||||
}}
|
||||
>
|
||||
帶進記分板
|
||||
選擇這組
|
||||
</button>
|
||||
<Link className="primary-button inline-link" to="/scoreboard" onClick={() => onUseGroup(group.id)}>
|
||||
進入記分板
|
||||
</Link>
|
||||
</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 key={`${group.id}-${team.id}`} className="team-stage-card">
|
||||
<span className="team-index">隊伍 {team.id}</span>
|
||||
<div className="team-name">{getTeamDisplayName(team)}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
))
|
||||
) : (
|
||||
<div className="empty-state">
|
||||
<h3>目前還沒有分組</h3>
|
||||
<p>先從資料庫讀取指定日期,或輸入 A、B 區名單後手動建立。</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user