"use client" // Horisontalt scorekort for en frittstående runde (ADR-033) -- egen, // dedikert side (ikke en del av round-stats.tsx sin aggregerte statistikk). // Presentasjon fra V0 (zip 17), datalag skrevet om fra mock til ekte fetch // mot samme `/rounds/{id}/participants/{id}/holes`-endepunkt som // round-detail.tsx og round-stats.tsx allerede bruker. // // Bevisst forskjell fra V0s eksport: V0s egen `strokesReceived()` var en // generisk modulo-formel -- byttet ut med backend sin ALLEREDE beregnede // `strokes_received` per hull (samme `allocate_strokes_by_index()`- // algoritme som resten av appen), for å unngå to ulike implementasjoner av // samme HCP-slagfordeling. import { useEffect, useState } from "react" import Link from "next/link" import { ArrowLeft } from "lucide-react" import { cn } from "@/lib/utils" // --- API-typer --------------------------------------------------------------- type ApiParticipant = { id: string user_id: string | null guest_name: string | null display_name: string is_owner: boolean } type ApiRound = { name: string | null id: string course_name_snapshot: string tee_name_snapshot: string played_at: string start_hole: number holes_planned: number participants: ApiParticipant[] } type ApiHole = { hole_number: number par: number stroke_index: number played: boolean score: number | null strokes_received: number | null } const dateFormatter = new Intl.DateTimeFormat("no-NO", { day: "numeric", month: "long", year: "numeric" }) // Viewer-relativt (ADR-036 fase 3-utvidelsen, 2026-07-26) -- se samme // begrunnelse i round-detail.tsx sin playerLabel(). function participantLabel(p: ApiParticipant, viewerId: string | null): string { return viewerId !== null && p.user_id === viewerId ? "Deg" : p.display_name } // --- Scoring helpers ------------------------------------------------------- function netScore(h: ApiHole): number | null { return h.played && h.score !== null && h.strokes_received !== null ? h.score - h.strokes_received : null } // Stableford: 2 pts for net par, +/- 1 per stroke, floored at 0. function stablefordPoints(h: ApiHole): number | null { const net = netScore(h) if (net === null) return null return Math.max(0, h.par - net + 2) } type ScoreClass = "eagle" | "birdie" | "par" | "bogey" | "double" function classify(gross: number, par: number): ScoreClass { const diff = gross - par if (diff <= -2) return "eagle" if (diff === -1) return "birdie" if (diff === 0) return "par" if (diff === 1) return "bogey" return "double" } function sum(values: number[]): number { return values.reduce((a, b) => a + b, 0) } function signedToPar(score: number, par: number): string { const diff = score - par if (diff === 0) return "E" return diff > 0 ? `+${diff}` : `−${Math.abs(diff)}` } // --- Score mark (the visual language for the Score row) -------------------- // Shape encodes direction (circle = under par, square = over par) so meaning // survives without color; fill encodes magnitude (filled = 2+ strokes off). function ScoreMark({ gross, par }: { gross: number | null; par: number }) { const shared = "flex size-6 items-center justify-center text-sm font-extrabold tabular-nums sm:size-7" if (gross === null) { return {"–"} } const kind = classify(gross, par) if (kind === "eagle") { return ( {gross} ) } if (kind === "birdie") { return ( {gross} ) } if (kind === "bogey") { return ( {gross} ) } if (kind === "double") { return ( {gross} ) } // par return {gross} } // --- One nine-hole block (a real table for row/column semantics) ----------- function ScoreBlock({ holes, summaryLabel, showStableford, }: { holes: ApiHole[] summaryLabel: string showStableford: boolean }) { const parSum = sum(holes.map((h) => h.par)) const playedHoles = holes.filter((h) => h.played && h.score !== null) const grossSum = playedHoles.length > 0 ? sum(playedHoles.map((h) => h.score as number)) : null const nettoValues = holes.map(netScore).filter((n): n is number => n !== null) const nettoSum = nettoValues.length > 0 ? sum(nettoValues) : null const pointsValues = holes.map(stablefordPoints).filter((n): n is number => n !== null) const pointsSum = pointsValues.length > 0 ? sum(pointsValues) : null const cell = "border border-border text-center tabular-nums" const sumCell = "border border-border bg-muted text-center font-extrabold tabular-nums" const rowLabel = "border border-border px-1 text-left text-xs font-bold text-muted-foreground" return (
| Rad | {holes.map((h) => ({h.hole_number} | ))}{summaryLabel} |
|---|---|---|
| Hcp | {holes.map((h) => ({h.stroke_index} | ))}|
| Par | {holes.map((h) => ({h.par} | ))}{parSum} |
| Score | {holes.map((h) => (
|
))}
{grossSum ?? "–"} |
| Netto | {holes.map((h) => ({netScore(h) ?? "–"} | ))}{nettoSum ?? "–"} |
| Poeng | {holes.map((h) => ({stablefordPoints(h) ?? "–"} | ))}{pointsSum ?? "–"} |
{error}
Tilbake til egne runder