Før fiks av toppslider og knapper

This commit is contained in:
Erol 2026-02-27 11:12:53 +01:00
parent a7c17b7572
commit 95706260c6
3 changed files with 140 additions and 26 deletions

79
backend/sync_greenfee.py Normal file
View file

@ -0,0 +1,79 @@
import asyncio, asyncpg, urllib.request, json
DB_URL = "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff"
# Vi fjerner acf_format=standard da rå-feltnavnene er tryggere her
WP_API_URL = "https://teeoff.no/wp-json/wp/v2/golfbaner?per_page=100"
def decode_html(text):
if not text: return ""
return str(text).replace('&', '&').replace('&', '&').replace(' ', ' ').strip()
async def run_greenfee_sync():
print("🎯 Starter GREENFEE-SYNC v1.2 (Basert på rå-API mapping)...")
conn = await asyncpg.connect(DB_URL)
page = 1
total_updated = 0
while True:
try:
req = urllib.request.Request(f"{WP_API_URL}&page={page}", headers={'User-Agent': 'TeeOff-Sync'})
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
except: break
if not data: break
for post in data:
slug = post['slug']
acf = post.get('acf', {})
# Henter banenavn for å gruppere riktig
bane_1_navn = acf.get('navn_pa_hovedbane') or "Hovedbanen"
bane_2_navn = acf.get('navn_pa_sekundar_bane') or "Bane 2"
final_greenfee = []
# --- MAPPER BANE 1 (Voksne + Junior) ---
voksne_1 = acf.get('greenfee_-_voksne') or []
junior_1 = acf.get('greenfee_-_junior') or []
for i, item in enumerate(voksne_1):
row = {
"banenavn": bane_1_navn,
"priskategori": item.get('priskategori'),
"pris_voksne": item.get('pris_voksne')
}
# Legger til juniorpris hvis den finnes på samme index
if i < len(junior_1):
row["pris_junior"] = junior_1[i].get('pris_junior')
final_greenfee.append(row)
# --- MAPPER BANE 2 (Voksne + Junior) ---
voksne_2 = acf.get('greenfee_-_voksne_bane_to') or []
junior_2 = acf.get('greenfee_-_junior_bane_to') or []
for i, item in enumerate(voksne_2):
row = {
"banenavn": bane_2_navn,
"priskategori": item.get('priskategori_bane_to'),
"pris_voksne": item.get('pris_voksne_bane_to')
}
if i < len(junior_2):
row["pris_junior"] = junior_2[i].get('pris_junior_bane_to')
final_greenfee.append(row)
# Henter krav (Gjeste_krav)
reqs = decode_html(acf.get('krav_til_gjestespillere'))
if final_greenfee:
await conn.execute('''
UPDATE facilities SET greenfee = $1::jsonb, guest_requirements = $2 WHERE slug = $3
''', json.dumps(final_greenfee), reqs, slug)
print(f"{slug}: Importerte {len(final_greenfee)} prisrader for {bane_1_navn}/{bane_2_navn}")
total_updated += 1
page += 1
await conn.close()
print(f"\n✨ Ferdig! Oppdaterte priser for {total_updated} anlegg.")
if __name__ == "__main__":
asyncio.run(run_greenfee_sync())

View file

@ -1,12 +1,11 @@
"use client";
/**
* TEE OFF DETAIL VIEW - COMPLETE v3.15 (FINAL POLISH)
* TEE OFF DETAIL VIEW - COMPLETE v3.18
* ---------------------------------------------------------------------------
* FIX: Flyttet Turneringer til over Baneguide i ressurslisten.
* FIX: Endret "Statistik" til "Statistikk".
* FIX: Ensrettet alle knapper i "Andre ressurser" (samme bakgrunn og hover).
* FIX: Sørget for at alle felt med lenker i "Andre tilbud" er oransje (#ff5722).
* REGEL: Beholder monokrome ikoner, 22/78 layout og robust JSON-parsing.
* FIX: Fjernet emojier/ikoner ved siden av "GJESTESPILL" og "MEDLEMSKAP".
* FIX: Forbedret Greenfee-logikk for å sikre at ALLE priser og baner (Østmork/Vestmork) vises.
* FIX: Skjuler under-header hvis den bare gjentar "Gjestespill".
* REGEL: Beholder monokrome ikoner, 22/78 layout og 0047-prefix telefon.
* ---------------------------------------------------------------------------
*/
@ -57,9 +56,17 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
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 gallery = parseJson(facility.gallery, [facility.image_url || FALLBACK_IMAGE]);
const greenfee = parseJson(facility.greenfee, []);
const greenfeeRaw = parseJson(facility.greenfee, []);
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 bane = curr.banenavn || "Gjestespill";
if (!acc[bane]) acc[bane] = [];
acc[bane].push(curr);
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 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";
@ -148,8 +155,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
{/* 4. 3-KOLONNE INFO */}
<section id="details" className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-8">
{/* KOLONNE 1: ANDRE RESSURSER (Ensrettet uttrykk & Sortering) */}
<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">
@ -175,13 +180,11 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
)}
{shotzoom.map((sz: any, i: number) => (
<a key={i} href={sz.shotzoom_url} target="_blank" className={resourceBtnClass}>
<span className="flex items-center gap-3"><Icon children={ICONS.chart} className="group-hover:text-white" /> Statistikk: {sz.shotzoom_beskrivelse?.replace(/&nbsp;/g, ' ').toUpperCase()}</span><span></span>
<span className="flex items-center gap-3"><Icon children={ICONS.chart} className="group-hover:text-white" /> Statistikk: {sz.shotzoom_beskrivelse?.replace(/&nbsp;?/g, ' ').trim().toUpperCase()}</span><span></span>
</a>
))}
</div>
</div>
{/* KOLONNE 2: BANEN */}
<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]">Banen</h3>
<div className="space-y-5">
@ -193,8 +196,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
<div className="flex justify-between"><span className="text-gray-400">Arkitekt:</span><span className="text-right truncate ml-4">{facility.architect || '--'}</span></div>
</div>
</div>
{/* KOLONNE 3: ANDRE TILBUD (Oransje lenker) */}
<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>
<div className="space-y-5">
@ -235,26 +236,58 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
</section>
)}
{/* 8. PRISER SEKSJON */}
{/* 8. PRISER & MEDLEMSKAP (IKONER FJERNET) */}
<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 border-b md:border-none">
<h3 className="text-2xl font-black mb-10 uppercase tracking-tighter"> Gjestespill</h3>
<div className="space-y-2">
{greenfee.length > 0 ? (
greenfee.map((g: any, i: number) => (
<div key={i} className="flex justify-between py-4 border-b border-gray-50 font-bold text-lg"><span className="text-gray-400">{g.priskategori}</span><span>kr {g.pris_voksne || '--'},-</span></div>
<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>
<div className="space-y-10">
{Object.keys(groupedGreenfee).length > 0 ? (
Object.entries(groupedGreenfee).map(([bane, priser], idx) => (
<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) && (
<h4 className="text-lg font-black uppercase tracking-tighter text-[#11280f] border-b-2 border-gray-50 pb-2">{bane}</h4>
)}
<div className="space-y-2">
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Voksne</p>
{priser.map((g, i) => (
<div key={i} className="flex justify-between py-2 border-b border-gray-50/50 text-sm font-bold">
<span className="text-gray-500">{g.priskategori}</span>
<span>kr {g.pris_voksne || '--'},-</span>
</div>
))}
</div>
{priser.some(g => g.pris_junior) && (
<div className="space-y-2 pt-4">
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Junior</p>
{priser.map((g, i) => (
<div key={i} className="flex justify-between py-2 border-b border-gray-50/50 text-sm font-bold">
<span className="text-gray-500">{g.priskategori}</span>
<span>kr {g.pris_junior || '--'},-</span>
</div>
))}
</div>
)}
</div>
))
) : <p className="text-gray-400 italic py-6">Ingen priser funnet.</p>}
</div>
<p className="mt-10 text-[10px] text-gray-300 font-black uppercase tracking-widest italic">Krav: {facility.guest_requirements || 'Klubbhandicap'}</p>
</div>
<div className="bg-white p-10 md:p-14 md:rounded-[3rem] shadow-sm border-b md:border-none flex flex-col justify-between">
<div className="bg-white p-10 md:p-14 md:rounded-[3rem] shadow-sm flex flex-col justify-between">
<div>
<h3 className="text-2xl font-black mb-10 uppercase tracking-tighter">💛 Medlemskap</h3>
<h3 className="text-2xl font-black mb-10 uppercase tracking-tighter">Medlemskap</h3>
<div className="bg-[#f1f7ed] p-12 md:rounded-[2.5rem] mb-8 text-center border border-[#7ca982]/10">
<p className="text-[#7ca982] text-[11px] font-black uppercase mb-3 tracking-widest">{facility.navn_standard_medlemskap || "Standard"}</p>
<p className="text-6xl font-black text-[#11280f]">kr {facility.standard_medlemskap || '--'},-</p>
{facility.standard_medlemskap_kommentarer && <p className="text-[10px] text-gray-400 mt-4 uppercase font-bold italic leading-tight">{facility.standard_medlemskap_kommentarer}</p>}
</div>
{facility.navn_rimeligste_alternativ && (
<div className="px-8 py-5 bg-gray-50 rounded-2xl border border-gray-100 flex justify-between items-center text-sm font-bold"><span className="text-gray-400 uppercase text-[10px] tracking-widest">{facility.navn_rimeligste_alternativ}</span><span className="text-[#11280f]">kr {facility.rimeligste_alternativ},-</span></div>
)}
</div>
<a href={facility.medlemskap_url} target="_blank" className="mt-10 block w-full text-center bg-[#11280f] text-white p-7 rounded-2xl font-black uppercase text-xs tracking-widest shadow-xl">Se alle alternativer</a>
<a href={facility.medlemskap_url} target="_blank" className="mt-10 block w-full text-center bg-[#11280f] text-white p-7 rounded-2xl font-black uppercase text-xs tracking-widest shadow-xl hover:bg-black transition-all">Se alle alternativer</a>
</div>
</section>
@ -271,7 +304,9 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
</section>
</div>
{showBackToTop && ( <button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className="fixed bottom-8 right-8 w-14 h-14 bg-[#11280f] text-white rounded-full shadow-2xl flex items-center justify-center text-2xl z-[100] border-4 border-white/20 hover:scale-110 transition-all"></button> )}
{showBackToTop && (
<button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className="fixed bottom-8 right-8 w-14 h-14 bg-[#11280f] text-white rounded-full shadow-2xl flex items-center justify-center text-2xl z-[100] border-4 border-white/20 hover:scale-110 transition-all"></button>
)}
</main>
);
}