'use client' import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { createClient } from '@/lib/supabase/client' import { upsertScore } from '@/app/actions/scores' import { strokesReceived } from '@/lib/handicap' import { HoleView } from './hole-view' import { FullGrid } from './full-grid' import { LeaderboardView } from './leaderboard-view' import { ScoreEntrySheet } from './score-entry-sheet' import { ChevronLeft } from 'lucide-react' import type { Hole, Player, ScoreRow, RoundInfo } from '@/app/(app)/rounds/[id]/scorecard/page' type View = 'hole' | 'grid' | 'leaderboard' type EntryState = { player: Player hole: Hole } function buildScoreMap(rows: ScoreRow[]): Record { return Object.fromEntries(rows.map((r) => [`${r.player_id}-${r.hole_id}`, r.strokes])) } export function ScorecardView({ round, holes, players, initialScores, currentUserId, }: { round: RoundInfo holes: Hole[] players: Player[] initialScores: ScoreRow[] currentUserId: string }) { const router = useRouter() const [scores, setScores] = useState>(buildScoreMap(initialScores)) const [activeHoleIdx, setActiveHoleIdx] = useState(0) const [view, setView] = useState('hole') const [entry, setEntry] = useState(null) const [saving, setSaving] = useState(false) const isActive = round.status === 'active' const activeHole = holes[activeHoleIdx] // ── Realtime subscription ────────────────────────────────── useEffect(() => { const supabase = createClient() const channel = supabase .channel(`scores:${round.id}`) .on( 'postgres_changes', { event: '*', schema: 'public', table: 'scores', filter: `round_id=eq.${round.id}`, }, (payload) => { if (payload.eventType === 'DELETE') return const row = payload.new as ScoreRow setScores((prev) => ({ ...prev, [`${row.player_id}-${row.hole_id}`]: row.strokes, })) }, ) .subscribe() return () => { supabase.removeChannel(channel) } }, [round.id]) // ── Running totals per player ────────────────────────────── const runningTotals = useMemo(() => { const totals: Record = {} for (const player of players) { let net = 0 let gross = 0 let played = 0 const ch = player.course_handicap ?? 0 for (const hole of holes) { const g = scores[`${player.user_id}-${hole.id}`] if (g !== undefined) { gross += g net += g - strokesReceived(ch, hole.stroke_index, holes.length) played++ } } totals[player.user_id] = { net, gross, holesPlayed: played } } return totals }, [scores, players, holes]) // ── Score save with optimistic update ───────────────────── async function handleSave(strokes: number) { if (!entry) return setSaving(true) const key = `${entry.player.user_id}-${entry.hole.id}` const prev = scores[key] // Optimistic update setScores((s) => ({ ...s, [key]: strokes })) setEntry(null) const result = await upsertScore(round.id, entry.hole.id, entry.player.user_id, strokes) if (result.error) { // Revert setScores((s) => { const next = { ...s } if (prev === undefined) delete next[key] else next[key] = prev return next }) } setSaving(false) } function openEntry(player: Player, hole?: Hole) { if (!isActive) return setEntry({ player, hole: hole ?? activeHole }) } return (
{/* Header */}
{round.name ?? round.course?.name ?? 'Scorecard'}
{round.tee?.name} tees · {round.holes_count} holes {!isActive && ( (completed) )}
{saving && ( Saving… )}
{/* View tabs */}
{(['hole', 'grid', 'leaderboard'] as View[]).map((v) => ( ))}
{/* Main content */}
{view === 'hole' && activeHole && ( setActiveHoleIdx((i) => Math.max(0, i - 1))} onNext={() => setActiveHoleIdx((i) => Math.min(holes.length - 1, i + 1))} onTapScore={(player) => openEntry(player)} /> )} {view === 'grid' && (
openEntry(player, hole)} />
)} {view === 'leaderboard' && (
)}
{/* Score entry sheet */} setEntry(null)} />
) }