Compare commits

..

2 commits

Author SHA1 Message Date
861a2ec1a7 Dokumenter gjest-utslag-fiksen i CHANGELOG.md
Punkt 47. Se forrige commit for koden.
2026-08-08 13:39:31 +02:00
ad3c47bfa3 Ny-runde-veiviser: legg utslag direkte i gjesteskjemaet
Brukertilbakemelding: valg av utslag for en nyopprettet gjest var lite
intuitivt -- måtte inn i det ferdigopprettede gjestekortets "Rediger"-
visning for i det hele tatt å se hvilket utslag som ble valgt.
addGuest() valgte allerede utslag automatisk (kjønnsbasert), men helt
stille -- selve GuestForm hadde aldri et synlig Utslag-felt.

Lagt til et kontrollert teeId-felt direkte i gjesteskjemaet, samme
NativeSelect-mønster og kjønn->utslag-resynk-logikk som PlayerCards
tilsvarende felt allerede hadde -- inkludert i "Fyll inn automatisk"-
knappen for kjente gjester (samme feilklasse, ikke tidligere rettet
der).

Scratch-verifisert: kjønnsbytte i skjemaet oppdaterer utslag-valget
umiddelbart, og det ferdige spillerkortet viser riktig utslag med det
samme uten å måtte åpne redigering. Se CHANGELOG.md punkt 47.
2026-08-08 13:38:00 +02:00
3 changed files with 70 additions and 5 deletions

View file

@ -9229,3 +9229,34 @@ Neste steg:
(migrasjonen aldri kjørt der, krever egen brukerbekreftelse), og (migrasjonen aldri kjørt der, krever egen brukerbekreftelse), og
`TEECUP_MAPBOX_SECRET_TOKEN` (for delings-satellittbilder) mangler `TEECUP_MAPBOX_SECRET_TOKEN` (for delings-satellittbilder) mangler
fortsatt. fortsatt.
47. **Ny-runde-veiviser: utslag lagt til direkte i gjesteskjemaet —
2026-08-08, brukertilbakemelding.** Brukeren rapporterte at
utslag-valget for en nyopprettet gjest ("midlertidig spiller") ikke
var intuitivt -- måtte inn i det ferdigopprettede gjestekortet
("Rediger") for i det hele tatt å SE hvilket utslag som var valgt.
Viste seg at `addGuest()` allerede valgte utslag automatisk
(`compatibleTees(course, gender)[0]?.id`), men helt stille -- selve
gjesteskjemaet (`GuestForm` i `step3-players.tsx`) hadde aldri et
synlig Utslag-felt, kun Fornavn/Etternavn/E-post/Kjønn/HCP/
Statistikk. Lagt til et kontrollert `teeId`-felt direkte i skjemaet,
samme `NativeSelect`-mønster som `PlayerCard`s tilsvarende felt,
med samme kjønn→utslag-resynk-logikk (`stillOk`-sjekk) som
`PlayerCard` allerede hadde -- inkludert i "Fyll inn automatisk"-
knappen for kjente gjester (samme feilklasse, ikke tidligere
rettet der).
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser**
(syttende scratch-miljø denne økten, samme migrasjon-002-
forsiktighet, `teecup_db` bekreftet uendret): opprettet en egen bane
med kjønns-eksklusive utslag (Gul kun menn, Rød kun kvinner) for å
bevise resynk-logikken fungerer -- byttet kjønn til Kvinne i
gjesteskjemaet, utslag-feltet oppdaterte seg umiddelbart til "Rød",
la til gjesten, og det ferdige spillerkortet viste "Utslag: Rød"
med det samme, uten å måtte åpne "Rediger". Ingen konsollfeil.
Scratch-miljøet ryddet opp fullstendig.
**Ikke rullet ut ennå** -- venter på samlet bekreftelse sammen med
resten av det som gjenstår (Mapbox-hemmelig-token, migrering av
`060_round_shot.sql`).

View file

@ -6,6 +6,7 @@ import { cn } from "@/lib/utils"
import { ChevronDown, Search, Sparkles, UserPlus, X } from "lucide-react" import { ChevronDown, Search, Sparkles, UserPlus, X } from "lucide-react"
import { useEffect, useMemo, useRef, useState } from "react" import { useEffect, useMemo, useRef, useState } from "react"
import { Avatar, Field, NativeSelect, Pill, TextInput, ToggleButton, useDebouncedValue } from "./primitives" import { Avatar, Field, NativeSelect, Pill, TextInput, ToggleButton, useDebouncedValue } from "./primitives"
import type { Tee } from "@/lib/ny-runde/types"
import { useWizard } from "./wizard-context" import { useWizard } from "./wizard-context"
const GENDERS: { value: Gender; label: string }[] = [ const GENDERS: { value: Gender; label: string }[] = [
@ -272,6 +273,7 @@ function AddPlayerPanel({
const [gender, setGender] = useState<Gender>("mann") const [gender, setGender] = useState<Gender>("mann")
const [hcp, setHcp] = useState("") const [hcp, setHcp] = useState("")
const [stat, setStat] = useState<StatLevel>(defaultStat) const [stat, setStat] = useState<StatLevel>(defaultStat)
const [teeId, setTeeId] = useState<string | undefined>(() => compatibleTees(course, "mann")[0]?.id)
const firstRef = useRef<HTMLInputElement>(null) const firstRef = useRef<HTMLInputElement>(null)
useEffect(() => { useEffect(() => {
@ -330,7 +332,7 @@ function AddPlayerPanel({
isGuest: true, isGuest: true,
email: email.trim() || undefined, email: email.trim() || undefined,
hcp: hcp.trim() === "" ? null : Number.parseFloat(hcp.replace(",", ".")), hcp: hcp.trim() === "" ? null : Number.parseFloat(hcp.replace(",", ".")),
teeId: compatibleTees(course, gender)[0]?.id, teeId,
statLevel: stat, statLevel: stat,
}) })
} }
@ -400,6 +402,7 @@ function AddPlayerPanel({
</div> </div>
) : ( ) : (
<GuestForm <GuestForm
course={course}
first={first} first={first}
setFirst={setFirst} setFirst={setFirst}
last={last} last={last}
@ -412,6 +415,8 @@ function AddPlayerPanel({
setHcp={setHcp} setHcp={setHcp}
stat={stat} stat={stat}
setStat={setStat} setStat={setStat}
teeId={teeId}
setTeeId={setTeeId}
firstRef={firstRef} firstRef={firstRef}
onBackToSearch={() => setMode("search")} onBackToSearch={() => setMode("search")}
onSubmit={addGuest} onSubmit={addGuest}
@ -422,6 +427,7 @@ function AddPlayerPanel({
} }
function GuestForm({ function GuestForm({
course,
first, first,
setFirst, setFirst,
last, last,
@ -434,10 +440,13 @@ function GuestForm({
setHcp, setHcp,
stat, stat,
setStat, setStat,
teeId,
setTeeId,
firstRef, firstRef,
onBackToSearch, onBackToSearch,
onSubmit, onSubmit,
}: { }: {
course?: Course
first: string first: string
setFirst: (v: string) => void setFirst: (v: string) => void
last: string last: string
@ -450,10 +459,13 @@ function GuestForm({
setHcp: (v: string) => void setHcp: (v: string) => void
stat: StatLevel stat: StatLevel
setStat: (v: StatLevel) => void setStat: (v: StatLevel) => void
teeId: string | undefined
setTeeId: (v: string | undefined) => void
firstRef: React.RefObject<HTMLInputElement | null> firstRef: React.RefObject<HTMLInputElement | null>
onBackToSearch: () => void onBackToSearch: () => void
onSubmit: () => void onSubmit: () => void
}) { }) {
const tees = compatibleTees(course, gender)
// kjent-gjest-oppslag på e-post (scoped til DENNE brukeren server-side) // kjent-gjest-oppslag på e-post (scoped til DENNE brukeren server-side)
const debouncedEmail = useDebouncedValue(email, 350) const debouncedEmail = useDebouncedValue(email, 350)
const [match, setMatch] = useState<KnownGuest | null>(null) const [match, setMatch] = useState<KnownGuest | null>(null)
@ -508,8 +520,12 @@ function GuestForm({
onClick={() => { onClick={() => {
setFirst(match.firstName) setFirst(match.firstName)
setLast(match.lastName) setLast(match.lastName)
if (match.gender === "m") setGender("mann") const matchedGender = match.gender === "m" ? "mann" : match.gender === "f" ? "kvinne" : null
else if (match.gender === "f") setGender("kvinne") if (matchedGender) {
const stillOk = compatibleTees(course, matchedGender).some((t) => t.id === teeId)
setGender(matchedGender)
if (!stillOk) setTeeId(compatibleTees(course, matchedGender)[0]?.id)
}
if (match.hcp != null) setHcp(String(match.hcp)) if (match.hcp != null) setHcp(String(match.hcp))
}} }}
className="mt-1 text-[13px] font-semibold text-[var(--nr-accent)] underline-offset-2 hover:underline" className="mt-1 text-[13px] font-semibold text-[var(--nr-accent)] underline-offset-2 hover:underline"
@ -521,7 +537,25 @@ function GuestForm({
) : null} ) : null}
<Field label="Kjønn"> <Field label="Kjønn">
<GenderChoice value={gender} onChange={setGender} /> <GenderChoice
value={gender}
onChange={(g) => {
const stillOk = compatibleTees(course, g).some((t) => t.id === teeId)
setGender(g)
if (!stillOk) setTeeId(compatibleTees(course, g)[0]?.id)
}}
/>
</Field>
<Field label="Utslag" htmlFor="g-tee">
<NativeSelect id="g-tee" value={teeId ?? ""} onChange={(e) => setTeeId(e.target.value || undefined)}>
<option value="">Velg utslag</option>
{tees.map((t: Tee) => (
<option key={t.id} value={t.id}>
{t.name}
</option>
))}
</NativeSelect>
</Field> </Field>
<Field label="HCP" optional htmlFor="g-hcp"> <Field label="HCP" optional htmlFor="g-hcp">

File diff suppressed because one or more lines are too long