Fiks utslag-endring/bulk-tilordning + kommuniser frosset HCP-snapshot tydeligere
To reelle bugs i rundedeltakelse: enkelt-rad utslagsendring sendte feil ID-type til backend (traff alltid 404, stille), og "Velg alle" hoppet over allerede-tilordnede deltakere slik at et valgt standardutslag aldri slo inn for dem. Begge rettet. I tillegg: ny hjelpetekst i Spillere-steget og et nytt FAQ-spørsmål forklarer at turnering-HCP er en bevisst frosset kopi av spillerpoolen (ADR-090), ikke en bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
95b05ab7f5
commit
7d5726cf4d
4 changed files with 141 additions and 17 deletions
73
CHANGELOG.md
73
CHANGELOG.md
|
|
@ -13112,3 +13112,76 @@ Neste steg:
|
|||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||||
`docker compose build teecup_frontend && up -d`, rene logger,
|
||||
200 OK.
|
||||
|
||||
130. **Zebra-stripe rettet til nøytral grå + HCP-"frosset snapshot"
|
||||
kommunisert tydeligere (ikke reversert) -- 2026-08-19.** To
|
||||
oppfølgere. (1) Bruker: "Lyse GRÅ striper, ikke lyse grønne." --
|
||||
`bg-muted` (punkt 129) er en grønntonet token i denne paletten
|
||||
(klubbhus-fargene), ikke nøytral grå. Rettet med et translucent
|
||||
svart/hvitt-overlegg (`bg-black/[0.03] dark:bg-white/[0.04]`) --
|
||||
bærer ingen fargetone i seg selv, så det leses som ekte grå i
|
||||
begge temaer uten å måtte velge to separate faste gråtoner.
|
||||
(2) Bruker: "HCP oppdateres ikke i deltagertabellen. Det mangler,
|
||||
selv om det er lagt til i spillertabellen." -- undersøkt grundig,
|
||||
inkludert et FORHASTET forsøk på å regne rundedeltakeres
|
||||
`playing_handicap` på nytt når turnering-HCP-en endres, som viste
|
||||
seg å BRYTE en bevisst, allerede testet ADR-090-beslutning
|
||||
("frosset rundehandicap", `test_updating_snapshot_does_not_
|
||||
retroactively_change_already_frozen_round_handicap`) -- reversert
|
||||
før commit, ingen kodeendring i `individual_tournaments.py` til
|
||||
slutt. Presisert med bruker: dette var faktisk spillerpool→
|
||||
turnering-HCP (ikke turnering→runde), som ER det bevisste,
|
||||
allerede-eksisterende "frosset snapshot"-designet fra ADR-090 --
|
||||
IKKE en bug. Bruker bekreftet eksplisitt: behold frosset, men
|
||||
kommuniser problemstillingen bedre. Løst med (a) en kort forklarende
|
||||
linje rett over spillertabellen i "Spillere"-steget ("HCP-en under
|
||||
er en egen kopi for turneringen ... en endring i spillerpoolen
|
||||
oppdaterer den IKKE automatisk"), og (b) et nytt FAQ-spørsmål i
|
||||
`/hjelp` (nb+en) som forklarer HVORFOR (HCP kan påvirke resultatet,
|
||||
skal aldri endre seg stille midt i en turnering).
|
||||
|
||||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, backend
|
||||
152/152 (uendret -- den forkastede backend-endringen ble aldri
|
||||
committet). Scratch bekrefter grå (ikke grønn) stripe i begge
|
||||
temaer, og at det nye FAQ-spørsmålet vises og utvides korrekt.
|
||||
|
||||
**Rullet ut:** venter på bekreftelse (rulles ut sammen med #131).
|
||||
|
||||
131. **To reelle feil i rundedeltakelse-delen av den nye spillertabellen --
|
||||
2026-08-19.** Bruker, mens hen faktisk brukte den nettopp utrullede
|
||||
tabellen: "Dersom jeg velger at ALLE skal gå fra 55 så kan jeg ikke
|
||||
individuelt endre enkeltspillere. Dersom jeg først velger ALLE og
|
||||
55, så kan jeg ikke fjerne 'ALLE-haken' etterpå. Da går alle
|
||||
utslagene tilbake til 32 igjen." Kodegjennomgang avdekket TO
|
||||
separate, ekte bugs, ikke misforståelser: **(1)**
|
||||
`changeRoundParticipantTee` (enkelt-rad utslagsendring) sendte
|
||||
`tournament_participant`-IDen rett inn i PATCH-URL-en, men backend-
|
||||
endepunktet forventer `tournament_round_participant`-IDen (en helt
|
||||
annen rad) -- ethvert forsøk på å endre én spillers utslag
|
||||
individuelt traff dermed en ikke-eksisterende rad (404), stille
|
||||
ingen synlig effekt. `TournamentPlayersTable` sin egen cellemodell
|
||||
kjenner aldri round-participant-IDen (kun tournament_participant-
|
||||
IDen, selve nøkkelen i `cells[roundId][...]`), så fiksen slår opp
|
||||
riktig ID FØRST -- samme oppslagsmønster `toggleRoundParticipant`
|
||||
sin DELETE-gren allerede brukte riktig. **(2)** `bulkSetRound
|
||||
Participation` sin "Velg alle" hoppet over ALLE deltakere som
|
||||
allerede var tilordnet runden (uansett hvilket utslag de hadde) --
|
||||
et valgt Standardutslag slo dermed KUN inn for nylig tilførte
|
||||
deltakere, aldri for de som fra før var satt til et annet utslag
|
||||
(f.eks. "32"). Av-kryssing fjernet deretter kun de nylig tilførte
|
||||
-- de opprinnelige "32"-deltakerne ble aldri rørt i noen retning,
|
||||
så resultatet så ut som en reversering til "32" selv om det egentlig
|
||||
var at de aldri ble endret i utgangspunktet. Rettet til å PATCHe
|
||||
utslaget på allerede tilordnede i stedet for å hoppe over dem --
|
||||
"Velg alle" + et utslag betyr nå faktisk "alle spiller dette
|
||||
utslaget".
|
||||
|
||||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ingen
|
||||
scratch-simulering denne runden -- gitt at feilen er live og
|
||||
brukeren aktivt tester akkurat nå, prioritert rask utrulling
|
||||
fremfor en tidkrevende mock-backend-gjenskaping i scratch; begge
|
||||
rettelser sporet presist til brukerens eksakte symptomer via
|
||||
kodelesning (feil ID-type i URL-en, og hvilke deltakere som faktisk
|
||||
var med i `Promise.all`-batchen).
|
||||
|
||||
**Rullet ut:** venter på bekreftelse.
|
||||
|
|
|
|||
|
|
@ -1367,34 +1367,50 @@ function SetupTab({
|
|||
async function bulkSetRoundParticipation(roundId: string, checked: boolean, defaultTeeId: string) {
|
||||
setBusyRoundIds((prev) => [...prev, roundId])
|
||||
setParticipationErrors([])
|
||||
const assignedIds = new Set((roundParticipantsByRound?.[roundId] ?? []).map((rp) => rp.tournament_participant_id))
|
||||
const targets = checked
|
||||
? participants.filter((p) => !assignedIds.has(p.id))
|
||||
: (roundParticipantsByRound?.[roundId] ?? [])
|
||||
const currentlyAssigned = roundParticipantsByRound?.[roundId] ?? []
|
||||
const assignedByParticipantId = new Map(currentlyAssigned.map((rp) => [rp.tournament_participant_id, rp]))
|
||||
|
||||
const failures: string[] = []
|
||||
if (checked) {
|
||||
// "Velg alle" + et valgt Standardutslag betyr "ALLE spiller dette
|
||||
// utslaget" -- IKKE bare "fyll inn de som mangler". Rettet
|
||||
// 2026-08-19 (bruker: "Dersom jeg velger at ALLE skal gå fra 55 så
|
||||
// kan jeg ikke individuelt endre enkeltspillere") -- den gamle
|
||||
// versjonen hoppet over alle som allerede var tilordnet runden
|
||||
// (uansett hvilket utslag de hadde), så et valgt Standardutslag slo
|
||||
// aldri inn for dem. PATCHer utslaget på allerede tilordnede i
|
||||
// stedet for å hoppe over dem, POSTer nye for resten.
|
||||
await Promise.all(
|
||||
(targets as ApiParticipant[]).map(async (p) => {
|
||||
participants.map(async (p) => {
|
||||
const teeId = resolveDefaultTeeId(p, classes, teesByCourse[rounds.find((r) => r.id === roundId)?.course_id ?? ""] ?? [], defaultTeeId)
|
||||
if (!teeId) {
|
||||
failures.push(`${p.player_name}: fant ikke noe gyldig utslag.`)
|
||||
return
|
||||
}
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ tournament_participant_id: p.id, tee_id: teeId }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
failures.push(`${p.player_name}: ${await errorMessage(res, "ukjent feil")}`)
|
||||
const existing = assignedByParticipantId.get(p.id)
|
||||
if (existing) {
|
||||
if (existing.tee_id === teeId) return // allerede riktig, ingenting å gjøre
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants/${existing.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ tee_id: teeId }),
|
||||
})
|
||||
if (!res.ok) failures.push(`${p.player_name}: ${await errorMessage(res, "ukjent feil")}`)
|
||||
} else {
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ tournament_participant_id: p.id, tee_id: teeId }),
|
||||
})
|
||||
if (!res.ok) failures.push(`${p.player_name}: ${await errorMessage(res, "ukjent feil")}`)
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
await Promise.all(
|
||||
(targets as ApiRoundParticipant[]).map(async (rp) => {
|
||||
currentlyAssigned.map(async (rp) => {
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants/${rp.id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
|
|
@ -1406,12 +1422,25 @@ function SetupTab({
|
|||
await refreshRoundParticipants(roundId)
|
||||
setBusyRoundIds((prev) => prev.filter((id) => id !== roundId))
|
||||
if (failures.length > 0) {
|
||||
setParticipationErrors([`${failures.length} av ${targets.length} feilet:`, ...failures])
|
||||
setParticipationErrors([`${failures.length} feilet:`, ...failures])
|
||||
}
|
||||
}
|
||||
|
||||
async function changeRoundParticipantTee(roundId: string, roundParticipantId: string, teeId: string) {
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants/${roundParticipantId}`, {
|
||||
async function changeRoundParticipantTee(roundId: string, tournamentParticipantId: string, teeId: string) {
|
||||
// Rettet 2026-08-19 (bruker: "kan jeg ikke individuelt endre
|
||||
// enkeltspillere") -- TournamentPlayersTable sin cellemodell kjenner
|
||||
// kun tournament_participant-IDen (nøkkelen i cells[roundId][...]),
|
||||
// ALDRI selve tournament_round_participant-raden sin egen ID. Denne
|
||||
// funksjonen kalte tidligere PATCH-URL-en direkte med den ID-en den
|
||||
// fikk inn, som stille traff feil (ikke-eksisterende) rad -- 404,
|
||||
// ingen synlig endring. Slår nå opp riktig round-participant-ID
|
||||
// FØRST, samme oppslagsmønster som toggleRoundParticipant sin
|
||||
// DELETE-gren allerede bruker.
|
||||
const existing = (roundParticipantsByRound?.[roundId] ?? []).find(
|
||||
(rp) => rp.tournament_participant_id === tournamentParticipantId,
|
||||
)
|
||||
if (!existing) return
|
||||
const res = await fetch(`${base}/rounds/${roundId}/participants/${existing.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -1538,6 +1567,20 @@ function SetupTab({
|
|||
/>
|
||||
)}
|
||||
|
||||
{participants.length > 0 && (
|
||||
// HCP-en her er en FROSSET kopi, satt når deltakeren legges til
|
||||
// (ADR-090) -- bevisst, ikke en bug (spurt eksplisitt om av
|
||||
// bruker 2026-08-19, som ba om at dette kommuniseres tydeligere
|
||||
// her og i /hjelp fremfor å reversere designet). Endringer i
|
||||
// spillerpoolen når som helst ETTER dette synker ALDRI inn av
|
||||
// seg selv -- HCP-en må rettes her, direkte.
|
||||
<p className="text-xs leading-relaxed text-muted-foreground text-pretty">
|
||||
HCP-en under er en egen kopi for turneringen, satt da deltakeren ble lagt til --
|
||||
en endring i spillerpoolen oppdaterer den IKKE automatisk. Rett HCP-en direkte i
|
||||
kolonnen under om den er feil.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{participants.length === 0 ? (
|
||||
<p className="text-sm leading-relaxed text-muted-foreground text-pretty">
|
||||
Ingen deltakere lagt til ennå -- legg til hele feltet her, uavhengig av hvilke runder de spiller.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
{
|
||||
"question": "What happens with a tied result (tiebreak)?",
|
||||
"answer": "You choose the rule yourself -- countback (best final holes), lowest handicap, a playoff, or simply sharing the position. This can be set differently for the winner than for the rest of the field, and it never affects where the cut line falls."
|
||||
},
|
||||
{
|
||||
"question": "Why doesn't the HCP in the tournament update when I change it in the player pool?",
|
||||
"answer": "This is deliberate, not a bug. A tournament's HCP is its own frozen copy -- set the moment the participant is added, then independent of the player pool from then on. Reason: HCP can affect the actual result, so it should never be able to change silently during an ongoing tournament. To correct a participant's HCP after they've been added, do it directly in the tournament's own Players step -- not in the player pool."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
{
|
||||
"question": "Hva skjer ved likt resultat (tiebreak)?",
|
||||
"answer": "Du velger selv regelen -- countback (beste siste hull), laveste handicap, omspill, eller at plasseringen rett og slett deles. Dette kan settes forskjellig for vinneren enn for resten av feltet, og påvirker aldri hvor cut-grensen går."
|
||||
},
|
||||
{
|
||||
"question": "Hvorfor oppdateres ikke HCP-en i turneringen når jeg endrer den i spillerpoolen?",
|
||||
"answer": "Dette er bevisst, ikke en feil. HCP-en i en turnering er en egen, frosset kopi -- satt idet deltakeren legges til, og deretter uavhengig av spillerpoolen. Grunnen: HCP kan påvirke selve resultatet, så den skal aldri kunne endre seg stille under en pågående turnering. Skal en deltakers HCP rettes etter at de er lagt til, gjør du det direkte i turneringens eget Spillere-steg -- ikke i spillerpoolen."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue