Modalen ("Administrer runde", tilgjengelig fra alle tre rundefanene) hadde en
egen, uavhengig "Fullfør runde"-vei som aldri sjekket setup_complete og
lukket dialogen umiddelbart uten å vente på fetch-svaret -- så en ufullstendig
formatoppsett (f.eks. Københavner med for få spillere) så ut til å fullføres,
men gjorde det aldri, uten noen feilmelding. Backend-valideringen var korrekt
fra før; kun frontend fulgte aldri opp svaret.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
"use client"
|
|
|
|
// Delt datalag for RoundHeader (ADR-040 Beslutning C/D) -- brukt av alle
|
|
// tre faneskjermene (Score/Scorekort/Leaderboard). Henter kun de feltene
|
|
// selve headeren trenger (ikke hele RoundOut) -- de tre sidene under
|
|
// gjør fortsatt sin egen, uavhengige, mer komplette henting, akkurat som
|
|
// før denne runden. onFinishRound/onDeleteRound er egne, selvstendige
|
|
// implementasjoner (ikke delt med round-detail.tsx sine interne
|
|
// finishRound/deleteRound) -- samme to ekte endepunkter
|
|
// (`POST .../complete`, `DELETE ...`), men denne komponenten må fungere
|
|
// uavhengig av round-detail.tsx sitt state siden den også brukes fra
|
|
// Scorekort- og Leaderboard-siden.
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { RoundHeader, type RoundTab } from "@/components/round-header"
|
|
|
|
type ApiRoundHeaderFields = {
|
|
name: string | null
|
|
course_name_snapshot: string
|
|
tee_name_snapshot: string
|
|
played_at: string
|
|
started_at: string | null
|
|
play_format: string
|
|
setup_complete: boolean
|
|
setup_message: string | null
|
|
}
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat("no-NO", { day: "numeric", month: "long", year: "numeric" })
|
|
const timeFormatter = new Intl.DateTimeFormat("no-NO", { hour: "2-digit", minute: "2-digit" })
|
|
|
|
export function RoundPageShell({
|
|
roundId,
|
|
activeTab,
|
|
children,
|
|
}: {
|
|
roundId: string
|
|
activeTab: RoundTab
|
|
children: React.ReactNode
|
|
}) {
|
|
const router = useRouter()
|
|
const [round, setRound] = useState<ApiRoundHeaderFields | null>(null)
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
fetch(`/rounds/${roundId}`, { credentials: "include" })
|
|
.then((res) => (res.ok ? res.json() : null))
|
|
.then((data: ApiRoundHeaderFields | null) => {
|
|
if (!cancelled && data) setRound(data)
|
|
})
|
|
.catch(() => {})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [roundId])
|
|
|
|
async function onFinishRound(): Promise<{ ok: boolean; message?: string } | null> {
|
|
if (!confirm("Fullføre runden? Du kan fortsatt se den, men ikke lenger endre registrerte hull.")) return null
|
|
const res = await fetch(`/rounds/${roundId}/complete`, { method: "POST", credentials: "include" })
|
|
if (res.ok) {
|
|
router.refresh()
|
|
return { ok: true }
|
|
}
|
|
const body = await res.json().catch(() => null)
|
|
return { ok: false, message: body?.detail?.message ?? "Klarte ikke å fullføre runden. Prøv igjen." }
|
|
}
|
|
|
|
async function onDeleteRound() {
|
|
const res = await fetch(`/rounds/${roundId}`, { method: "DELETE", credentials: "include" })
|
|
if (res.ok) router.replace("/my-rounds")
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-[100dvh] flex-col bg-background">
|
|
{round && (
|
|
<RoundHeader
|
|
activeTab={activeTab}
|
|
round={{
|
|
courseName: round.course_name_snapshot,
|
|
customName: round.name?.trim() || undefined,
|
|
teeName: round.tee_name_snapshot,
|
|
date: dateFormatter.format(new Date(round.played_at)),
|
|
teeTime: round.started_at ? timeFormatter.format(new Date(round.started_at)) : undefined,
|
|
isMatchPlay: round.play_format === "match",
|
|
}}
|
|
backHref="/my-rounds"
|
|
tabHrefs={{
|
|
score: `/my-rounds/${roundId}`,
|
|
scorecard: `/my-rounds/${roundId}/scorecard`,
|
|
leaderboard: `/my-rounds/${roundId}/leaderboard`,
|
|
}}
|
|
manageHref={`/my-rounds/${roundId}?tab=manage`}
|
|
onFinishRound={onFinishRound}
|
|
finishDisabledReason={
|
|
!round.setup_complete ? round.setup_message ?? "Oppsettet er ikke komplett ennå." : undefined
|
|
}
|
|
onDeleteRound={onDeleteRound}
|
|
/>
|
|
)}
|
|
<div className="flex-1">{children}</div>
|
|
</div>
|
|
)
|
|
}
|