Fiks utslagsvalg i spillertabellen: select forsvinner aldri lenger

Funnet via brukers skjermopptak av forsøk på å endre én spillers
utslag -- en ikke-tilordnet rad viste kun en død strek, ingen
interaktiv kontroll, og et feilklikk på den tette avkrysningsboksen
satt brukeren fast. Utslagsvelgeren vises nå alltid; å velge et
utslag tilordner raden automatisk. Boks og select har også fått
tydelig mer avstand for å redusere feilklikk.
This commit is contained in:
Erol Haagenrud 2026-08-19 17:37:26 +02:00
parent 24eda54d10
commit 724983fe03
2 changed files with 84 additions and 33 deletions

View file

@ -13187,3 +13187,46 @@ Neste steg:
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend. **Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
`docker compose build teecup_frontend && up -d`, rene logger, `docker compose build teecup_frontend && up -d`, rene logger,
200 OK. 200 OK.
132. **Utslagsvalg i den nye spillertabellen -- reelt UX-hull, funnet via
video -- 2026-08-19.** Bruker lastet opp en skjermopptak: "Den viser
hvor vanskelig det er å velge utslag." Bilde-for-bilde-gjennomgang
(video konvertert til enkeltbilder med `ffmpeg`, installert lokalt
for anledningen) viste at bruker klikket presist på avkrysnings-
boksen til én rad (ville trolig bare endre utslaget hennes fra "55"
til "50"), som avkrysset raden helt av -- og satt deretter fast:
når en rad ikke er tilordnet runden viste cellen KUN en død strek
(`<span></span>`), ingen interaktiv kontroll. Eneste vei tilbake
var den 16px avkrysningsboksen, tett inntil selecten, lett å bomme
på. Bekreftet med et oppklaringsspørsmål til bruker (var det
"massetildel 55, overstyr noen til 50"-flyten som var problemet?)
-- bruker bekreftet at DET allerede fungerte (endre en allerede
avkrysset rads select direkte er upåvirket av denne bug'en), det
var spesifikt fastlåsingen etter et (feilaktig eller bevisst)
klikk på boksen som var hovedproblemet.
**Fiks** (`components/tournament-players-table.tsx`, `PlayerRow`
sin runde-celle): utslagsvelgeren vises nå ALLTID, aldri en død
strek. På en ikke-tilordnet rad vises den stiplet/nedtonet med
rundens standardutslag som forhåndsvalg -- å velge ET HVILKET SOM
HELST utslag der kaller `onToggleRound(..., true, teeId)` og
tilordner raden i samme handling, ingen egen boks-runde nødvendig.
Avkrysningsboksen er nå viklet i en egen `<label>` med større
klikkflate, og adskilt fra selecten med en synlig skillelinje
(`gap-1` + en 1px `bg-border`-strek) i stedet for kun `gap-1.5` --
reduserer sjansen for feilklikk mellom de to kontrollene.
**Verifisert i scratch** (mock-data, lokal `next dev` på port 3100,
IKKE mot kjørende `teecup_frontend`/`teecup_api` -- en midlertidig
forhåndsvisningsrute under `frontend/app/tmp-preview-tee-select-
x7q9/`, fjernet igjen etter verifisering): brukt Chrome DevTools MCP
til faktisk å klikke gjennom scenarioet. Bekreftet (1) massetildeling
fortsatt fungerer identisk, (2) å endre en allerede tilordnet rads
utslag direkte i selecten fungerer uendret, (3) den faktiske bug'en
er løst -- avkryssing av en rad beholder nå en fungerende select
(viser siste kjente utslag, stiplet stil), og å velge et utslag der
tilordner raden på nytt i ett klikk. Ingen konsollfeil. `tsc --noEmit`
rent, `vitest run` 55/55.
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.

View file

@ -480,31 +480,44 @@ function PlayerRow({
const cell = cells[round.id]?.[player.participantId] const cell = cells[round.id]?.[player.participantId]
const assigned = cell?.assigned ?? false const assigned = cell?.assigned ?? false
const busy = busyRoundIds.includes(round.id) const busy = busyRoundIds.includes(round.id)
const previewTeeId = cell?.teeId ?? defaultTees[round.id] ?? round.tees[0]?.id ?? ""
return ( return (
<td key={round.id} className={TD}> <td key={round.id} className={TD}>
<div className="flex items-center gap-1.5 px-2"> <div className="flex items-center gap-1 px-2">
<label
className="flex shrink-0 cursor-pointer items-center py-2 pr-2"
title={assigned ? "Spiller runden" : "Spiller ikke runden"}
>
<input <input
type="checkbox" type="checkbox"
checked={assigned} checked={assigned}
disabled={busy} disabled={busy}
aria-label={`${player.playerName} spiller ${round.label}`} aria-label={`${player.playerName} spiller ${round.label}`}
title={assigned ? "Spiller runden" : "Spiller ikke runden"}
onChange={(e) => { onChange={(e) => {
const checked = e.target.checked const checked = e.target.checked
const teeId = checked const teeId = checked ? previewTeeId : cell?.teeId ?? previewTeeId
? defaultTees[round.id] ?? round.tees[0]?.id ?? ""
: cell?.teeId ?? round.tees[0]?.id ?? ""
onToggleRound(round.id, player.participantId, checked, teeId) onToggleRound(round.id, player.participantId, checked, teeId)
}} }}
className="size-4 shrink-0 rounded border-border text-primary outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" className="size-4 shrink-0 rounded border-border text-primary outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
/> />
{assigned ? ( </label>
<span aria-hidden="true" className="h-5 w-px shrink-0 bg-border" />
<select <select
aria-label={`Utslag for ${player.playerName} i ${round.label}`} aria-label={`Utslag for ${player.playerName} i ${round.label}`}
className="h-7 min-w-0 flex-1 rounded-md border border-border bg-background px-1.5 text-xs text-foreground outline-none transition-colors hover:border-primary/40 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" className={`h-7 min-w-0 flex-1 rounded-md border px-1.5 text-xs outline-none transition-colors hover:border-primary/40 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${
value={cell?.teeId ?? ""} assigned
? "border-border bg-background text-foreground"
: "border-dashed border-border/70 bg-transparent text-muted-foreground"
}`}
value={previewTeeId}
disabled={busy} disabled={busy}
onChange={(e) => onChangeRoundTee(round.id, player.participantId, e.target.value)} onChange={(e) => {
if (assigned) {
onChangeRoundTee(round.id, player.participantId, e.target.value)
} else {
onToggleRound(round.id, player.participantId, true, e.target.value)
}
}}
> >
{round.tees.map((tee) => ( {round.tees.map((tee) => (
<option key={tee.id} value={tee.id}> <option key={tee.id} value={tee.id}>
@ -512,11 +525,6 @@ function PlayerRow({
</option> </option>
))} ))}
</select> </select>
) : (
<span aria-hidden="true" className="flex-1 text-xs text-muted-foreground/60">
</span>
)}
</div> </div>
</td> </td>
) )