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?
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
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>
|
||
)
|
||
}
|