Files
2026-03-18 13:34:41 +01:00

132 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useRef, useState } from 'react'
import { Button } from '@/components/ui/button'
import type { Hole, Player } from '@/app/(app)/rounds/[id]/scorecard/page'
type Props = {
open: boolean
hole: Hole | null
player: Player | null
currentStrokes: number | null
onSave: (strokes: number) => void
onClose: () => void
}
export function ScoreEntrySheet({ open, hole, player, currentStrokes, onSave, onClose }: Props) {
const [strokes, setStrokes] = useState<number>(currentStrokes ?? hole?.par ?? 4)
const [editingNumber, setEditingNumber] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
// Reset when a new entry opens
useEffect(() => {
if (open) {
setStrokes(currentStrokes ?? hole?.par ?? 4)
setEditingNumber(false)
}
}, [open, hole?.id, player?.userId])
useEffect(() => {
if (editingNumber) inputRef.current?.select()
}, [editingNumber])
// Lock body scroll when open
useEffect(() => {
document.body.style.overflow = open ? 'hidden' : ''
return () => { document.body.style.overflow = '' }
}, [open])
if (!hole || !player) return null
const par = hole.par
const vsPar = strokes - par
return (
<>
{/* Backdrop */}
<div
className={`fixed inset-0 z-40 bg-black/50 transition-opacity duration-200 ${
open ? 'opacity-100' : 'pointer-events-none opacity-0'
}`}
onClick={onClose}
/>
{/* Sheet */}
<div
className={`fixed bottom-0 left-0 right-0 z-50 rounded-t-2xl bg-background pb-safe transition-transform duration-300 ${
open ? 'translate-y-0' : 'translate-y-full'
}`}
>
{/* Handle */}
<div className="flex justify-center pt-3 pb-1">
<div className="h-1 w-10 rounded-full bg-muted-foreground/30" />
</div>
{/* Player + hole info */}
<div className="flex items-center justify-between px-6 py-3 border-b">
<div>
<div className="font-semibold">{player.profile?.displayName}</div>
<div className="text-sm text-muted-foreground">
Hole {hole.holeNumber} · Par {par} · SI {hole.strokeIndex}
</div>
</div>
<div
className={`text-sm font-bold ${
vsPar < 0 ? 'text-red-500' : vsPar > 0 ? 'text-muted-foreground' : 'text-foreground'
}`}
>
{vsPar === 0 ? 'Par' : vsPar > 0 ? `+${vsPar}` : vsPar}
</div>
</div>
{/* Score input */}
<div className="flex items-center justify-center gap-8 py-8">
{/* Minus */}
<button
onClick={() => setStrokes(Math.max(1, strokes - 1))}
className="flex h-14 w-14 items-center justify-center rounded-full border-2 border-border text-2xl font-light hover:bg-muted active:scale-95"
>
</button>
{/* Score display / input */}
{editingNumber ? (
<input
ref={inputRef}
type="number"
value={strokes}
min={1}
max={20}
onChange={(e) => setStrokes(Math.max(1, parseInt(e.target.value) || 1))}
onBlur={() => setEditingNumber(false)}
className="h-20 w-20 rounded-xl border-2 border-primary bg-background text-center text-4xl font-bold outline-none"
/>
) : (
<button
onClick={() => setEditingNumber(true)}
className="flex h-20 w-20 items-center justify-center rounded-xl border-2 border-transparent text-5xl font-bold transition-colors hover:border-primary"
>
{strokes}
</button>
)}
{/* Plus */}
<button
onClick={() => setStrokes(Math.min(20, strokes + 1))}
className="flex h-14 w-14 items-center justify-center rounded-full border-2 border-border text-2xl font-light hover:bg-muted active:scale-95"
>
+
</button>
</div>
{/* Save */}
<div className="px-6 pb-8">
<Button className="w-full text-base h-12" onClick={() => onSave(strokes)}>
Save
</Button>
</div>
</div>
</>
)
}