Initial commit v1

This commit is contained in:
Rolf
2026-03-18 12:37:17 +01:00
commit 4897d3003f
55 changed files with 14241 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
'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?.user_id])
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?.display_name}</div>
<div className="text-sm text-muted-foreground">
Hole {hole.hole_number} · Par {par} · SI {hole.stroke_index}
</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>
</>
)
}