import { h } from 'preact'; import { useEffect, useState } from 'preact/hooks'; import modalStyles from '@lib/ui/Modal.module.css'; import styles from './GameSetupModal.module.css'; import { Button } from '@lib/ui/Button'; import { GAME_TYPES, RACE_TO_INFINITY, RACE_TO_QUICK_PICKS } from '@lib/domain/constants'; import { gameToSetupData } from '@lib/domain/gameUtils'; import { validateGameSetupData } from '@lib/domain/validation'; import type { BreakRule, GameSetupData, GameType, StandardGame } from '@lib/domain/types'; interface GameSetupModalProps { game: StandardGame; open: boolean; onSave: (setupData: GameSetupData) => void; onCancel: () => void; } type RaceToState = number | typeof RACE_TO_INFINITY; interface FormState { player1: string; player2: string; player3: string; hasPlayer3: boolean; score1: number; score2: number; score3: number; gameType: GameType; raceTo: RaceToState; raceToInput: string; breakRule: BreakRule; breakOrder: number[]; currentBreakerIdx: number; } function setupToFormState(setup: GameSetupData): FormState { const hasPlayer3 = Boolean(setup.player3?.trim()); const raceToIsInfinity = setup.raceTo === Infinity; return { player1: setup.player1, player2: setup.player2, player3: setup.player3 ?? '', hasPlayer3, score1: setup.score1, score2: setup.score2, score3: setup.score3 ?? 0, gameType: setup.gameType, raceTo: raceToIsInfinity ? RACE_TO_INFINITY : setup.raceTo, raceToInput: raceToIsInfinity ? '' : String(setup.raceTo), breakRule: setup.breakRule, breakOrder: [...setup.breakOrder], currentBreakerIdx: setup.currentBreakerIdx, }; } function parseRaceToValue(raceTo: RaceToState, raceToInput: string): number { if (raceTo === RACE_TO_INFINITY) return Infinity; if (typeof raceTo === 'number' && Number.isFinite(raceTo)) return raceTo; const parsed = parseInt(raceToInput, 10); return Number.isFinite(parsed) ? parsed : 0; } function formToSetupData(form: FormState): GameSetupData { const data: GameSetupData = { player1: form.player1, player2: form.player2, score1: form.score1, score2: form.score2, gameType: form.gameType, raceTo: parseRaceToValue(form.raceTo, form.raceToInput), breakRule: form.breakRule, breakOrder: form.breakOrder, currentBreakerIdx: form.currentBreakerIdx, }; if (form.hasPlayer3) { data.player3 = form.player3; data.score3 = form.score3; } return data; } function choiceClass(selected: boolean): string { return `${styles.choiceBtn} ${selected ? styles.choiceBtnSelected : ''}`; } export function GameSetupModal({ game, open, onSave, onCancel }: GameSetupModalProps) { const [form, setForm] = useState(() => setupToFormState(gameToSetupData(game))); const [errors, setErrors] = useState([]); useEffect(() => { if (open) { setForm(setupToFormState(gameToSetupData(game))); setErrors([]); } }, [open, game]); if (!open) return null; const activeBreakerPlayer = form.breakOrder[form.currentBreakerIdx] ?? form.breakOrder[0]; const playerLabels = [ { num: 1, name: form.player1, score: form.score1 }, { num: 2, name: form.player2, score: form.score2 }, ...(form.hasPlayer3 ? [{ num: 3, name: form.player3, score: form.score3 }] : []), ]; const updateForm = (patch: Partial) => setForm((prev) => ({ ...prev, ...patch })); const togglePlayer3 = () => { if (form.hasPlayer3) { const order = [1, 2]; let idx = form.currentBreakerIdx; const currentPlayer = form.breakOrder[idx]; if (currentPlayer === 3) idx = 0; else idx = Math.min(idx, order.length - 1); updateForm({ hasPlayer3: false, player3: '', score3: 0, breakOrder: order, currentBreakerIdx: idx, }); } else { const order = [1, 2, 3]; updateForm({ hasPlayer3: true, player3: 'Spieler 3', score3: 0, breakOrder: order, currentBreakerIdx: Math.min(form.currentBreakerIdx, order.length - 1), }); } }; const selectBreaker = (playerNum: number) => { const idx = form.breakOrder.indexOf(playerNum); if (idx >= 0) updateForm({ currentBreakerIdx: idx }); }; const handleRaceQuickPick = (value: number) => { updateForm({ raceTo: value, raceToInput: String(value) }); }; const handleRaceInfinity = () => { updateForm({ raceTo: RACE_TO_INFINITY, raceToInput: '' }); }; const handleRaceInput = (e: Event) => { const value = (e.target as HTMLInputElement).value; const parsed = parseInt(value, 10); if (value === '') { updateForm({ raceToInput: '', raceTo: 0 }); return; } if (!Number.isNaN(parsed)) { updateForm({ raceToInput: value, raceTo: parsed }); } }; const handleSave = () => { const data = formToSetupData(form); const result = validateGameSetupData(data); if (!result.isValid) { setErrors(result.errors); return; } setErrors([]); onSave(data); }; const raceToNumber = form.raceTo === RACE_TO_INFINITY ? null : typeof form.raceTo === 'number' ? form.raceTo : parseInt(form.raceToInput, 10); return (
Spiel einrichten
{playerLabels.map(({ num, name, score }) => (
Spieler {num}
{ const v = (e.target as HTMLInputElement).value; if (num === 1) updateForm({ player1: v }); else if (num === 2) updateForm({ player2: v }); else updateForm({ player3: v }); }} aria-label={`Name Spieler ${num}`} autoComplete="off" /> { const v = parseInt((e.target as HTMLInputElement).value, 10); const safe = Number.isNaN(v) ? 0 : Math.max(0, v); if (num === 1) updateForm({ score1: safe }); else if (num === 2) updateForm({ score2: safe }); else updateForm({ score3: safe }); }} aria-label={`Punkte Spieler ${num}`} />
))}
Spieltyp
{GAME_TYPES.map(({ value, label }) => ( ))}
Race To
{RACE_TO_QUICK_PICKS.map((value) => ( ))}
Break-Regel
{( [ { key: 'winnerbreak' as BreakRule, label: 'Winnerbreak' }, { key: 'wechselbreak' as BreakRule, label: 'Wechselbreak' }, ] as const ).map((opt) => ( ))}
Wer breakt?
{playerLabels.map(({ num, name }) => ( ))}
{errors.length > 0 && (
    {errors.map((err) => (
  • {err}
  • ))}
)}
); }