"use client" // Full historikk på ett hull, inkl. grafer (ADR-079, 2026-08-16) -- åpnes // ved klikk fra HoleHistoryPanel sin kompakte oppsummering (hole-stat- // inputs.tsx), som selv sitter i "skjermen før" slagvinduet/-arket (se // round-detail.tsx sin PlayerHoleCards og individual-tournament-detail.tsx // sin HoleStatsSheet-"Oversikt"-steg). INGEN backend-endring -- samme // GET .../holes/{n}/history-endepunkt som panelet selv allerede brukte // (app/hole_history.py), som allerede returnerer alt som trengs her. // // Grafene gjenbruker EKSAKT samme mønster/fargelogikk som round-stats.tsx // sin CategoryBar/DeviationBar (bekreftet med bruker: "grafer" betyr dette // etablerte stolpe-mønsteret, ikke et nytt graf-bibliotek) -- egne lokale // kopier her, samme "ikke delt kode på tvers av filer"-mønster som resten // av dette området (se DESIGN_SYSTEM.md). import { useEffect, useState } from "react" import { X } from "lucide-react" import { cn } from "@/lib/utils" type HoleHistoryInstance = { source: "personal" | "tournament" played_at: string | null label: string score: number par: number putts: number | null tee_shot_result: string | null approach_result: string | null chip_count: number | null bunker_shot_count: number | null penalty_strokes: number | null first_putt_distance_bucket: string | null } type HoleHistory = { times_played: number times_personal: number times_tournament: number average_score: number average_score_vs_par: number gir_percent: number | null fairway_hit_percent: number | null average_putts: number | null best_score: number worst_score: number instances: HoleHistoryInstance[] } // Samme fargeskala/kategori-inndeling som round-stats.tsx (chart-1..6, // grønn->rød golf-resultat-skala). const C = { eagle: "var(--chart-1)", birdie: "var(--chart-2)", par: "var(--chart-3)", bogey: "var(--chart-4)", dobbel: "var(--chart-5)", verre: "var(--chart-6)", } function signed(n: number): string { const sign = n > 0 ? "+" : n < 0 ? "−" : "±" return `${sign}${Math.abs(n).toFixed(2).replace(".", ",")}` } function pct(n: number): string { return `${Math.round(n)} %` } export function HoleHistoryDetail({ url, holeNumber, onClose, }: { url: string holeNumber: number onClose: () => void }) { const [history, setHistory] = useState(undefined) useEffect(() => { let cancelled = false fetch(url, { credentials: "include" }) .then((res) => (res.ok ? res.json() : null)) .then((data) => { if (!cancelled) setHistory(data) }) .catch(() => { if (!cancelled) setHistory(null) }) return () => { cancelled = true } }, [url]) const par = history?.instances[0]?.par ?? null return (
Hull {holeNumber} {par !== null ? ` · Par ${par}` : ""} Din historikk
{history === undefined ? (
) : history === null || history.times_played === 0 ? (

Ingen historikk å vise på dette hullet ennå.

) : ( <>
Snitt til par {signed(history.average_score_vs_par)}
{(history.gir_percent !== null || history.fairway_hit_percent !== null) && (
{history.gir_percent !== null && } {history.fairway_hit_percent !== null && ( )}
)}

Alle gangene

    {history.instances.map((inst, i) => (
  • {inst.label} {inst.played_at ?? "Ukjent dato"} · {inst.source === "personal" ? "Frittstående" : "Turnering"} {inst.score} slag {inst.putts !== null ? `, ${inst.putts} putt${inst.putts === 1 ? "" : "er"}` : ""}
  • ))}
)}
) } function StatTile({ label, value }: { label: string; value: string }) { return (
{label} {value}
) } // Samme stolpe-mønster som round-stats.tsx sin CategoryBar -- fargede, // prosent-bredde rektangler med store tall og etiketter. function ScoreDistribution({ instances }: { instances: HoleHistoryInstance[] }) { const diff = (inst: HoleHistoryInstance) => inst.score - inst.par const categories = [ { label: "Eagle+", test: (d: number) => d <= -2, color: C.eagle }, { label: "Birdie", test: (d: number) => d === -1, color: C.birdie }, { label: "Par", test: (d: number) => d === 0, color: C.par }, { label: "Bogey", test: (d: number) => d === 1, color: C.bogey }, { label: "Dobbel bogey", test: (d: number) => d === 2, color: C.dobbel }, { label: "Verre", test: (d: number) => d >= 3, color: C.verre }, ] .map((c) => { const count = instances.filter((inst) => c.test(diff(inst))).length const percentage = instances.length > 0 ? (count / instances.length) * 100 : 0 return { ...c, count, percentage } }) .filter((c) => c.count > 0) const maxPct = Math.max(...categories.map((c) => c.percentage), 1) return (

Score-fordeling

{categories.map((c) => { const width = maxPct > 0 ? (c.percentage / maxPct) * 100 : 0 return (
{c.label} {pct(c.percentage)} · {c.count}
) })}
) }