"use client" // Aggregert statistikk på tvers av "Egne runder" (ADR-033), lenket fra // dashbordets "Statistikk"-seksjon (2026-07-28, utvidet samme dag med // tidsvindu + miss-retning + forrige-periode-sammenligning). Egen backend- // aggregering (GET /rounds/stats/summary) -- ikke bare klientside-utledning // av round-stats.tsx sin logikk, siden putt/18-hull-regelen (padding av // uspilte hull til 2 putter, KUN for dette tallet) og "forrige periode"- // vinduet må regnes over ALLE fullførte runder samlet, ikke én om gangen. import type React from "react" import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import Link from "next/link" import { ArrowLeft, BarChart3, Flag, Target, TrendingDown, TrendingUp, Waves, Wind } from "lucide-react" type ApiStatsSummary = { rounds_completed: number avg_score_to_par: number | null avg_putts_per_18: number | null putts_tracked_rounds: number fairway_hit_pct: number | null fairway_left_pct: number | null fairway_right_pct: number | null fairway_tracked_holes: number gir_pct: number | null gir_tracked_holes: number green_miss_long_pct: number | null green_miss_short_pct: number | null green_miss_left_pct: number | null green_miss_right_pct: number | null green_miss_tracked_holes: number one_putt_pct: number | null scrambling_pct: number | null sand_save_pct: number | null avg_chip_per_round: number | null avg_bunker_per_round: number | null avg_penalty_per_round: number | null avg_anyway_per_round: number | null } type ApiWindowSummary = { window: string current: ApiStatsSummary previous: ApiStatsSummary } type WindowKey = "last_round" | "last_5" | "last_10" | "month" | "year" | "last_year" | "all" const WINDOW_OPTIONS: { key: WindowKey; label: string }[] = [ { key: "last_round", label: "Siste runde" }, { key: "last_5", label: "Siste 5" }, { key: "last_10", label: "Siste 10" }, { key: "month", label: "Denne måneden" }, { key: "year", label: "I år" }, { key: "last_year", label: "Siste år" }, { key: "all", label: "Alltid" }, ] function signed(n: number): string { const sign = n > 0 ? "+" : n < 0 ? "−" : "±" return `${sign}${Math.abs(n).toFixed(1).replace(".", ",")}` } function pct(n: number): string { return `${Math.round(n)} %` } // Prosentpoeng-differanse mellom to andeler (ikke "N % endring", men "N pp"). function signedPoints(n: number): string { const rounded = Math.round(n) const sign = rounded > 0 ? "+" : rounded < 0 ? "−" : "±" return `${sign}${Math.abs(rounded)} pp` } // "up" = høyere tall er bedre (fairwaytreff, GIR, ...), "down" = lavere // tall er bedre (snitt til par, putt/runde, ...). Utelatt = ren beskrivende // retning (miss-retning venstre/høyre osv.) -- ingen "bedre/verre" der. function Delta({ current, previous, format, goodDirection, }: { current: number | null previous: number | null format: (diff: number) => string goodDirection?: "up" | "down" }) { if (current === null || previous === null) return null const diff = current - previous if (diff === 0) return Uendret const improved = goodDirection === "up" ? diff > 0 : goodDirection === "down" ? diff < 0 : null const Icon = diff > 0 ? TrendingUp : TrendingDown const colorClass = improved === true ? "text-primary" : improved === false ? "text-brand-orange" : "text-muted-foreground" return ( ) } function StatTile({ label, value, hint, delta, }: { label: string value: string hint?: string delta?: React.ReactNode }) { return (
{label} {value} {delta} {hint && {hint}}
) } function Section({ title, icon: Icon, children, }: { title: string icon: typeof BarChart3 children: React.ReactNode }) { return (

{children}
) } // Horisontal fordelingsbar med retningsandeler (venstre/høyre eller // lang/kort/venstre/høyre) -- pooler kun faktisk registrerte bom. function MissBar({ tracked, segments, }: { tracked: number segments: { label: string; value: number | null; color: string }[] }) { if (tracked === 0) return

Ingen retning registrert ennå.

return (
{segments.map((s) => s.value && s.value > 0 ? (
) : null, )}
{segments.map((s) => ( ))}
) } export function RoundsStatsSummary() { const router = useRouter() const [window, setWindow] = useState("all") const [data, setData] = useState(null) const [error, setError] = useState(null) useEffect(() => { let cancelled = false async function load() { try { const res = await fetch(`/rounds/stats/summary?window=${window}`, { credentials: "include" }) if (res.status === 401) { router.replace("/") return } if (!res.ok) throw new Error(`stats: ${res.status}`) const json: ApiWindowSummary = await res.json() if (!cancelled) setData(json) } catch { if (!cancelled) setError("Klarte ikke å hente statistikken. Prøv igjen om litt.") } } void load() return () => { cancelled = true } }, [router, window]) const s = data?.current ?? null return (
{WINDOW_OPTIONS.map((opt) => ( ))}
{error && (

{error}

)} {data === null && !error ? (
) : s && s.rounds_completed === 0 ? (

Ingen fullførte runder i perioden

Prøv et annet tidsvindu, eller kom tilbake etter neste fullførte runde.

) : data ? ( (() => { const s = data.current const p = data.previous return ( <>

Aggregert over {s.rounds_completed} {s.rounds_completed === 1 ? "fullført runde" : "fullførte runder"} {p.rounds_completed > 0 && ( <> {" "} · sammenlignet med {p.rounds_completed} {p.rounds_completed === 1 ? "runde" : "runder"} i perioden før )} .

} /> } hint={ s.putts_tracked_rounds > 0 ? `${s.putts_tracked_rounds} ${s.putts_tracked_rounds === 1 ? "runde med" : "runder med"} puttsporing` : "Ingen runder med puttsporing ennå" } />
} hint={s.fairway_tracked_holes > 0 ? `${s.fairway_tracked_holes} hull registrert` : undefined} /> } hint={s.gir_tracked_holes > 0 ? `${s.gir_tracked_holes} hull registrert` : undefined} /> } />

Hvor bommer du fra utslaget?

Kun par 4/5, kun registrerte utslag.

Hvor bommer du på innspillet?

Kun hull der greentreff ble bommet og retning registrert.

} hint="Par eller bedre uten GIR" /> } hint="Par eller bedre fra bunker" />
} /> } /> } />
{s.avg_anyway_per_round !== null && (
} />
)} ) })() ) : null}
) }