teecup/frontend/lib/ny-runde/formats.ts
Erol Haagenrud 11d10f7661 Erstatt /my-rounds/new med ny-runde-veiviser fra egen V0-prompt
Gammel components/new-round.tsx (3364 linjer) slettet, erstattet av
components/ny-runde/ + lib/ny-runde/ -- V0-eksporten (full teknisk spec,
null designføring) ported mot de faktiske backend-kontraktene i
app/routers/rounds.py: Gender-oversettelse, forenklet Tee-form (aldri
CR/Slope til klienten), CourseMeta-discriminated-union for teeoff- vs.
egen-bane, ekte POST /rounds -> sides -> participants-sekvens, ekte
kontosøk/gjeste-oppslag, ekte banesøk inkl. geolokasjon.

Fire bugs funnet og rettet under scratch-verifisering: manglende
forwardRef på TextInput (i selve V0-eksporten), manglende
tee-synk til eierens spillerkort etter banevalg (egen portingsfeil),
manglende Ingen/Alle-hurtigknapp i steg 5 (samme mangel rettet i appen
2026-08-06), og en produksjonsbug oppdaget EKTE brukere etter utrulling:
fetchFacilityCourses() leste responsen fra offisielt banesøk som en rå
array i stedet for {slug,name,courses}-objektet backend faktisk
returnerer -- ga en uendelig "Laster baner..."-hang for enhver runde
startet på en offisiell (teeoff-)bane. Rettet + lagt til reell
feilhåndtering i OfficialCourses (manglet helt, feilen ble tidligere
en stille, ufanget promise-rejection).

Se CHANGELOG.md punkt 41 og 43 for full verifiseringsdetalj.
2026-08-08 12:34:25 +02:00

156 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { FormatId } from "./types"
export interface FormatDef {
id: FormatId
label: string
description: string
}
export const FORMATS: FormatDef[] = [
{ id: "slagspill", label: "Slagspill", description: "Vanlig individuell runde." },
{
id: "stableford",
label: "Stableford",
description: "Poeng per hull i stedet for slag kan plukke opp ved 0 poeng.",
},
{ id: "match", label: "Match", description: "To sider, hull mot hull." },
{ id: "skins", label: "Skins", description: "Hvert hull er en egen premie minst 3 spillere." },
{ id: "fourball", label: "Fourball", description: "To mot to, beste ball per side." },
{ id: "foursome", label: "Foursome", description: "To mot to, én delt ball, annenhver slår." },
{
id: "greensome",
label: "Greensome",
description: "To mot to, begge slår ut, velger så én ball.",
},
{
id: "scramble2",
label: "Scramble (2)",
description: "To mot to, velger beste slag hver gang.",
},
{
id: "scramble4",
label: "Scramble (4)",
description: "Fire mot fire, velger beste slag hver gang.",
},
{
id: "chapman",
label: "Chapman",
description: "To mot to, bytter utslag, velger så én ball som foursome fra andre slag.",
},
{
id: "copenhagen",
label: "Københavner",
description: "Nøyaktig 3 spillere 6 poeng deles per hull etter rangering.",
},
{
id: "bbb",
label: "Bingo Bango Bongo",
description: "Poeng for først på green, nærmest hull, først i hull.",
},
{
id: "flag",
label: "Flaggturnering",
description: "Fast slagbudsjett (par + HCP) flagget plantes der slagene tar slutt.",
},
{
id: "shamble",
label: "Shamble",
description: "2-4 spillere, alle slår ut, velger beste spiller så egen ball inn.",
},
{
id: "moneyball",
label: "Money Ball",
description: "Fire spillere, én roterer som 'money ball' per hull.",
},
{
id: "highlowhigh",
label: "High-low-high",
description: "To lag à to høy mot høy, lav mot lav, per hull.",
},
{
id: "scramblevssolo",
label: "Scramble mot enkeltspiller",
description:
"Et lag med delt ball (minst 2 spillere) mot én enkeltspiller — nettoscore avgjør.",
},
{
id: "scramblevssolomatch",
label: "Scramble mot enkeltspiller (matchspill)",
description: "Samme oppsett som over, men avgjort hull for hull i stedet for netto totalsum.",
},
]
export const FORMAT_MAP: Record<FormatId, FormatDef> = FORMATS.reduce(
(acc, f) => {
acc[f.id] = f
return acc
},
{} as Record<FormatId, FormatDef>,
)
/** Two-sided team formats (drive the standard step-4 A/B layout). */
export const TWO_SIDED: FormatId[] = [
"match",
"fourball",
"foursome",
"greensome",
"scramble2",
"scramble4",
"chapman",
"highlowhigh",
]
export const SCRAMBLE_VS_SOLO: FormatId[] = ["scramblevssolo", "scramblevssolomatch"]
// Samme sett som app/handicap.py sin SIDE_IS_UNIT (+ chapman-aliaset) --
// avgjør om HCP-prosenten bygges som "combined" (delt enhet) eller
// "per_player" i allowance_override.strategy.
export const SIDE_IS_UNIT_FORMAT: FormatId[] = ["foursome", "greensome", "scramble2", "scramble4", "chapman"]
// "Bruk course handicap-justering" er skjult for disse to (ingen reell
// nytteverdi), men holdes funksjonelt PÅ uansett -- se wizard-context.tsx.
export const ORDINARY_FORMATS: FormatId[] = ["slagspill", "stableford"]
export function isTwoSided(f: FormatId): boolean {
return TWO_SIDED.includes(f)
}
export function isScrambleVsSolo(f: FormatId): boolean {
return SCRAMBLE_VS_SOLO.includes(f)
}
/** Whether the Teams & order step (4) is shown for a given format. */
export function showsTeamStep(f: FormatId): boolean {
return isTwoSided(f) || f === "moneyball" || isScrambleVsSolo(f)
}
// Wizardens FormatId -> ekte round.play_format-verdi (RoundCreate).
export const FORMAT_TO_API: Record<FormatId, string> = {
slagspill: "stroke",
stableford: "stableford",
match: "match",
skins: "skins",
fourball: "fourball",
foursome: "foursome",
greensome: "greensome",
scramble2: "scramble_2",
scramble4: "scramble_4",
chapman: "chapman",
copenhagen: "copenhagen",
bbb: "bbb",
flag: "flag",
shamble: "shamble",
moneyball: "money_ball",
highlowhigh: "high_low_high",
scramblevssolo: "scramble_solo",
scramblevssolomatch: "scramble_solo_match",
}
export const API_TO_FORMAT: Record<string, FormatId> = Object.fromEntries(
Object.entries(FORMAT_TO_API).map(([k, v]) => [v, k as FormatId]),
) as Record<string, FormatId>
export function apiStatLevel(s: StatLevelLike): "strokes_only" | "strokes_and_putts" | "full" {
return s === "score" ? "strokes_only" : s === "score_putts" ? "strokes_and_putts" : "full"
}
type StatLevelLike = "score" | "score_putts" | "full"