Chip/Bunker/Straffeslag/Anywayslag som egen skjerm (ADR-078)
Splittet ScoringWizard sitt "details"-steg i "direction"/"holeDetails", og bygget om HoleStatsSheet (org-turneringer) fra én lang skjerm til tilsvarende steg-flyt -- samme opplevelse i begge scoringsflyter, uten å røre HoleStatsSheet sin egen (bevisst annerledes) lagringsmodell. Visuell polering via V0: de fire tellerne fikk et 2x2-rutenett av flis-kort i stedet for en 1-kolonne-stabel. Stepper-komponenten er delt mellom begge flyter, så endringen gjelder automatisk begge steder. tsc + 45/45 vitest, scratch-verifisert i begge flyter (lys+mørk). Rullet ut. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2ea4c26990
commit
b292401fe4
5 changed files with 304 additions and 103 deletions
|
|
@ -7663,6 +7663,73 @@ Containeren startet rent.
|
|||
|
||||
---
|
||||
|
||||
## ADR-078: Chip/Bunker/Straffeslag/Anywayslag som egen skjerm ("Steg 2" av tre forbedringer i score-registreringen) (2026-08-16)
|
||||
|
||||
Foranledning: bruker observerte at Chip/Bunker/Straffeslag/Anywayslag ble
|
||||
vist klemt inn under retningsvalget for utslag/innspill i begge
|
||||
scoringsflytene -- ikke godt nok, burde være en egen skjerm. Bekreftet
|
||||
(AskUserQuestion) at dette skal gjelde BEGGE flyter -- frittstående runder
|
||||
OG org-turneringer -- "opplevelsen for brukeren skal være lik uansett".
|
||||
|
||||
**Strukturell splitt, begge flyter:**
|
||||
- Frittstående runder (`ScoringWizard`, `round-detail.tsx`): `WizardStep`
|
||||
utvidet fra `"strokes" | "putts" | "puttDistance" | "details"` til
|
||||
`"strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"` --
|
||||
"details" splittet i to egne steg. GIR-auto-inferensen (2026-07-30)
|
||||
flyttet fra å trigge på `step !== "details"` til `step !== "direction"`.
|
||||
- Org-turneringer (`HoleStatsSheet`, `individual-tournament-detail.tsx`):
|
||||
bygget om fra ÉN lang scrollbar skjerm til en LITEN intern steg-flyt
|
||||
(samme stegrekkefølge som over), med samme auto-hopp og
|
||||
GIR-auto-inferens som runde-siden. **Bevisst avgrensning**: lagringen
|
||||
er UENDRET -- fortsatt ÉN batched `save()`-kall ved siste steg (nå
|
||||
trigget fra "Lagre"-knappen i stedet for en alltid-synlig knapp), IKKE
|
||||
konvertert til runde-sidens PATCH-per-felt-modell. De to flytene får nå
|
||||
samme STRUKTUR/interaksjon, ikke delt kode eller delt lagrings-
|
||||
mekanikk -- unngår å risikere en fungerende, annerledes arkitektur som
|
||||
ikke var en del av forespørselen.
|
||||
|
||||
**Visuell polering (V0-designet)**: siden det gjenstående "Detaljer per
|
||||
hull"-steget nå har en hel skjerm for seg selv, ble den tidligere
|
||||
1-kolonne-stabelen (mye tomrom under fire små tellere) erstattet med et
|
||||
FAST 2x2-rutenett av teller-FLISER -- egne kort med etikett + stort
|
||||
sentrert tall + -/+ nederst. Interaksjonskontrakten er uendret (minus
|
||||
disabled på 0, pluss disabled på `max`, alle trykkflater >=44px), kun det
|
||||
visuelle uttrykket er nytt. `Stepper`-komponenten (delt av begge flyter
|
||||
via `hole-stat-inputs.tsx`) oppdatert i selve komponenten -- automatisk
|
||||
identisk i begge flyter uten duplisert arbeid. `grid-cols-2` (ALDRI
|
||||
`grid-cols-3`) fordi seksjonen er fast `max-w-sm` (384px): to fliser gir
|
||||
~184px hver, nok til flisens to 44px-knapper side om side.
|
||||
|
||||
V0-prompt skrevet av Claude (se `feedback_frontend_via_v0`), kjørt av
|
||||
bruker, eksport (`tee-cup (12).zip`) integrert -- KUN den relevante
|
||||
`Stepper`-omdesignet + grid-endringen ble portert inn (eksporten var en
|
||||
full prosjekt-snapshot fra en delt V0-arbeidsflate, inkludert en eldre,
|
||||
FØR-splitt kopi av hele `round-detail.tsx` -- ingen fullstendig
|
||||
filerstatning, kun det faktisk endrede uttrykket for Stepper/rutenett
|
||||
overført til den ekte, allerede steg-splittede filen). Zip slettet fra
|
||||
`Temp-uploads/` etter integrering.
|
||||
|
||||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest (begge ganger --
|
||||
strukturell splitt, og etter V0-integrering). Egen scratch-database +
|
||||
scratch `teecup_api` + lokal `next dev`, to runder (port 18105/13105 for
|
||||
strukturell verifisering, port 18106/13106 for V0-integrering): begge
|
||||
flyter testet ende-til-ende gjennom ALLE steg (strokes → putts →
|
||||
puttDistance → direction → holeDetails), GIR-auto-inferens bekreftet
|
||||
fortsatt virker (trigget riktig på "direction"-steget), lagring bekreftet
|
||||
i begge flyter (round-siden PATCH-per-felt, turnering-siden batched
|
||||
"Lagre"), gjenåpning av et allerede utfylt hull viste korrekt lagrede
|
||||
verdier. 2x2-rutenettet med teller-flisene bekreftet identisk i begge
|
||||
flyter, interaksjon (øke/redusere, disabled ved 0/max) bekreftet
|
||||
fungerende. Lys+mørk bekreftet i begge runder. Scratch-stackene
|
||||
fullstendig revet ned -- ekte `teecup_db`/`teecup_api`/`teecup_frontend`
|
||||
urørt.
|
||||
|
||||
**Ingen migrasjon** -- ren frontend-endring.
|
||||
|
||||
**Rullet ut**: ikke ennå -- venter på bekreftelse fra bruker.
|
||||
|
||||
---
|
||||
|
||||
## Utviklingsplan (rekkefølge)
|
||||
|
||||
1. ✅ Land tenant-modell → **Organisasjon** (ADR-001/002/003)
|
||||
|
|
|
|||
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -11690,3 +11690,29 @@ Neste steg:
|
|||
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
|
||||
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
|
||||
Containeren startet rent.
|
||||
|
||||
94. **Chip/Bunker/Straffeslag/Anywayslag som egen skjerm (ADR-078) —
|
||||
2026-08-16.** Andre av tre forbedringer i score-registreringen. Disse
|
||||
fire tellerne var klemt inn under retningsvalget for utslag/innspill i
|
||||
begge scoringsflytene (frittstående runder OG org-turneringer, etter
|
||||
eksplisitt "opplevelsen skal være lik uansett").
|
||||
|
||||
`ScoringWizard`s "details"-steg splittet i "direction"/"holeDetails".
|
||||
`HoleStatsSheet` (org-turneringer) bygget om fra ÉN lang skjerm til en
|
||||
tilsvarende liten intern steg-flyt -- lagringen selv er BEVISST
|
||||
uendret (fortsatt én batched "Lagre" der, ikke konvertert til runde-
|
||||
sidens PATCH-per-felt). Visuell polering via V0 (Claude-skrevet
|
||||
prompt): de fire tellerne fikk et 2x2-rutenett av flis-kort i stedet
|
||||
for en 1-kolonne-stabel med mye tomrom -- `Stepper`-komponenten (delt
|
||||
av begge flyter) oppdatert ett sted, automatisk identisk begge steder.
|
||||
|
||||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. To scratch-runder
|
||||
(strukturell splitt, så V0-integrering): begge flyter testet
|
||||
ende-til-ende gjennom alle steg, GIR-auto-inferens fortsatt virker,
|
||||
lagring bekreftet i begge flyter, gjenåpning viste korrekt lagrede
|
||||
verdier, 2x2-rutenettet identisk og fungerende i begge flyter.
|
||||
Lys+mørk bekreftet. Scratch-stackene fullstendig revet ned.
|
||||
|
||||
**Ingen migrasjon.**
|
||||
|
||||
**Rullet ut**: ikke ennå.
|
||||
|
|
|
|||
|
|
@ -316,26 +316,42 @@ export function Stepper({
|
|||
max?: number
|
||||
}) {
|
||||
const atMax = max !== undefined && value >= max
|
||||
const atMin = value <= 0
|
||||
// Teller-FLIS (ADR-078, 2026-08-16, V0-designet) -- selvstendig kort med
|
||||
// etikett + stort tall + -/+ på bunnen, ment for et 2x2-rutenett (se
|
||||
// holeDetails-steget i round-detail.tsx/individual-tournament-detail.tsx).
|
||||
// Interaksjonskontrakten er uendret: -/tall/+, minus disabled på 0, pluss
|
||||
// disabled på max, alle trykkflater >=44px. Kun app-tokens.
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-base font-semibold text-foreground">{label}</span>
|
||||
<div className="flex items-center justify-between gap-2 rounded-2xl border border-border bg-card p-1.5">
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-border bg-card p-4">
|
||||
<span className="text-center text-sm font-bold leading-tight text-foreground">{label}</span>
|
||||
{/* Tallet bærer selve informasjonen (aldri farge alene); nullverdi
|
||||
dempes kun visuelt via muted-foreground, som fortsatt er AA på
|
||||
kort. */}
|
||||
<span
|
||||
className={cn(
|
||||
"text-4xl font-extrabold leading-none tabular-nums",
|
||||
atMin ? "text-muted-foreground" : "text-foreground",
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={readOnly || value <= 0}
|
||||
disabled={readOnly || atMin}
|
||||
onClick={() => onChange(Math.max(0, value - 1))}
|
||||
aria-label={`Færre ${label}`}
|
||||
className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
|
||||
className="flex h-11 flex-1 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
|
||||
>
|
||||
<Minus aria-hidden="true" className="size-5" />
|
||||
</button>
|
||||
<span className="min-w-8 text-center text-xl font-extrabold tabular-nums text-foreground">{value}</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={readOnly || atMax}
|
||||
onClick={() => onChange(value + 1)}
|
||||
aria-label={`Flere ${label}`}
|
||||
className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
|
||||
className="flex h-11 flex-1 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
|
||||
>
|
||||
<Plus aria-hidden="true" className="size-5" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -3236,6 +3236,23 @@ type HolePatch = {
|
|||
expected_version?: number | null
|
||||
}
|
||||
|
||||
// ADR-078 (2026-08-16): HoleStatsSheet bygget om fra ÉN lang scrollbar
|
||||
// skjerm til en liten intern steg-flyt -- samme visuelle språk (fulskjerm,
|
||||
// header/footer) og samme stegrekkefølge som ScoringWizard (frittstående
|
||||
// runder), etter eksplisitt ønske om at "opplevelsen for brukeren skal
|
||||
// være lik uansett". IKKE en gjenbruk av ScoringWizard-koden -- lagringen
|
||||
// er bevisst UENDRET (ÉN batched save() ved siste steg, ikke PATCH per
|
||||
// felt slik runde-siden gjør) for å unngå å røre en fungerende, annerledes
|
||||
// arkitektur mekanikk som ikke var en del av forespørselen. Se
|
||||
// wizardStepsFor()/WizardStep i round-detail.tsx for det speilede
|
||||
// mønsteret på runde-siden.
|
||||
type SheetStep = "strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"
|
||||
|
||||
function sheetStepsFor(statLevel: "strokes_and_putts" | "full"): SheetStep[] {
|
||||
if (statLevel === "full") return ["strokes", "putts", "puttDistance", "direction", "holeDetails"]
|
||||
return ["strokes", "putts", "puttDistance"]
|
||||
}
|
||||
|
||||
function HoleStatsSheet({
|
||||
hole,
|
||||
playerName,
|
||||
|
|
@ -3268,6 +3285,43 @@ function HoleStatsSheet({
|
|||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const steps = sheetStepsFor(statLevel)
|
||||
const [stepIndex, setStepIndex] = useState(0)
|
||||
const step = steps[Math.min(stepIndex, steps.length - 1)]
|
||||
const isFirstStep = stepIndex === 0
|
||||
const isLastStep = stepIndex === steps.length - 1
|
||||
const canProceed = step === "strokes" ? strokes !== null : true
|
||||
|
||||
// Auto-hopp, samme mønster som ScoringWizard (round-detail.tsx) --
|
||||
// hopper videre av seg selv når steget sitt eget felt får en verdi.
|
||||
const autoField = step === "strokes" ? "strokes" : step === "putts" ? "putts" : step === "puttDistance" ? "puttBucket" : null
|
||||
const enteredWithValueRef = useRef(false)
|
||||
const firedRef = useRef(false)
|
||||
useEffect(() => {
|
||||
firedRef.current = false
|
||||
enteredWithValueRef.current =
|
||||
autoField === "strokes" ? strokes !== null : autoField === "putts" ? putts !== null : autoField === "puttBucket" ? puttBucket !== null : false
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [step])
|
||||
useEffect(() => {
|
||||
if (!autoField || firedRef.current || enteredWithValueRef.current) return
|
||||
const value = autoField === "strokes" ? strokes : autoField === "putts" ? putts : puttBucket
|
||||
if (value !== null) {
|
||||
firedRef.current = true
|
||||
forward()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [strokes, putts, puttBucket])
|
||||
|
||||
// GIR-auto-inferens, samme formel/begrunnelse som ScoringWizard.
|
||||
useEffect(() => {
|
||||
if (step !== "direction") return
|
||||
if (approach !== null) return
|
||||
if (strokes === null || putts === null) return
|
||||
if (strokes - putts <= hole.par - 2) setApproach("hit")
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [step, strokes, putts, approach, hole.par])
|
||||
|
||||
async function save() {
|
||||
if (strokes === null) {
|
||||
setError("Registrer antall slag først.")
|
||||
|
|
@ -3296,6 +3350,21 @@ function HoleStatsSheet({
|
|||
}
|
||||
}
|
||||
|
||||
function back() {
|
||||
if (isFirstStep) {
|
||||
onClose()
|
||||
return
|
||||
}
|
||||
setStepIndex((i) => i - 1)
|
||||
}
|
||||
function forward() {
|
||||
if (!isLastStep) {
|
||||
setStepIndex((i) => i + 1)
|
||||
return
|
||||
}
|
||||
void save()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex flex-col bg-background">
|
||||
<header className="flex min-h-14 shrink-0 items-center gap-2 border-b border-border px-3">
|
||||
|
|
@ -3318,52 +3387,61 @@ function HoleStatsSheet({
|
|||
|
||||
<main className="flex min-h-0 flex-1 flex-col overflow-y-auto p-5 sm:p-6">
|
||||
<div className="mx-auto flex w-full max-w-sm flex-col gap-6">
|
||||
<NumberPicker
|
||||
label="Slag"
|
||||
value={strokes}
|
||||
directValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
|
||||
expandValues={[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]}
|
||||
expandLabel="10+"
|
||||
parValue={hole.par}
|
||||
showGolfTerms
|
||||
strokesReceived={strokesReceived}
|
||||
onChange={setStrokes}
|
||||
readOnly={false}
|
||||
/>
|
||||
{step === "strokes" && (
|
||||
<>
|
||||
<NumberPicker
|
||||
label="Slag"
|
||||
value={strokes}
|
||||
directValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
|
||||
expandValues={[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]}
|
||||
expandLabel="10+"
|
||||
parValue={hole.par}
|
||||
showGolfTerms
|
||||
strokesReceived={strokesReceived}
|
||||
onChange={setStrokes}
|
||||
readOnly={false}
|
||||
/>
|
||||
|
||||
{/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg selv når
|
||||
banen er custom/håndlaget eller spilleren ikke har historikk
|
||||
ennå (se HoleHistoryPanel/app/hole_history.py). */}
|
||||
<HoleHistoryPanel url={historyUrl} />
|
||||
{/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg
|
||||
selv når banen er custom/håndlaget eller spilleren ikke
|
||||
har historikk ennå (se HoleHistoryPanel/app/hole_
|
||||
history.py). */}
|
||||
<HoleHistoryPanel url={historyUrl} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<NumberPicker
|
||||
label="Putter"
|
||||
value={putts}
|
||||
directValues={[0, 1, 2, 3, 4, 5, 6]}
|
||||
expandValues={[7, 8, 9, 10]}
|
||||
expandLabel="7+"
|
||||
maxValue={strokes ?? undefined}
|
||||
accent="orange"
|
||||
onChange={setPutts}
|
||||
readOnly={false}
|
||||
/>
|
||||
{step === "putts" && (
|
||||
<NumberPicker
|
||||
label="Putter"
|
||||
value={putts}
|
||||
directValues={[0, 1, 2, 3, 4, 5, 6]}
|
||||
expandValues={[7, 8, 9, 10]}
|
||||
expandLabel="7+"
|
||||
maxValue={strokes ?? undefined}
|
||||
accent="orange"
|
||||
onChange={setPutts}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ChoiceRow
|
||||
label="Avstand første putt"
|
||||
options={[
|
||||
{ value: "<1m", label: "0-1m" },
|
||||
{ value: "<2m", label: "1-2m" },
|
||||
{ value: "<3m", label: "2-3m" },
|
||||
{ value: "<5m", label: "3-5m" },
|
||||
{ value: "<8m", label: "5-8m" },
|
||||
{ value: "8m+", label: "8m+" },
|
||||
]}
|
||||
value={puttBucket}
|
||||
onChange={setPuttBucket}
|
||||
readOnly={false}
|
||||
/>
|
||||
{step === "puttDistance" && (
|
||||
<ChoiceRow
|
||||
label="Avstand første putt"
|
||||
options={[
|
||||
{ value: "<1m", label: "0-1m" },
|
||||
{ value: "<2m", label: "1-2m" },
|
||||
{ value: "<3m", label: "2-3m" },
|
||||
{ value: "<5m", label: "3-5m" },
|
||||
{ value: "<8m", label: "5-8m" },
|
||||
{ value: "8m+", label: "8m+" },
|
||||
]}
|
||||
value={puttBucket}
|
||||
onChange={setPuttBucket}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{statLevel === "full" && (
|
||||
{step === "direction" && (
|
||||
<>
|
||||
<ClubPicker
|
||||
label="Kølle brukt ved utslaget"
|
||||
|
|
@ -3393,30 +3471,36 @@ function HoleStatsSheet({
|
|||
readOnly={false}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
<WizardSection title="Detaljer per hull">
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<Stepper label="Chip" value={chip} onChange={setChip} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Bunker" value={bunker} onChange={setBunker} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Straffeslag" value={penalty} onChange={setPenalty} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Anywayslag" value={anywayStrokes} onChange={setAnywayStrokes} readOnly={false} max={strokes ?? undefined} />
|
||||
</div>
|
||||
</WizardSection>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Teller-flis-rutenett (V0-designet, ADR-078, 2026-08-16) --
|
||||
samme 2x2-mønster som round-detail.tsx sitt speilede
|
||||
holeDetails-steg, se kommentaren der for begrunnelsen. */}
|
||||
{step === "holeDetails" && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Stepper label="Chip" value={chip} onChange={setChip} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Bunker" value={bunker} onChange={setBunker} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Straffeslag" value={penalty} onChange={setPenalty} readOnly={false} max={strokes ?? undefined} />
|
||||
<Stepper label="Anywayslag" value={anywayStrokes} onChange={setAnywayStrokes} readOnly={false} max={strokes ?? undefined} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-sm font-medium text-destructive">{error}</p>}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="shrink-0 border-t border-border p-4">
|
||||
<footer className="grid shrink-0 grid-cols-2 gap-3 border-t border-border p-4">
|
||||
<Button type="button" variant="outline" onClick={back} className="h-14 rounded-2xl text-base font-bold">
|
||||
{isFirstStep ? "Avbryt" : "Forrige"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={saving || strokes === null}
|
||||
onClick={save}
|
||||
className="h-14 w-full rounded-2xl text-base font-bold disabled:opacity-40"
|
||||
disabled={!canProceed || saving}
|
||||
onClick={forward}
|
||||
className="h-14 rounded-2xl text-base font-bold disabled:opacity-40"
|
||||
>
|
||||
{saving ? "Lagrer…" : "Lagre"}
|
||||
{isLastStep ? (saving ? "Lagrer…" : "Lagre") : "Neste"}
|
||||
</Button>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1921,12 +1921,17 @@ export function RoundDetail({ roundId }: { roundId: string }) {
|
|||
// 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" | "puttDistance" | "details"
|
||||
type WizardStep = "strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"
|
||||
|
||||
function wizardStepsFor(statLevel: StatLevel): WizardStep[] {
|
||||
if (statLevel === "strokes_only") return ["strokes"]
|
||||
if (statLevel === "strokes_and_putts") return ["strokes", "putts", "puttDistance"]
|
||||
return ["strokes", "putts", "puttDistance", "details"]
|
||||
// ADR-078 (2026-08-16): "details" splittet i to egne skjermer -- retning
|
||||
// (utslag/innspill, + kølle/slagmåling som hører til utslaget) og
|
||||
// Chip/Bunker/Straffeslag/Anywayslag fikk hver sin egen skjerm i stedet
|
||||
// for å dele én, etter tilbakemelding om at detaljene druknet under
|
||||
// retningsvalget.
|
||||
return ["strokes", "putts", "puttDistance", "direction", "holeDetails"]
|
||||
}
|
||||
|
||||
// Hvilket HoleStat-felt (om noen) som "eier" auto-hopp for et gitt steg --
|
||||
|
|
@ -2032,10 +2037,11 @@ function ScoringWizard({
|
|||
// ALDRI et allerede satt (manuelt ELLER tidligere auto-satt) approach-
|
||||
// valg, og gjelder aldri motsatt vei (GIR usann sier ingenting om
|
||||
// retning -- en scrambling-birdie gir identisk slag/putt-mønster som en
|
||||
// ekte GIR). Trigges når "details"-steget nås, siden slag+putt begge må
|
||||
// være kjent (kommer tidligere i wizard-rekkefølgen).
|
||||
// ekte GIR). Trigges når "direction"-steget nås (ADR-078: flyttet fra det
|
||||
// tidligere "details"-steget, som ble splittet i to), siden slag+putt
|
||||
// begge må være kjent (kommer tidligere i wizard-rekkefølgen).
|
||||
useEffect(() => {
|
||||
if (step !== "details") return
|
||||
if (step !== "direction") return
|
||||
if (stat.approach !== null) return
|
||||
if (stat.strokes === null || stat.putts === null) return
|
||||
if (stat.strokes - stat.putts <= hole.par - 2) {
|
||||
|
|
@ -2206,7 +2212,12 @@ function ScoringWizard({
|
|||
/>
|
||||
)}
|
||||
|
||||
{step === "details" && (
|
||||
{/* ADR-078 (2026-08-16): "Retning" er nå EGEN skjerm -- inkl.
|
||||
kølle/slagmåling, som begge hører til utslaget. Splittet fra
|
||||
det tidligere "details"-steget etter tilbakemelding om at
|
||||
Chip/Bunker/Straffeslag/Anywayslag druknet under retnings-
|
||||
valget. */}
|
||||
{step === "direction" && (
|
||||
<>
|
||||
<ClubPicker
|
||||
label="Kølle brukt ved utslaget"
|
||||
|
|
@ -2225,11 +2236,6 @@ function ScoringWizard({
|
|||
variant="inline"
|
||||
/>
|
||||
|
||||
{/* Seksjon 1 -- retning (2026-08-11): retningskorsene utgjør nå
|
||||
en tydelig egen gruppe med overskrift, adskilt fra tellerne
|
||||
under, så det er visuelt klart at skjermen består av to
|
||||
ulike typer inndata (brukertilbakemelding: umulig å vite at
|
||||
man måtte skrolle for å finne resten). */}
|
||||
<WizardSection title="Retning">
|
||||
{hole.par !== 3 && (
|
||||
<DirectionCross
|
||||
|
|
@ -2251,37 +2257,39 @@ function ScoringWizard({
|
|||
readOnly={readOnly}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
{/* Seksjon 2 -- detaljer per hull (2026-08-11): egen overskrift
|
||||
+ divider fra "Retning" over. Alle FIRE tellerne (Chip/
|
||||
Bunker/Straffeslag/Anywayslag) er konseptuelt samme type
|
||||
data (antall småhendelser per hull, typisk 0-3) og bruker nå
|
||||
ETT konsistent +/--stepper-mønster -- Anywayslag var
|
||||
tidligere det eneste tall-rutenettet, som brøt mønsteret
|
||||
uten grunn. Alltid stablet, ALDRI et sm:grid-cols-3-sprang:
|
||||
denne wizard-seksjonen er fast begrenset til max-w-sm
|
||||
(384px, se containeren over) uansett hvor bred selve
|
||||
VIEWPORTET er -- et viewport-basert grid-cols-3-brudd ved
|
||||
640px trigges derfor på skjermer godt over 640px bredde mens
|
||||
innholdskolonnen fortsatt kun er 384px, som får stepperne
|
||||
(hver ~190px minimum pga. faste 44px-knapper) til å
|
||||
overlappe hverandre i stedet for å faktisk ha plass. */}
|
||||
<WizardSection title="Detaljer per hull">
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<Stepper label="Chip" value={stat.chip} onChange={(v) => onUpdate({ chip: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper label="Bunker" value={stat.bunker} onChange={(v) => onUpdate({ bunker: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper label="Straffeslag" value={stat.penalty} onChange={(v) => onUpdate({ penalty: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper
|
||||
label="Anywayslag"
|
||||
value={stat.anywayStrokes ?? 0}
|
||||
onChange={(v) => onUpdate({ anywayStrokes: v })}
|
||||
readOnly={readOnly}
|
||||
max={stat.strokes ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
</WizardSection>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ADR-078 (2026-08-16): Chip/Bunker/Straffeslag/Anywayslag har nå
|
||||
EGEN skjerm, splittet fra "Retning" (se over). Alle FIRE
|
||||
tellerne er konseptuelt samme type data (antall småhendelser
|
||||
per hull, typisk 0-3) og bruker ETT konsistent
|
||||
+/--teller-flis-mønster (Stepper, hole-stat-inputs.tsx).
|
||||
|
||||
Layout (V0-designet, 2026-08-16): et FAST 2x2-rutenett av
|
||||
teller-fliser i stedet for en høy stabel som etterlot mye
|
||||
tomrom under nå som feltene har fått en egen skjerm. Fire
|
||||
likeverdige felt leses da som ett samlet sett og fyller
|
||||
kolonnen bevisst. grid-cols-2 er trygt -- og ALDRI
|
||||
grid-cols-3 -- fordi seksjonen er fast max-w-sm (384px, se
|
||||
containeren over): to fliser gir ~184px hver, rikelig til
|
||||
flisens to 44px-knapper side om side, mens tre ville presset
|
||||
hver flis under ~120px og fått knappene til å overlappe.
|
||||
Viewport-uavhengig, så det holder på alle skjermbredder. */}
|
||||
{step === "holeDetails" && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Stepper label="Chip" value={stat.chip} onChange={(v) => onUpdate({ chip: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper label="Bunker" value={stat.bunker} onChange={(v) => onUpdate({ bunker: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper label="Straffeslag" value={stat.penalty} onChange={(v) => onUpdate({ penalty: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
|
||||
<Stepper
|
||||
label="Anywayslag"
|
||||
value={stat.anywayStrokes ?? 0}
|
||||
onChange={(v) => onUpdate({ anywayStrokes: v })}
|
||||
readOnly={readOnly}
|
||||
max={stat.strokes ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollFade>
|
||||
</main>
|
||||
|
|
|
|||
Loading…
Reference in a new issue