All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
"use client"
|
|
|
|
|
|
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
// Aggregert statistikk på tvers av "Egne runder" (ADR-033), lenket fra
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
// dashbordets "Statistikk"-seksjon. Egen backend-aggregering
|
|
|
|
|
|
// (GET /rounds/stats/summary) -- ikke bare klientside-utledning av
|
|
|
|
|
|
// round-stats.tsx sin logikk, siden putt/18-hull-regelen (padding av
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
// 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.
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
//
|
|
|
|
|
|
// Visuell versjon 2 (2026-07-28): brukeren delte et referansebilde av en
|
|
|
|
|
|
// konkurrent-app sin statistikkskjerm (donuter med sentertall, kompass-
|
|
|
|
|
|
// diagram for miss-retning) og ba om noe tilsvarende -- håndkodet direkte
|
|
|
|
|
|
// (ingen V0-credits igjen), verifisert med ekte skjermbilder via Chrome
|
|
|
|
|
|
// DevTools MCP i stedet for kun typesjekk, se CLAUDE.md. Inspirert av
|
|
|
|
|
|
// referansen, ikke kopiert: egne farger/ikonografi, ingen flyt-diagram for
|
|
|
|
|
|
// scrambling/sand save (ville antydet en forgrening dataene ikke har -- det
|
|
|
|
|
|
// er to uavhengige prosenter, ikke en beslutningstre), ingen trendlinje
|
|
|
|
|
|
// ennå (for lite rundehistorikk til å være meningsfullt, se CLAUDE.md).
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
|
|
|
|
|
|
import type React from "react"
|
|
|
|
|
|
import { useEffect, useState } from "react"
|
|
|
|
|
|
import { useRouter } from "next/navigation"
|
|
|
|
|
|
import Link from "next/link"
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, BarChart3, Target, TrendingDown, TrendingUp, Waves, Wind } from "lucide-react"
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
fairway_left_pct: number | null
|
|
|
|
|
|
fairway_right_pct: number | null
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
fairway_tracked_holes: number
|
|
|
|
|
|
gir_pct: number | null
|
|
|
|
|
|
gir_tracked_holes: number
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
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
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
one_putt_pct: number | null
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
putt_dist_one_pct: number | null
|
|
|
|
|
|
putt_dist_two_pct: number | null
|
|
|
|
|
|
putt_dist_three_plus_pct: number | null
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
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
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
avg_to_par_with_gir: number | null
|
|
|
|
|
|
avg_to_par_without_gir: number | null
|
|
|
|
|
|
avg_to_par_fairway_hit: number | null
|
|
|
|
|
|
avg_to_par_fairway_miss: number | null
|
|
|
|
|
|
avg_to_par_with_bunker: number | null
|
|
|
|
|
|
avg_to_par_without_bunker: number | null
|
|
|
|
|
|
avg_to_par_with_anyway: number | null
|
|
|
|
|
|
avg_to_par_without_anyway: number | null
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
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" },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
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)} %`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
// 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 <span className="text-xs font-semibold text-muted-foreground">Uendret</span>
|
|
|
|
|
|
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 (
|
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-bold ${colorClass}`}>
|
|
|
|
|
|
<Icon aria-hidden="true" className="size-3.5" />
|
|
|
|
|
|
<span className="tabular-nums">{format(diff)}</span>
|
|
|
|
|
|
<span className="sr-only">vs. forrige periode</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function StatTile({
|
|
|
|
|
|
label,
|
|
|
|
|
|
value,
|
|
|
|
|
|
hint,
|
|
|
|
|
|
delta,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
label: string
|
|
|
|
|
|
value: string
|
|
|
|
|
|
hint?: string
|
|
|
|
|
|
delta?: React.ReactNode
|
|
|
|
|
|
}) {
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
return (
|
2026-07-29 12:44:51 +02:00
|
|
|
|
<div className="flex flex-col gap-1 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{label}</span>
|
|
|
|
|
|
<span className="text-2xl font-extrabold tabular-nums text-foreground sm:text-3xl">{value}</span>
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
{delta}
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
{hint && <span className="text-xs text-muted-foreground">{hint}</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
function Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
|
|
|
|
|
|
return (
|
2026-07-29 12:44:51 +02:00
|
|
|
|
<section className="flex flex-col gap-4 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8 sm:p-5">
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
<div>
|
|
|
|
|
|
<h2 className="text-lg font-extrabold tracking-tight text-foreground">{title}</h2>
|
|
|
|
|
|
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
function Section({
|
|
|
|
|
|
title,
|
|
|
|
|
|
icon: Icon,
|
|
|
|
|
|
children,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
title: string
|
|
|
|
|
|
icon: typeof BarChart3
|
|
|
|
|
|
children: React.ReactNode
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="flex flex-col gap-3">
|
|
|
|
|
|
<h2 className="flex items-center gap-2 text-lg font-extrabold tracking-tight text-foreground">
|
|
|
|
|
|
<Icon aria-hidden="true" className="size-5 text-primary" />
|
|
|
|
|
|
{title}
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">{children}</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
// --- Donut: sentertall + inntil 4 fargede segmenter (conic-gradient) -------
|
|
|
|
|
|
// Gjenbrukt for BÅDE puttfordelingen (3 segmenter) og de enkle rednings-
|
|
|
|
|
|
// gaugene (1 segment fylt + resten "spor"). Prosent er ALLTID skrevet ut i
|
|
|
|
|
|
// en forklaringsliste under -- fargen bærer aldri betydningen alene.
|
|
|
|
|
|
|
|
|
|
|
|
function Donut({
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
segments,
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
centerValue,
|
|
|
|
|
|
centerLabel,
|
|
|
|
|
|
size = 132,
|
|
|
|
|
|
thickness = 18,
|
|
|
|
|
|
showLegend = true,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
segments: { label: string; value: number; color: string }[]
|
|
|
|
|
|
centerValue: string
|
|
|
|
|
|
centerLabel: string
|
|
|
|
|
|
size?: number
|
|
|
|
|
|
thickness?: number
|
|
|
|
|
|
showLegend?: boolean
|
|
|
|
|
|
}) {
|
|
|
|
|
|
let cursor = 0
|
|
|
|
|
|
const stops: string[] = []
|
|
|
|
|
|
for (const seg of segments) {
|
|
|
|
|
|
const start = cursor
|
|
|
|
|
|
const end = cursor + seg.value
|
|
|
|
|
|
stops.push(`${seg.color} ${start}% ${end}%`)
|
|
|
|
|
|
cursor = end
|
|
|
|
|
|
}
|
|
|
|
|
|
if (cursor < 100) stops.push(`var(--muted) ${cursor}% 100%`)
|
|
|
|
|
|
const gradient = `conic-gradient(${stops.join(", ")})`
|
|
|
|
|
|
|
|
|
|
|
|
const ring = (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="relative shrink-0 rounded-full"
|
|
|
|
|
|
style={{ width: size, height: size, background: gradient }}
|
|
|
|
|
|
role="img"
|
|
|
|
|
|
aria-label={`${centerLabel}: ${centerValue}. ${segments.map((s) => `${s.label} ${Math.round(s.value)} prosent`).join(", ")}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="absolute rounded-full bg-card"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: size - thickness * 2,
|
|
|
|
|
|
height: size - thickness * 2,
|
|
|
|
|
|
top: thickness,
|
|
|
|
|
|
left: thickness,
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
|
|
|
|
<span className="text-xl font-extrabold tabular-nums text-foreground">{centerValue}</span>
|
|
|
|
|
|
<span className="text-[11px] font-semibold text-muted-foreground">{centerLabel}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (!showLegend) return ring
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col items-center gap-3 sm:flex-row sm:items-center">
|
|
|
|
|
|
{ring}
|
|
|
|
|
|
<ul className="flex flex-1 flex-col gap-1.5">
|
|
|
|
|
|
{segments.map((s) => (
|
|
|
|
|
|
<li key={s.label} className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
|
|
|
|
<span aria-hidden="true" className="size-2.5 shrink-0 rounded-full" style={{ backgroundColor: s.color }} />
|
|
|
|
|
|
<span className="flex-1">{s.label}</span>
|
|
|
|
|
|
<span className="font-bold tabular-nums text-foreground">{Math.round(s.value)} %</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Kompass-diagram for greentreff-miss-retning ----------------------------
|
|
|
|
|
|
// Greentreff i midten, langt over, kort under, venstre til venstre, høyre
|
|
|
|
|
|
// til høyre -- speiler hvor bommen faktisk gikk relativt til green, i
|
|
|
|
|
|
// stedet for en flat fordelingsbar. Alle fire retningsceller viser tallet
|
|
|
|
|
|
// sitt som tekst uansett -- retning er beskrivende, ikke god/dårlig, så
|
|
|
|
|
|
// ingen av dem fargekodes grønn/oransje (kun sentercellen, som ER et
|
|
|
|
|
|
// treff/bom-tall, får grønn fremheving).
|
|
|
|
|
|
|
|
|
|
|
|
function CompassCell({ icon: Icon, label, value }: { icon: typeof ArrowUp; label: string; value: number | null }) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex min-h-16 flex-col items-center justify-center gap-0.5 rounded-2xl border border-border bg-card p-2">
|
|
|
|
|
|
<Icon aria-hidden="true" className="size-4 text-muted-foreground" />
|
|
|
|
|
|
<span className="text-lg font-extrabold tabular-nums text-foreground">{value !== null ? `${Math.round(value)} %` : "–"}</span>
|
|
|
|
|
|
<span className="text-[11px] font-semibold text-muted-foreground">{label}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function GirCompass({
|
|
|
|
|
|
hitPct,
|
|
|
|
|
|
trackedHoles,
|
|
|
|
|
|
missTrackedHoles,
|
|
|
|
|
|
long,
|
|
|
|
|
|
short,
|
|
|
|
|
|
left,
|
|
|
|
|
|
right,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
hitPct: number | null
|
|
|
|
|
|
trackedHoles: number
|
|
|
|
|
|
missTrackedHoles: number
|
|
|
|
|
|
long: number | null
|
|
|
|
|
|
short: number | null
|
|
|
|
|
|
left: number | null
|
|
|
|
|
|
right: number | null
|
|
|
|
|
|
}) {
|
|
|
|
|
|
if (hitPct === null) {
|
|
|
|
|
|
return <p className="text-sm text-muted-foreground">Ingen greentreff-data registrert ennå.</p>
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col items-center gap-3">
|
|
|
|
|
|
<div className="grid w-full max-w-[280px] grid-cols-3 grid-rows-3 gap-2.5">
|
|
|
|
|
|
<div aria-hidden="true" />
|
|
|
|
|
|
<CompassCell icon={ArrowUp} label="Langt" value={long} />
|
|
|
|
|
|
<div aria-hidden="true" />
|
|
|
|
|
|
<CompassCell icon={ArrowLeft} label="Venstre" value={left} />
|
|
|
|
|
|
<div className="flex flex-col items-center justify-center gap-0.5 rounded-2xl border-2 border-primary bg-primary/15 p-2">
|
|
|
|
|
|
<Target aria-hidden="true" className="size-5 text-primary" />
|
|
|
|
|
|
<span className="text-2xl font-extrabold tabular-nums text-foreground">{Math.round(hitPct)} %</span>
|
|
|
|
|
|
<span className="text-[11px] font-semibold text-muted-foreground">Greentreff</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<CompassCell icon={ArrowRight} label="Høyre" value={right} />
|
|
|
|
|
|
<div aria-hidden="true" />
|
|
|
|
|
|
<CompassCell icon={ArrowDown} label="Kort" value={short} />
|
|
|
|
|
|
<div aria-hidden="true" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-center text-xs text-muted-foreground">
|
|
|
|
|
|
{trackedHoles} hull registrert
|
|
|
|
|
|
{missTrackedHoles > 0 && `, ${missTrackedHoles} bom med retning`}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Fairway: enklere venstre/fairway/høyre-bar (ingen langt/kort-akse
|
|
|
|
|
|
// fra utslaget, så ikke et fullt kompass) --------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
function FairwayBar({
|
|
|
|
|
|
tracked,
|
|
|
|
|
|
left,
|
|
|
|
|
|
hit,
|
|
|
|
|
|
right,
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
}: {
|
|
|
|
|
|
tracked: number
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
left: number | null
|
|
|
|
|
|
hit: number | null
|
|
|
|
|
|
right: number | null
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
}) {
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
if (tracked === 0) return <p className="text-sm text-muted-foreground">Ingen utslag registrert ennå.</p>
|
|
|
|
|
|
const segments = [
|
|
|
|
|
|
{ label: "Venstre", value: left, color: "var(--brand-orange)" },
|
|
|
|
|
|
{ label: "Fairway", value: hit, color: "var(--primary)" },
|
|
|
|
|
|
{ label: "Høyre", value: right, color: "var(--brand-orange)" },
|
|
|
|
|
|
]
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
<div className="flex h-4 w-full overflow-hidden rounded-full bg-muted">
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
{segments.map((s) =>
|
|
|
|
|
|
s.value && s.value > 0 ? (
|
|
|
|
|
|
<div key={s.label} style={{ width: `${s.value}%`, backgroundColor: s.color }} title={`${s.label}: ${Math.round(s.value)} %`} />
|
|
|
|
|
|
) : null,
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground">
|
|
|
|
|
|
{segments.map((s) => (
|
|
|
|
|
|
<span key={s.label} className="inline-flex items-center gap-1.5">
|
|
|
|
|
|
<span aria-hidden="true" className="size-2.5 rounded-full" style={{ backgroundColor: s.color }} />
|
|
|
|
|
|
{s.label}: <span className="font-semibold tabular-nums text-foreground">{s.value !== null ? pct(s.value) : "–"}</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
// --- Til par: med vs. uten en gitt hendelse på hullet -----------------------
|
|
|
|
|
|
// Brukerens eksplisitte instruks 2026-07-28: se snitt til par NÅR noe
|
|
|
|
|
|
// inntraff mot NÅR det ikke gjorde det (greentreff, fairwaytreff, bunker,
|
|
|
|
|
|
// anywayslag), i stedet for bare en isolert prosentandel. Fargen følger
|
|
|
|
|
|
// hvilken av de to som FAKTISK var lavest denne perioden (ikke en fast
|
|
|
|
|
|
// antakelse om hvilken side som "skal" være best), siden det er selve
|
|
|
|
|
|
// sammenligningen som er poenget.
|
|
|
|
|
|
|
|
|
|
|
|
function CompareToPar({
|
|
|
|
|
|
title,
|
|
|
|
|
|
aLabel,
|
|
|
|
|
|
aValue,
|
|
|
|
|
|
bLabel,
|
|
|
|
|
|
bValue,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
title: string
|
|
|
|
|
|
aLabel: string
|
|
|
|
|
|
aValue: number | null
|
|
|
|
|
|
bLabel: string
|
|
|
|
|
|
bValue: number | null
|
|
|
|
|
|
}) {
|
|
|
|
|
|
if (aValue === null && bValue === null) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="text-sm font-bold text-foreground">{title}</h3>
|
|
|
|
|
|
<p className="text-sm text-muted-foreground">Ingen data registrert ennå.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
const aBetter = aValue !== null && bValue !== null && aValue < bValue
|
|
|
|
|
|
const bBetter = aValue !== null && bValue !== null && bValue < aValue
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<h3 className="text-sm font-bold text-foreground">{title}</h3>
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
|
<div className={`rounded-xl border p-3 ${aBetter ? "border-primary bg-primary/10" : "border-border bg-card"}`}>
|
|
|
|
|
|
<span className="text-xs font-semibold text-muted-foreground">{aLabel}</span>
|
|
|
|
|
|
<div className="text-xl font-extrabold tabular-nums text-foreground">{aValue !== null ? signed(aValue) : "–"}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={`rounded-xl border p-3 ${bBetter ? "border-primary bg-primary/10" : "border-border bg-card"}`}>
|
|
|
|
|
|
<span className="text-xs font-semibold text-muted-foreground">{bLabel}</span>
|
|
|
|
|
|
<div className="text-xl font-extrabold tabular-nums text-foreground">{bValue !== null ? signed(bValue) : "–"}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
export function RoundsStatsSummary() {
|
|
|
|
|
|
const router = useRouter()
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
const [window, setWindow] = useState<WindowKey>("all")
|
|
|
|
|
|
const [data, setData] = useState<ApiWindowSummary | null>(null)
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let cancelled = false
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
try {
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
const res = await fetch(`/rounds/stats/summary?window=${window}`, { credentials: "include" })
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
if (res.status === 401) {
|
|
|
|
|
|
router.replace("/")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!res.ok) throw new Error(`stats: ${res.status}`)
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
const json: ApiWindowSummary = await res.json()
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
if (!cancelled) setData(json)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
if (!cancelled) setError("Klarte ikke å hente statistikken. Prøv igjen om litt.")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void load()
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
cancelled = true
|
|
|
|
|
|
}
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
}, [router, window])
|
|
|
|
|
|
|
|
|
|
|
|
const s = data?.current ?? null
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex min-h-[100dvh] flex-col bg-background">
|
|
|
|
|
|
<header className="sticky top-0 z-10 border-b border-border bg-background/95 backdrop-blur">
|
|
|
|
|
|
<div className="mx-auto flex min-h-14 max-w-xl items-center gap-2 px-4 py-2">
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href="/dashboard"
|
|
|
|
|
|
aria-label="Til dashbord"
|
|
|
|
|
|
className="flex size-11 shrink-0 items-center justify-center rounded-xl text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft aria-hidden="true" className="size-6" />
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<h1 className="text-lg font-extrabold tracking-tight text-foreground">Statistikk</h1>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
<main className="mx-auto flex w-full max-w-xl flex-1 flex-col gap-6 px-4 py-6 pb-16">
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
<div className="flex gap-2 overflow-x-auto pb-1">
|
|
|
|
|
|
{WINDOW_OPTIONS.map((opt) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={opt.key}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setWindow(opt.key)}
|
|
|
|
|
|
className={`min-h-11 shrink-0 rounded-full px-4 text-sm font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
|
|
|
|
|
window === opt.key ? "bg-primary text-primary-foreground" : "bg-muted text-foreground hover:bg-accent"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{opt.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
{error && (
|
|
|
|
|
|
<p role="alert" className="text-base font-medium text-destructive">
|
|
|
|
|
|
{error}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{data === null && !error ? (
|
|
|
|
|
|
<div role="status" aria-live="polite" className="flex flex-col items-center justify-center gap-4 py-20 text-center">
|
|
|
|
|
|
<div aria-hidden="true" className="size-9 animate-spin rounded-full border-4 border-primary/20 border-t-primary" />
|
|
|
|
|
|
<span className="text-base font-medium text-muted-foreground">Laster statistikk…</span>
|
|
|
|
|
|
</div>
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
) : s && s.rounds_completed === 0 ? (
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
<div className="flex flex-col items-center gap-5 rounded-3xl border border-dashed border-border bg-card/50 px-6 py-16 text-center">
|
2026-08-02 16:34:54 +02:00
|
|
|
|
<div className="flex size-16 items-center justify-center rounded-2xl bg-muted">
|
|
|
|
|
|
<BarChart3 aria-hidden="true" className="size-8 text-muted-foreground" />
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex max-w-md flex-col gap-2">
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
<h2 className="text-xl font-bold text-foreground">Ingen fullførte runder i perioden</h2>
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
<p className="text-lg leading-relaxed text-muted-foreground text-pretty">
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
Prøv et annet tidsvindu, eller kom tilbake etter neste fullførte runde.
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : data ? (
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
(() => {
|
|
|
|
|
|
const s = data.current
|
|
|
|
|
|
const p = data.previous
|
|
|
|
|
|
return (
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
<>
|
|
|
|
|
|
<p className="text-base leading-relaxed text-muted-foreground text-pretty">
|
|
|
|
|
|
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
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<Section title="Oversikt" icon={BarChart3}>
|
|
|
|
|
|
<StatTile label="Runder fullført" value={String(s.rounds_completed)} />
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Snitt til par"
|
|
|
|
|
|
value={s.avg_score_to_par !== null ? signed(s.avg_score_to_par) : "–"}
|
|
|
|
|
|
delta={<Delta current={s.avg_score_to_par} previous={p.avg_score_to_par} format={signed} goodDirection="down" />}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Putt / 18 hull"
|
|
|
|
|
|
value={s.avg_putts_per_18 !== null ? s.avg_putts_per_18.toFixed(1).replace(".", ",") : "–"}
|
|
|
|
|
|
delta={<Delta current={s.avg_putts_per_18} previous={p.avg_putts_per_18} format={signed} goodDirection="down" />}
|
|
|
|
|
|
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å"
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Section>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="Greentreff (GIR)" subtitle="Hvor treffer og bommer du innspillet, sett fra green?">
|
|
|
|
|
|
<GirCompass
|
|
|
|
|
|
hitPct={s.gir_pct}
|
|
|
|
|
|
trackedHoles={s.gir_tracked_holes}
|
|
|
|
|
|
missTrackedHoles={s.green_miss_tracked_holes}
|
|
|
|
|
|
long={s.green_miss_long_pct}
|
|
|
|
|
|
short={s.green_miss_short_pct}
|
|
|
|
|
|
left={s.green_miss_left_pct}
|
|
|
|
|
|
right={s.green_miss_right_pct}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex items-center justify-between border-t border-border pt-3">
|
|
|
|
|
|
<span className="text-sm font-semibold text-muted-foreground">vs. forrige periode</span>
|
|
|
|
|
|
<Delta current={s.gir_pct} previous={p.gir_pct} format={signedPoints} goodDirection="up" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="Fra utslaget" subtitle="Kun par 4/5, kun registrerte utslag.">
|
|
|
|
|
|
<FairwayBar tracked={s.fairway_tracked_holes} left={s.fairway_left_pct} hit={s.fairway_hit_pct} right={s.fairway_right_pct} />
|
|
|
|
|
|
<div className="flex items-center justify-between border-t border-border pt-3">
|
|
|
|
|
|
<span className="text-sm font-semibold text-muted-foreground">Fairwaytreff vs. forrige periode</span>
|
|
|
|
|
|
<Delta current={s.fairway_hit_pct} previous={p.fairway_hit_pct} format={signedPoints} goodDirection="up" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="Putting">
|
|
|
|
|
|
<Donut
|
|
|
|
|
|
centerValue={s.avg_putts_per_18 !== null ? s.avg_putts_per_18.toFixed(1).replace(".", ",") : "–"}
|
|
|
|
|
|
centerLabel="putt / 18 hull"
|
|
|
|
|
|
segments={
|
|
|
|
|
|
s.putt_dist_one_pct !== null
|
|
|
|
|
|
? [
|
|
|
|
|
|
{ label: "1-putt", value: s.putt_dist_one_pct ?? 0, color: "var(--primary)" },
|
|
|
|
|
|
{ label: "2-putt", value: s.putt_dist_two_pct ?? 0, color: "var(--info)" },
|
|
|
|
|
|
{ label: "3-putt+", value: s.putt_dist_three_plus_pct ?? 0, color: "var(--brand-orange)" },
|
|
|
|
|
|
]
|
|
|
|
|
|
: []
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{s.putt_dist_one_pct === null && <p className="text-sm text-muted-foreground">Ingen puttfordeling registrert ennå.</p>}
|
|
|
|
|
|
<div className="flex items-center justify-between border-t border-border pt-3">
|
|
|
|
|
|
<span className="text-sm font-semibold text-muted-foreground">Én-putt vs. forrige periode</span>
|
|
|
|
|
|
<Delta current={s.one_putt_pct} previous={p.one_putt_pct} format={signedPoints} goodDirection="up" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="Redning" subtitle="Par eller bedre etter en bom.">
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<div className="flex flex-col items-center gap-2">
|
|
|
|
|
|
<Donut
|
|
|
|
|
|
size={104}
|
|
|
|
|
|
thickness={14}
|
|
|
|
|
|
showLegend={false}
|
|
|
|
|
|
centerValue={s.scrambling_pct !== null ? `${Math.round(s.scrambling_pct)}%` : "–"}
|
|
|
|
|
|
centerLabel=""
|
|
|
|
|
|
segments={s.scrambling_pct !== null ? [{ label: "Scrambling", value: s.scrambling_pct, color: "var(--primary)" }] : []}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-sm font-bold text-foreground">Scrambling</span>
|
|
|
|
|
|
<Delta current={s.scrambling_pct} previous={p.scrambling_pct} format={signedPoints} goodDirection="up" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col items-center gap-2">
|
|
|
|
|
|
<Donut
|
|
|
|
|
|
size={104}
|
|
|
|
|
|
thickness={14}
|
|
|
|
|
|
showLegend={false}
|
|
|
|
|
|
centerValue={s.sand_save_pct !== null ? `${Math.round(s.sand_save_pct)}%` : "–"}
|
|
|
|
|
|
centerLabel=""
|
|
|
|
|
|
segments={s.sand_save_pct !== null ? [{ label: "Sand save", value: s.sand_save_pct, color: "var(--primary)" }] : []}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-sm font-bold text-foreground">Sand save</span>
|
|
|
|
|
|
<Delta current={s.sand_save_pct} previous={p.sand_save_pct} format={signedPoints} goodDirection="up" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="Til par: med vs. uten" subtitle="Snitt til par på enkelthull, splittet på om noe faktisk inntraff.">
|
|
|
|
|
|
<CompareToPar
|
|
|
|
|
|
title="Greentreff (GIR)"
|
|
|
|
|
|
aLabel="Med GIR"
|
|
|
|
|
|
aValue={s.avg_to_par_with_gir}
|
|
|
|
|
|
bLabel="Uten GIR"
|
|
|
|
|
|
bValue={s.avg_to_par_without_gir}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<CompareToPar
|
|
|
|
|
|
title="Fairwaytreff"
|
|
|
|
|
|
aLabel="Traff fairway"
|
|
|
|
|
|
aValue={s.avg_to_par_fairway_hit}
|
|
|
|
|
|
bLabel="Bom fairway"
|
|
|
|
|
|
bValue={s.avg_to_par_fairway_miss}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<CompareToPar
|
|
|
|
|
|
title="Bunker"
|
|
|
|
|
|
aLabel="I bunker"
|
|
|
|
|
|
aValue={s.avg_to_par_with_bunker}
|
|
|
|
|
|
bLabel="Ikke i bunker"
|
|
|
|
|
|
bValue={s.avg_to_par_without_bunker}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<CompareToPar
|
|
|
|
|
|
title="Anywayslag"
|
|
|
|
|
|
aLabel="Med anywayslag"
|
|
|
|
|
|
aValue={s.avg_to_par_with_anyway}
|
|
|
|
|
|
bLabel="Uten anywayslag"
|
|
|
|
|
|
bValue={s.avg_to_par_without_anyway}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Section title="Chip, bunker og straffeslag" icon={Waves}>
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Chip / runde"
|
|
|
|
|
|
value={s.avg_chip_per_round !== null ? s.avg_chip_per_round.toFixed(1).replace(".", ",") : "–"}
|
|
|
|
|
|
delta={<Delta current={s.avg_chip_per_round} previous={p.avg_chip_per_round} format={signed} goodDirection="down" />}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Bunkerslag / runde"
|
|
|
|
|
|
value={s.avg_bunker_per_round !== null ? s.avg_bunker_per_round.toFixed(1).replace(".", ",") : "–"}
|
|
|
|
|
|
delta={<Delta current={s.avg_bunker_per_round} previous={p.avg_bunker_per_round} format={signed} goodDirection="down" />}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Straffeslag / runde"
|
|
|
|
|
|
value={s.avg_penalty_per_round !== null ? s.avg_penalty_per_round.toFixed(1).replace(".", ",") : "–"}
|
|
|
|
|
|
delta={<Delta current={s.avg_penalty_per_round} previous={p.avg_penalty_per_round} format={signed} goodDirection="down" />}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Section>
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
|
Alt er ferdig, verifisert og live. Oppsummert:
Hullet lukket: hele WHS-HCP-motoren fantes og var testet, men ble aldri kalt — score_differential ble lagret per runde, men ingen indeks ble noensinne regnet ut. Nå har du:
To atskilte tall: manuelt satt HCP (uendret bruk) og et nytt, automatisk beregnet «faktisk HCP» (beste 8-av-≤20 nyeste tellende runder, WHS Rule 5.2, med Low-HCP-cap).
«Bruk som mitt HCP»-knapp på /account for å eksplisitt overføre.
Eksklusjon per deltaker — hver innlogget spiller (ikke bare eieren) styrer selv om egen deltakelse skal telle, uansett fullført-status.
Selvdeklarert spilleform (slagspill/matchspill) — matchspill forhåndsforeslår (ikke tvinger) eksklusjon, begrunnet i WHS sin «most likely score»-regel som TeeCup ikke kan garantere presist.
Verifisert med 111/111 scratch-sjekker (inkl. håndregnet WHS-matte) og en ekte nettleser-gjennomgang av hele flyten. Migrasjon 030 kjørt mot ekte teecup_db, begge containere redeployet, alt grønt, teeoff.no upåvirket.
Gjenstår (dokumentert i CLAUDE.md/FEATURE_BACKLOG.md, ikke bygget nå): offline-kø for frittstående runder, Stableford, rundedeling/visibility, flere flighter i én runde.
2026-07-28 11:04:51 +02:00
|
|
|
|
{s.avg_anyway_per_round !== null && (
|
|
|
|
|
|
<Section title="Annet" icon={Wind}>
|
|
|
|
|
|
<StatTile
|
|
|
|
|
|
label="Anywayslag / runde"
|
|
|
|
|
|
value={s.avg_anyway_per_round.toFixed(1).replace(".", ",")}
|
|
|
|
|
|
delta={<Delta current={s.avg_anyway_per_round} previous={p.avg_anyway_per_round} format={signed} goodDirection="down" />}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Section>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
All shipped, tested, and browser-verified. Summary of this round:
GIR question answered, not a gap: score − putts ≤ par − 2 is already the general formula, already ported to the new endpoint — it's not case-by-case, it works for every combination. The one real limit: it needs putts, so strokes_only rounds can't get a reliable GIR (score alone almost never proves it — a chipped-in birdie and a real GIR+1-putt look identical).
Miss direction — added fairway left/right and green long/short/left/right as pooled distribution bars on the aggregate stats page, using the same fields the per-round page already tracks.
Time-window picker — Siste runde / 5 / 10 / Denne måneden / I år / Siste år / Alltid, filters the whole page.
"vs. forrige periode" deltas — added, per your ask. One rule for every window type: "previous" = the same length (rounds or days) immediately before the current window's start, so there's no special-casing per calendar type. Colored by direction (green = improvement, orange = worse) except on the miss-direction numbers, which have no "better" side.
Verified with standalone logic tests against hand-computed scenarios, a full app import check, a clean typecheck, and live in the browser (clicked through windows, confirmed real numbers and a correct empty-previous case since your account only has 2 completed rounds so far).
2026-07-28 07:43:47 +02:00
|
|
|
|
)
|
|
|
|
|
|
})()
|
All done and verified live in the browser. Summary of this round:
"Kommende runder" → "Runder" on the dashboard.
Tjøme course-name duplicate fixed in the real database (one round's stale course_name_snapshot corrected) — all three of your rounds now group under one "Tjøme Golfklubb" entry.
Two new colors, per your request for two: lifted the already-validated --chart-3 (blue → --info) and --chart-4 (gold → --gold) into core design tokens. First real uses: a blue "Hcp spilt til X" badge on round cards, and a gold "Personlig rekord" badge on your best completed round.
Clicking a course under "Spilte baner" now opens a new page listing every round played there (/my-rounds/course/[name]) — caught and fixed a real double-encoding bug here via an actual browser screenshot before shipping.
Aggregated statistics, clickable from the dashboard's "Statistikk" section (/my-rounds/stats): rounds completed, avg to-par, putts/18 holes (implementing your padding rule exactly — unplayed holes count as 2 putts, only for rounds where putt-tracking was on), fairway%, GIR%, one-putt%, scrambling%, sand save%, and chip/bunker/penalty/anywayslag averages per round.
Everything is typechecked, the putts-padding logic is verified against a hand-computed synthetic dataset, and all new screens were checked live in the browser with no console errors.
2026-07-28 07:04:56 +02:00
|
|
|
|
) : null}
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|