From 9ec6f1c9ed99908eec23c1dffeb09f768154a4fd Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Sun, 26 Jul 2026 19:54:18 +0200 Subject: [PATCH] Live. Kort oppsummert: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full skjermovertagende veiviser — trykk på en spillers score-knapp åpner nå et eget fullskjermbilde for akkurat den spilleren på det hullet, med Forrige/Neste gjennom stegene (Slag → Putter → detaljer, avhengig av statistikknivå). Siste steg for siste spiller blir "Ferdig" og går videre til neste hull automatisk. Akkumulert score synlig for alle — hver spiller viser nå "HCP X · E/+N så langt (Y hull)" hele tiden i hovedlisten, ikke bare for én valgt spiller. Hovedsiden er kraftig forenklet: det gamle lange skjemaet er borte, erstattet med én kompakt rad per spiller. Gitt at forrige forsøk så dårlig ut til tross for at logikken var riktig, er dette en større usikkerhet enn vanlig — jeg oppfordrer deg til å teste dette grundig i nettleseren før du stoler på det. --- .claude/settings.json | 5 +- CLAUDE.md | 53 +++ frontend/components/round-detail.tsx | 629 +++++++++++++++++---------- 3 files changed, 461 insertions(+), 226 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 5192318..de5c798 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -78,7 +78,10 @@ "Bash(node /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/test_scoring_wizard_logic.mjs)", "Bash(tee /tmp/wizard_setup.json)", "Bash(python3 -c \"import json; print\\(json.load\\(open\\('/tmp/wizard_setup.json'\\)\\)['round_id']\\)\")", - "Bash(python3 -c \"import json; print\\(json.load\\(open\\('/tmp/wizard_setup.json'\\)\\)['session_cookie']\\)\")" + "Bash(python3 -c \"import json; print\\(json.load\\(open\\('/tmp/wizard_setup.json'\\)\\)['session_cookie']\\)\")", + "Bash(grep -n \"activePlayerIndex\\\\|isLastPlayer\\\\|nextPlayer\\\\|canAdvance\\\\|advanceToNextPlayerOrHole\\\\|isEntryComplete\\\\|activePlayerIdRef\\\\|const loadHoles\\\\|useEffect\\(\\(\\) => {$\" /opt/teecup/frontend/components/round-detail.tsx)", + "Bash(node /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/test_scoring_wizard_v2.mjs)", + "Bash(tee /tmp/wizard_setup2.json)" ], "additionalDirectories": [ "/opt/teecup/frontend/app/my-rounds/[id]" diff --git a/CLAUDE.md b/CLAUDE.md index da6f866..35aef7d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3412,6 +3412,59 @@ Ferdig og verifisert: ingen migrasjon, `docker compose up -d --build teecup_api teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. +- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren + testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig: + "ingen forbedring i det hele tatt", "visuelt like overveldende og + rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte + skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga. + risiko uten visuell testing) var det som faktisk kreves. Bygget nå for + ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN + synlig for HVER spiller samtidig (ikke bare aktiv), matchende + konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn. + **Datalasting endret** (nødvendig for punktet over): laster nå hull for + ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun + for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på + nytt, ikke bare énes. + **Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen + stack utenfor `
`): tre steg maks, drevet av `stat_level` -- + "strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første + putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/ + bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn + konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing, + og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig). + Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i + veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri + oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene; + siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull, + starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i + SAMME veiviser, nullstiller til steg 1). + **Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere + detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste, + ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og + en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og + åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra + forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet + var ikke bortkastet). + **Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/ + bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker", + "siste steg for siste spiller fullfører") og akkumulert-score- + beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur- + script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville + sendt (fullt detalj-steg for en "full"-spiller, kun slag for en + "strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon + (35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql` + 12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte + rent, samme engangs `next dev`-container-sjekk ga 200. + **Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen + ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist + nettopp fordi den så gal ut i praksis til tross for at logikken var + korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør + teste grundig før tillit. + **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: + ingen migrasjon, `docker compose up -d --build teecup_api + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` + → 200, `teeoff.no` upåvirket. Neste steg: 0a. **Spillerliste-redesign — LIVE, men ikke nettleser-testet diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx index d01925a..5beaf46 100644 --- a/frontend/components/round-detail.tsx +++ b/frontend/components/round-detail.tsx @@ -113,16 +113,21 @@ function statKey(playerId: string, holeNumber: number) { return `${playerId}:${holeNumber}` } -// "Er dette godt nok registrert til å gå videre til neste spiller?" -// (2026-07-26, inspirert av en konkurrentapps samlebånd-flyt: én spiller -// om gangen, automatisk videre uten å måtte navigere manuelt tilbake til -// spillerlisten). Krever KUN det stat_level faktisk gjør obligatorisk -- -// "full"-nivåets ekstra detaljer (kølle/retning/chip osv.) er fortsatt -// valgfrie og blokkerer ALDRI videre fremdrift, samme som før. -function isEntryComplete(stat: HoleStat, statLevel: StatLevel): boolean { - if (stat.strokes === null) return false - if (statLevel === "strokes_only") return true - return stat.putts !== null +// Til-par-formatering, golfvis (ekte minustegn) -- samme konvensjon som +// round-scorecard.tsx/round-leaderboard.tsx (2026-07-26, brukt for +// akkumulert score-så-langt per spiller i den nye scoringsflyten). +function formatToPar(value: number): string { + if (value === 0) return "E" + return value > 0 ? `+${value}` : `−${Math.abs(value)}` +} + +function computeRunningToPar(holes: ApiHole[] | undefined): { toPar: number; holesPlayed: number } | null { + if (!holes) return null + const played = holes.filter((h) => h.played && h.score !== null) + if (played.length === 0) return null + const score = played.reduce((sum, h) => sum + (h.score as number), 0) + const par = played.reduce((sum, h) => sum + h.par, 0) + return { toPar: score - par, holesPlayed: played.length } } function apiGenderToUi(g: ApiGender): Gender { @@ -296,11 +301,10 @@ export function RoundDetail({ roundId }: { roundId: string }) { // panelet ligger nå INNI hvert spillerkort i den vertikale listen, kan // derfor gjelde en ANNEN rad enn den aktive/valgte, ulikt tidligere). const [editingPlayerId, setEditingPlayerId] = useState(null) - // "Flere detaljer" er kollapset som default (etterspurt av bruker - // 2026-07-25 -- scorekortet var "veldig dårlig designet", altfor mye - // stablet oppå hverandre for et enkelt slag-registrering) slik at en - // rask slag-/putt-registrering ikke krever noe scrolling i det hele tatt. - const [detailsOpen, setDetailsOpen] = useState(false) + // Hvilken spillers scoringsveiviser (fullskjerm) som er åpen for AKTIVT + // HULL, om noen (2026-07-26 -- erstatter den tidligere "vis alt inline for + // aktiv spiller"-flyten, inspirert av en konkurrentapps samlebåndsflyt). + const [wizardPlayerId, setWizardPlayerId] = useState(null) // Hull-panelet gjenbrukes ved bytte av hull -- "Forrige"/"Neste hull" ligger // NEDERST i panelet, så uten dette ville brukeren blitt stående scrollet // helt ned (der knappene er) mens det NYE hullets Slag-felt (øverst i @@ -357,36 +361,40 @@ export function RoundDetail({ roundId }: { roundId: string }) { [roundId], ) + // Laster hull for ALLE deltakere (2026-07-26, ikke bare aktiv spiller) -- + // den nye scoringslisten viser akkumulert score-så-langt for HVER spiller + // samtidig, ikke kun den ene som tilfeldigvis er "aktiv". useEffect(() => { - if (activePlayerId && !holesByParticipant[activePlayerId]) { - void loadHoles(activePlayerId) + if (!round) return + for (const p of round.participants) { + if (!holesByParticipant[p.id]) { + void loadHoles(p.id) + } } - }, [activePlayerId, holesByParticipant, loadHoles]) + }, [round, holesByParticipant, loadHoles]) - // Alltid gjeldende activePlayerId tilgjengelig INNI WebSocket-handleren - // under, uten å måtte koble socket-en til/fra ved hvert fanebytte (som en - // vanlig closure over `activePlayerId` i effektens dependency-array ville - // krevd -- ville ødelagt WS-tilkoblingen hver gang brukeren bytter fane). - const activePlayerIdRef = useRef(null) + // Alltid gjeldende deltaker-id-er tilgjengelig INNI WebSocket-handleren + // under, uten å måtte koble socket-en til/fra ved hver ny runde-lasting + // (en vanlig closure over `round` i effektens dependency-array ville + // krevd det). + const participantIdsRef = useRef([]) useEffect(() => { - activePlayerIdRef.current = activePlayerId - }, [activePlayerId]) + participantIdsRef.current = round?.participants.map((p) => p.id) ?? [] + }, [round]) // Sanntid (2026-07-26, oppfølging av ADR-027): et rent "noe endret seg"- // signal -- IKKE selve dataen. Klienten reagerer med de vanlige REST- - // kallene den uansett gjør ved førstegangslasting. Aktiv spillers hull - // hentes på nytt DIREKTE (overskriver når svaret kommer, ingen mellomsteg - // med tom stat som ville blinket til for spilleren som selv nettopp - // registrerte et slag) -- de ANDRE spillernes cache droppes i stedet, slik - // at et senere fanebytte henter friskt fremfor en stale verdi. + // kallene den uansett gjør ved førstegangslasting. Henter nå ALLE + // deltakeres hull på nytt (ikke bare én aktiv) siden scoringslisten + // viser alles akkumulerte score-så-langt samtidig. useEffect(() => { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:" const socket = new WebSocket(`${protocol}//${window.location.host}/ws/rounds/${roundId}/live`) socket.onmessage = () => { void loadRound() - const active = activePlayerIdRef.current - setHolesByParticipant((prev) => (active && prev[active] ? { [active]: prev[active] } : {})) - if (active) void loadHoles(active) + for (const id of participantIdsRef.current) { + void loadHoles(id) + } } return () => socket.close() }, [roundId, loadRound, loadHoles]) @@ -446,19 +454,17 @@ export function RoundDetail({ roundId }: { roundId: string }) { hole !== null && currentStat.strokes - currentStat.putts <= hole.par - 2 - // Samlebånd-fremdrift (2026-07-26): når aktiv spiller er ferdig registrert - // for hullet, gå til NESTE spiller i rekkefølgen -- er hen den siste, - // gå til neste hull og start på spiller 1 igjen. Fjerner behovet for å - // manuelt skrolle opp til spillerlisten og velge neste spiller for hånd. - const activePlayerIndex = activePlayer ? players.findIndex((p) => p.id === activePlayer.id) : -1 - const isLastPlayer = activePlayerIndex === -1 || activePlayerIndex === players.length - 1 - const nextPlayer = !isLastPlayer ? players[activePlayerIndex + 1] : null - const canAdvance = !readOnly && activePlayer !== null && isEntryComplete(currentStat, activePlayer.statLevel) + // Veiviserens mål (2026-07-26) -- HVEM sin score som registreres akkurat + // nå, uavhengig av `activePlayerId` (som fortsatt kun styrer hvem sin + // "Så langt i runden"-oversikt lenger ned som vises). + const wizardPlayer = players.find((p) => p.id === wizardPlayerId) ?? null + const wizardApiHole = wizardPlayerId ? (holesByParticipant[wizardPlayerId]?.find((h) => h.hole_number === activeHole) ?? null) : null + const wizardStat = wizardApiHole ? apiHoleToStat(wizardApiHole) : emptyStat() - async function updateStat(patch: Partial) { - if (readOnly || !activePlayerId || !currentApiHole) return - const merged: HoleStat = { ...currentStat, ...patch } - const res = await fetch(`/rounds/${roundId}/participants/${activePlayerId}/holes/${activeHole}`, { + async function updateWizardStat(patch: Partial) { + if (readOnly || !wizardPlayerId) return + const merged: HoleStat = { ...wizardStat, ...patch } + const res = await fetch(`/rounds/${roundId}/participants/${wizardPlayerId}/holes/${activeHole}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", @@ -468,7 +474,7 @@ export function RoundDetail({ roundId }: { roundId: string }) { const updated: ApiHole = await res.json() setHolesByParticipant((prev) => ({ ...prev, - [activePlayerId]: (prev[activePlayerId] ?? []).map((h) => (h.hole_number === updated.hole_number ? updated : h)), + [wizardPlayerId]: (prev[wizardPlayerId] ?? []).map((h) => (h.hole_number === updated.hole_number ? updated : h)), })) } @@ -487,11 +493,19 @@ export function RoundDetail({ roundId }: { roundId: string }) { scrollToHolePanel() } - function advanceToNextPlayerOrHole() { - if (nextPlayer) { - setActivePlayerId(nextPlayer.id) - scrollToHolePanel() + // Samlebånd-fremdrift (2026-07-26): kalles fra veiviserens siste steg for + // siste spiller på hullet -- bytter til NESTE spiller i rekkefølgen (samme + // veiviser, nullstilt til steg 1), eller -- er aktiv spiller den siste -- + // lukker veiviseren og går videre til neste hull, klar for spiller 1 igjen. + function advanceWizardPlayer() { + const idx = players.findIndex((p) => p.id === wizardPlayerId) + const isLast = idx === -1 || idx === players.length - 1 + if (!isLast) { + const next = players[idx + 1] + setActivePlayerId(next.id) + setWizardPlayerId(next.id) } else { + setWizardPlayerId(null) setActivePlayerId(players[0]?.id ?? null) goNext() } @@ -836,186 +850,77 @@ export function RoundDetail({ roundId }: { roundId: string }) { - {/* Slag/Putter og (ved statLevel="full") alle detaljene vises - ALLTID samlet under hverandre -- ikke bak en fane man må - oppdage og trykke på (funnet 2026-07-25: brukeren huket - av "All statistikk", men fikk aldri se noe utover - slag/putter siden det lå bak en fane). */} -
- updateStat({ strokes: v, played: true })} - readOnly={readOnly} - /> - - {activePlayer.statLevel !== "strokes_only" && ( - updateStat({ putts: v })} - readOnly={readOnly} - /> - )} - - {activePlayer.statLevel === "full" && ( - <> - {/* Rett under antall putter (etterspurt av bruker 2026-07-25) -- - hører naturlig sammen med Putter-feltet rett over, ikke med - kølle/retning/chip-gruppen lenger ned. */} - updateStat({ firstPuttBucket: v as PuttBucket })} - readOnly={readOnly} - /> - - {/* Alt annet lever bak én kollapsbar seksjon, lukket som - default -- panelet forblir kort for det vanlige - slag-/putt-registreringstilfellet. */} -
+ {/* Registrering (2026-07-26, inspirert av en konkurrentapps + samlebåndsflyt): én kompakt rad per spiller med + akkumulert score-så-langt for RUNDEN alltid synlig, og en + trykkflate som åpner en fokusert, skjermovertagende + veiviser for AKKURAT denne spilleren og hullet -- i + stedet for ett langt skjema man skrollet gjennom for én + spiller om gangen, med manuell fanebytting mellom + spillere (rapportert av bruker 2026-07-26 som "ingen + forbedring", "visuelt like overveldende og rotete"). */} +
    + {players.map((player) => { + const playerHoles = holesByParticipant[player.id] + const playerApiHole = playerHoles?.find((h) => h.hole_number === activeHole) ?? null + const hasScore = Boolean(playerApiHole?.played) && playerApiHole?.score !== null + const running = computeRunningToPar(playerHoles) + return ( +
  • + + + {player.name} + {player.isSelf && ( + + Deg + + )} + + + HCP {player.hcp !== null ? player.hcp.toFixed(1).replace(".", ",") : "ikke satt"} + {running && ( + <> + {" · "} + {formatToPar(running.toPar)} så langt ({running.holesPlayed} hull) + + )} + + - {detailsOpen && ( -
    -
    - - {activePlayer.isSelf && ownBagClubs.length > 0 ? ( -
    - {ownBagClubs.map((club) => { - const selected = currentStat.club === club - return ( - - ) - })} -
    - ) : ( - updateStat({ club: e.target.value })} - placeholder="F.eks. Driver, 3-jern" - disabled={readOnly} - className="h-12 rounded-2xl text-base" - /> - )} -
    +
  • + ) + })} +
- {hole.par !== 3 && ( - updateStat({ teeShot: v as TeeShot })} - readOnly={readOnly} - /> - )} - - updateStat({ approach: v as Approach })} - readOnly={readOnly} - /> - -
- updateStat({ chip: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} /> - updateStat({ bunker: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} /> - updateStat({ penalty: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} /> -
- - updateStat({ anywayStrokes: v })} - readOnly={readOnly} - /> -
- )} -
- - )} - - -
-
- - {/* Samlebånd-knapp (2026-07-26): går til NESTE SPILLER på - samme hull, eller -- er aktiv spiller den siste -- - videre til neste hull og starter på spiller 1 igjen. - Erstatter den manuelle "skroll opp, velg neste - spiller"-runden man tidligere måtte gjøre for hånd. */} - -
- {!readOnly && !canAdvance && ( -

- {activePlayer?.statLevel === "strokes_only" - ? "Registrer slag for å gå videre." - : "Registrer slag og putter for å gå videre."} -

- )} +
+ +
)} @@ -1023,6 +928,280 @@ export function RoundDetail({ roundId }: { roundId: string }) { )}
+ + {/* Skjermovertagende scoringsveiviser (2026-07-26) -- rendres HELT + UTENFOR
, øverst i DOM-treet, slik at den dekker hele + skjermen uavhengig av hvor langt man har skrollet i bakgrunnen. */} + {wizardPlayer && hole && ( + setWizardPlayerId(null)} + onAdvance={advanceWizardPlayer} + readOnly={readOnly} + ownBagClubs={ownBagClubs} + /> + )} + + ) +} + +// --- Scoringsveiviser (2026-07-26) ------------------------------------------ +// Skjermovertagende, ett-spiller-om-gangen registreringsflyt -- inspirert av +// en konkurrentapps samlebåndsflyt (video studert bilde for bilde), men +// BEVISST forenklet til TeeCups eget stat_level-drevne steg-antall i stedet +// for å kopiere konkurrentens nøyaktige skjermrekkefølge/design: +// - "strokes_only": ett steg (Slag) +// - "strokes_and_putts": to steg (Slag, Putter+avstand første putt) +// - "full": tre steg (Slag, Putter+avstand første putt, resten samlet) +// "Neste"/"Forrige" beveger seg gjennom stegene for ÉN spiller; på siste +// steg blir knappen "Neste: {navn}" (bytter til neste spiller, nullstiller +// til steg 1) eller "Ferdig" (er spilleren den siste, lukker veiviseren og +// varsler forelderen om å gå videre til neste hull). + +type WizardStep = "strokes" | "putts" | "details" + +function wizardStepsFor(statLevel: StatLevel): WizardStep[] { + if (statLevel === "strokes_only") return ["strokes"] + if (statLevel === "strokes_and_putts") return ["strokes", "putts"] + return ["strokes", "putts", "details"] +} + +function ScoringWizard({ + players, + player, + hole, + stat, + onUpdate, + onClose, + onAdvance, + readOnly, + ownBagClubs, +}: { + players: Player[] + player: Player + hole: Hole + stat: HoleStat + onUpdate: (patch: Partial) => void + onClose: () => void + onAdvance: () => void + readOnly: boolean + ownBagClubs: string[] +}) { + const playerIndex = players.findIndex((p) => p.id === player.id) + const isLastPlayer = playerIndex === -1 || playerIndex === players.length - 1 + const nextPlayer = !isLastPlayer ? players[playerIndex + 1] : null + + const steps = wizardStepsFor(player.statLevel) + const [stepIndex, setStepIndex] = useState(0) + // Nullstill til steg 1 hver gang veiviseren åpnes for en NY spiller -- + // uten dette ville "Neste: {navn}" hoppet rett til der FORRIGE spiller + // slapp, i stedet for å starte på Slag igjen for den nye spilleren. + useEffect(() => { + setStepIndex(0) + }, [player.id]) + + const step = steps[Math.min(stepIndex, steps.length - 1)] + const isFirstStep = stepIndex === 0 + const isLastStep = stepIndex === steps.length - 1 + + const canProceed = step === "strokes" ? stat.strokes !== null : step === "putts" ? stat.putts !== null : true + + function back() { + if (isFirstStep) { + onClose() + return + } + setStepIndex((i) => i - 1) + } + function forward() { + if (!isLastStep) { + setStepIndex((i) => i + 1) + return + } + onAdvance() + } + + return ( +
+
+ +
+ + Hull {hole.holeNumber} · Par {hole.par} + + {player.name} +
+
+ + {/* Kontekst-rad: alle spillerne, aktiv fremhevet -- samme prinsipp som + konkurrentappen: man mister aldri oversikten over hvem som allerede + er ferdig på hullet. Ren informasjon, ikke en snarvei -- bytte + spiller midt i et steg ville latt uferdige felt henge løst. */} +
+ {players.map((p) => ( + + {p.name} + + ))} +
+ +
+
+ {step === "strokes" && ( + onUpdate({ strokes: v, played: true })} + readOnly={readOnly} + /> + )} + + {step === "putts" && ( + <> + onUpdate({ putts: v })} + readOnly={readOnly} + /> + onUpdate({ firstPuttBucket: v as PuttBucket })} + readOnly={readOnly} + /> + + )} + + {step === "details" && ( + <> +
+ + {player.isSelf && ownBagClubs.length > 0 ? ( +
+ {ownBagClubs.map((club) => { + const selected = stat.club === club + return ( + + ) + })} +
+ ) : ( + onUpdate({ club: e.target.value })} + placeholder="F.eks. Driver, 3-jern" + disabled={readOnly} + className="h-12 rounded-2xl text-base" + /> + )} +
+ + {hole.par !== 3 && ( + onUpdate({ teeShot: v as TeeShot })} + readOnly={readOnly} + /> + )} + + onUpdate({ approach: v as Approach })} + readOnly={readOnly} + /> + +
+ onUpdate({ chip: v })} readOnly={readOnly} max={stat.strokes ?? undefined} /> + onUpdate({ bunker: v })} readOnly={readOnly} max={stat.strokes ?? undefined} /> + onUpdate({ penalty: v })} readOnly={readOnly} max={stat.strokes ?? undefined} /> +
+ + onUpdate({ anywayStrokes: v })} + readOnly={readOnly} + /> + + )} +
+
+ +
) }