"use client" // Order of Merit (ADR-043 [OOM]): sesong-sammenlagt rangering av spillere // på tvers av flere individuelle turneringer (ADR-037) i samme // organisasjon. Håndkodet (ikke V0 -- ingen V0-credits tilgjengelig denne // runden), men bevisst bygget til å matche samme visuelle presisjon som // V0-eksportene ellers i appen (samme design-tokens, samme kort-/ // header-mønster som org-members.tsx). import type React from "react" import { useEffect, useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { ArrowLeft, ChevronRight, Plus, Trophy, Users } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { cn } from "@/lib/utils" type OomKind = "player" | "team" type OomResultType = "points" | "stableford" | "gross" | "net" | "money" type OomAggregationMode = "sum" | "average" | "eclectic" // Eclectic-aggregering (ADR-075) krever kind='player' og en resultattype // med per-hull-granularitet (stableford/brutto/netto) -- håndhevet // server-side (order_of_merit.py), speilet her for å skjule valget når // det uansett ville blitt avvist. function eclecticAllowed(kind: OomKind, resultType: OomResultType): boolean { return kind === "player" && (resultType === "stableford" || resultType === "gross" || resultType === "net") } type ApiOrderOfMerit = { id: string name: string kind: OomKind result_type: OomResultType aggregation_mode: OomAggregationMode count_best_n: number | null min_results_required: number | null birth_year_from: number | null birth_year_to: number | null public_visible: boolean } const KIND_LABELS: Record = { player: "Spiller", team: "Lag" } const RESULT_TYPE_LABELS: Record = { points: "Poeng etter plassering", stableford: "Stableford-sum", gross: "Bruttoscore-sum", net: "Nettoscore-sum", money: "Pengeliste", } const AGGREGATION_LABELS: Record = { sum: "Sum", average: "Snitt", eclectic: "Eclectic (drømmerunde)", } export function OrderOfMeritList({ organizationId, orgName }: { organizationId: string; orgName: string }) { const router = useRouter() const [items, setItems] = useState(null) const [creating, setCreating] = useState(false) const [error, setError] = useState(null) async function load() { const res = await fetch(`/orgs/${organizationId}/order-of-merits`, { credentials: "include" }) if (res.ok) setItems(await res.json()) } useEffect(() => { void load() // eslint-disable-next-line react-hooks/exhaustive-deps }, [organizationId]) async function create(input: { name: string; kind: OomKind; result_type: OomResultType; aggregation_mode: OomAggregationMode }) { setError(null) try { const res = await fetch(`/orgs/${organizationId}/order-of-merits`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(input), }) if (!res.ok) throw new Error(`create: ${res.status}`) const created: ApiOrderOfMerit = await res.json() setCreating(false) router.push(`/organizations/${organizationId}/order-of-merit/${created.id}?name=${encodeURIComponent(orgName)}`) } catch { setError("Klarte ikke å opprette Order of Merit. Prøv igjen.") } } return (
{error && (

{error}

)}

Sesong-sammenlagt rangering av spillere på tvers av flere turneringer.

{!creating && ( )}
{creating && (
setCreating(false)} />
)} {items === null ? (
) : items.length === 0 && !creating ? (

Ingen Order of Merit-er ennå

Sett opp en sesong-sammenlagt rangering på tvers av flere turneringer.

) : (
    {items.map((oom) => (
  • {oom.kind === "team" ? (
    {oom.name}
    {KIND_LABELS[oom.kind]} {RESULT_TYPE_LABELS[oom.result_type]} {AGGREGATION_LABELS[oom.aggregation_mode]}
  • ))}
)}
) } function Badge({ children }: { children: React.ReactNode }) { return ( {children} ) } function SegmentButton({ selected, onClick, children, }: { selected: boolean onClick: () => void children: React.ReactNode }) { return ( ) } function CreateForm({ onCreate, onCancel, }: { onCreate: (input: { name: string; kind: OomKind; result_type: OomResultType; aggregation_mode: OomAggregationMode }) => void onCancel: () => void }) { const [name, setName] = useState("") const [kind, setKind] = useState("player") const [resultType, setResultType] = useState("stableford") const [aggregationMode, setAggregationMode] = useState("sum") const [submitting, setSubmitting] = useState(false) const showEclectic = eclecticAllowed(kind, resultType) function selectKind(k: OomKind) { setKind(k) if (aggregationMode === "eclectic" && !eclecticAllowed(k, resultType)) setAggregationMode("sum") } function selectResultType(rt: OomResultType) { setResultType(rt) if (aggregationMode === "eclectic" && !eclecticAllowed(kind, rt)) setAggregationMode("sum") } async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!name.trim() || submitting) return setSubmitting(true) await onCreate({ name: name.trim(), kind, result_type: resultType, aggregation_mode: aggregationMode }) setSubmitting(false) } return (

Ny Order of Merit

setName(e.target.value)} placeholder="F.eks. Klubbmesterskap-serien 2026" className="h-11 rounded-xl" />
Type
selectKind("player")}> Spiller selectKind("team")}> Lag
Resultattype
{(Object.keys(RESULT_TYPE_LABELS) as OomResultType[]).map((rt) => ( selectResultType(rt)}> {RESULT_TYPE_LABELS[rt]} ))}
Aggregering
setAggregationMode("sum")}> Sum setAggregationMode("average")}> Snitt {showEclectic && ( setAggregationMode("eclectic")}> Eclectic )}
) }