"use client" import { useEffect, useRef, useState } from "react" import { Loader2, TriangleAlert } from "lucide-react" import { cn } from "@/lib/utils" // V0-generert (ADR-093, 2026-08-19) -- ren kontrollert/presentasjonell // komponent, ingen egne fetch-kall. SetupTab (individual-tournament- // detail.tsx) eier all datahenting/orkestrering og sender ferdig // sammenslått data + callback-props inn. export type RoundParticipationPlayer = { participantId: string playerId: string name: string gender: "m" | "f" | "x" | null birthDate: string | null // ISO "YYYY-MM-DD", null if unknown } export type RoundParticipationRound = { id: string label: string // e.g. "Runde 1" tees: { id: string; name: string }[] // this round's course's available tees } export type RoundParticipationCell = { assigned: boolean teeId: string | null // null when not assigned teeName: string | null } export type RoundParticipationTableProps = { players: RoundParticipationPlayer[] rounds: RoundParticipationRound[] // cells[roundId][participantId] cells: Record> onToggle: (roundId: string, participantId: string, checked: boolean, teeId: string) => void onBulkToggle: (roundId: string, checked: boolean, teeId: string) => void onChangeTee: (roundId: string, participantId: string, teeId: string) => void onChangeGender: (playerId: string, gender: "m" | "f" | "x") => void onChangeBirthDate: (playerId: string, birthDate: string) => void busyRoundIds: string[] errors: string[] } // Standard age-from-birthdate: subtract years, then step back one if this // year's birthday hasn't happened yet. function computeAge(birthDate: string): number { const birth = new Date(birthDate) const now = new Date() let age = now.getFullYear() - birth.getFullYear() const monthDiff = now.getMonth() - birth.getMonth() if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < birth.getDate())) { age -= 1 } return age } const selectClass = "h-10 rounded-lg border border-border bg-background px-2 text-sm font-medium text-foreground transition-all duration-200 ease-in-out hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" export function RoundParticipationTable({ players, rounds, cells, onToggle, onBulkToggle, onChangeTee, onChangeGender, onChangeBirthDate, busyRoundIds, errors, }: RoundParticipationTableProps) { // Local-only convenience state: each round column's "Standardutslag" // selection, used to pre-fill the teeId when checking a box. Resets to each // round's first tee on mount / when the round set changes. const [defaultTees, setDefaultTees] = useState>(() => Object.fromEntries(rounds.map((r) => [r.id, r.tees[0]?.id ?? ""])), ) useEffect(() => { setDefaultTees((prev) => { const next: Record = {} for (const r of rounds) { // keep an existing valid selection, otherwise fall back to first tee const existing = prev[r.id] const stillValid = existing && r.tees.some((t) => t.id === existing) next[r.id] = stillValid ? existing : (r.tees[0]?.id ?? "") } return next }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [rounds.map((r) => r.id + ":" + r.tees.map((t) => t.id).join(",")).join("|")]) return (
{errors.length > 0 && (
    {errors.map((msg, i) => (
  • {msg}
  • ))}
)}
{rounds.map((round) => { const busy = busyRoundIds.includes(round.id) return ( ) })} {players.map((player) => { const age = player.birthDate ? computeAge(player.birthDate) : null return ( {/* Kjønn */} {/* Alder */} {/* Round columns */} {rounds.map((round) => { const busy = busyRoundIds.includes(round.id) const cell = cells[round.id]?.[player.participantId] const assigned = cell?.assigned ?? false const cellId = `cell-${round.id}-${player.participantId}` return ( ) })} ) })}
Spiller Kjønn Alder
{round.label} {busy && ( )}
onBulkToggle(round.id, checked, defaultTees[round.id] ?? "") } />
{player.name}
onChangeBirthDate(player.playerId, e.target.value)} className={cn(selectClass, "w-36")} aria-label={`Fødselsdato for ${player.name}`} /> {age !== null ? `${age} år` : "Ukjent"}
{assigned && ( )}
) } function BulkToggle({ round, players, cells, busy, onBulkToggle, }: { round: RoundParticipationRound players: RoundParticipationPlayer[] cells: Record> busy: boolean onBulkToggle: (checked: boolean) => void }) { const ref = useRef(null) const assignedCount = players.reduce( (acc, p) => acc + (cells[round.id]?.[p.participantId]?.assigned ? 1 : 0), 0, ) const total = players.length const allAssigned = total > 0 && assignedCount === total const someAssigned = assignedCount > 0 && assignedCount < total useEffect(() => { if (ref.current) ref.current.indeterminate = someAssigned }, [someAssigned]) const id = `bulk-${round.id}` return ( ) }