- Remove plus/minus score controls in GameDetail; tap on score increments by +1 - Keep global Undo control for reversals; maintain keyboard accessibility on score - No changes to scoring logic beyond UI control removal
105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
import { h } from 'preact';
|
|
import { useState } from 'preact/hooks';
|
|
import styles from './GameDetail.module.css';
|
|
import type { Game, EndlosGame } from '../types/game';
|
|
|
|
interface GameDetailProps {
|
|
game: Game | undefined;
|
|
onFinishGame: () => void;
|
|
onUpdateScore: (player: number, change: number) => void;
|
|
onUpdateGame?: (game: EndlosGame) => void;
|
|
onUndo?: () => void;
|
|
onForfeit?: () => void;
|
|
onBack: () => void;
|
|
}
|
|
|
|
/**
|
|
* Game detail view for a single game.
|
|
*/
|
|
const GameDetail = ({ game, onFinishGame, onUpdateScore, onUpdateGame, onUndo, onForfeit, onBack }: GameDetailProps) => {
|
|
if (!game) return null;
|
|
|
|
const handleScoreUpdate = (playerIndex: number, change: number) => {
|
|
onUpdateScore(playerIndex, change);
|
|
// Silent update; toast notifications removed
|
|
};
|
|
|
|
|
|
const isCompleted = game.status === 'completed';
|
|
|
|
const playerNames = [game.player1, game.player2, game.player3].filter(Boolean);
|
|
const scores = [game.score1, game.score2, game.score3].filter((_, i) => playerNames[i]);
|
|
|
|
return (
|
|
<div className={styles['game-detail']}>
|
|
<div className={styles['game-title']}>
|
|
{game.gameType}{game.raceTo ? ` | Race to ${game.raceTo}` : ''}
|
|
</div>
|
|
<div className={styles['scores-container']}>
|
|
{playerNames.map((name, idx) => {
|
|
const currentScore = scores[idx];
|
|
const progressPercentage = game.raceTo ? Math.min((currentScore / game.raceTo) * 100, 100) : 0;
|
|
|
|
return (
|
|
<div
|
|
className={styles['player-score'] + (name === 'Fränky' ? ' ' + styles['franky'] : '')}
|
|
key={name + idx}
|
|
>
|
|
<span className={styles['player-name']}>
|
|
{name}
|
|
{(() => {
|
|
const order = (game as any).breakOrder as number[] | undefined;
|
|
const breakerIdx = (game as any).currentBreakerIdx as number | undefined;
|
|
if (order && typeof breakerIdx === 'number' && order[breakerIdx] === idx + 1) {
|
|
return <span title="Break" aria-label="Break" style={{ display: 'inline-block', width: '1em', height: '1em', borderRadius: '50%', background: '#fff', marginLeft: 6, verticalAlign: 'middle' }} />;
|
|
}
|
|
return null;
|
|
})()}
|
|
</span>
|
|
<div className={styles['progress-bar']}>
|
|
<div
|
|
className={styles['progress-fill']}
|
|
style={{ width: `${progressPercentage}%` }}
|
|
/>
|
|
</div>
|
|
<span
|
|
className={styles['score']}
|
|
id={`score${idx + 1}`}
|
|
onClick={() => !isCompleted && onUpdateScore(idx + 1, 1)}
|
|
onKeyDown={(e) => {
|
|
if (!isCompleted && (e.key === 'Enter' || e.key === ' ')) {
|
|
e.preventDefault();
|
|
onUpdateScore(idx + 1, 1);
|
|
}
|
|
}}
|
|
role="button"
|
|
tabIndex={isCompleted ? -1 : 0}
|
|
aria-label={`Aktueller Punktestand für ${name}: ${scores[idx]}. Klicken oder Enter drücken zum Erhöhen.`}
|
|
>
|
|
{scores[idx]}
|
|
</span>
|
|
{/* +/- buttons removed per issue #29. Tap score to +1; use Undo to revert. */}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className={styles['game-detail-controls']}>
|
|
<button className="btn" onClick={onBack} aria-label="Zurück zur Liste">Zurück zur Liste</button>
|
|
{onUndo && (
|
|
<button
|
|
className="btn btn--secondary"
|
|
onClick={() => {
|
|
onUndo();
|
|
}}
|
|
aria-label="Rückgängig"
|
|
>
|
|
↶ Rückgängig
|
|
</button>
|
|
)}
|
|
<button className="btn" disabled={isCompleted} onClick={onFinishGame} aria-label={isCompleted ? 'Abgeschlossen' : 'Spiel beenden'}>{isCompleted ? 'Abgeschlossen' : 'Spiel beenden'}</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GameDetail;
|