2026-02-26 09:20:51 +01:00
|
|
|
|
"use client";
|
2026-02-27 09:49:42 +01:00
|
|
|
|
/**
|
2026-03-13 08:28:17 +01:00
|
|
|
|
* TEE OFF DETAIL VIEW - COMPLETE v3.4 (FINAL LAYOUT FIX)
|
2026-02-27 09:49:42 +01:00
|
|
|
|
* ---------------------------------------------------------------------------
|
2026-02-27 14:53:30 +01:00
|
|
|
|
* FIX: Gjenopprettet "Turneringer" i den flytende knapperaden over bildet.
|
|
|
|
|
|
* FIX: Byttet plass på tekst og sidebar (Tekst øverst på mobil).
|
|
|
|
|
|
* FIX: Økt padding (pb-32) i Hero-teksten på mobil for å unngå krasj med knapper.
|
|
|
|
|
|
* FIX: Alle 4 kontaktpunkter i sidebar er klikkbare (tel:0047 fix inkludert).
|
2026-03-08 10:26:56 +01:00
|
|
|
|
* NEW: Sosiale Medier, Footnote og Samarbeidende klubber integrert.
|
2026-03-13 08:28:17 +01:00
|
|
|
|
* NEW: Priser (Medlemskap + Greenfee) i 2-kolonne Grid (xl:grid-cols-2).
|
|
|
|
|
|
* NEW: Veien til Golf (VTG) i full bredde under prisene, med robust array-parsing.
|
2026-02-27 14:53:30 +01:00
|
|
|
|
* REGEL: Beholder monokrome ikoner, 22/78 layout og robust JSON-parsing.
|
2026-02-27 09:49:42 +01:00
|
|
|
|
* ---------------------------------------------------------------------------
|
|
|
|
|
|
*/
|
2026-02-26 09:20:51 +01:00
|
|
|
|
|
2026-02-26 16:32:34 +01:00
|
|
|
|
import { useState, useEffect } from 'react';
|
2026-04-17 09:25:32 +02:00
|
|
|
|
import dynamic from "next/dynamic";
|
2026-02-26 09:20:51 +01:00
|
|
|
|
import { STATUS_MAP, FALLBACK_IMAGE } from "@/config/constants";
|
2026-04-15 09:19:16 +02:00
|
|
|
|
import { STATUS_ICON_PATHS, buildMapUrl, getPrimaryStatus, parseJson as parseSharedJson, slugify } from "@/app/facilityData";
|
2026-02-26 09:20:51 +01:00
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
|
import CourseDisplay from './CourseDisplay';
|
2026-04-19 13:35:48 +02:00
|
|
|
|
import FacilityFeedbackForm from './FacilityFeedbackForm';
|
2026-02-26 09:20:51 +01:00
|
|
|
|
|
2026-04-17 09:25:32 +02:00
|
|
|
|
const FacilityDetailLeafletMap = dynamic(() => import("./FacilityDetailLeafletMap"), {
|
|
|
|
|
|
ssr: false,
|
|
|
|
|
|
loading: () => (
|
|
|
|
|
|
<div className="flex h-[450px] w-full items-center justify-center bg-[#f1f7ed] text-sm font-bold text-[#617063] md:h-[650px]">
|
|
|
|
|
|
Laster kart…
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
|
|
|
|
|
});
|
2026-04-14 16:31:28 +02:00
|
|
|
|
|
2026-02-27 10:15:52 +01:00
|
|
|
|
const formatPhoneForUrl = (phone: string) => {
|
|
|
|
|
|
if (!phone) return "";
|
2026-04-18 09:00:16 +02:00
|
|
|
|
const normalized = phone.replace(/[^\d+]/g, "");
|
|
|
|
|
|
return normalized.startsWith("00") ? `+${normalized.slice(2)}` : normalized;
|
2026-02-27 10:15:52 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-19 13:35:48 +02:00
|
|
|
|
const renderValue = (val: unknown, fallback = "Nei") => {
|
|
|
|
|
|
const raw = String(val || "").trim();
|
|
|
|
|
|
if (!raw) return fallback;
|
|
|
|
|
|
|
|
|
|
|
|
const hasInlineHtml = /<\s*a\b/i.test(raw) || /<\s*(strong|b|em|i|br)\b/i.test(raw);
|
|
|
|
|
|
const html = hasInlineHtml
|
|
|
|
|
|
? sanitizeRichText(raw)
|
|
|
|
|
|
: raw.replace(
|
|
|
|
|
|
/((?:https?:\/\/|mailto:|tel:|www\.)[^\s<]+)/gi,
|
|
|
|
|
|
(match: string) => {
|
|
|
|
|
|
const href = sanitizeHref(match.startsWith("www.") ? `https://${match}` : match);
|
|
|
|
|
|
const escapedHref = escapeHtml(href);
|
|
|
|
|
|
const escapedLabel = escapeHtml(match);
|
|
|
|
|
|
if (isInternalTeeoffHref(href)) {
|
|
|
|
|
|
return `<a href="${escapedHref}">${escapedLabel}</a>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return `<a href="${escapedHref}" target="_blank" rel="noreferrer noopener">${escapedLabel}</a>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
const hasLink = html.includes("<a ");
|
2026-02-27 14:53:30 +01:00
|
|
|
|
return (
|
|
|
|
|
|
<span
|
2026-03-06 13:39:11 +01:00
|
|
|
|
className={hasLink ? "text-[#ff5722] font-bold hover:underline" : "text-[#11280f]"}
|
2026-04-19 13:35:48 +02:00
|
|
|
|
dangerouslySetInnerHTML={{ __html: html }}
|
2026-02-27 14:53:30 +01:00
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-12 15:19:39 +02:00
|
|
|
|
const escapeHtml = (value: string) =>
|
|
|
|
|
|
value
|
|
|
|
|
|
.replace(/&/g, "&")
|
|
|
|
|
|
.replace(/</g, "<")
|
|
|
|
|
|
.replace(/>/g, ">")
|
|
|
|
|
|
.replace(/"/g, """)
|
|
|
|
|
|
.replace(/'/g, "'");
|
|
|
|
|
|
|
|
|
|
|
|
const sanitizeHref = (value: string) => {
|
|
|
|
|
|
const href = value.trim();
|
|
|
|
|
|
return /^(https?:|mailto:|tel:|\/|#)/i.test(href) ? href : "#";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-17 09:25:32 +02:00
|
|
|
|
const getSiteOrigin = () => {
|
|
|
|
|
|
if (typeof window !== "undefined" && window.location?.origin) {
|
|
|
|
|
|
return window.location.origin;
|
|
|
|
|
|
}
|
|
|
|
|
|
return process.env.NEXT_PUBLIC_SITE_URL || "https://teeoff.no";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-13 13:18:25 +02:00
|
|
|
|
const isInternalTeeoffHref = (href: string) => {
|
|
|
|
|
|
if (!href || href.startsWith("/") || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("tel:")) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-04-17 09:25:32 +02:00
|
|
|
|
const url = new URL(href, getSiteOrigin());
|
2026-04-13 13:18:25 +02:00
|
|
|
|
return url.hostname === "teeoff.no" || url.hostname.endsWith(".teeoff.no");
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-12 15:19:39 +02:00
|
|
|
|
const sanitizeRichText = (value: string | null | undefined) => {
|
|
|
|
|
|
const source = String(value || "").replace(/\r\n?/g, "\n");
|
|
|
|
|
|
if (!source.trim()) return "";
|
|
|
|
|
|
|
|
|
|
|
|
const placeholders = new Map<string, string>();
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
const keep = (html: string) => {
|
|
|
|
|
|
const key = `__HTML_TOKEN_${index++}__`;
|
|
|
|
|
|
placeholders.set(key, html);
|
|
|
|
|
|
return key;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let safe = source
|
|
|
|
|
|
.replace(/<\s*br\s*\/?\s*>/gi, () => keep("<br />"))
|
|
|
|
|
|
.replace(/<\s*(strong|b)\s*>/gi, () => keep("<strong>"))
|
|
|
|
|
|
.replace(/<\s*\/\s*(strong|b)\s*>/gi, () => keep("</strong>"))
|
|
|
|
|
|
.replace(/<\s*(em|i)\s*>/gi, () => keep("<em>"))
|
|
|
|
|
|
.replace(/<\s*\/\s*(em|i)\s*>/gi, () => keep("</em>"))
|
|
|
|
|
|
.replace(/<\s*p\s*>/gi, () => keep("<p>"))
|
|
|
|
|
|
.replace(/<\s*\/\s*p\s*>/gi, () => keep("</p>"))
|
|
|
|
|
|
.replace(/<\s*(ul|ol)\s*>/gi, (_, tag: string) => keep(`<${tag.toLowerCase()}>`))
|
|
|
|
|
|
.replace(/<\s*\/\s*(ul|ol)\s*>/gi, (_, tag: string) => keep(`</${tag.toLowerCase()}>`))
|
|
|
|
|
|
.replace(/<\s*li\s*>/gi, () => keep("<li>"))
|
|
|
|
|
|
.replace(/<\s*\/\s*li\s*>/gi, () => keep("</li>"))
|
|
|
|
|
|
.replace(/<\s*a\b([^>]*)>/gi, (_, attrs: string) => {
|
|
|
|
|
|
const hrefMatch = attrs.match(/href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
|
|
|
|
|
|
const href = sanitizeHref(hrefMatch?.[1] || hrefMatch?.[2] || hrefMatch?.[3] || "#");
|
2026-04-13 13:18:25 +02:00
|
|
|
|
if (isInternalTeeoffHref(href)) {
|
|
|
|
|
|
return keep(`<a href="${escapeHtml(href)}">`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return keep(`<a href="${escapeHtml(href)}" target="_blank" rel="noreferrer noopener">`);
|
2026-04-12 15:19:39 +02:00
|
|
|
|
})
|
|
|
|
|
|
.replace(/<\s*\/\s*a\s*>/gi, () => keep("</a>"));
|
|
|
|
|
|
|
|
|
|
|
|
safe = escapeHtml(safe).replace(/\n/g, "<br />");
|
|
|
|
|
|
|
|
|
|
|
|
for (const [token, html] of placeholders) {
|
|
|
|
|
|
safe = safe.replaceAll(token, html);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return safe;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-26 16:32:34 +01:00
|
|
|
|
const Icon = ({ children, className = "w-5 h-5" }: { children: React.ReactNode, className?: string }) => (
|
2026-02-27 10:15:52 +01:00
|
|
|
|
<svg
|
2026-03-08 10:26:56 +01:00
|
|
|
|
className={`${className} flex-shrink-0 text-current`}
|
2026-02-27 10:27:25 +01:00
|
|
|
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
|
|
|
|
style={{ width: '20px', height: '20px' }}
|
2026-02-27 10:15:52 +01:00
|
|
|
|
>
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</svg>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const ICONS = {
|
2026-02-27 10:15:52 +01:00
|
|
|
|
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"/></>,
|
2026-02-26 16:32:34 +01:00
|
|
|
|
phone: <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />,
|
|
|
|
|
|
mail: <><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /><polyline points="22,6 12,13 2,6" /></>,
|
2026-02-27 10:15:52 +01:00
|
|
|
|
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" /></>,
|
2026-02-27 10:27:25 +01:00
|
|
|
|
booking: <><path d="M3 10h18M7 15h.01M11 15h.01M15 15h.01M7 19h.01M11 19h.01M15 19h.01M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2zM16 3v4M8 3v4"/></>,
|
|
|
|
|
|
trophy: <><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2z"/></>,
|
|
|
|
|
|
guide: <><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 4.5A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5V4.5z"/></>,
|
|
|
|
|
|
camera: <><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></>,
|
2026-03-06 13:39:11 +01:00
|
|
|
|
webcam: <><path d="M23 7l-7 5 7 5V7z"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></>,
|
2026-02-27 10:27:25 +01:00
|
|
|
|
chart: <><path d="M18 20V10M12 20V4M6 20v-6"/></>,
|
2026-02-27 10:15:52 +01:00
|
|
|
|
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"/></>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
|
const SOCIAL_ICONS: Record<string, React.ReactNode> = {
|
|
|
|
|
|
facebook: <path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />,
|
|
|
|
|
|
instagram: <><rect x="2" y="2" width="20" height="20" rx="5" ry="5" /><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /><line x1="17.5" y1="6.5" x2="17.51" y2="6.5" /></>,
|
|
|
|
|
|
twitter: <path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" />,
|
|
|
|
|
|
x: <path d="M4 4l16 16M4 20L20 4" />,
|
|
|
|
|
|
linkedin: <><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" /><rect x="2" y="9" width="4" height="12" /><circle cx="4" cy="4" r="2" /></>,
|
|
|
|
|
|
youtube: <><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33 2.78 2.78 0 0 0 1.94 2c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.33 29 29 0 0 0-.46-5.33z" /><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02" /></>,
|
|
|
|
|
|
tiktok: <path d="M9 12a4 4 0 1 0 4 4V2a5 5 0 0 0 5 5h-2a3 3 0 0 1-3-3V16a2 2 0 1 1-2-2v-2z" />,
|
|
|
|
|
|
snapchat: <path d="M12 2C8.5 2 6 5 6 8.5c0 1.5.5 3 1.5 4-1 .5-2.5 1-3.5 1-.5 0-1 .5-1 1s.5 1 1.5 1h15c1 0 1.5-.5 1.5-1s-.5-1-1-1c-1 0-2.5-.5-3.5-1 1-1 1.5-2.5 1.5-4C18 5 15.5 2 12 2zm0 15c-3 0-5-1-5-1s.5 1.5 1.5 2h7C16.5 17.5 17 16 17 16s-2 1-5 1z" />
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-26 09:20:51 +01:00
|
|
|
|
export default function FacilityDetailView({ facility }: { facility: any }) {
|
2026-02-26 16:32:34 +01:00
|
|
|
|
const [showBackToTop, setShowBackToTop] = useState(false);
|
|
|
|
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
|
|
|
|
|
2026-02-27 09:49:42 +01:00
|
|
|
|
const parseJson = (val: any, fallback: any) => {
|
2026-04-14 16:31:28 +02:00
|
|
|
|
return parseSharedJson(val, fallback);
|
2026-02-27 09:49:42 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const rawCourses = parseJson(facility.courses, []);
|
|
|
|
|
|
const activeCourses = Array.isArray(rawCourses) ? rawCourses.filter((c: any) => c.holes && (typeof c.holes === 'string' || c.holes.length > 0)) : [];
|
|
|
|
|
|
const amenities = parseJson(facility.amenities, {});
|
2026-02-27 14:53:30 +01:00
|
|
|
|
const galleryRaw = parseJson(facility.gallery, []);
|
|
|
|
|
|
const gallery = galleryRaw.length > 0 ? galleryRaw : [facility.image_url || FALLBACK_IMAGE];
|
2026-02-27 09:49:42 +01:00
|
|
|
|
const shotzoom = parseJson(facility.shotzoom, []);
|
2026-03-06 13:39:11 +01:00
|
|
|
|
|
2026-03-13 08:28:17 +01:00
|
|
|
|
// Pris og kurs-arrays
|
|
|
|
|
|
const greenfeeRaw = parseJson(facility.greenfee, []);
|
2026-04-15 08:15:53 +02:00
|
|
|
|
const golfpakkerRaw = parseJson(facility.golfpakker, []);
|
2026-03-13 08:28:17 +01:00
|
|
|
|
const vtgDatoer = parseJson(facility.vtg_datoer, []);
|
|
|
|
|
|
|
2026-03-06 13:39:11 +01:00
|
|
|
|
const golfamoreData = parseJson(facility.golfamore_data, {});
|
|
|
|
|
|
const nsgData = parseJson(facility.nsg_data, {});
|
2026-03-08 10:26:56 +01:00
|
|
|
|
const socialLinksRaw = parseJson(facility.social_links, []);
|
|
|
|
|
|
const socialLinks = Array.isArray(socialLinksRaw) ? socialLinksRaw : [];
|
|
|
|
|
|
|
|
|
|
|
|
const coopClubsRaw = parseJson(facility.cooperating_clubs, []);
|
|
|
|
|
|
const cooperatingClubs = Array.isArray(coopClubsRaw) ? coopClubsRaw : [];
|
2026-03-06 13:39:11 +01:00
|
|
|
|
|
2026-04-19 13:35:48 +02:00
|
|
|
|
const hasGolfamore = facility.golfamore === true || Boolean(facility.golfamore_url) || Object.keys(golfamoreData).length > 0;
|
2026-03-06 13:39:11 +01:00
|
|
|
|
const hasNSG = facility.nsg_url || (nsgData && Object.keys(nsgData).length > 0);
|
2026-04-14 16:31:28 +02:00
|
|
|
|
const hasVtg = Boolean(
|
|
|
|
|
|
facility.vtg_pris ||
|
|
|
|
|
|
facility.vtg_beskrivelse ||
|
|
|
|
|
|
facility.vtg_lenke ||
|
|
|
|
|
|
(Array.isArray(vtgDatoer) && vtgDatoer.length > 0)
|
|
|
|
|
|
);
|
|
|
|
|
|
const mapUrl = buildMapUrl(facility.lat, facility.lng);
|
|
|
|
|
|
const primaryStatus = getPrimaryStatus(
|
|
|
|
|
|
activeCourses.map((course: any) => ({ status: course.status }))
|
|
|
|
|
|
);
|
2026-02-27 11:12:53 +01:00
|
|
|
|
|
2026-04-14 16:31:28 +02:00
|
|
|
|
const sidebarLinkClass = "group flex items-center gap-4 text-[#11280f] transition-colors hover:text-[#ff5722]";
|
2026-04-15 09:19:16 +02:00
|
|
|
|
const sidebarLinkTextClass = "transition-colors group-hover:text-[#ff5722]";
|
|
|
|
|
|
const detailMetaLinkClass =
|
|
|
|
|
|
"text-[10px] font-black uppercase tracking-widest text-[#7ca982] transition-colors hover:text-[#ff5722]";
|
|
|
|
|
|
const resourceBtnClass = "btn-panel group w-full p-5";
|
|
|
|
|
|
const sectionNavButtonClass =
|
|
|
|
|
|
"btn btn-secondary h-10 shrink-0 whitespace-nowrap px-3 text-[9px] tracking-[0.12em] md:h-auto md:px-4 md:text-[11px] md:tracking-[0.16em]";
|
|
|
|
|
|
const mobileSectionNavButtonClass =
|
|
|
|
|
|
"whitespace-nowrap text-[8px] font-black uppercase tracking-[0.04em] text-gray-500 transition-colors hover:text-[#11280f]";
|
|
|
|
|
|
const placeSlug = slugify(facility.county || "") || "norge";
|
|
|
|
|
|
const sectionNavItems = [
|
|
|
|
|
|
{ id: 'intro', label: 'Info', showOnMobile: false },
|
|
|
|
|
|
{ id: 'weather', label: 'Vær', showOnMobile: false },
|
|
|
|
|
|
{ id: 'details', label: 'Detaljer', showOnMobile: true },
|
|
|
|
|
|
mapUrl ? { id: 'map', label: 'Kart', showOnMobile: true } : null,
|
|
|
|
|
|
facility.video_url ? { id: 'video', label: 'Video', showOnMobile: true } : null,
|
|
|
|
|
|
{ id: 'prices', label: 'Priser', showOnMobile: true },
|
|
|
|
|
|
hasVtg ? { id: 'vtg', label: 'VTG', showOnMobile: true } : null,
|
|
|
|
|
|
{ id: 'scorecards', label: 'Scorekort', showOnMobile: true },
|
|
|
|
|
|
].filter(
|
|
|
|
|
|
(item): item is { id: string; label: string; showOnMobile: boolean } => Boolean(item)
|
|
|
|
|
|
);
|
|
|
|
|
|
const mobileSectionNavItems = sectionNavItems.filter((item) => item.showOnMobile);
|
2026-02-26 16:32:34 +01:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (gallery.length <= 1) return;
|
|
|
|
|
|
const timer = setInterval(() => setCurrentSlide((p) => (p + 1) % gallery.length), 5000);
|
|
|
|
|
|
return () => clearInterval(timer);
|
|
|
|
|
|
}, [gallery.length]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleScroll = () => setShowBackToTop(window.scrollY > 500);
|
|
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const scrollTo = (id: string) => {
|
|
|
|
|
|
const el = document.getElementById(id);
|
|
|
|
|
|
if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.pageYOffset - 80, behavior: 'smooth' });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-27 08:53:14 +01:00
|
|
|
|
const formatDate = (d: string) => d ? new Date(d).toLocaleDateString('nb-NO', { day: 'numeric', month: 'long', year: 'numeric' }) : null;
|
2026-02-26 09:20:51 +01:00
|
|
|
|
const weatherImg = facility.weather_url?.replace("/graf/dag/", "/innhold/").replace(/\/$/, "") + "/meteogram.svg";
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<main className="min-h-screen bg-[#f1f7ed] pb-20 relative font-sans text-[#11280f]">
|
2026-02-26 09:20:51 +01:00
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 1. HERO SLIDER */}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<div className="h-[55vh] min-h-[450px] relative overflow-hidden bg-[#11280f]">
|
|
|
|
|
|
{gallery.map((img: string, i: number) => (
|
|
|
|
|
|
<img key={i} src={img} className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-1000 ${i === currentSlide ? 'opacity-100 z-10' : 'opacity-0 z-0'}`} alt="" />
|
|
|
|
|
|
))}
|
|
|
|
|
|
<div className="absolute inset-0 bg-gradient-to-t from-[#11280f]/90 via-transparent to-black/10 z-20" />
|
2026-02-27 14:53:30 +01:00
|
|
|
|
|
|
|
|
|
|
{/* BANESTATUS BADGES */}
|
|
|
|
|
|
<div className="absolute top-8 right-8 z-40 flex flex-col items-end gap-2">
|
|
|
|
|
|
<div className="flex flex-wrap justify-end gap-2">
|
|
|
|
|
|
{activeCourses.map((c: any) => (
|
|
|
|
|
|
<span key={c.id} className="px-3 py-1.5 rounded-lg text-[10px] font-black uppercase bg-[#7ca982] text-white shadow-xl">
|
|
|
|
|
|
{c.name.toUpperCase()}: {STATUS_MAP[c.status] || c.status}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{facility.status_updated_at && (
|
|
|
|
|
|
<span className="text-white/60 text-[10px] uppercase font-black tracking-widest bg-black/20 px-2 py-1 rounded">
|
2026-03-06 13:39:11 +01:00
|
|
|
|
Sist oppdatert: {formatDate(facility.status_updated_at)}
|
2026-02-27 14:53:30 +01:00
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
|
{/* FLYTENDE HURTIGKNAPPER */}
|
|
|
|
|
|
<div className="absolute bottom-8 right-8 z-40 flex gap-2.5 bg-black/30 backdrop-blur-md p-2 rounded-2xl border border-white/10 shadow-2xl text-[#11280f]">
|
2026-04-14 16:31:28 +02:00
|
|
|
|
{facility.website_url && <a href={facility.website_url} target="_blank" className="btn-icon"><Icon children={ICONS.web} /></a>}
|
|
|
|
|
|
{facility.golfbox_booking_url && <a href={facility.golfbox_booking_url} target="_blank" className="btn-icon"><Icon children={ICONS.booking} /></a>}
|
|
|
|
|
|
{facility.golfbox_tournament_url && <a href={facility.golfbox_tournament_url} target="_blank" className="btn-icon"><Icon children={ICONS.trophy} /></a>}
|
|
|
|
|
|
{mapUrl && <a href={mapUrl} target="_blank" rel="noreferrer" className="btn-icon"><Icon children={ICONS.pin} /></a>}
|
|
|
|
|
|
{facility.weather_url && <a href={facility.weather_url} target="_blank" className="btn-icon"><Icon children={ICONS.weather} /></a>}
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</div>
|
2026-02-27 14:53:30 +01:00
|
|
|
|
|
2026-03-06 13:39:11 +01:00
|
|
|
|
{/* HERO TEXT */}
|
2026-02-27 14:53:30 +01:00
|
|
|
|
<div className="relative z-30 max-w-[1200px] mx-auto px-6 w-full h-full flex flex-col justify-end pb-32 md:pb-12">
|
2026-02-27 10:15:52 +01:00
|
|
|
|
{facility.logo_url && (
|
|
|
|
|
|
<div className="hidden md:block mb-8 w-24 h-24 bg-white p-2 rounded-2xl shadow-2xl border-4 border-white/20 overflow-hidden"><img src={facility.logo_url} className="w-full h-full object-contain" alt="Logo" /></div>
|
|
|
|
|
|
)}
|
2026-02-27 08:53:14 +01:00
|
|
|
|
<h1 className="text-5xl md:text-8xl font-black text-white mb-3 tracking-tighter drop-shadow-2xl">{facility.name}</h1>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<p className="text-[#7ca982] uppercase tracking-[0.4em] font-black text-xs md:text-sm pl-1">{facility.county} • {facility.city}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 2. STICKY NAV */}
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<nav className="sticky top-0 z-50 border-b border-gray-100 bg-white/95 shadow-sm backdrop-blur-md">
|
|
|
|
|
|
<div className="mx-auto max-w-[1200px]">
|
|
|
|
|
|
<div className="flex flex-wrap items-center justify-center gap-x-1.5 gap-y-0.5 px-2 py-2 md:hidden">
|
|
|
|
|
|
{mobileSectionNavItems.map((item, index) => (
|
|
|
|
|
|
<div key={item.id} className="flex items-center gap-1.5">
|
|
|
|
|
|
{index > 0 && <span className="text-[8px] text-gray-300">/</span>}
|
|
|
|
|
|
<button className={mobileSectionNavButtonClass} onClick={() => scrollTo(item.id)}>{item.label}</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="hidden items-center gap-2 overflow-x-auto px-3 py-3 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden md:flex md:h-16 md:gap-4 md:px-6 md:py-0 lg:h-20 lg:gap-6 xl:gap-8">
|
|
|
|
|
|
{sectionNavItems.map((item) => (
|
|
|
|
|
|
<button key={item.id} className={sectionNavButtonClass} onClick={() => scrollTo(item.id)}>{item.label}</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</nav>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<div className="max-w-[1200px] mx-auto px-0 md:px-6 space-y-4 md:space-y-12 mt-0 md:mt-12">
|
|
|
|
|
|
|
2026-03-06 13:39:11 +01:00
|
|
|
|
{/* 3. INTRO & SIDEBAR */}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<section id="intro" className="flex flex-col lg:flex-row gap-0 md:gap-8 items-stretch">
|
2026-03-06 13:39:11 +01:00
|
|
|
|
{/* HOVEDINNHOLD (78%) */}
|
2026-02-27 14:53:30 +01:00
|
|
|
|
<div className="lg:w-[78%] bg-white p-10 md:p-16 md:rounded-[3rem] shadow-sm border-b md:border-none">
|
2026-03-08 10:26:56 +01:00
|
|
|
|
{facility.footnote && (
|
2026-04-12 11:19:35 +02:00
|
|
|
|
<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>
|
2026-04-19 12:33:05 +02:00
|
|
|
|
<div
|
|
|
|
|
|
className="italic text-[#ff5722] text-lg font-serif leading-8 [&_a]:font-bold [&_a]:text-[#d53300] [&_a]:underline [&_a]:underline-offset-2 hover:[&_a]:text-[#a82800] [&_em]:italic [&_strong]:font-bold"
|
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: sanitizeRichText(facility.footnote) }}
|
|
|
|
|
|
/>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-04-12 15:19:39 +02:00
|
|
|
|
<div
|
|
|
|
|
|
className="leading-relaxed text-lg md:text-xl text-gray-600 [&_a]:font-bold [&_a]:text-[#ff5722] [&_a]:underline-offset-2 hover:[&_a]:text-[#d53300] [&_em]:italic [&_ol]:my-5 [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:mb-5 [&_p:last-child]:mb-0 [&_strong]:font-bold [&_ul]:my-5 [&_ul]:list-disc [&_ul]:pl-6"
|
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: sanitizeRichText(facility.description || 'Ingen beskrivelse tilgjengelig.') }}
|
|
|
|
|
|
/>
|
2026-02-27 14:53:30 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-06 13:39:11 +01:00
|
|
|
|
{/* SIDEBAR (22%) */}
|
2026-02-27 14:53:30 +01:00
|
|
|
|
<div className="lg:w-[22%] bg-white p-10 md:rounded-[3rem] shadow-sm flex flex-col order-last lg:order-none">
|
2026-02-27 10:15:52 +01:00
|
|
|
|
<h3 className="text-[10px] font-black text-gray-300 uppercase tracking-widest mb-10">Kontakt & Adresse</h3>
|
|
|
|
|
|
<div className="flex-grow space-y-7 text-sm font-bold">
|
2026-04-18 09:00:16 +02:00
|
|
|
|
{facility.website_url ? (
|
|
|
|
|
|
<a href={facility.website_url} target="_blank" rel="noreferrer" className={sidebarLinkClass}>
|
|
|
|
|
|
<Icon children={ICONS.web} /> <span className={sidebarLinkTextClass}>Besøk nettsiden</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex items-center gap-4 text-[#8A9488]">
|
|
|
|
|
|
<Icon children={ICONS.web} /> <span>Nettside ikke oppgitt</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{facility.phone ? (
|
|
|
|
|
|
<a href={`tel:${formatPhoneForUrl(facility.phone)}`} className={sidebarLinkClass}>
|
|
|
|
|
|
<Icon children={ICONS.phone} /> <span className={sidebarLinkTextClass}>{facility.phone}</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex items-center gap-4 text-[#8A9488]">
|
|
|
|
|
|
<Icon children={ICONS.phone} /> <span>Telefon ikke oppgitt</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{facility.email ? (
|
|
|
|
|
|
<a href={`mailto:${facility.email}`} className={sidebarLinkClass}>
|
|
|
|
|
|
<Icon children={ICONS.mail} /> <span className={`truncate ${sidebarLinkTextClass}`}>{facility.email}</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex items-center gap-4 text-[#8A9488]">
|
|
|
|
|
|
<Icon children={ICONS.mail} /> <span>E-post ikke oppgitt</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-02-27 10:15:52 +01:00
|
|
|
|
<div className="pt-2 border-t border-gray-50 mt-4">
|
2026-04-18 09:00:16 +02:00
|
|
|
|
{mapUrl ? (
|
|
|
|
|
|
<a href={mapUrl} 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 className="flex items-start gap-4 pt-4 text-[#8A9488] leading-tight">
|
|
|
|
|
|
<Icon children={ICONS.pin} /> <span>{facility.address || "Adresse ikke oppgitt"}{facility.city ? <><br />{facility.city}</> : null}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-02-27 10:15:52 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
|
|
|
|
|
|
{/* SOSIALE MEDIER IKONER */}
|
|
|
|
|
|
{socialLinks.length > 0 && (
|
|
|
|
|
|
<div className="pt-6 border-t border-gray-50 mt-6 flex flex-wrap gap-3">
|
|
|
|
|
|
{socialLinks.map((social: any, idx: number) => {
|
|
|
|
|
|
const platform = (social.platform || '').toLowerCase().trim();
|
|
|
|
|
|
const iconData = SOCIAL_ICONS[platform] || <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" />;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-14 16:31:28 +02:00
|
|
|
|
<a key={idx} href={social.url} target="_blank" rel="noreferrer" title={social.platform} className="btn-icon h-10 w-10 rounded-full shadow-sm">
|
2026-03-08 10:26:56 +01:00
|
|
|
|
<Icon children={iconData} className="w-4 h-4 text-current" />
|
|
|
|
|
|
</a>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="mt-10 pt-6 border-t border-gray-50">
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<Link href={`/sted/${placeSlug}`} className={`${detailMetaLinkClass} flex items-center gap-1`}>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
Se alle baner i {facility.county} →
|
2026-02-27 14:53:30 +01:00
|
|
|
|
</Link>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 4. 3-KOLONNE INFO */}
|
|
|
|
|
|
<section id="details" className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-8">
|
|
|
|
|
|
<div className="bg-white p-10 md:rounded-[3rem] shadow-sm">
|
|
|
|
|
|
<h3 className="text-lg font-black mb-8 uppercase tracking-tighter">Andre Ressurser</h3>
|
|
|
|
|
|
<div className="space-y-2.5">
|
|
|
|
|
|
{facility.golfbox_booking_url && (
|
|
|
|
|
|
<a href={facility.golfbox_booking_url} target="_blank" className={resourceBtnClass}>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.booking} className="group-hover:text-white" /> Book Starttid</span><span>→</span>
|
2026-02-27 10:27:25 +01:00
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{facility.golfbox_tournament_url && (
|
|
|
|
|
|
<a href={facility.golfbox_tournament_url} target="_blank" className={resourceBtnClass}>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.trophy} className="group-hover:text-white" /> Turneringer</span><span>→</span>
|
2026-02-27 10:27:25 +01:00
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{facility.baneguide_url && (
|
|
|
|
|
|
<a href={facility.baneguide_url} target="_blank" className={resourceBtnClass}>
|
|
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.guide} className="group-hover:text-white" /> Baneguide</span><span>→</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{facility.flyfoto_url && (
|
|
|
|
|
|
<a href={facility.flyfoto_url} target="_blank" className={resourceBtnClass}>
|
|
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.camera} className="group-hover:text-white" /> Flyfoto</span><span>→</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
2026-03-06 13:39:11 +01:00
|
|
|
|
{facility.webcam_url && (
|
|
|
|
|
|
<a href={facility.webcam_url} target="_blank" className={resourceBtnClass}>
|
|
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.webcam} className="group-hover:text-white" /> Webkamera</span><span>→</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
{shotzoom.map((sz: any, i: number) => (
|
2026-02-27 10:27:25 +01:00
|
|
|
|
<a key={i} href={sz.shotzoom_url} target="_blank" className={resourceBtnClass}>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<span className="flex items-center gap-3"><Icon children={ICONS.chart} className="group-hover:text-white" /> Statistikk: {sz.shotzoom_beskrivelse?.replace(/ ?/g, ' ').trim().toUpperCase()}</span><span>→</span>
|
2026-02-27 10:27:25 +01:00
|
|
|
|
</a>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
))}
|
2026-03-06 13:39:11 +01:00
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
<div className="bg-white p-10 md:rounded-[3rem] shadow-sm text-sm font-bold text-gray-700 flex flex-col">
|
2026-02-27 10:27:25 +01:00
|
|
|
|
<h3 className="text-lg font-black mb-8 uppercase tracking-tighter text-[#11280f]">Banen</h3>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
<div className="space-y-5 flex-grow">
|
2026-02-27 10:27:25 +01:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Hull:</span><span>{amenities.antall_hull || '--'}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Lengde:</span><span>{facility.length_meters ? `${facility.length_meters}m` : '--'}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Sesong:</span><span>{facility.season || '--'}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Byggeår:</span><span>{facility.established_year || '--'}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Banetype:</span><span>{facility.banetype || 'Park/Skog'}</span></div>
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-gray-400">Arkitekt:</span><span className="text-right truncate ml-4">{facility.architect || '--'}</span></div>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</div>
|
2026-02-27 10:27:25 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-white p-10 md:rounded-[3rem] shadow-sm text-sm font-bold text-gray-700">
|
|
|
|
|
|
<h3 className="text-lg font-black mb-8 uppercase tracking-tighter text-[#11280f]">Andre Tilbud</h3>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<div className="space-y-4">
|
2026-04-19 13:35:48 +02:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Drivingrange:</span><span className="text-right ml-4">{renderValue(amenities.drivingrange, 'Nei')}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Nærspill:</span><span className="text-right ml-4">{renderValue(amenities.treningsgreen, 'Ja')}</span></div>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Proshop:</span><span className="text-right ml-4">{renderValue(amenities.proshop)}</span></div>
|
2026-04-19 13:35:48 +02:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Kølleutleie:</span><span className="text-right ml-4">{renderValue(amenities.kolleutleie, 'Ja')}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Bilutleie:</span><span className="text-right ml-4">{renderValue(amenities.bilutleie, 'Nei')}</span></div>
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Simulator:</span><span className="text-right ml-4">{renderValue(amenities.simulator)}</span></div>
|
|
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2"><span className="text-gray-400">Kafé:</span><span className="text-right ml-4">{renderValue(amenities.kafe)}</span></div>
|
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
|
{/* Golfamore og NSG */}
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2">
|
|
|
|
|
|
<span className="text-gray-400">Golfamore:</span>
|
|
|
|
|
|
<span className="text-right ml-4">
|
2026-04-19 13:35:48 +02:00
|
|
|
|
{hasGolfamore && facility.golfamore_url ? (
|
|
|
|
|
|
<a
|
|
|
|
|
|
href={facility.golfamore_url}
|
|
|
|
|
|
target="_blank"
|
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
|
className="font-black text-[#ff5722] transition-colors hover:underline"
|
|
|
|
|
|
>
|
|
|
|
|
|
{golfamoreData.terms || golfamoreData.gyldighet || "Ja"}
|
|
|
|
|
|
</a>
|
|
|
|
|
|
) : hasGolfamore ? (
|
|
|
|
|
|
<span className="text-[#ff5722] font-black">{golfamoreData.terms || golfamoreData.gyldighet || "Ja"}</span>
|
|
|
|
|
|
) : "Nei"}
|
2026-03-06 13:39:11 +01:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
<div className="flex justify-between border-b border-gray-50 pb-2">
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<span className="text-gray-400">Seniorgolf (NSG):</span>
|
|
|
|
|
|
<span className="text-right ml-4">
|
|
|
|
|
|
{hasNSG && facility.nsg_url
|
2026-04-19 13:35:48 +02:00
|
|
|
|
? <a href={facility.nsg_url} target="_blank" rel="noopener noreferrer" className="font-black text-[#ff5722] transition-colors hover:underline">Ja (Vis avtale)</a>
|
|
|
|
|
|
: (hasNSG ? <span className="text-[#ff5722] font-black">Ja</span> : "Nei")
|
2026-03-06 13:39:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
|
2026-04-15 08:15:53 +02:00
|
|
|
|
{golfpakkerRaw.length > 0 && (
|
|
|
|
|
|
<div className="pt-4">
|
|
|
|
|
|
<span className="text-gray-400 block mb-2">Golfpakker:</span>
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
{golfpakkerRaw.map((pakke: any, index: number) => (
|
|
|
|
|
|
<div key={index} className="rounded-2xl border border-gray-100 bg-white p-4">
|
|
|
|
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="font-black text-[#11280f]">{pakke.navn || 'Golfpakke'}</p>
|
|
|
|
|
|
{pakke.beskrivelse && <p className="mt-1 text-sm text-gray-500">{pakke.beskrivelse}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col items-start gap-2 sm:items-end">
|
|
|
|
|
|
<span className="text-xs font-black uppercase tracking-widest text-[#7ca982]">
|
|
|
|
|
|
{pakke.pris ? `${pakke.pris},-` : 'Pris på forespørsel'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{pakke.lenke && (
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<a href={pakke.lenke} target="_blank" rel="noopener noreferrer" className="text-xs font-black uppercase tracking-widest text-blue-600 transition-colors hover:text-[#ff5722] hover:underline">
|
2026-04-15 08:15:53 +02:00
|
|
|
|
Les mer ↗
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 5. VÆR SEKSJON */}
|
|
|
|
|
|
<section id="weather" className="bg-white p-0 md:p-12 md:rounded-[3rem] shadow-sm border-b md:border-none overflow-hidden text-center">
|
|
|
|
|
|
<h3 className="text-[10px] font-black text-gray-300 uppercase tracking-[0.2em] py-8 md:py-0 md:mb-10 flex items-center justify-center gap-3"><Icon children={ICONS.weather} /> Vær for {facility.name}</h3>
|
|
|
|
|
|
<div className="w-full flex justify-center px-4 md:px-0">
|
2026-04-18 09:00:16 +02:00
|
|
|
|
{facility.weather_url ? ( <img src={weatherImg} className="w-full h-auto block max-w-5xl" alt={`Værvarsel for ${facility.name}`} loading="lazy" decoding="async" /> ) : <p className="text-center py-24 text-gray-300 italic text-sm">Værvarsel ikke tilgjengelig</p>}
|
2026-02-27 10:27:25 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 6. KART SEKSJON */}
|
2026-04-14 16:31:28 +02:00
|
|
|
|
{mapUrl && (
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<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>
|
2026-04-14 16:31:28 +02:00
|
|
|
|
<div className="teeoff-map overflow-hidden md:rounded-[3rem] border-y-4 md:border-[12px] border-white bg-white shadow-xl">
|
2026-04-17 09:25:32 +02:00
|
|
|
|
<FacilityDetailLeafletMap
|
|
|
|
|
|
lat={facility.lat as number}
|
|
|
|
|
|
lng={facility.lng as number}
|
|
|
|
|
|
name={facility.name}
|
|
|
|
|
|
city={facility.city}
|
|
|
|
|
|
county={facility.county}
|
|
|
|
|
|
primaryStatus={primaryStatus}
|
|
|
|
|
|
mapUrl={mapUrl}
|
|
|
|
|
|
/>
|
2026-04-14 16:31:28 +02:00
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</section>
|
2026-04-14 16:31:28 +02:00
|
|
|
|
)}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 7. VIDEO SEKSJON */}
|
|
|
|
|
|
{facility.video_url && (
|
|
|
|
|
|
<section id="video" className="space-y-6">
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<h2 className="text-3xl md:text-4xl font-black uppercase tracking-tighter flex items-center gap-5 ml-6 md:ml-0">Video <span className="h-1 flex-grow bg-gray-100 rounded-full" /></h2>
|
2026-02-27 10:27:25 +01:00
|
|
|
|
<div className="w-full md:rounded-[3rem] overflow-hidden shadow-2xl aspect-video bg-black border-y-4 md:border-[12px] border-white">
|
|
|
|
|
|
<iframe src={facility.video_url} className="w-full h-full" allowFullScreen />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-04-14 16:31:28 +02:00
|
|
|
|
{/* 8. PRISER (MEDLEMSKAP & GREENFEE) */}
|
2026-03-13 08:28:17 +01:00
|
|
|
|
<section id="prices" className="pt-10">
|
|
|
|
|
|
<h2 className="text-3xl md:text-4xl font-black uppercase tracking-tighter flex items-center gap-5 ml-6 md:ml-0 mb-8">
|
|
|
|
|
|
Priser <span className="h-1 flex-grow bg-gray-100 rounded-full" />
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6 lg:gap-8 items-start">
|
|
|
|
|
|
|
|
|
|
|
|
{/* VENSTRE KOLONNE: MEDLEMSKAP */}
|
|
|
|
|
|
{(facility.standard_medlemskap || facility.rimeligste_alternativ) && (
|
|
|
|
|
|
<div className="bg-white border-2 border-[#11280f]/5 rounded-3xl p-6 lg:p-8 shadow-sm h-full flex flex-col">
|
|
|
|
|
|
<div className="flex justify-between items-center mb-6">
|
|
|
|
|
|
<h3 className="text-xl font-black text-[#11280f] uppercase tracking-tighter flex items-center gap-2">
|
|
|
|
|
|
<span>⛳</span> Medlemskap
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{facility.medlemskap_url && (
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<a href={facility.medlemskap_url.split(',')[0].trim()} target="_blank" className={detailMetaLinkClass}>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
Se alle →
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-6 flex-grow">
|
|
|
|
|
|
{facility.standard_medlemskap && (
|
|
|
|
|
|
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-100 relative overflow-hidden group hover:border-[#8bc34a]/30 transition-colors">
|
|
|
|
|
|
<div className="absolute top-0 right-0 bg-[#8bc34a] text-white text-[9px] font-black uppercase tracking-widest px-3 py-1 rounded-bl-xl">Mest valgte</div>
|
|
|
|
|
|
<span className="block text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Standard</span>
|
|
|
|
|
|
<span className="text-3xl font-black text-[#11280f]">{facility.standard_medlemskap},-</span>
|
|
|
|
|
|
{facility.standard_medlemskap_navn && <p className="text-xs font-bold mt-1 text-[#8bc34a]">{facility.standard_medlemskap_navn}</p>}
|
|
|
|
|
|
{facility.standard_medlemskap_kommentarer && (
|
|
|
|
|
|
<p className="text-[10px] text-gray-500 mt-2 uppercase font-bold italic leading-tight border-t border-gray-200 pt-2">
|
|
|
|
|
|
{facility.standard_medlemskap_kommentarer.split('\n').map((line: string, i: number) => (
|
|
|
|
|
|
<span key={i}>{line}<br /></span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-02-27 11:12:53 +01:00
|
|
|
|
</div>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{facility.rimeligste_alternativ && (
|
|
|
|
|
|
<div className="bg-white rounded-2xl p-5 border border-gray-100 hover:border-[#8bc34a]/30 transition-colors">
|
|
|
|
|
|
<span className="block text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Rimeligste golfkort</span>
|
|
|
|
|
|
<span className="text-xl font-black text-gray-600">{facility.rimeligste_alternativ},-</span>
|
|
|
|
|
|
{facility.rimeligste_navn && <p className="text-[10px] font-bold mt-1 text-gray-500">{facility.rimeligste_navn}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-27 11:12:53 +01:00
|
|
|
|
)}
|
2026-03-13 08:28:17 +01:00
|
|
|
|
|
|
|
|
|
|
{/* HØYRE KOLONNE: GREENFEE */}
|
|
|
|
|
|
{greenfeeRaw && greenfeeRaw.length > 0 && (
|
|
|
|
|
|
<div className="bg-white border-2 border-[#11280f]/5 rounded-3xl p-6 lg:p-8 shadow-sm h-full flex flex-col">
|
|
|
|
|
|
<div className="flex justify-between items-center mb-6">
|
|
|
|
|
|
<h3 className="text-xl font-black text-[#11280f] uppercase tracking-tighter flex items-center gap-2">
|
|
|
|
|
|
<span>🎫</span> Greenfee
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{facility.greenfee_url && (
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<a href={facility.greenfee_url.split(',')[0].trim()} target="_blank" className={detailMetaLinkClass}>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
Alle priser →
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="overflow-x-auto hide-scrollbar flex-grow">
|
|
|
|
|
|
<table className="w-full text-left min-w-[300px]">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr className="border-b-2 border-gray-100 text-[10px] font-black uppercase tracking-widest text-gray-400">
|
|
|
|
|
|
<th className="pb-3 pr-2">Bane/Kat.</th>
|
|
|
|
|
|
<th className="pb-3 text-right pr-2">Voksen</th>
|
|
|
|
|
|
<th className="pb-3 text-right">Junior</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody className="text-xs font-bold text-[#11280f]">
|
|
|
|
|
|
{greenfeeRaw.map((gf: any, idx: number) => (
|
|
|
|
|
|
<tr key={idx} className="border-b border-gray-50 hover:bg-gray-50 transition-colors">
|
|
|
|
|
|
<td className="py-3 pr-2 leading-tight">
|
|
|
|
|
|
<span className="block truncate max-w-[150px] sm:max-w-[200px]" title={gf.banenavn}>{gf.banenavn}</span>
|
2026-04-15 09:19:16 +02:00
|
|
|
|
<span className="block max-w-[150px] whitespace-normal break-words text-[9px] uppercase tracking-widest text-gray-400 sm:max-w-[200px]" title={gf.priskategori}>{gf.priskategori}</span>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
</td>
|
|
|
|
|
|
<td className="py-3 text-right pr-2 text-[#8bc34a] font-black text-sm whitespace-nowrap">
|
|
|
|
|
|
{gf.pris_voksne ? `${gf.pris_voksne},-` : '-'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="py-3 text-right text-blue-400 whitespace-nowrap">
|
|
|
|
|
|
{gf.pris_junior ? `${gf.pris_junior},-` : '-'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{facility.guest_requirements && (
|
|
|
|
|
|
<p className="mt-4 text-[10px] text-gray-400 font-black uppercase tracking-widest italic pt-4 border-t border-gray-50">
|
|
|
|
|
|
Krav: {facility.guest_requirements}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-04-17 09:25:32 +02:00
|
|
|
|
{cooperatingClubs.length > 0 && (
|
|
|
|
|
|
<div className="mt-4 border-t border-gray-50 pt-4">
|
|
|
|
|
|
<span className="mb-3 block text-[10px] font-black uppercase tracking-widest text-gray-400">
|
|
|
|
|
|
Samarbeidende klubber
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
{cooperatingClubs.map((slug: string) => (
|
|
|
|
|
|
<Link key={slug} href={`/golfbaner/${slug}`} className="btn btn-sm btn-secondary">
|
|
|
|
|
|
{slug.replace('-golfklubb', '').replace(/-/g, ' ')}
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-13 08:28:17 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-14 16:31:28 +02:00
|
|
|
|
</section>
|
2026-04-12 21:53:54 +02:00
|
|
|
|
|
2026-04-14 16:31:28 +02:00
|
|
|
|
{hasVtg && (
|
|
|
|
|
|
<section id="vtg" className="pt-10 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">
|
|
|
|
|
|
Veien til Golf <span className="h-1 flex-grow bg-gray-100 rounded-full" />
|
|
|
|
|
|
</h2>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
|
2026-04-14 16:31:28 +02:00
|
|
|
|
<div className="bg-[#8bc34a] text-white rounded-3xl p-6 lg:p-10 shadow-lg relative overflow-hidden group">
|
|
|
|
|
|
<div className="absolute -right-20 -top-20 opacity-10 text-[200px] pointer-events-none transform group-hover:scale-110 transition-transform duration-700">🏌️♂️</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="relative z-10">
|
|
|
|
|
|
<div className="mb-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
|
|
|
|
<h3 className="text-2xl font-black uppercase tracking-tighter flex items-center gap-3">
|
|
|
|
|
|
Nybegynnerkurs hos {facility.name}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href="/vtg"
|
|
|
|
|
|
className="inline-flex items-center text-[10px] font-black uppercase tracking-[0.18em] text-white/80 transition hover:text-white"
|
|
|
|
|
|
>
|
|
|
|
|
|
Se alle VTG-kurs →
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{facility.vtg_beskrivelse && (
|
|
|
|
|
|
<p className="text-sm md:text-base text-white/90 mb-8 leading-relaxed font-medium max-w-4xl">
|
|
|
|
|
|
{facility.vtg_beskrivelse}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col lg:flex-row gap-6 items-start lg:items-center justify-between bg-white/10 rounded-2xl p-6 backdrop-blur-sm border border-white/20">
|
|
|
|
|
|
{facility.vtg_pris && (
|
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
|
<span className="block text-[10px] font-black uppercase tracking-widest text-white/70 mb-1">Standard voksenpris</span>
|
|
|
|
|
|
<span className="text-4xl font-black">{facility.vtg_pris},-</span>
|
2026-03-13 08:28:17 +01:00
|
|
|
|
</div>
|
2026-04-14 16:31:28 +02:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{vtgDatoer && vtgDatoer.length > 0 && (
|
|
|
|
|
|
<div className="flex-grow w-full lg:w-auto lg:px-6">
|
|
|
|
|
|
<h4 className="text-[10px] font-black uppercase tracking-widest text-white/70 mb-3">Kommende kurs:</h4>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
{vtgDatoer.map((kurs: any, i: number) => {
|
|
|
|
|
|
const status = (kurs.status || '').toLowerCase();
|
|
|
|
|
|
const isFull = status.includes('full');
|
|
|
|
|
|
const isWaitlist = status.includes('vente') || status.includes('få');
|
|
|
|
|
|
|
|
|
|
|
|
let badgeColor = "bg-white/20 text-white";
|
|
|
|
|
|
if (isFull) badgeColor = "bg-red-500/80 text-white line-through opacity-75";
|
|
|
|
|
|
if (isWaitlist) badgeColor = "bg-yellow-400 text-[#11280f]";
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={i} className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-bold border border-white/10 ${badgeColor}`}>
|
|
|
|
|
|
<span>{kurs.dato}</span>
|
|
|
|
|
|
<span className="text-[8px] uppercase tracking-widest opacity-80 border-l border-white/20 pl-2 ml-1">{kurs.status}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{facility.vtg_lenke && (
|
|
|
|
|
|
<a href={facility.vtg_lenke.split(',')[0].trim()} target="_blank" rel="noopener noreferrer" className="btn btn-lg btn-primary mt-4 w-full flex-shrink-0 text-center lg:mt-0 lg:w-auto">
|
2026-04-15 09:19:16 +02:00
|
|
|
|
Gå til klubben ↗
|
2026-04-14 16:31:28 +02:00
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
2026-03-13 08:28:17 +01:00
|
|
|
|
</div>
|
2026-04-14 16:31:28 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
|
2026-02-27 10:27:25 +01:00
|
|
|
|
{/* 9. SCOREKORT SEKSJON */}
|
2026-02-26 16:32:34 +01:00
|
|
|
|
<section id="scorecards" className="pt-10 space-y-20 overflow-hidden">
|
|
|
|
|
|
<h3 className="text-center text-3xl md:text-5xl font-black uppercase tracking-tighter">Scorekort</h3>
|
|
|
|
|
|
<div className="w-full flex flex-col items-center gap-20">
|
|
|
|
|
|
{activeCourses.map((c: any) => (
|
|
|
|
|
|
<div key={c.id} className="w-full overflow-x-auto md:overflow-visible no-scrollbar">
|
2026-03-06 13:39:11 +01:00
|
|
|
|
<div className="min-w-[800px] md:min-w-0"><CourseDisplay course={c} /></div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
2026-04-19 13:35:48 +02:00
|
|
|
|
|
|
|
|
|
|
<FacilityFeedbackForm
|
|
|
|
|
|
facilityId={Number(facility.id)}
|
|
|
|
|
|
facilityName={String(facility.name || "dette golfanlegget")}
|
|
|
|
|
|
/>
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</div>
|
2026-02-26 16:32:34 +01:00
|
|
|
|
|
2026-02-27 11:12:53 +01:00
|
|
|
|
{showBackToTop && (
|
2026-04-14 16:31:28 +02:00
|
|
|
|
<button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className="btn btn-ink fixed bottom-8 right-8 z-[100] flex h-14 w-14 items-center justify-center rounded-full border-4 border-white/20 p-0 text-2xl shadow-2xl">↑</button>
|
2026-02-27 11:12:53 +01:00
|
|
|
|
)}
|
2026-02-26 09:20:51 +01:00
|
|
|
|
</main>
|
|
|
|
|
|
);
|
2026-04-12 11:19:35 +02:00
|
|
|
|
}
|