teecup/frontend/components/tournament-card.tsx
Erol Haagenrud 90bb02e6de Lag/roster-skjermen er live på https://teecup.teeoff.no (klikk et turneringskort på dashbordet). Oppsummert:
Bygget:

/tournaments/[id] — to lag side ved side, hvert med egen fargevelger, spillerliste og en type-ahead for "legg til spiller" (søker i organisasjonens spillerpool, tilbyr inline "opprett ny spiller" hvis ingen treff, markerer spillere allerede rostret på det andre laget som utilgjengelige).
Fant et reelt hull før integrering, ikke etter: V0-skjermen bygger inn "fjern spiller" og "gjør til kaptein" — backend hadde bare GET/POST på roster, ingen DELETE/PATCH. Spurte deg, du sa bygg dem nå — lagt til og scratch-verifisert (PATCH setter kaptein riktig, DELETE gir 204 og er idempotent, test_isolation.sql fortsatt 12/12).
Navigasjon fra dashbordet er kablet opp (turneringskort er nå en ekte lenke).
Bevisst forenkling notert i backloggen: ingen håndheving av "kun én kaptein per lag" ennå — flere kan merkes samtidig. Hører sammen med det uavklarte brukerroller-punktet, løses ikke isolert her.

Verifisert: ekte typesjekket build (5 ruter), begge containere redeployet (backend hadde nye endepunkter), teeoff.no upåvirket. Selve skrive-flyten (opprett lag/spiller) er ikke testet med ekte data — samme som sist, venter på deg. Du har allerede "De Gamle er Eldst" liggende i "Tjøme Gents" — vil du prøve å sette opp de to lagene der?
2026-07-18 07:15:38 +02:00

69 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Link from "next/link"
import { Calendar, ChevronRight } from "lucide-react"
import { TournamentStatusBadge, type TournamentStatus } from "@/components/tournament-status-badge"
export type Tournament = {
id: string
name: string
status: TournamentStatus
startDate?: string
endDate?: string
}
const dateFormatter = new Intl.DateTimeFormat("no-NO", {
day: "numeric",
month: "short",
year: "numeric",
})
function formatDate(value?: string) {
if (!value) return null
const parsed = new Date(value)
if (Number.isNaN(parsed.getTime())) return null
return dateFormatter.format(parsed)
}
function formatDateRange(start?: string, end?: string) {
const from = formatDate(start)
const to = formatDate(end)
if (from && to) return from === to ? from : `${from} ${to}`
return from ?? to ?? null
}
export function TournamentCard({
tournament,
orgId,
}: {
tournament: Tournament
orgId: string
}) {
const dateRange = formatDateRange(tournament.startDate, tournament.endDate)
return (
<Link
href={`/tournaments/${tournament.id}?org=${orgId}&name=${encodeURIComponent(tournament.name)}`}
className="group flex w-full items-center gap-4 rounded-2xl border border-border bg-card p-4 text-left shadow-sm shadow-black/5 transition-colors hover:border-primary/50 hover:bg-accent/50 sm:p-5"
>
<div className="flex min-w-0 flex-1 flex-col gap-2">
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<h3 className="truncate text-base font-bold text-foreground sm:text-lg">
{tournament.name}
</h3>
<TournamentStatusBadge status={tournament.status} />
</div>
{dateRange ? (
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
<Calendar aria-hidden="true" className="size-4 shrink-0" />
<span className="tabular-nums">{dateRange}</span>
</div>
) : (
<span className="text-sm text-muted-foreground">Ingen datoer satt</span>
)}
</div>
<ChevronRight
aria-hidden="true"
className="size-5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5 group-hover:text-foreground"
/>
</Link>
)
}