Siste før scraping
This commit is contained in:
parent
95706260c6
commit
131593c06e
1 changed files with 69 additions and 28 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
/**
|
/**
|
||||||
* TEE OFF DETAIL VIEW - COMPLETE v3.18
|
* TEE OFF DETAIL VIEW - COMPLETE v3.20
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* FIX: Fjernet emojier/ikoner ved siden av "GJESTESPILL" og "MEDLEMSKAP".
|
* FIX: Gjenopprettet "Turneringer" i den flytende knapperaden over bildet.
|
||||||
* FIX: Forbedret Greenfee-logikk for å sikre at ALLE priser og baner (Østmork/Vestmork) vises.
|
* FIX: Byttet plass på tekst og sidebar (Tekst øverst på mobil).
|
||||||
* FIX: Skjuler under-header hvis den bare gjentar "Gjestespill".
|
* FIX: Økt padding (pb-32) i Hero-teksten på mobil for å unngå krasj med knapper.
|
||||||
* REGEL: Beholder monokrome ikoner, 22/78 layout og 0047-prefix på telefon.
|
* FIX: Alle 4 kontaktpunkter i sidebar er klikkbare (tel:0047 fix inkludert).
|
||||||
|
* REGEL: Beholder monokrome ikoner, 22/78 layout og robust JSON-parsing.
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -19,6 +20,17 @@ const formatPhoneForUrl = (phone: string) => {
|
||||||
return phone.replace('+', '00').replace(/\s/g, '');
|
return phone.replace('+', '00').replace(/\s/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderValue = (val: string) => {
|
||||||
|
if (!val) return "Nei";
|
||||||
|
const hasLink = val.includes('<a');
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={hasLink ? "text-orange-600 font-bold" : "text-[#11280f]"}
|
||||||
|
dangerouslySetInnerHTML={{ __html: val }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Icon = ({ children, className = "w-5 h-5" }: { children: React.ReactNode, className?: string }) => (
|
const Icon = ({ children, className = "w-5 h-5" }: { children: React.ReactNode, className?: string }) => (
|
||||||
<svg
|
<svg
|
||||||
className={`${className} flex-shrink-0 text-[#11280f]`}
|
className={`${className} flex-shrink-0 text-[#11280f]`}
|
||||||
|
|
@ -55,11 +67,11 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
const rawCourses = parseJson(facility.courses, []);
|
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 activeCourses = Array.isArray(rawCourses) ? rawCourses.filter((c: any) => c.holes && (typeof c.holes === 'string' || c.holes.length > 0)) : [];
|
||||||
const amenities = parseJson(facility.amenities, {});
|
const amenities = parseJson(facility.amenities, {});
|
||||||
const gallery = parseJson(facility.gallery, [facility.image_url || FALLBACK_IMAGE]);
|
const galleryRaw = parseJson(facility.gallery, []);
|
||||||
|
const gallery = galleryRaw.length > 0 ? galleryRaw : [facility.image_url || FALLBACK_IMAGE];
|
||||||
const greenfeeRaw = parseJson(facility.greenfee, []);
|
const greenfeeRaw = parseJson(facility.greenfee, []);
|
||||||
const shotzoom = parseJson(facility.shotzoom, []);
|
const shotzoom = parseJson(facility.shotzoom, []);
|
||||||
|
|
||||||
// Grupper priser etter bane (f.eks Østmork, Vestmork)
|
|
||||||
const groupedGreenfee: Record<string, any[]> = greenfeeRaw.reduce((acc: any, curr: any) => {
|
const groupedGreenfee: Record<string, any[]> = greenfeeRaw.reduce((acc: any, curr: any) => {
|
||||||
const bane = curr.banenavn || "Gjestespill";
|
const bane = curr.banenavn || "Gjestespill";
|
||||||
if (!acc[bane]) acc[bane] = [];
|
if (!acc[bane]) acc[bane] = [];
|
||||||
|
|
@ -67,7 +79,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const linkClass = "text-orange-600 hover:underline transition-colors font-bold";
|
|
||||||
const sidebarLinkClass = "flex items-center gap-4 hover:text-orange-600 transition-colors group";
|
const sidebarLinkClass = "flex items-center gap-4 hover:text-orange-600 transition-colors group";
|
||||||
const resourceBtnClass = "flex justify-between items-center p-5 bg-gray-50 rounded-2xl text-[11px] font-black uppercase hover:bg-orange-600 hover:text-white transition-all group";
|
const resourceBtnClass = "flex justify-between items-center p-5 bg-gray-50 rounded-2xl text-[11px] font-black uppercase hover:bg-orange-600 hover:text-white transition-all group";
|
||||||
|
|
||||||
|
|
@ -100,12 +111,34 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
<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="" />
|
<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" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#11280f]/90 via-transparent to-black/10 z-20" />
|
||||||
<div className="absolute top-8 right-8 z-40 flex flex-wrap justify-end gap-2">
|
|
||||||
|
{/* 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) => (
|
{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">{STATUS_MAP[c.status] || c.status}</span>
|
<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>
|
</div>
|
||||||
<div className="relative z-30 max-w-[1200px] mx-auto px-6 w-full h-full flex flex-col justify-end pb-12">
|
{facility.status_updated_at && (
|
||||||
|
<span className="text-white/60 text-[10px] uppercase font-black tracking-widest bg-black/20 px-2 py-1 rounded">
|
||||||
|
Sist oppdatert: {formatDate(facility.status_updated_at)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FLYTENDE HURTIGKNAPPER (Inkludert Turneringer) */}
|
||||||
|
<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">
|
||||||
|
{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-orange-600 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-orange-600 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-orange-600 hover:text-white transition-all"><Icon children={ICONS.trophy} /></a>}
|
||||||
|
<a href={`https://www.google.com/maps/search/?api=1&query=${facility.lat},${facility.lng}`} target="_blank" className="w-9 h-9 bg-white rounded-xl flex items-center justify-center hover:bg-orange-600 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-orange-600 hover:text-white transition-all"><Icon children={ICONS.weather} /></a>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* HERO TEXT - pb-32 på mobil hindrer overlap med knapper */}
|
||||||
|
<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">
|
||||||
{facility.logo_url && (
|
{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>
|
<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>
|
||||||
)}
|
)}
|
||||||
|
|
@ -129,14 +162,25 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
|
|
||||||
<div className="max-w-[1200px] mx-auto px-0 md:px-6 space-y-4 md:space-y-12 mt-0 md:mt-12">
|
<div className="max-w-[1200px] mx-auto px-0 md:px-6 space-y-4 md:space-y-12 mt-0 md:mt-12">
|
||||||
|
|
||||||
{/* 3. INTRO & SIDEBAR */}
|
{/* 3. INTRO & SIDEBAR (PRESENTASJON FØRST FOR MOBIL) */}
|
||||||
<section id="intro" className="flex flex-col lg:flex-row gap-0 md:gap-8 items-stretch">
|
<section id="intro" className="flex flex-col lg:flex-row gap-0 md:gap-8 items-stretch">
|
||||||
<div className="lg:w-[22%] bg-white p-10 md:rounded-[3rem] shadow-sm flex flex-col">
|
{/* HOVEDINNHOLD (78%) - Kommer først på mobil pga koderrekkefølge */}
|
||||||
|
<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-gray-400 text-lg font-serif">{facility.footnote}</div>}
|
||||||
|
<div className="leading-relaxed text-lg md:text-xl text-gray-600" dangerouslySetInnerHTML={{ __html: facility.description || 'Ingen beskrivelse tilgjengelig.' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SIDEBAR (22%) - Kommer etter tekst på mobil */}
|
||||||
|
<div className="lg:w-[22%] bg-white p-10 md:rounded-[3rem] shadow-sm flex flex-col order-last lg:order-none">
|
||||||
<h3 className="text-[10px] font-black text-gray-300 uppercase tracking-widest mb-10">Kontakt & Adresse</h3>
|
<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">
|
<div className="flex-grow space-y-7 text-sm font-bold">
|
||||||
<a href={facility.website_url} target="_blank" className={sidebarLinkClass}><Icon children={ICONS.web} /> Besøk nettsiden</a>
|
<a href={facility.website_url} target="_blank" className={sidebarLinkClass}><Icon children={ICONS.web} /> Besøk nettsiden</a>
|
||||||
<a href={`tel:${formatPhoneForUrl(facility.phone)}`} className={sidebarLinkClass}><Icon children={ICONS.phone} /> {facility.phone || 'Ikke oppgitt'}</a>
|
<a href={`tel:${formatPhoneForUrl(facility.phone)}`} className={sidebarLinkClass}>
|
||||||
<a href={`mailto:${facility.email}`} className={sidebarLinkClass}><Icon children={ICONS.mail} /> <span className="truncate">{facility.email || 'Ikke oppgitt'}</span></a>
|
<Icon children={ICONS.phone} /> {facility.phone || 'Ikke oppgitt'}
|
||||||
|
</a>
|
||||||
|
<a href={`mailto:${facility.email}`} className={sidebarLinkClass}>
|
||||||
|
<Icon children={ICONS.mail} /> <span className="truncate">{facility.email || 'Ikke oppgitt'}</span>
|
||||||
|
</a>
|
||||||
<div className="pt-2 border-t border-gray-50 mt-4">
|
<div className="pt-2 border-t border-gray-50 mt-4">
|
||||||
<a href={`https://www.google.com/maps/search/?api=1&query=${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" className={sidebarLinkClass + " pt-4 leading-tight items-start"}>
|
||||||
<Icon children={ICONS.pin} /> <span className="text-gray-400 group-hover:text-orange-600 transition-colors">{facility.address}<br/>{facility.city}</span>
|
<Icon children={ICONS.pin} /> <span className="text-gray-400 group-hover:text-orange-600 transition-colors">{facility.address}<br/>{facility.city}</span>
|
||||||
|
|
@ -144,13 +188,11 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-auto pt-10 border-t border-gray-50">
|
<div className="mt-auto pt-10 border-t border-gray-50">
|
||||||
<Link href={`/`} className="text-[10px] font-black uppercase tracking-widest text-[#7ca982] hover:text-[#11280f] transition-all flex items-center gap-1">Se alle baner i {facility.county} →</Link>
|
<Link href={`/`} className="text-[10px] font-black uppercase tracking-widest text-[#7ca982] hover:text-[#11280f] transition-all flex items-center gap-1">
|
||||||
|
Se alle baner i {facility.county} →
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:w-[78%] bg-white p-10 md:p-16 md:rounded-[3rem] shadow-sm">
|
|
||||||
{facility.footnote && <div className="mb-8 pb-8 border-b border-gray-50 italic text-gray-400 text-lg font-serif">{facility.footnote}</div>}
|
|
||||||
<div className="leading-relaxed text-lg md:text-xl text-gray-600" dangerouslySetInnerHTML={{ __html: facility.description || 'Ingen beskrivelse tilgjengelig.' }} />
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 4. 3-KOLONNE INFO */}
|
{/* 4. 3-KOLONNE INFO */}
|
||||||
|
|
@ -201,11 +243,11 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Drivingrange:</span><span>{amenities.drivingrange || 'Nei'}</span></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Drivingrange:</span><span>{amenities.drivingrange || 'Nei'}</span></div>
|
||||||
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Nærspill:</span><span>{amenities.treningsgreen || 'Ja'}</span></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Nærspill:</span><span>{amenities.treningsgreen || 'Ja'}</span></div>
|
||||||
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Proshop:</span><span className="text-right ml-4 text-orange-600" dangerouslySetInnerHTML={{ __html: amenities.proshop || 'Nei' }} /></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Proshop:</span><span className="text-right ml-4">{renderValue(amenities.proshop)}</span></div>
|
||||||
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Kølleutleie:</span><span>{amenities.kolleutleie || 'Ja'}</span></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Kølleutleie:</span><span>{amenities.kolleutleie || 'Ja'}</span></div>
|
||||||
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Simulator:</span><span className="text-orange-600" dangerouslySetInnerHTML={{ __html: amenities.simulator || 'Nei' }} /></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><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-3"><span className="text-gray-400">Head Pro:</span><span className="text-orange-600" dangerouslySetInnerHTML={{ __html: amenities.pro || '--' }} /></div>
|
<div className="flex justify-between border-b border-gray-50 pb-3"><span className="text-gray-400">Head Pro:</span><span className="text-right ml-4">{renderValue(amenities.pro)}</span></div>
|
||||||
<div className="flex justify-between"><span className="text-gray-400">Kafé:</span><span className="text-orange-600" dangerouslySetInnerHTML={{ __html: amenities.kafe || '--' }} /></div>
|
<div className="flex justify-between"><span className="text-gray-400">Kafé:</span><span className="text-right ml-4">{renderValue(amenities.kafe)}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -236,7 +278,7 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 8. PRISER & MEDLEMSKAP (IKONER FJERNET) */}
|
{/* 8. PRISER & GJESTESPILL */}
|
||||||
<section id="prices" className="grid grid-cols-1 lg:grid-cols-2 gap-0 md:gap-8">
|
<section id="prices" className="grid grid-cols-1 lg:grid-cols-2 gap-0 md:gap-8">
|
||||||
<div className="bg-white p-10 md:p-14 md:rounded-[3rem] shadow-sm">
|
<div className="bg-white p-10 md:p-14 md:rounded-[3rem] shadow-sm">
|
||||||
<h3 className="text-2xl font-black mb-10 uppercase tracking-tighter">Gjestespill</h3>
|
<h3 className="text-2xl font-black mb-10 uppercase tracking-tighter">Gjestespill</h3>
|
||||||
|
|
@ -244,7 +286,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
|
||||||
{Object.keys(groupedGreenfee).length > 0 ? (
|
{Object.keys(groupedGreenfee).length > 0 ? (
|
||||||
Object.entries(groupedGreenfee).map(([bane, priser], idx) => (
|
Object.entries(groupedGreenfee).map(([bane, priser], idx) => (
|
||||||
<div key={idx} className="space-y-4">
|
<div key={idx} className="space-y-4">
|
||||||
{/* Skjul baneheader hvis den bare gjentar "Gjestespill" og det bare er én gruppe */}
|
|
||||||
{!(bane === "Gjestespill" && Object.keys(groupedGreenfee).length === 1) && (
|
{!(bane === "Gjestespill" && Object.keys(groupedGreenfee).length === 1) && (
|
||||||
<h4 className="text-lg font-black uppercase tracking-tighter text-[#11280f] border-b-2 border-gray-50 pb-2">{bane}</h4>
|
<h4 className="text-lg font-black uppercase tracking-tighter text-[#11280f] border-b-2 border-gray-50 pb-2">{bane}</h4>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue