teecup/frontend/components/target-distance.tsx

177 lines
5.1 KiB
TypeScript
Raw Normal View History

"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>
)
}