Før fiks av toppslider og knapper
This commit is contained in:
parent
a7c17b7572
commit
95706260c6
3 changed files with 140 additions and 26 deletions
Binary file not shown.
79
backend/sync_greenfee.py
Normal file
79
backend/sync_greenfee.py
Normal 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())
|
||||
|
|
@ -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 på 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(/ /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(/ ?/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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue