Del C (ADR-068, migrasjon 072, siste del av den tredelte utvidelsen som startet med Flaggturnering GPS/kart, se ADR-066/067): nytt format eclectic_gross/eclectic_net/eclectic_stableford -- beste resultat per hull på tvers av en turnerings egne runder, krever samme bane (avvist tydelig ved rundeopprettelse ellers). Regnes ut ved lesing, ingen nye tabeller. Bevisst avvik fra opprinnelig plan: integrert som en ny gren i eksisterende individual-leaderboard-endepunkt fremfor et nytt eget endepunkt -- se ADR-068 for begrunnelsen. Tre ikke-relaterte, brukerrapporterte UI-rettelser tatt med i samme runde: avstandsindikatoren brukte "grønn"/"Midt" i stedet for riktige golf-uttrykk "green"/"senter", og "Oppdateres live"-badgen fjernet. "Antall hull"-bryteren i Ny runde-veiviseren fikk samme grønne aksent-valgt-stil som resten av samme skjerm (delt Segmented-primitiv). Se ARCHITECTURE_DECISIONS.md (ADR-068) og CHANGELOG.md (punkt 84) for full begrunnelse og verifiseringslogg. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
176 lines
5.1 KiB
TypeScript
176 lines
5.1 KiB
TypeScript
"use client"
|
|
|
|
import { TriangleAlert, Satellite } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
/**
|
|
* TargetDistance — ren visningskomponent for gjenstående avstand til green.
|
|
*
|
|
* "Dum" med vilje: den mottar ferdigregnede tall som props. Ingen GPS,
|
|
* ingen fetch, ingen kart. Følger TeeCups clubhouse-palett (grønn/oransje
|
|
* aksenter, samme korthjørner/skygge-stil som resten av scorekortet).
|
|
*/
|
|
export type TargetDistanceProps = {
|
|
distances: {
|
|
front: number // meter
|
|
middle: number
|
|
back: number
|
|
} | null // null = ingen koordinatdata for dette hullet (normal, forventet tilstand)
|
|
nearestHazard?: {
|
|
label: string // f.eks. "Bunker (front)"
|
|
distanceMeters: number
|
|
} | null
|
|
/** "compact" = badge ved siden av GIR-merket, "full" = eget panel. */
|
|
size?: "compact" | "full"
|
|
className?: string
|
|
}
|
|
|
|
/** Hindring-linje: kun synlig når det finnes en relevant hindring. Oransje varsel, ikke rødt. */
|
|
function HazardLine({
|
|
hazard,
|
|
compact,
|
|
}: {
|
|
hazard: { label: string; distanceMeters: number }
|
|
compact?: boolean
|
|
}) {
|
|
return (
|
|
<p
|
|
className={cn(
|
|
"inline-flex items-center gap-1.5 font-medium text-cup-strong",
|
|
compact ? "text-[11px]" : "text-sm",
|
|
)}
|
|
>
|
|
<TriangleAlert aria-hidden="true" className={compact ? "size-3.5 shrink-0" : "size-4 shrink-0"} />
|
|
<span className="text-pretty">
|
|
{hazard.label}: <span className="tabular-nums font-bold">{hazard.distanceMeters}</span> m
|
|
</span>
|
|
</p>
|
|
)
|
|
}
|
|
|
|
/** En avstandskolonne (Front/Midt/Bak). Alltid tekst-label + tall + enhet. */
|
|
function DistanceColumn({
|
|
label,
|
|
value,
|
|
emphasized,
|
|
compact,
|
|
}: {
|
|
label: string
|
|
value: number
|
|
emphasized?: boolean
|
|
compact?: boolean
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-end gap-0.5">
|
|
<span
|
|
className={cn(
|
|
"font-semibold uppercase tracking-wide text-clubhouse-muted",
|
|
compact ? "text-[10px]" : "text-xs",
|
|
)}
|
|
>
|
|
{label}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
"flex items-baseline gap-0.5 leading-none tabular-nums",
|
|
emphasized ? "text-tee-strong" : "text-clubhouse-ink",
|
|
// Stort nok til å leses på avstand, i sollys — som en klokke-visning.
|
|
compact
|
|
? emphasized
|
|
? "text-3xl font-black"
|
|
: "text-xl font-bold"
|
|
: emphasized
|
|
? "text-6xl font-black"
|
|
: "text-4xl font-bold",
|
|
)}
|
|
>
|
|
{value}
|
|
<span
|
|
className={cn(
|
|
"font-semibold text-clubhouse-muted",
|
|
compact ? "text-[10px]" : "text-sm",
|
|
)}
|
|
>
|
|
m
|
|
</span>
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Nøytral, lavmælt placeholder når banen ikke har koordinatdata. Aldri en feilmelding. */
|
|
function NoData({ compact, className }: { compact?: boolean; className?: string }) {
|
|
return (
|
|
<div
|
|
role="note"
|
|
className={cn(
|
|
"inline-flex items-center gap-2 rounded-xl border border-clubhouse-border bg-clubhouse-field text-clubhouse-muted",
|
|
compact ? "px-2.5 py-1.5 text-[11px]" : "px-4 py-3 text-sm",
|
|
className,
|
|
)}
|
|
>
|
|
<Satellite aria-hidden="true" className={compact ? "size-3.5" : "size-4"} />
|
|
<span>Avstand ikke tilgjengelig</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function TargetDistance({
|
|
distances,
|
|
nearestHazard,
|
|
size = "full",
|
|
className,
|
|
}: TargetDistanceProps) {
|
|
const compact = size === "compact"
|
|
|
|
// Punkt 4: ingen data er en normal, forventet tilstand — vis en nøytral placeholder.
|
|
if (!distances) {
|
|
return <NoData compact={compact} className={className} />
|
|
}
|
|
|
|
if (compact) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"inline-flex flex-col gap-1 rounded-xl border border-clubhouse-border bg-clubhouse-card px-3 py-2 shadow-sm",
|
|
className,
|
|
)}
|
|
>
|
|
<div className="flex items-end gap-3">
|
|
<DistanceColumn label="Front" value={distances.front} compact />
|
|
<DistanceColumn label="Senter" value={distances.middle} emphasized compact />
|
|
<DistanceColumn label="Bak" value={distances.back} compact />
|
|
</div>
|
|
{nearestHazard ? (
|
|
<div className="flex items-center justify-between gap-2">
|
|
<HazardLine hazard={nearestHazard} compact />
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<section
|
|
aria-label="Avstand til green"
|
|
className={cn(
|
|
"flex w-full flex-col gap-3 rounded-2xl border border-clubhouse-border bg-clubhouse-card p-5 shadow-sm",
|
|
className,
|
|
)}
|
|
>
|
|
<h3 className="text-sm font-semibold uppercase tracking-wide text-clubhouse-muted">Til green</h3>
|
|
|
|
<div className="flex items-end justify-between gap-2">
|
|
<DistanceColumn label="Front" value={distances.front} />
|
|
<DistanceColumn label="Senter" value={distances.middle} emphasized />
|
|
<DistanceColumn label="Bak" value={distances.back} />
|
|
</div>
|
|
|
|
{nearestHazard ? (
|
|
<div className="border-t border-clubhouse-border pt-3">
|
|
<HazardLine hazard={nearestHazard} />
|
|
</div>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|