132 lines
4.2 KiB
TypeScript
132 lines
4.2 KiB
TypeScript
'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>
|
||
</>
|
||
)
|
||
}
|