"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 ( ) } 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 (

{error}

Tilbake til runden
) } if (!data) { return (
) } const ranked = rankEntries(data.entries, mode) return (
{data.flights.length} flighter satt opp sammen
    {data.flights.map((f) => (
  • {f.name || f.course_name_snapshot} · {f.participant_count} spiller{f.participant_count !== 1 ? "e" : ""} {f.completed_at ? " · Ferdig" : ""}
  • ))}

Stilling

    {ranked.map((entry) => { const value = valueFor(entry, mode) const isLeader = entry.rank === 1 return (
  • {entry.display_name} {entry.flight_label} {value !== null ? ( ) : ( )}
  • ) })}
{ranked.length === 0 && (

Ingen registrerte spillere i noen av flightene ennå.

)}
) }