補上歷史戰績列表與 NAS 部署說明
This commit is contained in:
@@ -1,49 +1,162 @@
|
||||
import type { MatchHistoryItem } from '../types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { loadHistoryList } from '../lib/api'
|
||||
import type { HistoryListItem } from '../types'
|
||||
|
||||
type HistoryPageProps = {
|
||||
history: MatchHistoryItem[]
|
||||
}
|
||||
export function HistoryPage() {
|
||||
const [history, setHistory] = useState<HistoryListItem[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [selectedItem, setSelectedItem] = useState<HistoryListItem | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
|
||||
const run = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const nextHistory = await loadHistoryList()
|
||||
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
setHistory(nextHistory)
|
||||
} catch (fetchError) {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
setError(fetchError instanceof Error ? fetchError.message : '無法讀取歷史戰績。')
|
||||
} finally {
|
||||
if (active) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run()
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
export function HistoryPage({ history }: HistoryPageProps) {
|
||||
return (
|
||||
<section className="page-grid">
|
||||
<article className="panel panel-hero">
|
||||
<p className="panel-kicker">History</p>
|
||||
<h2>歷史戰績</h2>
|
||||
<p className="panel-copy">這裡會顯示本機目前這次操作中,已經成功上傳到 DB 的比賽結果。</p>
|
||||
</article>
|
||||
<>
|
||||
<section className="page-grid">
|
||||
<article className="panel panel-hero">
|
||||
<p className="panel-kicker">History</p>
|
||||
<h2>歷史戰績</h2>
|
||||
<p className="panel-copy">
|
||||
這裡直接顯示 DB 的 `history` 列表。點任一筆戰績,可快速查看該場的得分紀錄。
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="panel full-span">
|
||||
{history.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<h3>目前還沒有戰績</h3>
|
||||
<p>完成比賽結算並上傳到 DB 後,這裡就會看到紀錄。</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="history-list">
|
||||
{history.map((item) => (
|
||||
<article className="history-card" key={item.id}>
|
||||
<div className="history-head">
|
||||
<div>
|
||||
<p className="panel-kicker">{item.playedAt}</p>
|
||||
<h3>
|
||||
{item.leftTeamName} vs {item.rightTeamName}
|
||||
</h3>
|
||||
<article className="panel full-span">
|
||||
{loading ? (
|
||||
<div className="empty-state">
|
||||
<h3>正在讀取戰績</h3>
|
||||
<p>請稍候一下,正在從 DB 載入列表。</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="empty-state">
|
||||
<h3>讀取失敗</h3>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
) : history.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<h3>目前沒有戰績</h3>
|
||||
<p>DB 的 `history` 資料表目前沒有可顯示的紀錄。</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="history-list">
|
||||
{history.map((item) => (
|
||||
<button
|
||||
className="history-card history-card-button"
|
||||
key={item.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedItem(item)}
|
||||
>
|
||||
<div className="history-head">
|
||||
<div>
|
||||
<p className="panel-kicker">
|
||||
{item.playedAt} / {item.dayLabel}
|
||||
</p>
|
||||
<h3>
|
||||
{item.leftTeamName} vs {item.rightTeamName}
|
||||
</h3>
|
||||
</div>
|
||||
<span className="winner-badge">勝方:{item.winnerTeamName}</span>
|
||||
</div>
|
||||
<span className="winner-badge">勝方:{item.winner}</span>
|
||||
</div>
|
||||
|
||||
<div className="history-meta">
|
||||
<span>比賽日期:{item.matchDate || '-'}</span>
|
||||
<span>資料來源:{item.source === 'db' ? 'DB' : item.source === 'manual' ? '手動' : '-'}</span>
|
||||
<span>第 {item.groupId} 組</span>
|
||||
<span>比分:{item.scoreLeft} - {item.scoreRight}</span>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</section>
|
||||
<div className="history-meta">
|
||||
<span>比分:{item.score[0]} - {item.score[1]}</span>
|
||||
<span>類型:{item.typeLabel}</span>
|
||||
<span>勝利分數:{item.winScore}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{selectedItem ? (
|
||||
<HistoryReplayModal item={selectedItem} onClose={() => setSelectedItem(null)} />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type HistoryReplayModalProps = {
|
||||
item: HistoryListItem
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
function HistoryReplayModal({ item, onClose }: HistoryReplayModalProps) {
|
||||
return (
|
||||
<div className="history-modal-overlay" role="presentation" onClick={onClose}>
|
||||
<div aria-modal="true" className="history-modal" role="dialog">
|
||||
<p className="panel-kicker">點任意位置關閉</p>
|
||||
<h3>
|
||||
{item.leftTeamName} vs {item.rightTeamName}
|
||||
</h3>
|
||||
|
||||
<div className="history-modal-score">
|
||||
<div>
|
||||
<strong>{item.score[0]}</strong>
|
||||
<span>{item.leftTeamName}</span>
|
||||
</div>
|
||||
<div className="history-modal-score-divider">:</div>
|
||||
<div>
|
||||
<strong>{item.score[1]}</strong>
|
||||
<span>{item.rightTeamName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="history-modal-summary">
|
||||
<span>{item.playedAt}</span>
|
||||
<span>{item.typeLabel}</span>
|
||||
<span>勝方:{item.winnerTeamName}</span>
|
||||
</div>
|
||||
|
||||
<div className="history-replay-list">
|
||||
{item.scoreList.length === 0 ? (
|
||||
<p className="history-replay-empty">這筆資料沒有得分過程。</p>
|
||||
) : (
|
||||
item.scoreList.map(([round, starter, winCount, winner]) => (
|
||||
<div className="history-replay-row" key={`${item.id}-${round}`}>
|
||||
<span>第 {round + 1} 球</span>
|
||||
<span>發球 #{starter + 1}</span>
|
||||
<span>連得 {winCount + 1}</span>
|
||||
<strong>{winner === 0 ? item.leftTeamName : item.rightTeamName}</strong>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user