"use client"
// Samlet leaderboard på tvers av flere flighter satt opp SAMMEN i én
// handling (migrasjon 035, retning 1 -- se FEATURE_BACKLOG.md/
// ARCHITECTURE_DECISIONS.md "Frittstående runder: flere flighter i én
// 'vanlig' runde"). Egen, lettere lokal variant av round-leaderboard.tsx
// sitt rangerings-/mode-toggle-mønster (samme "liten lokal duplisering
// fremfor cross-file-import"-konvensjon som resten av den filen) -- denne
// siden slår sammen FLERE flighters egne leaderboard til én rangert liste,
// tagget med hvilken flight hver rad kom fra. Ingen egen hull-for-hull-
// detalj her (den finnes allerede på hver enkelt flights EGET leaderboard,
// lenket til fra hver rad) -- denne siden er en oversikt, ikke en duplikat.
import { useEffect, useState } from "react"
import Link from "next/link"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
type ApiFlightSummary = {
round_id: string
name: string | null
course_name_snapshot: string
played_at: string
started_at: string | null
completed_at: string | null
participant_count: number
is_anchor: boolean
}
type ApiEntry = {
round_id: string
flight_label: string
participant_id: string
display_name: string
is_owner: boolean
holes_played: number
total_score: number | null
score_to_par: number | null
net_score_to_par: number | null
total_points: number | null
}
type ApiFlightGroupLeaderboard = {
flight_group_id: string | null
flights: ApiFlightSummary[]
entries: ApiEntry[]
}
type Mode = "gross" | "net" | "points"
function valueFor(entry: ApiEntry, mode: Mode): number | null {
if (mode === "gross") return entry.score_to_par
if (mode === "net") return entry.net_score_to_par
return entry.total_points
}
type RankedEntry = ApiEntry & { rank: number | null; tied: boolean }
function rankEntries(entries: ApiEntry[], mode: Mode): RankedEntry[] {
const withValue = entries.filter((e) => valueFor(e, mode) !== null)
const withoutValue = entries.filter((e) => valueFor(e, mode) === null)
const direction = mode === "points" ? -1 : 1
withValue.sort((a, b) => direction * ((valueFor(a, mode) as number) - (valueFor(b, mode) as number)))
const ranked: RankedEntry[] = []
let rank = 0
let prevValue: number | null = null
for (let i = 0; i < withValue.length; i++) {
const e = withValue[i]
const v = valueFor(e, mode) as number
if (v !== prevValue) {
rank = i + 1
prevValue = v
}
const tied = withValue.filter((o) => valueFor(o, mode) === v).length > 1
ranked.push({ ...e, rank, tied })
}
for (const e of withoutValue) ranked.push({ ...e, rank: null, tied: false })
return ranked
}
function formatToPar(value: number): string {
if (value === 0) return "E"
return value > 0 ? `+${value}` : `−${Math.abs(value)}`
}
function ValueMark({ mode, value, emphasize }: { mode: Mode; value: number; emphasize: boolean }) {
if (mode === "points") {
return (
{value}
)
}
if (value === 0) {
return E
}
const under = value < 0
return (
{formatToPar(value)}
)
}
function RankBadge({ rank, tied, isLeader }: { rank: number | null; tied: boolean; isLeader: boolean }) {
return (
{rank !== null ? (tied ? `T-${rank}` : `#${rank}`) : "–"}
)
}
function ModeToggle({ mode, onChange }: { mode: Mode; onChange: (m: Mode) => void }) {
const options: { value: Mode; label: string }[] = [
{ value: "gross", label: "Brutto" },
{ value: "net", label: "Netto" },
{ value: "points", label: "Poeng" },
]
return (
{options.map((opt) => (
))}
)
}
export function FlightGroupLeaderboard({ roundId }: { roundId: string }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [mode, setMode] = useState("gross")
useEffect(() => {
let cancelled = false
fetch(`/rounds/${roundId}/flight-group/leaderboard`, { credentials: "include" })
.then((res) => {
if (!res.ok) throw new Error(String(res.status))
return res.json()
})
.then((json: ApiFlightGroupLeaderboard) => {
if (!cancelled) setData(json)
})
.catch(() => {
if (!cancelled) setError("Klarte ikke å hente det samlede leaderboardet.")
})
return () => {
cancelled = true
}
}, [roundId])
if (error) {
return (