2026-07-18 17:15:50 +02:00
|
|
|
|
"use client"
|
|
|
|
|
|
|
|
|
|
|
|
import type React from "react"
|
|
|
|
|
|
import { useEffect, useMemo, useState } from "react"
|
|
|
|
|
|
import Link from "next/link"
|
Update Todos
Undersøk kode-kontekst for alle tre byggeoppgavene
Avklar åpne designspørsmål med bruker
Bygg: flytte spiller mellom lag (backend + frontend)
Bygg: individuell rangering i turnering (backend + frontend)
Bygg: midlertidige spillere + etter-runde-e-post (backend + frontend)
Typesjekket produksjonsbuild av frontend
Scratch-verifiser alt tre + full regresjon
Browser-verifiser alt tre (Chrome DevTools)
Oppdater CLAUDE.md/FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md
Rull ut mot ekte systemer (med bekreftelse)
Alle tre er live. Oppsummert:
Flytte spiller mellom lag — ny "Flytt til {annet lag}"-handling i lag-oversikten, atomisk, blokkert hvis spilleren allerede er lagt til i en match.
Individuell rangering per økt — ny brutto/netto/poeng-rangering for singles/fourball-slagspill-økter, ved siden av det eksisterende lag-poeng-leaderboardet, lenket fra blind draw-skjermen.
Midlertidige spillere: etter-runde-e-post — ny knapp som sender scorekort + ekte innloggingslenke til spillere uten konto, med dobbel-utsendingssperre.
Alt verifisert grundig: 54/54 nye scratch-sjekker + full regresjon (5+28+19 eksisterende sjekker), typesjekket produksjonsbuild, og en fullstendig gjennomgang i ekte nettleser (Chrome DevTools) av alle tre — ingen konsollfeil. Migrasjon 034 kjørt mot ekte database, begge containere redeployet, teeoff.no upåvirket. Samtidig rettet jeg opp noen steder der FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md hadde blitt hengende etter den faktiske, allerede leverte tilstanden (ADR-039-utrulling, PWA-offline-browsertesting).
2026-07-28 20:56:08 +02:00
|
|
|
|
import { ArrowLeft, Clock, EyeOff, Lock, Mail, PartyPopper, Plus, Trophy, Users, X } from "lucide-react"
|
2026-07-18 17:15:50 +02:00
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
|
|
|
|
|
|
|
|
// --- Types (matcher API-kontrakten i app/routers/tournaments.py/matches.py/courses.py)
|
|
|
|
|
|
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Fire nye org-lagturnering-formater (2026-07-30) -- se tournament-program.tsx.
|
|
|
|
|
|
type Format =
|
|
|
|
|
|
| "foursome"
|
|
|
|
|
|
| "greensome"
|
|
|
|
|
|
| "scramble_2"
|
|
|
|
|
|
| "scramble_4"
|
|
|
|
|
|
| "fourball"
|
|
|
|
|
|
| "singles"
|
|
|
|
|
|
| "chapman"
|
|
|
|
|
|
| "shamble"
|
|
|
|
|
|
| "money_ball"
|
|
|
|
|
|
| "high_low_high"
|
2026-07-18 17:15:50 +02:00
|
|
|
|
type HoleConfig = "full_18" | "front_9" | "back_9"
|
|
|
|
|
|
type ScoringMode = "stroke" | "hole_result"
|
|
|
|
|
|
|
|
|
|
|
|
type ApiTeam = { id: string; name: string; color: string | null }
|
|
|
|
|
|
|
|
|
|
|
|
type ApiRosterEntry = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
player_id: string
|
|
|
|
|
|
display_name: string
|
|
|
|
|
|
handicap_index_snapshot: number | null
|
|
|
|
|
|
is_captain: boolean
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
// Konkurranseklasse (2026-08-03) -- brukt KUN til å forhåndsutfylle riktig
|
|
|
|
|
|
// utslag under, se AddSlotForm. Ingen egen resultatliste her.
|
|
|
|
|
|
class_id: string | null
|
|
|
|
|
|
class_name: string | null
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
Update Todos
Diagnostisere HCP-bug mot ekte data (gjort)
Fiks 1: filtrer valgte spillere bort fra listen (blind draw)
Fiks 3: tallvelger for slagregistrering (scorekort)
Fiks 4: hardkode full_18-scope i handicap-utregning (bekreftet bug)
Punkt 2: migrasjon 014 + kode (courses.py, handicap.py, matches.py, tournaments.py, frontend)
Scratch-verifisere punkt 2 (fletting, import, validering, remap)
Skrive ADR-029 + oppdatere FEATURE_BACKLOG.md
Presentere full plan og be om bekreftelse (ekte migrasjon + deploy)
Alle fire punkter er nå bygget og grundig scratch-verifisert. Oppsummering før jeg ber om bekreftelse på å rulle ut mot ekte systemer:
1 — Spillerliste: Fikset. Valgte spillere filtreres nå bort i stedet for å bare vises gråtonet.
3 — Tallvelger: Fikset. Ny StrokePicker (1–9, med utvidbar "10+"→10–19).
4 — HCP-bug: Bekreftet ekte kodebug (ikke bare synlighet). Jeg leste ekte produksjonsdata for testøkten din: front_9 + foursome, og alle fire deltakerne hadde NULL i handicap-feltene. Årsak: handicap-utregningen lette etter en tee-rating scopet til øktens hole_config, men slike rader lages i praksis kun med full_18-omfang — så den fant aldri noe for en front_9-økt. Din beskrevne formel (kombinert hcp/2, laveste side til 0 mottatte slag, resten fordelt fra stroke index 1) var allerede riktig implementert — bugen var at den aldri kjørte. Fikset og verifisert: identisk bruttoscore på begge sider ga nå et ikke-delt resultat, som beviser at hcp faktisk brukes.
2 — Tee/kjønn: Du hadde helt rett i presiseringen din — dette var større enn jeg først trodde. Kjønn er nå flyttet fra selve utslaget til ratingen (ny migrasjon 014_tee_gender_to_rating.sql), tee-valget i blind draw er blitt helt automatisk (ingen kjønnsvelger lenger), og manglende kjønn/rating avvises tydelig i stedet for å gjettes. Fikk egen ADR-029. Verifisert i fem separate runder, inkludert mot ekte teeoff-data (Borregaard) og en gjenskaping av akkurat Tjøme-mønsteret.
Bifunn, ikke fikset: en liten, urelatert 500-krasj (stroke-registrering på en bane uten registrerte hull) — vil du at jeg tar den også, eller lar den ligge som egen sak?
Utrullingsplan — dette er første gang i denne runden noe rører ekte data:
Migrasjon 014_tee_gender_to_rating.sql mot ekte teecup_db — slår sammen Tjømes 8 tee-rader til 4 (bevarer alle ratinger, reparerer alle referanser; mekanisk identisk med det jeg nettopp verifiserte i scratch)
2026-07-20 08:53:53 +02:00
|
|
|
|
// ADR-029: kjønn hører til RATINGEN, ikke selve utslaget -- riktig
|
|
|
|
|
|
// kjønnsspesifikk rating løses automatisk server-side ut fra spilleren, så
|
|
|
|
|
|
// frontend trenger kun å vise/velge blant de fysiske utslagene.
|
2026-07-18 17:15:50 +02:00
|
|
|
|
type ApiTee = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
type ApiTournamentClass = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
default_tee_id: string | null
|
|
|
|
|
|
default_tee_name: string | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-18 17:15:50 +02:00
|
|
|
|
type ApiSession = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string | null
|
|
|
|
|
|
format: Format
|
|
|
|
|
|
hole_config: HoleConfig
|
|
|
|
|
|
course_id: string
|
|
|
|
|
|
points_per_match: number
|
|
|
|
|
|
scoring_mode: ScoringMode
|
|
|
|
|
|
locked_team_ids: string[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ApiParticipant = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
team_side: "a" | "b"
|
|
|
|
|
|
team_roster_id: string
|
|
|
|
|
|
player_name: string
|
|
|
|
|
|
tee_id: string
|
|
|
|
|
|
tee_name: string
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Money Ball (2026-07-30) -- fast plass i rotasjonsrekkefølgen (0-3).
|
|
|
|
|
|
lineup_order: number | null
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ApiMatch = {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
sequence: number
|
|
|
|
|
|
team_a_id: string
|
|
|
|
|
|
team_b_id: string
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
status_text: string | null
|
|
|
|
|
|
points_side_a: number | null
|
|
|
|
|
|
points_side_b: number | null
|
|
|
|
|
|
leading_side: "a" | "b" | null
|
2026-07-18 17:15:50 +02:00
|
|
|
|
tee_time: string | null
|
|
|
|
|
|
participants: ApiParticipant[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Labels ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
const FORMAT_LABELS: Record<Format, string> = {
|
|
|
|
|
|
foursome: "Foursome",
|
|
|
|
|
|
greensome: "Greensome",
|
|
|
|
|
|
scramble_2: "Scramble (2)",
|
|
|
|
|
|
scramble_4: "Scramble (4)",
|
|
|
|
|
|
fourball: "Fourball",
|
|
|
|
|
|
singles: "Singel",
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
chapman: "Chapman",
|
|
|
|
|
|
shamble: "Shamble",
|
|
|
|
|
|
money_ball: "Money Ball",
|
|
|
|
|
|
high_low_high: "High-low-high",
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
const HOLE_LABELS: Record<HoleConfig, string> = { full_18: "18 hull", front_9: "Front 9", back_9: "Back 9" }
|
|
|
|
|
|
const SCORING_LABELS: Record<ScoringMode, string> = { stroke: "Slag for slag", hole_result: "Kun hullresultat" }
|
|
|
|
|
|
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Minimum påkrevd per side FØR laget kan låses (brukt av teamIsComplete OG
|
|
|
|
|
|
// av "N spillere per side"-teksten). Shamble er BEVISST forskjellig fra sitt
|
|
|
|
|
|
// eget tak (se maxSlotsPerSide) -- fleksibel 2-4, ingen fast antall.
|
2026-07-18 17:15:50 +02:00
|
|
|
|
function slotsPerSide(format: Format): number {
|
|
|
|
|
|
if (format === "singles") return 1
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
if (format === "scramble_4" || format === "money_ball") return 4
|
|
|
|
|
|
if (format === "shamble") return 2
|
2026-07-18 17:15:50 +02:00
|
|
|
|
return 2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Maks antall AddSlotForm skal tillate å legge til (backend sin egen
|
|
|
|
|
|
// 409 LIMIT_REACHED-grense) -- kun Shamble skiller seg fra slotsPerSide.
|
|
|
|
|
|
function maxSlotsPerSide(format: Format): number {
|
|
|
|
|
|
return format === "shamble" ? 4 : slotsPerSide(format)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-18 17:15:50 +02:00
|
|
|
|
// --- Component ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
export function SessionBlindDraw({
|
|
|
|
|
|
organizationId,
|
|
|
|
|
|
tournamentId,
|
|
|
|
|
|
sessionId,
|
|
|
|
|
|
tournamentName,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
organizationId: string
|
|
|
|
|
|
tournamentId: string
|
|
|
|
|
|
sessionId: string
|
|
|
|
|
|
tournamentName: string
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const [session, setSession] = useState<ApiSession | null>(null)
|
|
|
|
|
|
const [teams, setTeams] = useState<[ApiTeam, ApiTeam] | null>(null)
|
|
|
|
|
|
const [rosters, setRosters] = useState<Record<string, ApiRosterEntry[]>>({})
|
|
|
|
|
|
const [tees, setTees] = useState<ApiTee[]>([])
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
const [classes, setClasses] = useState<ApiTournamentClass[]>([])
|
2026-07-18 17:15:50 +02:00
|
|
|
|
const [matches, setMatches] = useState<ApiMatch[]>([])
|
|
|
|
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
|
const [confirmingLock, setConfirmingLock] = useState<string | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let cancelled = false
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [sessionsRes, teamsRes] = await Promise.all([
|
|
|
|
|
|
fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/sessions`, { credentials: "include" }),
|
|
|
|
|
|
fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/teams`, { credentials: "include" }),
|
|
|
|
|
|
])
|
|
|
|
|
|
if (!sessionsRes.ok || !teamsRes.ok) throw new Error("load failed")
|
|
|
|
|
|
const sessionsData: ApiSession[] = await sessionsRes.json()
|
|
|
|
|
|
const foundSession = sessionsData.find((s) => s.id === sessionId)
|
|
|
|
|
|
if (!foundSession) throw new Error("session not found")
|
|
|
|
|
|
const teamsData: ApiTeam[] = await teamsRes.json()
|
|
|
|
|
|
if (teamsData.length !== 2) throw new Error("expected 2 teams")
|
|
|
|
|
|
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
const [rosterARes, rosterBRes, teesRes, matchesRes, classesRes] = await Promise.all([
|
2026-07-18 17:15:50 +02:00
|
|
|
|
fetch(`/orgs/${organizationId}/teams/${teamsData[0].id}/roster`, { credentials: "include" }),
|
|
|
|
|
|
fetch(`/orgs/${organizationId}/teams/${teamsData[1].id}/roster`, { credentials: "include" }),
|
|
|
|
|
|
fetch(`/orgs/${organizationId}/courses/${foundSession.course_id}/tees`, { credentials: "include" }),
|
|
|
|
|
|
fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, { credentials: "include" }),
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/classes`, { credentials: "include" }),
|
2026-07-18 17:15:50 +02:00
|
|
|
|
])
|
|
|
|
|
|
if (!rosterARes.ok || !rosterBRes.ok || !teesRes.ok || !matchesRes.ok) throw new Error("load failed")
|
|
|
|
|
|
|
|
|
|
|
|
if (cancelled) return
|
|
|
|
|
|
setSession(foundSession)
|
|
|
|
|
|
setTeams([teamsData[0], teamsData[1]])
|
|
|
|
|
|
setRosters({
|
|
|
|
|
|
[teamsData[0].id]: await rosterARes.json(),
|
|
|
|
|
|
[teamsData[1].id]: await rosterBRes.json(),
|
|
|
|
|
|
})
|
|
|
|
|
|
setTees(await teesRes.json())
|
|
|
|
|
|
setMatches(await matchesRes.json())
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
if (classesRes.ok) setClasses(await classesRes.json())
|
2026-07-18 17:15:50 +02:00
|
|
|
|
} catch {
|
|
|
|
|
|
if (!cancelled) setError("Klarte ikke å laste blind draw-siden. Prøv å laste siden på nytt.")
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (!cancelled) setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void load()
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
cancelled = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [organizationId, tournamentId, sessionId])
|
|
|
|
|
|
|
|
|
|
|
|
const sortedMatches = useMemo(() => [...matches].sort((a, b) => a.sequence - b.sequence), [matches])
|
|
|
|
|
|
const nextSequence = useMemo(
|
|
|
|
|
|
() => (matches.length === 0 ? 1 : Math.max(...matches.map((m) => m.sequence)) + 1),
|
|
|
|
|
|
[matches],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const bothLocked = (session?.locked_team_ids.length ?? 0) >= 2
|
|
|
|
|
|
|
|
|
|
|
|
async function refetchMatches() {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, { credentials: "include" })
|
|
|
|
|
|
if (res.ok) setMatches(await res.json())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function addMatch() {
|
|
|
|
|
|
if (!teams) return
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
body: JSON.stringify({ sequence: nextSequence, team_a_id: teams[0].id, team_b_id: teams[1].id }),
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!res.ok) throw new Error(`create match: ${res.status}`)
|
|
|
|
|
|
const created: ApiMatch = await res.json()
|
|
|
|
|
|
setMatches((prev) => [...prev, created])
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setError("Klarte ikke å legge til match. Prøv igjen.")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
async function addParticipant(
|
|
|
|
|
|
matchId: string,
|
|
|
|
|
|
teamSide: "a" | "b",
|
|
|
|
|
|
teamRosterId: string,
|
|
|
|
|
|
teeId: string,
|
|
|
|
|
|
lineupOrder: number | null = null,
|
|
|
|
|
|
) {
|
2026-07-18 17:15:50 +02:00
|
|
|
|
setError(null)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/matches/${matchId}/participants`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
credentials: "include",
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
team_side: teamSide,
|
|
|
|
|
|
team_roster_id: teamRosterId,
|
|
|
|
|
|
tee_id: teeId,
|
|
|
|
|
|
lineup_order: lineupOrder,
|
|
|
|
|
|
}),
|
2026-07-18 17:15:50 +02:00
|
|
|
|
})
|
|
|
|
|
|
if (!res.ok) throw new Error(`add participant: ${res.status}`)
|
|
|
|
|
|
const created: ApiParticipant = await res.json()
|
|
|
|
|
|
setMatches((prev) =>
|
|
|
|
|
|
prev.map((m) => (m.id === matchId ? { ...m, participants: [...m.participants, created] } : m)),
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setError("Klarte ikke å legge til spilleren. Kanskje laget allerede har låst, eller du mangler tilgang.")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function removeParticipant(matchId: string, participantId: string) {
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/matches/${matchId}/participants/${participantId}`, {
|
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!res.ok && res.status !== 204) throw new Error(`remove participant: ${res.status}`)
|
|
|
|
|
|
setMatches((prev) =>
|
|
|
|
|
|
prev.map((m) =>
|
|
|
|
|
|
m.id === matchId
|
|
|
|
|
|
? { ...m, participants: m.participants.filter((p) => p.id !== participantId) }
|
|
|
|
|
|
: m,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setError("Klarte ikke å fjerne spilleren. Prøv igjen.")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function lockTeam(teamId: string) {
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/lock`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
body: JSON.stringify({ team_id: teamId }),
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!res.ok) throw new Error(`lock: ${res.status}`)
|
|
|
|
|
|
setSession((prev) =>
|
|
|
|
|
|
prev ? { ...prev, locked_team_ids: [...prev.locked_team_ids, teamId] } : prev,
|
|
|
|
|
|
)
|
|
|
|
|
|
// Motstanderens deltakere er skjult server-side inntil begge har låst
|
|
|
|
|
|
// (ADR-013) -- hent matchene på nytt for å få den avslørte visningen
|
|
|
|
|
|
// hvis dette var det andre laget som nettopp låste.
|
|
|
|
|
|
await refetchMatches()
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setError("Klarte ikke å låse oppstillingen. Prøv igjen.")
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setConfirmingLock(null)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const detailHref = `/tournaments/${tournamentId}?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`
|
|
|
|
|
|
const programHref = `/tournaments/${tournamentId}/program?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`
|
|
|
|
|
|
|
|
|
|
|
|
if (loading || !session || !teams) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex min-h-[100dvh] flex-col bg-background">
|
|
|
|
|
|
<header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
|
|
|
|
|
|
<div className="mx-auto flex w-full max-w-5xl items-center gap-3 px-5 py-4">
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href={programHref}
|
|
|
|
|
|
aria-label="Tilbake til program"
|
|
|
|
|
|
className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft aria-hidden="true" className="size-5" />
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="flex min-w-0 flex-col">
|
|
|
|
|
|
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
|
|
|
|
Turnering
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">
|
|
|
|
|
|
{tournamentName}
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<main className="mx-auto flex w-full max-w-5xl flex-1 items-center justify-center px-5 py-6">
|
|
|
|
|
|
{error ? (
|
|
|
|
|
|
<p role="alert" className="text-sm font-medium text-destructive">
|
|
|
|
|
|
{error}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const perSide = slotsPerSide(session.format)
|
|
|
|
|
|
|
|
|
|
|
|
function usedPlayerIds(teamId: string, exceptMatchId: string): Set<string> {
|
|
|
|
|
|
const side: "a" | "b" = teamId === teams![0].id ? "a" : "b"
|
|
|
|
|
|
const used = new Set<string>()
|
|
|
|
|
|
for (const m of matches) {
|
|
|
|
|
|
if (m.id === exceptMatchId) continue
|
|
|
|
|
|
for (const p of m.participants) {
|
|
|
|
|
|
if (p.team_side === side) used.add(p.team_roster_id)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return used
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function teamIsComplete(teamId: string): boolean {
|
|
|
|
|
|
const side: "a" | "b" = teamId === teams![0].id ? "a" : "b"
|
|
|
|
|
|
if (matches.length === 0) return false
|
|
|
|
|
|
return matches.every((m) => m.participants.filter((p) => p.team_side === side).length >= perSide)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex min-h-[100dvh] flex-col bg-background">
|
|
|
|
|
|
<header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
|
|
|
|
|
|
<div className="mx-auto flex w-full max-w-5xl items-center gap-3 px-5 py-4">
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href={programHref}
|
|
|
|
|
|
aria-label="Tilbake til program"
|
|
|
|
|
|
className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft aria-hidden="true" className="size-5" />
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="flex min-w-0 flex-col">
|
|
|
|
|
|
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
|
|
|
|
Turnering
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">
|
|
|
|
|
|
{tournamentName}
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<span className="truncate text-sm text-muted-foreground">
|
|
|
|
|
|
{session.name?.trim() || "Økt"} · {FORMAT_LABELS[session.format]}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<main className="mx-auto w-full max-w-5xl flex-1 px-5 py-6 sm:py-8">
|
|
|
|
|
|
<div className="mb-4 flex flex-wrap items-center gap-2">
|
|
|
|
|
|
<MetaBadge>{FORMAT_LABELS[session.format]}</MetaBadge>
|
|
|
|
|
|
<MetaBadge>{HOLE_LABELS[session.hole_config]}</MetaBadge>
|
|
|
|
|
|
<MetaBadge>{SCORING_LABELS[session.scoring_mode]}</MetaBadge>
|
|
|
|
|
|
<MetaBadge>{formatPoints(session.points_per_match)} poeng</MetaBadge>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
Update Todos
Undersøk kode-kontekst for alle tre byggeoppgavene
Avklar åpne designspørsmål med bruker
Bygg: flytte spiller mellom lag (backend + frontend)
Bygg: individuell rangering i turnering (backend + frontend)
Bygg: midlertidige spillere + etter-runde-e-post (backend + frontend)
Typesjekket produksjonsbuild av frontend
Scratch-verifiser alt tre + full regresjon
Browser-verifiser alt tre (Chrome DevTools)
Oppdater CLAUDE.md/FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md
Rull ut mot ekte systemer (med bekreftelse)
Alle tre er live. Oppsummert:
Flytte spiller mellom lag — ny "Flytt til {annet lag}"-handling i lag-oversikten, atomisk, blokkert hvis spilleren allerede er lagt til i en match.
Individuell rangering per økt — ny brutto/netto/poeng-rangering for singles/fourball-slagspill-økter, ved siden av det eksisterende lag-poeng-leaderboardet, lenket fra blind draw-skjermen.
Midlertidige spillere: etter-runde-e-post — ny knapp som sender scorekort + ekte innloggingslenke til spillere uten konto, med dobbel-utsendingssperre.
Alt verifisert grundig: 54/54 nye scratch-sjekker + full regresjon (5+28+19 eksisterende sjekker), typesjekket produksjonsbuild, og en fullstendig gjennomgang i ekte nettleser (Chrome DevTools) av alle tre — ingen konsollfeil. Migrasjon 034 kjørt mot ekte database, begge containere redeployet, teeoff.no upåvirket. Samtidig rettet jeg opp noen steder der FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md hadde blitt hengende etter den faktiske, allerede leverte tilstanden (ADR-039-utrulling, PWA-offline-browsertesting).
2026-07-28 20:56:08 +02:00
|
|
|
|
{(session.format === "singles" || session.format === "fourball") && session.scoring_mode === "stroke" && (
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href={`/tournaments/${tournamentId}/sessions/${sessionId}/individual-leaderboard?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`}
|
|
|
|
|
|
className="mb-4 flex w-fit items-center gap-1.5 rounded-xl border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-accent/60"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trophy aria-hidden="true" className="size-4 text-primary" />
|
|
|
|
|
|
Individuell rangering
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{matches.length > 0 && <SendInvitationsButton organizationId={organizationId} sessionId={sessionId} />}
|
|
|
|
|
|
|
2026-07-18 17:15:50 +02:00
|
|
|
|
{error && (
|
|
|
|
|
|
<p role="alert" className="mb-4 text-sm font-medium text-destructive">
|
|
|
|
|
|
{error}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<StatusBanner teams={teams} lockedTeamIds={session.locked_team_ids} bothLocked={bothLocked} />
|
|
|
|
|
|
|
|
|
|
|
|
{bothLocked ? (
|
2026-07-18 17:47:33 +02:00
|
|
|
|
<RevealedView
|
|
|
|
|
|
teams={teams}
|
|
|
|
|
|
matches={sortedMatches}
|
|
|
|
|
|
scorecardHrefFor={(matchId) =>
|
|
|
|
|
|
`/tournaments/${tournamentId}/sessions/${sessionId}/matches/${matchId}?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
2026-07-18 17:15:50 +02:00
|
|
|
|
) : (
|
|
|
|
|
|
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
|
|
|
|
|
{teams.map((team, teamIndex) => (
|
|
|
|
|
|
<TeamColumn
|
|
|
|
|
|
key={team.id}
|
|
|
|
|
|
team={team}
|
|
|
|
|
|
side={teamIndex === 0 ? "a" : "b"}
|
|
|
|
|
|
roster={rosters[team.id] ?? []}
|
|
|
|
|
|
tees={tees}
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes={classes}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
matches={sortedMatches}
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
perSide={maxSlotsPerSide(session.format)}
|
|
|
|
|
|
format={session.format}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
locked={session.locked_team_ids.includes(team.id)}
|
|
|
|
|
|
usedPlayerIds={usedPlayerIds}
|
|
|
|
|
|
onAddParticipant={addParticipant}
|
|
|
|
|
|
onRemoveParticipant={removeParticipant}
|
|
|
|
|
|
onAddMatch={addMatch}
|
|
|
|
|
|
complete={teamIsComplete(team.id)}
|
|
|
|
|
|
confirming={confirmingLock === team.id}
|
|
|
|
|
|
onRequestLock={() => setConfirmingLock(team.id)}
|
|
|
|
|
|
onCancelLock={() => setConfirmingLock(null)}
|
|
|
|
|
|
onConfirmLock={() => lockTeam(team.id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Status banner -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
function StatusBanner({
|
|
|
|
|
|
teams,
|
|
|
|
|
|
lockedTeamIds,
|
|
|
|
|
|
bothLocked,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
teams: [ApiTeam, ApiTeam]
|
|
|
|
|
|
lockedTeamIds: string[]
|
|
|
|
|
|
bothLocked: boolean
|
|
|
|
|
|
}) {
|
|
|
|
|
|
if (bothLocked) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center gap-3 rounded-3xl bg-primary px-5 py-4 text-primary-foreground shadow-sm">
|
|
|
|
|
|
<div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-primary-foreground/15">
|
|
|
|
|
|
<PartyPopper aria-hidden="true" className="size-6" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span className="text-base font-extrabold tracking-tight">Oppstilling avslørt!</span>
|
|
|
|
|
|
<span className="text-sm text-primary-foreground/80">
|
|
|
|
|
|
Begge lag har låst. Her er paringene for økten.
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lockedCount = lockedTeamIds.length
|
|
|
|
|
|
if (lockedCount === 1) {
|
|
|
|
|
|
const lockedTeam = lockedTeamIds.includes(teams[0].id) ? teams[0] : teams[1]
|
|
|
|
|
|
const otherTeam = lockedTeam.id === teams[0].id ? teams[1] : teams[0]
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center gap-3 rounded-3xl border border-border bg-card px-5 py-4 shadow-sm">
|
|
|
|
|
|
<div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-secondary">
|
|
|
|
|
|
<Clock aria-hidden="true" className="size-6 text-muted-foreground" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span className="text-base font-bold tracking-tight text-foreground text-pretty">
|
|
|
|
|
|
{lockedTeam.name} har låst
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-sm text-muted-foreground">
|
|
|
|
|
|
Venter på at {otherTeam.name} låser sin oppstilling.
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center gap-3 rounded-3xl border border-border bg-card px-5 py-4 shadow-sm">
|
|
|
|
|
|
<div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-secondary">
|
|
|
|
|
|
<EyeOff aria-hidden="true" className="size-6 text-muted-foreground" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span className="text-base font-bold tracking-tight text-foreground">Begge lag setter opp i skjul</span>
|
|
|
|
|
|
<span className="text-sm text-muted-foreground text-pretty">
|
|
|
|
|
|
Ingen ser motstanderens valg før begge har låst oppstillingen.
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Team column ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
function TeamColumn({
|
|
|
|
|
|
team,
|
|
|
|
|
|
side,
|
|
|
|
|
|
roster,
|
|
|
|
|
|
tees,
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes,
|
2026-07-18 17:15:50 +02:00
|
|
|
|
matches,
|
|
|
|
|
|
perSide,
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
format,
|
2026-07-18 17:15:50 +02:00
|
|
|
|
locked,
|
|
|
|
|
|
usedPlayerIds,
|
|
|
|
|
|
onAddParticipant,
|
|
|
|
|
|
onRemoveParticipant,
|
|
|
|
|
|
onAddMatch,
|
|
|
|
|
|
complete,
|
|
|
|
|
|
confirming,
|
|
|
|
|
|
onRequestLock,
|
|
|
|
|
|
onCancelLock,
|
|
|
|
|
|
onConfirmLock,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
team: ApiTeam
|
|
|
|
|
|
side: "a" | "b"
|
|
|
|
|
|
roster: ApiRosterEntry[]
|
|
|
|
|
|
tees: ApiTee[]
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes: ApiTournamentClass[]
|
2026-07-18 17:15:50 +02:00
|
|
|
|
matches: ApiMatch[]
|
|
|
|
|
|
perSide: number
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
format: Format
|
2026-07-18 17:15:50 +02:00
|
|
|
|
locked: boolean
|
|
|
|
|
|
usedPlayerIds: (teamId: string, exceptMatchId: string) => Set<string>
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
onAddParticipant: (
|
|
|
|
|
|
matchId: string,
|
|
|
|
|
|
side: "a" | "b",
|
|
|
|
|
|
teamRosterId: string,
|
|
|
|
|
|
teeId: string,
|
|
|
|
|
|
lineupOrder?: number | null,
|
|
|
|
|
|
) => void
|
2026-07-18 17:15:50 +02:00
|
|
|
|
onRemoveParticipant: (matchId: string, participantId: string) => void
|
|
|
|
|
|
onAddMatch: () => void
|
|
|
|
|
|
complete: boolean
|
|
|
|
|
|
confirming: boolean
|
|
|
|
|
|
onRequestLock: () => void
|
|
|
|
|
|
onCancelLock: () => void
|
|
|
|
|
|
onConfirmLock: () => void
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const color = team.color ?? "#64748b"
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section
|
2026-07-29 12:44:51 +02:00
|
|
|
|
className="flex flex-col gap-3 rounded-3xl border border-border bg-card/40 p-4 shadow-md shadow-black/8 sm:p-5"
|
2026-07-18 17:15:50 +02:00
|
|
|
|
style={{ borderLeftWidth: 6, borderLeftColor: color }}
|
|
|
|
|
|
aria-label={`Oppstilling for ${team.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<div className="flex items-center gap-2.5">
|
|
|
|
|
|
<span aria-hidden="true" className="size-4 shrink-0 rounded-full" style={{ backgroundColor: color }} />
|
|
|
|
|
|
<h2 className="text-lg font-extrabold tracking-tight text-foreground">{team.name}</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
|
|
|
|
|
|
<Users aria-hidden="true" className="size-4" />
|
|
|
|
|
|
{matches.length} {matches.length === 1 ? "match" : "matcher"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{locked && (
|
|
|
|
|
|
<div className="inline-flex w-fit items-center gap-1.5 rounded-full bg-secondary px-3 py-1 text-xs font-bold text-secondary-foreground">
|
|
|
|
|
|
<Lock aria-hidden="true" className="size-3.5" />
|
|
|
|
|
|
Låst
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-3">
|
|
|
|
|
|
{matches.map((match) => {
|
|
|
|
|
|
const sideParticipants = match.participants.filter((p) => p.team_side === side)
|
|
|
|
|
|
const disabled = usedPlayerIds(team.id, match.id)
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={match.id} className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="flex size-8 items-center justify-center rounded-xl bg-primary text-sm font-extrabold tabular-nums text-primary-foreground">
|
|
|
|
|
|
{match.sequence}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
|
|
|
|
<Clock aria-hidden="true" className="size-4" />
|
|
|
|
|
|
{match.tee_time ? `Utslag ${formatTime(match.tee_time)}` : "Tid ikke satt"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-2 border-t border-border pt-3">
|
|
|
|
|
|
{sideParticipants.map((p) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={p.id}
|
|
|
|
|
|
className="flex items-center justify-between gap-3 rounded-xl bg-secondary/60 px-3 py-2.5"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="truncate text-sm font-semibold text-foreground">{p.player_name}</span>
|
|
|
|
|
|
<div className="flex shrink-0 items-center gap-2">
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
{format === "money_ball" && p.lineup_order !== null && (
|
2026-08-02 16:34:54 +02:00
|
|
|
|
<span className="inline-flex size-6 items-center justify-center rounded-full bg-muted text-xs font-extrabold text-muted-foreground">
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
{p.lineup_order}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
<span className="inline-flex items-center gap-1.5 rounded-full bg-card px-2.5 py-1 text-xs font-semibold text-foreground">
|
|
|
|
|
|
{p.tee_name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{!locked && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => onRemoveParticipant(match.id, p.id)}
|
|
|
|
|
|
aria-label={`Fjern ${p.player_name}`}
|
|
|
|
|
|
className="flex size-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<X aria-hidden="true" className="size-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
|
|
{!locked && sideParticipants.length < perSide && (
|
|
|
|
|
|
<AddSlotForm
|
|
|
|
|
|
roster={roster}
|
|
|
|
|
|
tees={tees}
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes={classes}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
disabledPlayerIds={disabled}
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Money Ball -- 0-3 ledig, avhengig av hva laget allerede har brukt.
|
|
|
|
|
|
availableLineupOrders={
|
|
|
|
|
|
format === "money_ball"
|
|
|
|
|
|
? [0, 1, 2, 3].filter((n) => !sideParticipants.some((p) => p.lineup_order === n))
|
|
|
|
|
|
: null
|
|
|
|
|
|
}
|
|
|
|
|
|
onAdd={(teamRosterId, teeId, lineupOrder) =>
|
|
|
|
|
|
onAddParticipant(match.id, side, teamRosterId, teeId, lineupOrder)
|
|
|
|
|
|
}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{perSide > 1 && (
|
|
|
|
|
|
<p className="text-xs text-muted-foreground">{perSide} spillere per side i dette formatet</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{!locked && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onAddMatch}
|
|
|
|
|
|
className="flex items-center justify-center gap-2 rounded-2xl border border-dashed border-border bg-background/50 px-4 py-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Plus aria-hidden="true" className="size-4" />
|
|
|
|
|
|
Legg til match
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{!locked && (
|
|
|
|
|
|
<div className="mt-1">
|
|
|
|
|
|
{confirming ? (
|
|
|
|
|
|
<div className="flex flex-col gap-3 rounded-2xl border border-border bg-background/60 p-4">
|
|
|
|
|
|
<p className="text-sm font-medium text-foreground text-pretty">
|
|
|
|
|
|
Er du sikker? Du kan ikke endre oppstillingen etter at du har låst.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<Button type="button" variant="outline" onClick={onCancelLock} className="h-11 flex-1 rounded-2xl font-semibold">
|
|
|
|
|
|
Avbryt
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button type="button" onClick={onConfirmLock} className="h-11 flex-1 rounded-2xl font-bold">
|
|
|
|
|
|
<Lock aria-hidden="true" className="size-4" />
|
|
|
|
|
|
Lås
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onRequestLock}
|
|
|
|
|
|
disabled={!complete}
|
|
|
|
|
|
className="h-12 w-full rounded-2xl text-base font-bold shadow-sm"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Lock aria-hidden="true" className="size-5" />
|
|
|
|
|
|
Lås oppstilling
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{!complete && !confirming && (
|
|
|
|
|
|
<p className="mt-2 text-center text-xs text-muted-foreground text-pretty">
|
|
|
|
|
|
Velg spiller og tee for alle plasser før du kan låse.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Add-slot inline form --------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
function AddSlotForm({
|
|
|
|
|
|
roster,
|
|
|
|
|
|
tees,
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes,
|
2026-07-18 17:15:50 +02:00
|
|
|
|
disabledPlayerIds,
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
availableLineupOrders,
|
2026-07-18 17:15:50 +02:00
|
|
|
|
onAdd,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
roster: ApiRosterEntry[]
|
|
|
|
|
|
tees: ApiTee[]
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
classes: ApiTournamentClass[]
|
2026-07-18 17:15:50 +02:00
|
|
|
|
disabledPlayerIds: Set<string>
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
// Money Ball (2026-07-30) -- ledige 0-3-plasser, null for alle andre formater.
|
|
|
|
|
|
availableLineupOrders: number[] | null
|
|
|
|
|
|
onAdd: (teamRosterId: string, teeId: string, lineupOrder: number | null) => void
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}) {
|
|
|
|
|
|
const [rosterId, setRosterId] = useState("")
|
|
|
|
|
|
const [teeId, setTeeId] = useState("")
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
const [lineupOrder, setLineupOrder] = useState("")
|
|
|
|
|
|
|
Alt er bygget og grundig scratch-verifisert — både API-nivå og ekte nettleser-klikk, på begge turneringstyper. Før jeg ruller ut mot ekte teecup_db, her er planen:
Hva som skjer:
Kjør migrasjon 053_tournament_classes.sql mot ekte teecup_db — ny tournament_class-tabell + nullable class_id-kolonne på team_roster og tournament_participant (rent additivt, ingen eksisterende data røres).
docker compose up -d --build teecup_api teecup_frontend — begge containere bygges og redeployes.
Hva som er verifisert:
Klasser kan opprettes/slettes for begge turneringstyper, med valgfritt standardutslag (kaskaderende bane→utslag-valg)
I lagturneringer: klasse foreslår riktig utslag automatisk når en spiller legges til en kamp (bekreftet: Kari→44, Ola→56), ingen endring i selve kampresultatet
I individuelle turneringer: samme utslag-forslag ved runde-oppsett, PLUSS leaderboardet deler seg nå i egne seksjoner per klasse med riktig rangering innad i hver (bekreftet: Damer viste Kari #1/Siv #2, Herrer viste Ola #1 — matcher håndregnet slagsum)
Sletter man en klasse, mister spillerne bare merkelappen (ikke fjernet fra laget/turneringen)
Ingen klasser opprettet = identisk visning som før (bakoverkompatibelt)
98/98 enhetstester og 12/12 isolasjonstester uendret, ekte typesjekket produksjonsbuild
2026-08-04 03:44:16 +02:00
|
|
|
|
// Konkurranseklasser (2026-08-03) -- forhåndsvelg spillerens klasses
|
|
|
|
|
|
// standardutslag, MEN kun hvis det utslaget faktisk finnes blant DENNE
|
|
|
|
|
|
// øktens baneutslag (klassens standardutslag kan tilhøre en annen bane
|
|
|
|
|
|
// enn økten er satt opp med) -- fortsatt fritt overstyrbart under.
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!rosterId) return
|
|
|
|
|
|
const entry = roster.find((r) => r.id === rosterId)
|
|
|
|
|
|
const cls = entry?.class_id ? classes.find((c) => c.id === entry.class_id) : null
|
|
|
|
|
|
const defaultTee = cls?.default_tee_id ? tees.find((t) => t.id === cls.default_tee_id) : null
|
|
|
|
|
|
if (defaultTee) setTeeId(defaultTee.id)
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
}, [rosterId])
|
|
|
|
|
|
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
const needsLineupOrder = availableLineupOrders !== null
|
|
|
|
|
|
const canAdd = rosterId && teeId && (!needsLineupOrder || lineupOrder !== "")
|
2026-07-18 17:15:50 +02:00
|
|
|
|
|
|
|
|
|
|
function handleAdd() {
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
if (!canAdd) return
|
|
|
|
|
|
onAdd(rosterId, teeId, needsLineupOrder ? Number(lineupOrder) : null)
|
2026-07-18 17:15:50 +02:00
|
|
|
|
setRosterId("")
|
|
|
|
|
|
setTeeId("")
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
setLineupOrder("")
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
<div className="flex flex-col gap-2 rounded-xl border border-dashed border-border bg-background/20 p-2">
|
|
|
|
|
|
<div className="grid grid-cols-[1fr_1fr_auto] gap-2">
|
|
|
|
|
|
<label className="flex flex-col gap-1">
|
|
|
|
|
|
<span className="sr-only">Velg spiller</span>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={rosterId}
|
|
|
|
|
|
onChange={(e) => setRosterId(e.target.value)}
|
|
|
|
|
|
className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Velg spiller</option>
|
|
|
|
|
|
{roster
|
|
|
|
|
|
.filter((p) => !disabledPlayerIds.has(p.id))
|
|
|
|
|
|
.map((p) => (
|
|
|
|
|
|
<option key={p.id} value={p.id}>
|
|
|
|
|
|
{p.display_name}
|
|
|
|
|
|
{p.handicap_index_snapshot !== null ? ` (${formatHcp(p.handicap_index_snapshot)})` : ""}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label className="flex flex-col gap-1">
|
|
|
|
|
|
<span className="sr-only">Velg tee</span>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={teeId}
|
|
|
|
|
|
onChange={(e) => setTeeId(e.target.value)}
|
|
|
|
|
|
className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Velg tee</option>
|
|
|
|
|
|
{tees.map((t) => (
|
|
|
|
|
|
<option key={t.id} value={t.id}>
|
|
|
|
|
|
{t.name}
|
Update Todos
Diagnostisere HCP-bug mot ekte data (gjort)
Fiks 1: filtrer valgte spillere bort fra listen (blind draw)
Fiks 3: tallvelger for slagregistrering (scorekort)
Fiks 4: hardkode full_18-scope i handicap-utregning (bekreftet bug)
Punkt 2: migrasjon 014 + kode (courses.py, handicap.py, matches.py, tournaments.py, frontend)
Scratch-verifisere punkt 2 (fletting, import, validering, remap)
Skrive ADR-029 + oppdatere FEATURE_BACKLOG.md
Presentere full plan og be om bekreftelse (ekte migrasjon + deploy)
Alle fire punkter er nå bygget og grundig scratch-verifisert. Oppsummering før jeg ber om bekreftelse på å rulle ut mot ekte systemer:
1 — Spillerliste: Fikset. Valgte spillere filtreres nå bort i stedet for å bare vises gråtonet.
3 — Tallvelger: Fikset. Ny StrokePicker (1–9, med utvidbar "10+"→10–19).
4 — HCP-bug: Bekreftet ekte kodebug (ikke bare synlighet). Jeg leste ekte produksjonsdata for testøkten din: front_9 + foursome, og alle fire deltakerne hadde NULL i handicap-feltene. Årsak: handicap-utregningen lette etter en tee-rating scopet til øktens hole_config, men slike rader lages i praksis kun med full_18-omfang — så den fant aldri noe for en front_9-økt. Din beskrevne formel (kombinert hcp/2, laveste side til 0 mottatte slag, resten fordelt fra stroke index 1) var allerede riktig implementert — bugen var at den aldri kjørte. Fikset og verifisert: identisk bruttoscore på begge sider ga nå et ikke-delt resultat, som beviser at hcp faktisk brukes.
2 — Tee/kjønn: Du hadde helt rett i presiseringen din — dette var større enn jeg først trodde. Kjønn er nå flyttet fra selve utslaget til ratingen (ny migrasjon 014_tee_gender_to_rating.sql), tee-valget i blind draw er blitt helt automatisk (ingen kjønnsvelger lenger), og manglende kjønn/rating avvises tydelig i stedet for å gjettes. Fikk egen ADR-029. Verifisert i fem separate runder, inkludert mot ekte teeoff-data (Borregaard) og en gjenskaping av akkurat Tjøme-mønsteret.
Bifunn, ikke fikset: en liten, urelatert 500-krasj (stroke-registrering på en bane uten registrerte hull) — vil du at jeg tar den også, eller lar den ligge som egen sak?
Utrullingsplan — dette er første gang i denne runden noe rører ekte data:
Migrasjon 014_tee_gender_to_rating.sql mot ekte teecup_db — slår sammen Tjømes 8 tee-rader til 4 (bevarer alle ratinger, reparerer alle referanser; mekanisk identisk med det jeg nettopp verifiserte i scratch)
2026-07-20 08:53:53 +02:00
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
disabled={!canAdd}
|
|
|
|
|
|
onClick={handleAdd}
|
|
|
|
|
|
className="h-11 w-11 shrink-0 rounded-lg"
|
|
|
|
|
|
aria-label="Legg til spiller"
|
2026-07-18 17:15:50 +02:00
|
|
|
|
>
|
Update Todos
Chapman: format-valg i new-round.tsx + tournament-program.tsx (session)
Nassau: nytt vindu-resultatvisning i round-detail.tsx + session-scorecard.tsx
Københavner: format-valg + poengtabell-visning i individual-tournament-detail.tsx
Bingo Bango Bongo: format-valg + per-hull picker + poengtabell
Flaggturnering: format-valg + nedtelling/resultatvisning
Shamble: format-valg + best_n + lagvisning (frittstående + org-lag)
Money Ball: format-valg + lineup_order + lagvisning (frittstående + org-lag)
High-low-high: format-valg + løpende poeng-resultatvisning (alle 3 flater)
Ekte produksjonsbuild -- FERDIG, kompilerte rent (27 ruter)
Browserverifisert alle åtte formatene -- fant og fikset ekte backend-bug (side-handicap-recompute)
Rullet ut mot ekte teecup_api/teecup_frontend -- FERDIG, health checks grønne, teeoff.no upåvirket
Rullet ut live 2026-07-30. Frontend for alle åtte nye turneringsformatene (Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, High-low-high) er nå bygget, browserverifisert og live — dekker alle tre flatene (frittstående runder, org-lagturneringer, org-individuelle turneringer). Fant og fikset én reell backend-bug underveis (side-handicap ble ikke regnet på nytt når en deltakers HCP ble satt/endret etter at de allerede var tildelt en side). Ingen migrasjon i denne runden, teeoff.no upåvirket. ADR-039/åtte-formater-arbeidet er dermed helt ferdig, backend og frontend.
2026-08-01 06:51:53 +02:00
|
|
|
|
<Plus aria-hidden="true" className="size-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{needsLineupOrder && (
|
|
|
|
|
|
<label className="flex flex-col gap-1">
|
|
|
|
|
|
<span className="text-xs font-semibold text-muted-foreground">Plass i rekkefølgen (Money Ball)</span>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={lineupOrder}
|
|
|
|
|
|
onChange={(e) => setLineupOrder(e.target.value)}
|
|
|
|
|
|
className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Velg plass</option>
|
|
|
|
|
|
{availableLineupOrders!.map((n) => (
|
|
|
|
|
|
<option key={n} value={n}>
|
|
|
|
|
|
{n}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
)}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Revealed view ---------------------------------------------------------
|
|
|
|
|
|
|
2026-07-18 17:47:33 +02:00
|
|
|
|
function RevealedView({
|
|
|
|
|
|
teams,
|
|
|
|
|
|
matches,
|
|
|
|
|
|
scorecardHrefFor,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
teams: [ApiTeam, ApiTeam]
|
|
|
|
|
|
matches: ApiMatch[]
|
|
|
|
|
|
scorecardHrefFor: (matchId: string) => string
|
|
|
|
|
|
}) {
|
2026-07-18 17:15:50 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="mt-6 flex flex-col gap-3">
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
{matches.map((match) => {
|
|
|
|
|
|
// Fargekoding etter ledende lag (ADR-020) -- umiddelbar visuell
|
|
|
|
|
|
// status i stedet for kun tekst, samme mønster som TV-dekning av
|
|
|
|
|
|
// Ryder Cup. Ingen farge når leading_side er null (ikke startet/AS).
|
|
|
|
|
|
const leadingTeam =
|
|
|
|
|
|
match.leading_side === "a" ? teams[0] : match.leading_side === "b" ? teams[1] : null
|
|
|
|
|
|
const leadColor = leadingTeam?.color ?? null
|
|
|
|
|
|
const decided = match.points_side_a !== null
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Link
|
|
|
|
|
|
key={match.id}
|
|
|
|
|
|
href={scorecardHrefFor(match.id)}
|
2026-07-29 12:44:51 +02:00
|
|
|
|
className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-4 shadow-md shadow-black/8 transition-colors hover:border-primary/50 hover:bg-accent/50 sm:p-5"
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
style={leadColor ? { borderTopWidth: 4, borderTopColor: leadColor } : undefined}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="flex size-9 items-center justify-center rounded-xl bg-primary text-base font-extrabold tabular-nums text-primary-foreground">
|
|
|
|
|
|
{match.sequence}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
</span>
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
{match.status_text ? (
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-extrabold uppercase tracking-wide",
|
|
|
|
|
|
decided ? "text-white" : "text-foreground",
|
|
|
|
|
|
)}
|
|
|
|
|
|
style={{ backgroundColor: leadColor ? `${leadColor}${decided ? "" : "26"}` : undefined }}
|
|
|
|
|
|
>
|
|
|
|
|
|
{decided && <PartyPopper aria-hidden="true" className="size-3.5" />}
|
|
|
|
|
|
{match.status_text}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
|
|
|
|
<Clock aria-hidden="true" className="size-4" />
|
|
|
|
|
|
{match.tee_time ? `Utslag ${formatTime(match.tee_time)}` : "Tid ikke satt"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
</div>
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-[1fr_auto_1fr] items-stretch gap-2 border-t border-border pt-3 sm:gap-3">
|
|
|
|
|
|
<RevealSide
|
|
|
|
|
|
team={teams[0]}
|
|
|
|
|
|
participants={match.participants.filter((p) => p.team_side === "a")}
|
|
|
|
|
|
align="left"
|
|
|
|
|
|
leading={match.leading_side === "a"}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex items-center justify-center">
|
|
|
|
|
|
<span className="rounded-full bg-secondary px-2.5 py-1 text-xs font-extrabold uppercase tracking-wide text-secondary-foreground">
|
|
|
|
|
|
vs
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<RevealSide
|
|
|
|
|
|
team={teams[1]}
|
|
|
|
|
|
participants={match.participants.filter((p) => p.team_side === "b")}
|
|
|
|
|
|
align="right"
|
|
|
|
|
|
leading={match.leading_side === "b"}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function RevealSide({
|
|
|
|
|
|
team,
|
|
|
|
|
|
participants,
|
|
|
|
|
|
align,
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
leading,
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}: {
|
|
|
|
|
|
team: ApiTeam
|
|
|
|
|
|
participants: ApiParticipant[]
|
|
|
|
|
|
align: "left" | "right"
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
leading: boolean
|
2026-07-18 17:15:50 +02:00
|
|
|
|
}) {
|
|
|
|
|
|
const right = align === "right"
|
|
|
|
|
|
const color = team.color ?? "#64748b"
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={cn("flex flex-col gap-2 rounded-2xl bg-background/50 p-3", right ? "items-end text-right" : "items-start text-left")}
|
Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):
tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:
Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.
Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00
|
|
|
|
style={{
|
|
|
|
|
|
...(right ? { borderRightWidth: 4, borderRightColor: color } : { borderLeftWidth: 4, borderLeftColor: color }),
|
|
|
|
|
|
backgroundColor: leading ? `${color}14` : undefined,
|
|
|
|
|
|
}}
|
2026-07-18 17:15:50 +02:00
|
|
|
|
>
|
|
|
|
|
|
<div className={cn("flex items-center gap-2", right && "flex-row-reverse")}>
|
|
|
|
|
|
<span aria-hidden="true" className="size-3 shrink-0 rounded-full" style={{ backgroundColor: color }} />
|
|
|
|
|
|
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">{team.name}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{participants.map((p) => (
|
|
|
|
|
|
<div key={p.id} className={cn("flex flex-col", right ? "items-end" : "items-start")}>
|
|
|
|
|
|
<span className="text-sm font-semibold text-foreground text-pretty">{p.player_name}</span>
|
|
|
|
|
|
<span className="text-xs text-muted-foreground">{p.tee_name}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Update Todos
Undersøk kode-kontekst for alle tre byggeoppgavene
Avklar åpne designspørsmål med bruker
Bygg: flytte spiller mellom lag (backend + frontend)
Bygg: individuell rangering i turnering (backend + frontend)
Bygg: midlertidige spillere + etter-runde-e-post (backend + frontend)
Typesjekket produksjonsbuild av frontend
Scratch-verifiser alt tre + full regresjon
Browser-verifiser alt tre (Chrome DevTools)
Oppdater CLAUDE.md/FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md
Rull ut mot ekte systemer (med bekreftelse)
Alle tre er live. Oppsummert:
Flytte spiller mellom lag — ny "Flytt til {annet lag}"-handling i lag-oversikten, atomisk, blokkert hvis spilleren allerede er lagt til i en match.
Individuell rangering per økt — ny brutto/netto/poeng-rangering for singles/fourball-slagspill-økter, ved siden av det eksisterende lag-poeng-leaderboardet, lenket fra blind draw-skjermen.
Midlertidige spillere: etter-runde-e-post — ny knapp som sender scorekort + ekte innloggingslenke til spillere uten konto, med dobbel-utsendingssperre.
Alt verifisert grundig: 54/54 nye scratch-sjekker + full regresjon (5+28+19 eksisterende sjekker), typesjekket produksjonsbuild, og en fullstendig gjennomgang i ekte nettleser (Chrome DevTools) av alle tre — ingen konsollfeil. Migrasjon 034 kjørt mot ekte database, begge containere redeployet, teeoff.no upåvirket. Samtidig rettet jeg opp noen steder der FEATURE_BACKLOG.md/ARCHITECTURE_DECISIONS.md hadde blitt hengende etter den faktiske, allerede leverte tilstanden (ADR-039-utrulling, PWA-offline-browsertesting).
2026-07-28 20:56:08 +02:00
|
|
|
|
// --- Midlertidige spillere: etter-runde-invitasjon (migrasjon 034) --------
|
|
|
|
|
|
// Eksplisitt organisator-knapp, økt-nivå (FEATURE_BACKLOG.md, avklart
|
|
|
|
|
|
// 2026-07-28) -- sender scorekort + innloggingslenke KUN til spillere uten
|
|
|
|
|
|
// egen konto ennå, KUN én gang per (økt, deltaker), håndtert av backend.
|
|
|
|
|
|
|
|
|
|
|
|
type SendInvitationsResult = {
|
|
|
|
|
|
sent: number
|
|
|
|
|
|
skipped_has_account: number
|
|
|
|
|
|
skipped_no_email: number
|
|
|
|
|
|
skipped_already_sent: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function SendInvitationsButton({ organizationId, sessionId }: { organizationId: string; sessionId: string }) {
|
|
|
|
|
|
const [sending, setSending] = useState(false)
|
|
|
|
|
|
const [result, setResult] = useState<SendInvitationsResult | null>(null)
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
async function handleSend() {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!confirm(
|
|
|
|
|
|
"Send scorekort og innloggingslenke til alle spillere med registrert e-post i denne økten som ikke har konto ennå?",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
setSending(true)
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
setResult(null)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/send-scorecard-invitations`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!res.ok) throw new Error(`send: ${res.status}`)
|
|
|
|
|
|
setResult(await res.json())
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setError("Klarte ikke å sende invitasjoner. Prøv igjen.")
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSending(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="mb-4 flex flex-col gap-2">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={handleSend}
|
|
|
|
|
|
disabled={sending}
|
|
|
|
|
|
className="h-10 w-fit rounded-xl font-semibold"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Mail aria-hidden="true" className="size-4" />
|
|
|
|
|
|
{sending ? "Sender …" : "Send scorekort til alle med e-post"}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
{result && (
|
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
|
{result.sent} {result.sent === 1 ? "invitasjon sendt" : "invitasjoner sendt"}
|
|
|
|
|
|
{result.skipped_already_sent > 0 && `, ${result.skipped_already_sent} allerede sendt tidligere`}
|
|
|
|
|
|
{result.skipped_has_account > 0 && `, ${result.skipped_has_account} har allerede konto`}
|
|
|
|
|
|
{result.skipped_no_email > 0 && `, ${result.skipped_no_email} mangler registrert e-post`}.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{error && <p className="text-sm font-medium text-destructive">{error}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-18 17:15:50 +02:00
|
|
|
|
// --- Small helpers ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
function MetaBadge({ children }: { children: React.ReactNode }) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<span className="inline-flex items-center rounded-full bg-secondary px-2.5 py-1 text-xs font-semibold text-secondary-foreground">
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatHcp(value: number | null) {
|
|
|
|
|
|
if (value === null) return "–"
|
|
|
|
|
|
return value.toFixed(1).replace(".", ",")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatTime(iso: string) {
|
|
|
|
|
|
const date = new Date(iso)
|
|
|
|
|
|
if (Number.isNaN(date.getTime())) return iso
|
|
|
|
|
|
return date.toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatPoints(points: number) {
|
|
|
|
|
|
return Number.isInteger(points) ? String(points) : points.toFixed(1).replace(".", ",")
|
|
|
|
|
|
}
|