Før enda en endring av kort og scorekort (Codex)
This commit is contained in:
parent
1e2f79e36b
commit
73916f4056
3 changed files with 208 additions and 19 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue