Før enda en endring av kort og scorekort (Codex)

This commit is contained in:
Erol 2026-04-12 11:19:35 +02:00
parent 1e2f79e36b
commit 73916f4056
3 changed files with 208 additions and 19 deletions

View file

@ -22,6 +22,10 @@ type Facility = {
banetype?: string | null;
image_url?: string | null;
phone?: string | null;
website_url?: string | null;
golfbox_booking_url?: string | null;
golfbox_tournament_url?: string | null;
weather_url?: string | null;
lat?: number | null;
lng?: number | null;
golfamore?: boolean | null;
@ -203,6 +207,11 @@ const formatUpdatedDate = (value: string | null | undefined) => {
const getStatusLabel = (status: string) => STATUS_MAP[status] || "Ukjent";
const buildMapUrl = (lat?: number | null, lng?: number | null) => {
if (typeof lat !== "number" || typeof lng !== "number") return null;
return `https://www.google.com/maps/search/?api=1&query=${lat},${lng}`;
};
const getAreaLabel = (value: string, countyOptions: Array<{ slug: string; label: string }>) => {
if (!value) return "Hele Norge";
const builtIn = HIERARCHICAL_AREA_OPTIONS.find((option) => option.value === value);
@ -246,6 +255,12 @@ const noteClampStyle: CSSProperties = {
overflow: "hidden",
};
const actionIconClassName =
"flex h-12 w-12 items-center justify-center rounded-[1.1rem] bg-white text-[#112015] shadow-sm transition hover:bg-[#FF5722] hover:text-white";
const cardActionShellClassName =
"flex flex-wrap gap-2 rounded-[1.35rem] bg-[#25312A] p-2 text-[#112015]";
export default function FacilitySearch({
initialFacilities,
variant = "catalog",
@ -553,11 +568,11 @@ export default function FacilitySearch({
) : (
<div className="mt-6 grid grid-cols-1 gap-5 md:grid-cols-2 2xl:grid-cols-3">
{processedFacilities.map((facility) => (
<Link
href={`/golfbaner/${facility.slug}`}
<article
key={facility.id}
className="surface-card group overflow-hidden rounded-[2rem] transition hover:-translate-y-1 hover:shadow-xl"
>
<Link href={`/golfbaner/${facility.slug}`} className="block">
<div className="relative h-56 overflow-hidden bg-[#D9DED5] sm:h-60">
<Image
src={facility.image_url || "/Toppbilde-standard.jpg"}
@ -596,6 +611,8 @@ export default function FacilitySearch({
</div>
</div>
</Link>
<div className="space-y-5 p-5">
<div className="flex flex-wrap gap-2">
<span className="rounded-full bg-[#EEF5E4] px-3 py-1.5 text-[10px] font-extrabold uppercase tracking-[0.15em] text-[#112015]">
@ -613,11 +630,8 @@ export default function FacilitySearch({
{facility.footnote && (
<div className="rounded-[1.35rem] border border-[#FFD9CC] bg-[#FFF7F3] px-4 py-3">
<p className="text-[10px] font-extrabold uppercase tracking-[0.18em] text-[#C94F2D]">
Viktig beskjed
<span className="ml-2 text-[#9A6A5E]">
{formatUpdatedDate(facility.footnote_updated_at || facility.status_updated_at)}
</span>
<p className="text-[10px] font-extrabold uppercase tracking-[0.18em] text-[#9A6A5E]">
{formatUpdatedDate(facility.footnote_updated_at || facility.status_updated_at)}
</p>
<p className="mt-2 text-[15px] italic leading-7 text-[#7B3D2C]" style={noteClampStyle}>
{facility.footnote}
@ -642,12 +656,42 @@ export default function FacilitySearch({
</div>
</div>
<div className="flex items-center justify-between text-sm font-bold text-[#112015]">
<span className="text-[#617063]">{facility.phone ? facility.phone : "Se detaljer"}</span>
<span className="text-[#FF5722] transition group-hover:text-[#C94F2D]">Se anlegg </span>
<div className={cardActionShellClassName}>
{facility.website_url && (
<a href={facility.website_url} target="_blank" rel="noreferrer" className={actionIconClassName} aria-label={`Besøk nettsiden til ${facility.name}`}>
<ActionIcon type="web" />
</a>
)}
{facility.golfbox_booking_url && (
<a href={facility.golfbox_booking_url} target="_blank" rel="noreferrer" className={actionIconClassName} aria-label={`Book starttid hos ${facility.name}`}>
<ActionIcon type="booking" />
</a>
)}
{facility.golfbox_tournament_url && (
<a href={facility.golfbox_tournament_url} target="_blank" rel="noreferrer" className={actionIconClassName} aria-label={`Se turneringer hos ${facility.name}`}>
<ActionIcon type="trophy" />
</a>
)}
{buildMapUrl(facility.lat, facility.lng) && (
<a href={buildMapUrl(facility.lat, facility.lng) || "#"} target="_blank" rel="noreferrer" className={actionIconClassName} aria-label={`Åpne kart for ${facility.name}`}>
<ActionIcon type="pin" />
</a>
)}
{facility.weather_url && (
<a href={facility.weather_url} target="_blank" rel="noreferrer" className={actionIconClassName} aria-label={`Se været for ${facility.name}`}>
<ActionIcon type="weather" />
</a>
)}
</div>
<div className="flex items-center justify-between gap-3 text-sm font-bold text-[#112015]">
<span className="truncate text-[#617063]">{facility.phone ? facility.phone : facility.city || "Se detaljer"}</span>
<Link href={`/golfbaner/${facility.slug}`} className="shrink-0 text-[#FF5722] transition hover:text-[#C94F2D]">
Se anlegg
</Link>
</div>
</div>
</Link>
</article>
))}
</div>
)}
@ -655,6 +699,69 @@ export default function FacilitySearch({
);
}
function ActionIcon({ type }: { type: "web" | "booking" | "trophy" | "pin" | "weather" }) {
return (
<svg
className="h-5 w-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{type === "web" && (
<>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</>
)}
{type === "booking" && (
<>
<path d="M3 10h18" />
<path d="M7 15h.01" />
<path d="M11 15h.01" />
<path d="M15 15h.01" />
<path d="M7 19h.01" />
<path d="M11 19h.01" />
<path d="M15 19h.01" />
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2z" />
<path d="M16 3v4" />
<path d="M8 3v4" />
</>
)}
{type === "trophy" && (
<>
<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6" />
<path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18" />
<path d="M4 22h16" />
<path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22" />
<path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22" />
<path d="M18 2H6v7a6 6 0 0 0 12 0V2z" />
</>
)}
{type === "pin" && (
<>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" />
</>
)}
{type === "weather" && (
<>
<path d="M12 2v2" />
<path d="m4.93 4.93 1.41 1.41" />
<path d="M20 12h2" />
<path d="m19.07 4.93-1.41 1.41" />
<path d="M15.947 12.65a4 4 0 0 0-5.925-4.128" />
<path d="M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z" />
</>
)}
</svg>
);
}
function FieldSelect({
label,
value,

View file

@ -69,12 +69,65 @@ export default function CourseDisplay({ course }: { course: any }) {
const sumPar = (holes: any[]) => holes.reduce((acc, h) => acc + (h.par || 0), 0);
const sumLen = (holes: any[], key: string) => holes.reduce((acc, h) => acc + (h.lengths?.[key] || 0), 0);
const selectedColumn = activeColumns[selectedTeeIndex] || activeColumns[0] || null;
const selectedTeeLabel = selectedColumn?.label || activeTee?.navn_utslag || activeTee?.navn_utslag_damer || 'Valgt utslag';
// Formater utløpsdato
const slopeExpiry = course.slope_valid_until
? new Date(course.slope_valid_until).toLocaleDateString('nb-NO', { year: 'numeric', month: 'short', day: 'numeric' })
: 'Ukjent';
const renderMobileHoleCard = (hole: any) => {
const extra = getExtraStrokes(hole.hcp_index);
const selectedLength = selectedColumn ? hole.lengths?.[selectedColumn.key] : null;
return (
<div key={hole.id} className="rounded-[1.6rem] border border-gray-100 bg-[#fbfcf8] p-4 shadow-sm">
<div className="mb-3 flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-black uppercase tracking-[0.2em] text-[#7ca982]">Hull {hole.hole_number}</p>
<p className="mt-1 text-2xl font-black tracking-tight text-[#11280f]">Par {hole.par}</p>
</div>
<div className="rounded-2xl bg-white px-3 py-2 text-right shadow-sm">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-gray-400">HCP</p>
<p className="mt-1 text-lg font-black text-[#11280f]">{hole.hcp_index || '--'}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="rounded-2xl bg-white p-3 shadow-sm">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-[#7ca982]">Mottatt</p>
<p className="mt-1 text-lg font-black text-[#11280f]">{extra > 0 ? `+${extra}` : '-'}</p>
</div>
<div className="rounded-2xl bg-[#eef5e4] p-3 shadow-sm">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-[#7ca982]">Din Par</p>
<p className="mt-1 text-lg font-black text-[#11280f]">{hole.par + extra}</p>
</div>
<div className="col-span-2 rounded-2xl bg-white p-3 shadow-sm">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-gray-400">{selectedTeeLabel}</p>
<p className="mt-1 text-lg font-black text-[#11280f]">{selectedLength || '--'} meter</p>
</div>
</div>
</div>
);
};
const renderMobileSummaryCard = (label: string, holes: any[]) => (
<div className="rounded-[1.6rem] bg-[#11280f] p-4 text-white shadow-sm">
<p className="text-[11px] font-black uppercase tracking-[0.2em] text-white/60">{label}</p>
<div className="mt-3 grid grid-cols-2 gap-3">
<div className="rounded-2xl bg-white/10 p-3">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-white/60">Par</p>
<p className="mt-1 text-xl font-black">{sumPar(holes)}</p>
</div>
<div className="rounded-2xl bg-white/10 p-3">
<p className="text-[10px] font-black uppercase tracking-[0.16em] text-white/60">{selectedTeeLabel}</p>
<p className="mt-1 text-xl font-black">{selectedColumn ? sumLen(holes, selectedColumn.key) : '--'}</p>
</div>
</div>
</div>
);
return (
<div className="bg-white rounded-[3rem] shadow-sm border border-gray-200 overflow-hidden mb-12">
@ -111,8 +164,32 @@ export default function CourseDisplay({ course }: { course: any }) {
</div>
</div>
{/* MOBIL SCOREKORT */}
<div className="space-y-4 p-4 md:hidden">
<div className="rounded-[1.8rem] bg-[#f5f8ef] p-4 text-sm text-[#617063] shadow-sm">
<p className="text-[11px] font-black uppercase tracking-[0.2em] text-[#7ca982]">Mobilscorekort</p>
<p className="mt-2 leading-6">
Viser valgte utslag for {gender}. Bytt utslag i kalkulatoren over for å oppdatere lengdene.
</p>
</div>
<div className="space-y-3">
{holesOut.map(renderMobileHoleCard)}
{renderMobileSummaryCard("Ut", holesOut)}
</div>
{hasInHoles && (
<div className="space-y-3">
{holesIn.map(renderMobileHoleCard)}
{renderMobileSummaryCard("Inn", holesIn)}
</div>
)}
{renderMobileSummaryCard("Totalt", allHoles)}
</div>
{/* SCOREKORT TABELL */}
<div className="overflow-x-auto">
<div className="hidden overflow-x-auto md:block">
<table className="w-full text-center border-collapse table-fixed min-w-[850px]">
<thead>
<tr className="bg-white text-[10px] text-gray-400 font-black uppercase">
@ -203,4 +280,4 @@ export default function CourseDisplay({ course }: { course: any }) {
</div>
</div>
);
}
}

View file

@ -156,7 +156,7 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
{facility.website_url && <a href={facility.website_url} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.web} /></a>}
{facility.golfbox_booking_url && <a href={facility.golfbox_booking_url} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.booking} /></a>}
{facility.golfbox_tournament_url && <a href={facility.golfbox_tournament_url} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.trophy} /></a>}
<a href={`https://www.google.com/maps/search/?api=1&query=$?q=${facility.lat},${facility.lng}`} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.pin} /></a>
<a href={`https://www.google.com/maps/search/?api=1&query=${facility.lat},${facility.lng}`} target="_blank" rel="noreferrer" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.pin} /></a>
{facility.weather_url && <a href={facility.weather_url} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-[#ff5722] hover:text-white transition-all"><Icon children={ICONS.weather} /></a>}
</div>
@ -190,8 +190,13 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
{/* HOVEDINNHOLD (78%) */}
<div className="lg:w-[78%] bg-white p-10 md:p-16 md:rounded-[3rem] shadow-sm border-b md:border-none">
{facility.footnote && (
<div className="mb-8 pb-8 border-b border-gray-50 italic text-[#ff5722] text-lg font-serif">
{facility.footnote}
<div className="mb-8 pb-8 border-b border-gray-50">
<p className="mb-3 text-[11px] font-black uppercase tracking-[0.24em] text-[#C98B76]">
{formatDate(facility.footnote_updated_at || facility.status_updated_at)}
</p>
<div className="italic text-[#ff5722] text-lg font-serif">
{facility.footnote}
</div>
</div>
)}
<div className="leading-relaxed text-lg md:text-xl text-gray-600" dangerouslySetInnerHTML={{ __html: facility.description || 'Ingen beskrivelse tilgjengelig.' }} />
@ -209,7 +214,7 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
<Icon children={ICONS.mail} /> <span className="truncate">{facility.email || 'Ikke oppgitt'}</span>
</a>
<div className="pt-2 border-t border-gray-50 mt-4">
<a href={`https://www.google.com/maps/search/?api=1&query=$?q=${facility.lat},${facility.lng}`} target="_blank" className={sidebarLinkClass + " pt-4 leading-tight items-start"}>
<a href={`https://www.google.com/maps/search/?api=1&query=${facility.lat},${facility.lng}`} target="_blank" rel="noreferrer" className={sidebarLinkClass + " pt-4 leading-tight items-start"}>
<Icon children={ICONS.pin} /> <span className="text-gray-400 group-hover:text-[#ff5722] transition-colors">{facility.address}<br/>{facility.city}</span>
</a>
</div>
@ -344,7 +349,7 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
<section id="map" className="space-y-6">
<h2 className="text-3xl md:text-4xl font-black uppercase tracking-tighter flex items-center gap-5 ml-6 md:ml-0">Kart <span className="h-1 flex-grow bg-gray-100 rounded-full" /></h2>
<div className="w-full md:rounded-[3rem] overflow-hidden shadow-xl h-[450px] md:h-[650px] border-y-4 md:border-[12px] border-white bg-gray-100">
<iframe width="100%" height="100%" style={{ border: 0 }} src={`https://www.google.com/maps/search/?api=1&query=$?q=${facility.lat},${facility.lng}&t=k&z=15&ie=UTF8&iwloc=&output=embed`} allowFullScreen />
<iframe width="100%" height="100%" style={{ border: 0 }} src={`https://www.google.com/maps/search/?api=1&query=${facility.lat},${facility.lng}&t=k&z=15&ie=UTF8&iwloc=&output=embed`} allowFullScreen />
</div>
</section>
@ -540,4 +545,4 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
)}
</main>
);
}
}