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.
156 lines
4.8 KiB
TypeScript
156 lines
4.8 KiB
TypeScript
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"
|