Implement scoresheet page with scoring logic and confirmation dialog

- Add src/game/scoring.ts with computeRunningTotals and isGameOver
- Build ScoreSheet grid: player columns, per-round scores, running totals after round 2+
- Small red "New Game" button in header top-right with full-screen confirmation overlay
- Extend PageFrame with headerAction and overlay props
- Move press animation/shadow from .pressable class to global button selector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:34:23 -05:00
parent e0a6aed4cf
commit 813d18e133
11 changed files with 237 additions and 24 deletions
+68 -5
View File
@@ -1,15 +1,78 @@
import { Fragment, useState, type CSSProperties } from 'react'
import { type GameState } from '../types'
import { computeRunningTotals, isGameOver } from '../game/scoring'
import PageFrame from '../components/PageFrame'
import PrimaryButton from '../components/PrimaryButton'
import styles from './ScoreSheet.module.css'
interface Props {
game: GameState
onScoreRound: () => void
onNewGame: () => void
}
export default function ScoreSheet({ onScoreRound, onNewGame }: Props) {
export default function ScoreSheet({ game, onScoreRound, onNewGame }: Props) {
const [confirmingNewGame, setConfirmingNewGame] = useState(false)
const totals = computeRunningTotals(game.rounds)
const gameOver = isGameOver(totals)
const newGameButton = (
<button className={styles.newGameBtn} onClick={() => setConfirmingNewGame(true)}>
New Game
</button>
)
const confirmOverlay = (
<div className={styles.confirmContent}>
<p className={styles.confirmText}>End this game?</p>
<div className={styles.confirmButtons}>
<button className={styles.cancelBtn} onClick={() => setConfirmingNewGame(false)}>
Cancel
</button>
<button className={styles.endGameBtn} onClick={onNewGame}>
End Game
</button>
</div>
</div>
)
return (
<PageFrame title="Score Sheet">
<button onClick={onScoreRound}>Score Round</button>
<button onClick={onNewGame} style={{ fontSize: '0.75em' }}>New Game</button>
<PageFrame
title="Score Sheet"
headerAction={newGameButton}
overlay={confirmingNewGame ? confirmOverlay : undefined}
footer={<PrimaryButton onClick={onScoreRound} disabled={gameOver}>Score Round</PrimaryButton>}
>
<div className={styles.tableWrapper}>
<div
className={styles.grid}
style={{ '--player-count': game.players.length } as CSSProperties}
>
<div className={styles.labelCell} />
{game.players.map((name, j) => (
<div key={j} className={styles.headerCell}>{name}</div>
))}
{game.rounds.map((roundScores, i) => (
<Fragment key={i}>
<div className={styles.labelCell}>Round {i + 1}</div>
{roundScores.map((score, j) => (
<div key={j} className={styles.scoreCell}>{score}</div>
))}
{i > 0 && (
<>
<div className={`${styles.labelCell} ${styles.divider}`} />
{totals[i].map((total, j) => (
<div key={j} className={`${styles.totalCell} ${styles.divider}`}>{total}</div>
))}
</>
)}
</Fragment>
))}
</div>
</div>
</PageFrame>
)
}
}