diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index 665cc72..81e3fbe 100644 Binary files a/backend/__pycache__/main.cpython-311.pyc and b/backend/__pycache__/main.cpython-311.pyc differ diff --git a/backend/import_wp.py b/backend/import_wp.py index 99c8117..3eac73e 100644 --- a/backend/import_wp.py +++ b/backend/import_wp.py @@ -7,58 +7,34 @@ MEDIA_ENDPOINT = "https://teeoff.no/wp-json/wp/v2/media" MEDIA_DIR = "./public/media" os.makedirs(MEDIA_DIR, exist_ok=True) - -# Cache for Γ₯ slippe Γ₯ spΓΈrre om samme bilde-ID flere ganger media_cache = {} def get_url_from_id(media_id): - """SlΓ₯r opp en WordPress Media-ID og returnerer den faktiske bilde-URLen""" - if not media_id or not isinstance(media_id, int): - return None - - if media_id in media_cache: - return media_cache[media_id] - + if not media_id or not isinstance(media_id, int): return None + if media_id in media_cache: return media_cache[media_id] try: - print(f" πŸ” SlΓ₯r opp Media-ID: {media_id}...") resp = requests.get(f"{MEDIA_ENDPOINT}/{media_id}", timeout=10) if resp.status_code == 200: url = resp.json().get('source_url') media_cache[media_id] = url return url - except Exception as e: - print(f" ⚠️ Kunne ikke finne URL for Media-ID {media_id}: {e}") - - return None + except: return None def download_media(url, slug, prefix): - if not isinstance(url, str) or not url: - return None - - # Reparer "triple-slash" og andre WP-feil + if not isinstance(url, str) or not url: return None clean_url = url.replace("https:///", "https://").replace("http:///", "http://") - - if "teeoff.no" not in clean_url: - return clean_url - + if "teeoff.no" not in clean_url: return clean_url try: ext = clean_url.split('.')[-1].split('?')[0].lower() if len(ext) > 4 or len(ext) < 3: ext = "jpg" - filename = f"{prefix}_{slug}.{ext}" filepath = os.path.join(MEDIA_DIR, filename) - - if os.path.exists(filepath): - return f"/media/{filename}" - + if os.path.exists(filepath): return f"/media/{filename}" response = requests.get(clean_url, timeout=15) if response.status_code == 200: - with open(filepath, 'wb') as f: - f.write(response.content) + with open(filepath, 'wb') as f: f.write(response.content) return f"/media/{filename}" - except Exception as e: - print(f" ⚠️ Feil ved nedlasting: {e}") - + except: pass return None def decode_html(text): @@ -78,17 +54,19 @@ def extract_url(val): return None async def run_master_import(): - print("πŸš€ Starter MASTER IMPORT v8.9.2 (Media ID Resolver)...") + print("πŸš€ Starter MASTER IMPORT v9.2 (Robust datakonvertering & Banetype)...") conn = await asyncpg.connect(DB_URL) - await conn.execute("TRUNCATE facilities, courses, holes RESTART IDENTITY CASCADE;") + + # TΓΈmmer kun courses og holes (hjelpetabeller) + await conn.execute("TRUNCATE courses, holes RESTART IDENTITY CASCADE;") page = 1 while True: try: - req = urllib.request.Request(f"{WP_API_URL}&page={page}", headers={'User-Agent': 'TeeOff-V8.9.2'}) + req = urllib.request.Request(f"{WP_API_URL}&page={page}", headers={'User-Agent': 'TeeOff-V9.2'}) with urllib.request.urlopen(req) as response: data = json.loads(response.read().decode()) - except Exception: break + except: break if not data: break for post in data: @@ -97,85 +75,65 @@ async def run_master_import(): name = decode_html(post.get('title', {}).get('rendered', '')) print(f"πŸ“¦ Mapper {name}...") - # --- 1. HOVEDBILDE --- - featured_img = post.get('_embedded', {}).get('wp:featuredmedia', [{}])[0].get('source_url') - local_main_img = download_media(featured_img, slug, "main") + # Media & Identifiers + local_main_img = download_media(post.get('_embedded', {}).get('wp:featuredmedia', [{}])[0].get('source_url'), slug, "main") + local_logo = download_media(get_url_from_id(acf.get('logo')) if isinstance(acf.get('logo'), int) else extract_url(acf.get('logo')), slug, "logo") - # --- 2. LOGO --- - logo_field = acf.get('logo') - logo_url = extract_url(logo_field) - if not logo_url and isinstance(logo_field, int): - logo_url = get_url_from_id(logo_field) - local_logo = download_media(logo_url, slug, "logo") - - # --- 3. GALLERI (SLIDER) --- + # Galleri slides = acf.get('slides') or [] - local_gallery = [] - if isinstance(slides, list): - for idx, s in enumerate(slides): - url = None - if isinstance(s, int): # DIN CASE: Vi har en ID - url = get_url_from_id(s) - elif isinstance(s, dict): - url = s.get('url') - elif isinstance(s, str): - url = s - - if url: - res = download_media(url, f"{slug}_{idx}", "slide") - if res: local_gallery.append(res) + local_gallery = [download_media(get_url_from_id(s) if isinstance(s, int) else extract_url(s), f"{slug}_{i}", "slide") for i, s in enumerate(slides)] + local_gallery = [url for url in local_gallery if url] - # --- GOLFBOX & SOSIALE --- + # Golfbox booking_id = acf.get('golfbox_booking_id') gb_booking_url = f"http://www.golfbox.no/site/system/redirect.asp?locale=nb_NO&rUrl=%2Fsite%2Fressources%2Fbooking%2Fgrid.asp%3FRessource_GUID%3D%{{{str(booking_id).strip().replace('{','').replace('}','')}}}" if booking_id else None - - # --- INSERT FACILITY --- + + # --- UPSERT FACILITY --- + # Merk: $16 (status_updated_at) pakkes nΓ₯ inn i TO_DATE for Γ₯ unngΓ₯ krasj await conn.execute(''' INSERT INTO facilities ( - name, slug, description, established_year, season, address, city, county, - lat, lng, email, phone, website_url, image_url, amenities, greenfee, - status_updated_at, logo_url, video_url, guest_requirements, - faqs, shotzoom, gallery, ngf_number, golfbox_club_id, golfbox_booking_url, - facebook_url, instagram_url, baneguide_url, flyfoto_url, golfbox_tournament_url, - footnote, social_links, webcam_url, weather_url, architect, + name, slug, description, address, city, county, established_year, season, + email, phone, website_url, image_url, logo_url, video_url, + amenities, status_updated_at, gallery, banetype, + ngf_number, golfbox_club_id, golfbox_booking_url, + facebook_url, instagram_url, baneguide_url, flyfoto_url, + golfbox_tournament_url, footnote, social_links, webcam_url, + weather_url, architect, navn_standard_medlemskap, standard_medlemskap, standard_medlemskap_kommentarer, navn_rimeligste_alternativ, rimeligste_alternativ, rimeligste_alternativ_kommentarer, medlemskap_url - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15::jsonb, - $16::jsonb, TO_DATE(NULLIF($17, ''), 'YYYYMMDD'), - $18, $19, $20, $21::jsonb, $22::jsonb, $23::jsonb, - $24, $25, $26, $27, $28, $29, $30, $31, $32, $33::jsonb, $34, $35, $36, - $37, $38, $39, $40, $41, $42, $43) - ''', - name, slug, decode_html(acf.get('beskrivelse')), parse_int(acf.get('byggear')), acf.get('sesong'), - acf.get('gateadresse'), acf.get('postnummer_og_poststed'), acf.get('fylke'), - float(acf.get('banekart', {}).get('lat', 0)) if acf.get('banekart') else None, - float(acf.get('banekart', {}).get('lng', 0)) if acf.get('banekart') else None, - acf.get('e-post'), acf.get('telefon'), extract_url(acf.get('hjemmeside')), - local_main_img, - json.dumps({"drivingrange": decode_html(acf.get("drivingrange")), "treningsgreen": decode_html(acf.get("treningsgreen")), "proshop": decode_html(acf.get("proshop")), "kafe": decode_html(acf.get("kafe")), "bilutleie": decode_html(acf.get("bilutleie")), "kolleutleie": decode_html(acf.get("kolleutleie")), "pro": decode_html(acf.get("pro")), "simulator": decode_html(acf.get("golfsimulator")), "antall_hull": decode_html(acf.get("antall_hull"))}), - json.dumps(acf.get('greenfee_-_voksne') or []), - acf.get('dato_for_oppdatert_status'), local_logo, - None, decode_html(acf.get('krav_til_gjestespillere')), - json.dumps([]), json.dumps(acf.get('shotzoom') or []), json.dumps(local_gallery), - parse_int(acf.get('klubbnummer_norges_golfforbund')), parse_int(acf.get('klubbnummer_golfbox')), - gb_booking_url, extract_url(acf.get('facebook_url')), extract_url(acf.get('instagram_url')), - extract_url(acf.get('baneguide')), extract_url(acf.get('flyfoto')), extract_url(acf.get('golfbox')), - decode_html(acf.get('fotnote')), json.dumps(acf.get('sosiale_lenker') or []), - decode_html(acf.get('webkamera')), extract_url(acf.get('varmelding_yr')), decode_html(acf.get('arkitekt')), - decode_html(acf.get('navn_standard_medlemskap')), parse_int(acf.get('standard_medlemskap')), - decode_html(acf.get('standard_medlemskap_kommentarer')), decode_html(acf.get('navn_rimeligste_alternativ')), - parse_int(acf.get('rimeligste_alternativ')), decode_html(acf.get('rimeligste_alternativ_kommentarer')), - extract_url(acf.get('medlemskap_url'))) + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15::jsonb, + TO_DATE(NULLIF($16, ''), 'YYYYMMDD'), + $17::jsonb, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28::jsonb, + $29, $30, $31, $32, $33, $34, $35, $36, $37, $38 + ) + ON CONFLICT (slug) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + address = EXCLUDED.address, + city = EXCLUDED.city, + phone = EXCLUDED.phone, + email = EXCLUDED.email, + website_url = EXCLUDED.website_url, + image_url = EXCLUDED.image_url, + logo_url = EXCLUDED.logo_url, + amenities = EXCLUDED.amenities, + gallery = EXCLUDED.gallery, + status_updated_at = EXCLUDED.status_updated_at, + banetype = EXCLUDED.banetype, + architect = EXCLUDED.architect + ''', name, slug, decode_html(acf.get('beskrivelse')), acf.get('gateadresse'), acf.get('postnummer_og_poststed'), acf.get('fylke'), parse_int(acf.get('byggear')), acf.get('sesong'), acf.get('e-post'), acf.get('telefon'), extract_url(acf.get('hjemmeside')), local_main_img, local_logo, None, json.dumps({"drivingrange": decode_html(acf.get("drivingrange")), "treningsgreen": decode_html(acf.get("treningsgreen")), "proshop": decode_html(acf.get("proshop")), "kafe": decode_html(acf.get("kafe")), "bilutleie": decode_html(acf.get("bilutleie")), "kolleutleie": decode_html(acf.get("kolleutleie")), "pro": decode_html(acf.get("pro")), "simulator": decode_html(acf.get("golfsimulator")), "antall_hull": decode_html(acf.get("antall_hull"))}), + acf.get('dato_for_oppdatert_status'), # $16 + json.dumps(local_gallery), decode_html(acf.get('banetype')), + parse_int(acf.get('klubbnummer_norges_golfforbund')), parse_int(acf.get('klubbnummer_golfbox')), gb_booking_url, extract_url(acf.get('facebook_url')), extract_url(acf.get('instagram_url')), extract_url(acf.get('baneguide')), extract_url(acf.get('flyfoto')), extract_url(acf.get('golfbox')), decode_html(acf.get('fotnote')), json.dumps(acf.get('sosiale_lenker') or []), decode_html(acf.get('webkamera')), extract_url(acf.get('varmelding_yr')), decode_html(acf.get('arkitekt')), decode_html(acf.get('navn_standard_medlemskap')), parse_int(acf.get('standard_medlemskap')), decode_html(acf.get('standard_medlemskap_kommentarer')), decode_html(acf.get('navn_rimeligste_alternativ')), parse_int(acf.get('rimeligste_alternativ')), decode_html(acf.get('rimeligste_alternativ_kommentarer')), extract_url(acf.get('medlemskap_url'))) - # Hent facility id for baner - fac_row = await conn.fetchrow("SELECT id FROM facilities WHERE slug = $1", slug) - fac_id = fac_row['id'] + fac_id = (await conn.fetchrow("SELECT id FROM facilities WHERE slug = $1", slug))['id'] - # --- BANER OG HULL (Samme som fΓΈr) --- + # Baner og Hull fac_main_len = 0 for suffix in ['', '_bane_to']: - c_name = acf.get('navn_pa_hovedbane' if suffix == '' else 'navn_pa_sekundar_bane') or ('Hovedbane' if suffix == '' else 'Bane 2') + c_name = acf.get('navn_pa_hovedbane' if suffix == '' else 'navn_pa_sekundar_bane') or ('Hovedbanen' if suffix == '' else 'Bane 2') status = acf.get('banestatus' if suffix == '' else 'banestatus_sekundar_bane') if suffix == '_bane_to' and (status == 'finnes_ingen_bane_to' or not parse_int(acf.get('hull_1_par_bane_to'))): continue course_id = await conn.fetchval('INSERT INTO courses (facility_id, name, status, par, is_main_course, tee_boxes, architect) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id', fac_id, c_name, status, parse_int(acf.get('totalt_par' if suffix == '' else 'totalt_par_bane_to')), (suffix == ''), json.dumps({"herrer": acf.get(f"utslag_herrer{suffix}"), "damer": acf.get(f"utslag_damer{suffix}")}), decode_html(acf.get('arkitekt'))) @@ -190,9 +148,10 @@ async def run_master_import(): await conn.execute("UPDATE courses SET length_meters = $1 WHERE id = $2", curr_len, course_id) if suffix == '': fac_main_len = curr_len await conn.execute("UPDATE facilities SET length_meters = $1 WHERE id = $2", fac_main_len, fac_id) + page += 1 await conn.close() - print("βœ… GALLERI-BILDER RESOLVED OG IMPORT FERDIG!") + print("βœ… IMPORT FERDIG!") if __name__ == "__main__": asyncio.run(run_master_import()) \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 5a69052..5b37976 100644 --- a/backend/main.py +++ b/backend/main.py @@ -9,27 +9,53 @@ DB_URL = "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff" @asynccontextmanager async def lifespan(app: FastAPI): - app.state.pool = await asyncpg.create_pool(DB_URL, min_size=5, max_size=20) + try: + app.state.pool = await asyncpg.create_pool(DB_URL, min_size=5, max_size=20) + print("βœ… Database tilkoblet og pool opprettet") + except Exception as e: + print(f"❌ Databasefeil: {e}") + raise e yield await app.state.pool.close() -app = FastAPI(lifespan=lifespan) -app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) +app = FastAPI(title="TeeOff API v2.4", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) def format_row(row): if row is None: return None d = dict(row) - for key in ['status_updated_at', 'created_at']: - if isinstance(d.get(key), (date, datetime)): d[key] = d[key].isoformat() - # JSONB vask - Mapper direkte mot kolonnene i struktur_dump.txt - json_fields = ['amenities', 'greenfee', 'membership', 'vtg', 'faqs', 'shotzoom', 'translations', 'course_statuses', 'courses', 'gallery', 'nsg_data', 'golfamore_data'] - for field in json_fields: + # 1. Dato-hΓ₯ndtering + for key in ['status_updated_at', 'created_at']: + if isinstance(d.get(key), (date, datetime)): + d[key] = d[key].isoformat() + + # 2. Garanter riktige datatyper (Vaskeliste) + list_fields = ['course_statuses', 'courses', 'gallery', 'greenfee', 'faqs', 'shotzoom', 'social_links', 'holes'] + dict_fields = ['amenities', 'vtg', 'nsg_data', 'golfamore_data'] + + for field in list_fields: if field in d: - if isinstance(d[field], str): + if d[field] is None: + d[field] = [] + elif isinstance(d[field], str): try: d[field] = json.loads(d[field]) - except: pass - if d[field] is None: d[field] = [] if field in ['courses', 'faqs', 'gallery'] else {} + except: d[field] = [] + + for field in dict_fields: + if field in d: + if d[field] is None: + d[field] = {} + elif isinstance(d[field], str): + try: d[field] = json.loads(d[field]) + except: d[field] = {} + return d @app.get("/api/facilities") @@ -39,7 +65,7 @@ async def get_facilities(): SELECT f.*, ( SELECT jsonb_agg(cs) FROM ( SELECT name, status FROM courses - WHERE facility_id = f.id AND status NOT IN ('finnes_ingen_bane_to', 'nedlagt') + WHERE facility_id = f.id AND status != 'finnes_ingen_bane_to' ORDER BY is_main_course DESC, id ASC ) cs ) as course_statuses @@ -59,11 +85,11 @@ async def get_facility(slug: str): ) as holes FROM courses c WHERE c.facility_id = f.id - AND (c.is_main_course = true OR (c.status NOT IN ('finnes_ingen_bane_to', 'nedlagt', 'ukjent'))) + AND (c.is_main_course = true OR (c.status NOT IN ('finnes_ingen_bane_to', 'ukjent'))) ORDER BY c.is_main_course DESC, c.id ASC ) c_data ) as courses FROM facilities f WHERE f.slug = $1 """, slug) if not row: raise HTTPException(status_code=404) - return format_row(row) + return format_row(row) \ No newline at end of file diff --git a/eksport_script.py b/eksport_script.py index 3561194..288a706 100644 --- a/eksport_script.py +++ b/eksport_script.py @@ -4,8 +4,8 @@ from pathlib import Path # --- KONFIGURASJON --- KILDE_MAPPE = "/opt/teeoff/" -EKSPORT_MAPPE = "/opt/teeoff/kode_eksport/" -TRE_FIL = "/opt/teeoff/filtre.txt" +EKSPORT_MAPPE = "/opt/teeoff/kode_eksport_1/" +TRE_FIL = "/opt/teeoff/fil-tre.txt" # Filtyper vi vil kopiere FILTYPER = ['.py', '.ts', '.tsx'] diff --git a/fil-tre.txt b/fil-tre.txt new file mode 100644 index 0000000..7c5984a --- /dev/null +++ b/fil-tre.txt @@ -0,0 +1,705 @@ + πŸ“ teeoff/ + πŸ“„ filtre.txt + πŸ“„ seed.sql + πŸ“„ teeoff_backup_2.sql + πŸ“„ eksport_script.py + πŸ“„ update_golfbox.sql + πŸ“„ struktur_dump.txt + πŸ“„ teeoff_backup_1.sql + πŸ“„ teeoff_backup.sql + πŸ“„ docker-compose.yml + πŸ“„ schema.sql + πŸ“„ init.sql +πŸ“ frontend/ + πŸ“„ eslint.config.mjs + πŸ“„ next-env.d.ts + πŸ“„ tsconfig.json + πŸ“„ README.md + πŸ“„ next.config.ts + πŸ“„ postcss.config.mjs + πŸ“„ package-lock.json + πŸ“„ .gitignore + πŸ“„ package.json + πŸ“„ Dockerfile + πŸ“ public/ + πŸ“„ globe.svg + πŸ“„ vercel.svg + πŸ“„ Toppbilde-standard.jpg + πŸ“„ TeeOff-logo-Retina-1.png + πŸ“„ window.svg + πŸ“„ next.svg + πŸ“„ file.svg + πŸ“ media/ + πŸ“„ slide_naeroysund-golfklubb_5.jpg + πŸ“„ main_hakadal-golfklubb.jpg + πŸ“„ slide_baerum-golfklubb_1.jpg + πŸ“„ slide_egersund-golfklubb_7.jpg + πŸ“„ main_hammerfest-og-kvalsund-golfklubb.jpg + πŸ“„ main_odda-golfklubb.jpg + πŸ“„ slide_holtsmark-golfklubb_1.jpg + πŸ“„ main_voss-golfklubb.jpeg + πŸ“„ slide_soon-golfklubb_2.jpg + πŸ“„ logo_ogna-golfklubb.jpg + πŸ“„ slide_tyrifjord-golfklubb_4.jpg + πŸ“„ slide_tjome-golfklubb_2.jpg + πŸ“„ logo_garder-golfklubb.jpg + πŸ“„ slide_helgeland-golfklubb_1.jpg + πŸ“„ slide_kvinnherad-golfklubb_0.jpg + πŸ“„ main_rauma-golfklubb.jpg + πŸ“„ slide_naeroysund-golfklubb_0.jpg + πŸ“„ slide_hallingdal-golfklubb_1.jpeg + πŸ“„ slide_hakadal-golfklubb_13.jpg + πŸ“„ slide_sunndal-golfklubb_2.jpg + πŸ“„ logo_skjeberg-golfklubb.jpg + πŸ“„ slide_larvik-golfklubb_0.jpg + πŸ“„ logo_tingvoll-golfklubb.jpg + πŸ“„ slide_sandefjord-golfklubb_4.jpg + πŸ“„ logo_onsoy-golfklubb.jpg + πŸ“„ logo_oppdal-golfklubb.jpg + πŸ“„ main_nordhaug-golfklubb.jpg + πŸ“„ main_hof-golfklubb.jpg + πŸ“„ main_hauger-golfklubb.jpg + πŸ“„ slide_vradal-golfklubb_1.jpg + πŸ“„ slide_holtsmark-golfklubb_5.jpg + πŸ“„ main_baerum-golfklubb.jpg + πŸ“„ logo_huseby-hanko-golfklubb.png + πŸ“„ slide_solum-golfklubb_4.jpg + πŸ“„ logo_hardanger-golfklubb.png + πŸ“„ logo_kristiansund-og-omegn-golfklubb.jpg + πŸ“„ main_byneset-golf.jpg + πŸ“„ logo_laerdal-golfklubb.jpg + πŸ“„ slide_kjekstad-golfklubb_0.jpg + πŸ“„ main_namdal-golfklubb.jpg + πŸ“„ main_hallingdal-golfklubb.jpg + πŸ“„ logo_ibestad-golfklubb.jpg + πŸ“„ logo_alta-golfklubb.png + πŸ“„ slide_norsjo-golfklubb_4.jpg + πŸ“„ main_elverum-golfklubb.jpg + πŸ“„ logo_hammerfest-og-kvalsund-golfklubb.jpg + πŸ“„ main_garder-golfklubb.jpg + πŸ“„ logo_lommedalen-golfklubb.jpg + πŸ“„ slide_ski-golfklubb_1.jpg + πŸ“„ logo_gronmo-golfklubb.jpg + πŸ“„ logo_hvam-golfklubb.jpg + πŸ“„ main_nesbyen-golfklubb.jpg + πŸ“„ slide_naeroysund-golfklubb_2.jpg + πŸ“„ slide_tjome-golfklubb_0.jpg + πŸ“„ logo_miklagard-golfklubb.png + πŸ“„ main_nordfjord-golfklubb.jpg + πŸ“„ slide_bjaavann-golfklubb_1.jpg + πŸ“„ main_jaeren-golfklubb.jpg + πŸ“„ slide_salten-golfklubb-bodo-golfpark_1.jpg + πŸ“„ slide_larvik-golfklubb_1.jpg + πŸ“„ main_modum-golfklubb.jpg + πŸ“„ logo_volda-golfklubb.jpg + πŸ“„ main_grini-golfklubb.jpg + πŸ“„ slide_aurskog-golfpark_0.jpg + πŸ“„ main_miklagard-golfklubb.jpg + πŸ“„ main_bjaavann-golfklubb.jpg + πŸ“„ main_fet-golfklubb.jpg + πŸ“„ slide_atlungstad-golfklubb_2.jpg + πŸ“„ logo_kvinnherad-golfklubb.jpg + πŸ“„ slide_borregaard-golfklubb_1.jpg + πŸ“„ slide_tjome-golfklubb_4.jpg + πŸ“„ main_rjukan-og-tinn-golfklubb.jpg + πŸ“„ slide_kjekstad-golfklubb_1.jpg + πŸ“„ slide_norsjo-golfklubb_3.jpg + πŸ“„ logo_surnadal-golfklubb.png + πŸ“„ slide_kongsberg-golfklubb_0.jpg + πŸ“„ slide_hovden-golfklubb_1.jpg + πŸ“„ main_hemsedal-golfklubb.jpg + πŸ“„ slide_tyrifjord-golfklubb_5.jpg + πŸ“„ logo_alesund-golfklubb.jpg + πŸ“„ slide_tingvoll-golfklubb_0.jpg + πŸ“„ slide_tyrifjord-golfklubb_2.jpg + πŸ“„ slide_bleik-golfstrombane_0.jpg + πŸ“„ slide_nesbyen-golfklubb_0.jpg + πŸ“„ main_sunnfjord-golfklubb.jpg + πŸ“„ slide_hasvik-golfklubb_0.jpg + πŸ“„ logo_stavanger-golfklubb.png + πŸ“„ slide_ostmarka-golfklubb_3.jpg + πŸ“„ main_land-golfklubb.jpg + πŸ“„ slide_helgeland-golfklubb_5.jpg + πŸ“„ logo_bergen-golfklubb.png + πŸ“„ slide_lofoten-golfklubb_1.jpg + πŸ“„ logo_rjukan-og-tinn-golfklubb.jpg + πŸ“„ main_alta-golfklubb.jpg + πŸ“„ slide_asker-golfklubb_1.jpg + πŸ“„ slide_krokhol-golfklubb_5.jpg + πŸ“„ logo_nordvegen-golfklubb.png + πŸ“„ logo_kristiansand-golfklubb.jpg + πŸ“„ main_asker-golfklubb.jpg + πŸ“„ slide_sleneset-golfklubb_0.jpg + πŸ“„ logo_re-golfklubb.jpg + πŸ“„ slide_oustoen-country-club_0.jpg + πŸ“„ logo_hallingdal-golfklubb.png + πŸ“„ logo_fet-golfklubb.jpg + πŸ“„ slide_trondheim-golfklubb_3.jpg + πŸ“„ slide_borregaard-golfklubb_3.jpg + πŸ“„ main_hovden-golfklubb.jpg + πŸ“„ slide_ostmarka-golfklubb_2.jpg + πŸ“„ slide_sandane-golfklubb_2.jpg + πŸ“„ slide_trondheim-golfklubb_6.jpg + πŸ“„ slide_helgeland-golfklubb_9.jpg + πŸ“„ logo_borre-golfklubb.png + πŸ“„ main_karasjok-golfklubb.jpg + πŸ“„ slide_hallingdal-golfklubb_2.jpeg + πŸ“„ main_north-cape-golf-club.jpg + πŸ“„ logo_selbu-golfklubb.png + πŸ“„ logo_sola-golfklubb-forus.jpg + πŸ“„ slide_asker-golfklubb_0.jpg + πŸ“„ main_hafjell-golfklubb.jpg + πŸ“„ logo_mjosen-golfklubb.png + πŸ“„ slide_atlungstad-golfklubb_0.jpg + πŸ“„ main_moa-golfsenter.jpg + πŸ“„ slide_bleik-golfstrombane_7.jpg + πŸ“„ main_kvinnherad-golfklubb.jpg + πŸ“„ main_re-golfklubb.jpg + πŸ“„ logo_haga-golfklubb.jpg + πŸ“„ main_roros-golfklubb.jpg + πŸ“„ main_vestfold-golfklubb.jpg + πŸ“„ slide_egersund-golfklubb_2.jpg + πŸ“„ slide_volda-golfklubb_2.jpeg + πŸ“„ slide_egersund-golfklubb_5.jpg + πŸ“„ main_gjersjoen-golfklubb.jpg + πŸ“„ main_gamle-fredrikstad-golfklubb.jpg + πŸ“„ slide_naeroysund-golfklubb_3.jpg + πŸ“„ slide_oustoen-country-club_1.jpg + πŸ“„ logo_hitra-golfklubb.png + πŸ“„ logo_rygge-flystasjon-golf-klubb.jpg + πŸ“„ slide_tyrifjord-golfklubb_9.jpg + πŸ“„ slide_trondheim-golfklubb_5.jpg + πŸ“„ slide_aurskog-golfpark_1.jpg + πŸ“„ main_sola-golfklubb-solastranden.jpg + πŸ“„ logo_trysil-golfklubb.jpg + πŸ“„ logo_harstad-golfklubb.jpg + πŸ“„ slide_ogna-golfklubb_1.jpg + πŸ“„ logo_gamle-fredrikstad-golfklubb.jpg + πŸ“„ main_stranda-golfklubb.jpg + πŸ“„ logo_sauda-golfklubb.png + πŸ“„ slide_egersund-golfklubb_0.jpg + πŸ“„ logo_solum-golfklubb.jpg + πŸ“„ main_bjornefjorden-golfklubb.jpg + πŸ“„ main_vestlia-golf.jpg + πŸ“„ slide_sandefjord-golfklubb_1.jpg + πŸ“„ logo_borregaard-golfklubb.jpg + πŸ“„ main_grimstad-golfklubb.jpg + πŸ“„ logo_vesteralen-golfklubb.png + πŸ“„ main_kristiansund-og-omegn-golfklubb.jpg + πŸ“„ slide_hallingdal-golfklubb_3.jpeg + πŸ“„ logo_sandnes-golfklubb.jpg + πŸ“„ main_alesund-golfklubb.jpeg + πŸ“„ main_steinkjer-golfklubb.jpg + πŸ“„ main_skei-golfklubb.jpg + πŸ“„ slide_nesbyen-golfklubb_3.jpg + πŸ“„ slide_hardanger-golfklubb_0.jpg + πŸ“„ slide_gumoy-golf_1.jpg + πŸ“„ main_grenland-og-omegn-golfklubb.jpg + πŸ“„ main_karmoy-golfklubb.jpg + πŸ“„ logo_egersund-golfklubb.jpg + πŸ“„ logo_baerum-golfklubb.png + πŸ“„ main_kristiansand-golfklubb.jpg + πŸ“„ slide_kongsberg-golfklubb_2.jpg + πŸ“„ slide_gumoy-golf_3.jpg + πŸ“„ logo_smola-golfklubb.jpg + πŸ“„ logo_grimstad-golfklubb.png + πŸ“„ logo_stord-golfklubb.png + πŸ“„ slide_helgeland-golfklubb_6.jpg + πŸ“„ logo_rauma-golfklubb.png + πŸ“„ logo_hinnoy-golfklubb.jpg + πŸ“„ slide_holtsmark-golfklubb_0.jpg + πŸ“„ logo_varanger-golfklubb.jpg + πŸ“„ logo_mandal-golfklubb.jpg + πŸ“„ slide_trondheim-golfklubb_1.jpg + πŸ“„ logo_tjome-golfklubb.png + πŸ“„ slide_lofoten-golfklubb_2.jpg + πŸ“„ main_haga-golfklubb.jpg + πŸ“„ slide_bjaavann-golfklubb_2.jpg + πŸ“„ slide_hakadal-golfklubb_8.jpg + πŸ“„ logo_herdla-golfklubb.jpg + πŸ“„ main_stavanger-golfklubb.jpg + πŸ“„ main_lommedalen-golfklubb.jpg + πŸ“„ main_sirdal-fjellgolf-klubb.jpg + πŸ“„ main_alsten-golfklubb.jpg + πŸ“„ logo_lillestrom-golfklubb.png + πŸ“„ main_ogna-golfklubb.jpg + πŸ“„ slide_helgeland-golfklubb_8.jpg + πŸ“„ slide_nesbyen-golfklubb_2.jpg + πŸ“„ main_tjome-golfklubb.jpg + πŸ“„ main_sandefjord-golfklubb.jpg + πŸ“„ main_namsos-golfklubb.jpg + πŸ“„ logo_notteroy-golfklubb.jpg + πŸ“„ main_trondheim-golfklubb.jpg + πŸ“„ slide_solum-golfklubb_1.jpg + πŸ“„ slide_kongsberg-golfklubb_4.jpg + πŸ“„ main_utsikten-golfklubb.jpg + πŸ“„ main_vesteralen-golfklubb.jpg + πŸ“„ slide_sandefjord-golfklubb_0.jpg + πŸ“„ slide_hakadal-golfklubb_9.jpg + πŸ“„ logo_polarsirkelen-golfklubb.jpg + πŸ“„ logo_sandane-golfklubb.jpg + πŸ“„ slide_groruddalen-golfklubb_2.jpg + πŸ“„ main_ski-golfklubb.jpg + πŸ“„ logo_bjornefjorden-golfklubb.png + πŸ“„ main_polarsirkelen-golfklubb.jpg + πŸ“„ slide_krokhol-golfklubb_1.jpg + πŸ“„ main_groruddalen-golfklubb.jpg + πŸ“„ slide_krokhol-golfklubb_4.jpg + πŸ“„ main_vanylven-golfklubb.jpg + πŸ“„ main_borregaard-golfklubb.jpg + πŸ“„ slide_sandefjord-golfklubb_5.jpg + πŸ“„ slide_helgeland-golfklubb_2.jpg + πŸ“„ main_hinnoy-golfklubb.jpg + πŸ“„ slide_sunndal-golfklubb_0.jpg + πŸ“„ logo_groruddalen-golfklubb.png + πŸ“„ main_tingvoll-golfklubb.jpg + πŸ“„ slide_tjome-golfklubb_3.jpg + πŸ“„ main_lindesnes-golfklubb.jpg + πŸ“„ slide_kongsvingers-golfklubb_5.jpg + πŸ“„ slide_tromso-golfklubb_0.jpg + πŸ“„ main_randaberg-golfklubb.jpg + πŸ“„ logo_krokhol-golfklubb.png + πŸ“„ main_smola-golfklubb.jpg + πŸ“„ logo_sande-golfklubb.jpg + πŸ“„ logo_oslo-golfklubb.jpg + πŸ“„ logo_preikestolen.jpg + πŸ“„ logo_sunnfjord-golfklubb.jpg + πŸ“„ slide_lofoten-golfklubb_3.jpg + πŸ“„ main_bleik-golfstrombane.jpg + πŸ“„ slide_gumoy-golf_0.jpg + πŸ“„ logo_modum-golfklubb.png + πŸ“„ slide_tingvoll-golfklubb_1.jpg + πŸ“„ logo_bodo-golfklubb.jpg + πŸ“„ logo_nesbyen-golfklubb.png + πŸ“„ main_selbu-golfklubb.jpg + πŸ“„ slide_holtsmark-golfklubb_2.jpg + πŸ“„ logo_imjelt-pitch-putt.jpg + πŸ“„ logo_ullensaker-golfklubb.png + πŸ“„ slide_elverum-golfklubb_1.jpg + πŸ“„ logo_ringerike-golfklubb.jpg + πŸ“„ slide_hakadal-golfklubb_6.jpg + πŸ“„ slide_volda-golfklubb_4.jpg + πŸ“„ slide_tyrifjord-golfklubb_6.jpg + πŸ“„ main_solum-golfklubb.jpg + πŸ“„ slide_helgeland-golfklubb_4.jpg + πŸ“„ main_stiklestad-golfklubb.jpg + πŸ“„ slide_stavanger-golfklubb_0.jpg + πŸ“„ logo_vestlia-golf.jpg + πŸ“„ slide_vestfold-golfklubb_0.jpg + πŸ“„ logo_floro-golfklubb.jpg + πŸ“„ logo_voss-golfklubb.jpg + πŸ“„ slide_tyrifjord-golfklubb_8.jpg + πŸ“„ slide_sotra-golfklubb_2.jpg + πŸ“„ logo_jaeren-golfklubb.png + πŸ“„ slide_moss-rygge-golfklubb_0.jpg + πŸ“„ logo_asker-golfklubb.png + πŸ“„ slide_bleik-golfstrombane_6.jpg + πŸ“„ slide_bjaavann-golfklubb_3.jpg + πŸ“„ main_hvam-golfklubb.jpg + πŸ“„ logo_eiker-golfklubb.jpg + πŸ“„ slide_bleik-golfstrombane_9.jpg + πŸ“„ slide_namdal-golfklubb_0.jpg + πŸ“„ logo_nordhaug-golfklubb.jpg + πŸ“„ slide_borregaard-golfklubb_0.jpg + πŸ“„ slide_kongsberg-golfklubb_5.jpg + πŸ“„ main_hardanger-golfklubb.jpg + πŸ“„ slide_ostmarka-golfklubb_5.jpg + πŸ“„ logo_oppegard-golfklubb.png + πŸ“„ slide_vradal-golfklubb_0.jpg + πŸ“„ main_narvik-golfklubb.jpg + πŸ“„ logo_midt-troms-golfklubb.jpg + πŸ“„ slide_solum-golfklubb_2.jpg + πŸ“„ slide_sunndal-golfklubb_1.jpg + πŸ“„ slide_gjersjoen-golfklubb_1.jpg + πŸ“„ slide_preikestolen_0.jpg + πŸ“„ main_huseby-hanko-golfklubb.jpg + πŸ“„ slide_holtsmark-golfklubb_4.jpg + πŸ“„ main_larvik-golfklubb.jpg + πŸ“„ slide_hasvik-golfklubb_1.jpg + πŸ“„ slide_herdla-golfklubb_1.jpg + πŸ“„ logo_kjekstad-golfklubb.jpg + πŸ“„ main_austratt-golfklubb.jpg + πŸ“„ slide_sandefjord-golfklubb_3.jpg + πŸ“„ logo_hakadal-golfklubb.png + πŸ“„ slide_bleik-golfstrombane_3.jpg + πŸ“„ logo_vanylven-golfklubb.jpg + πŸ“„ main_skjeberg-golfklubb.jpg + πŸ“„ slide_hovden-golfklubb_3.jpg + πŸ“„ slide_ostmarka-golfklubb_0.jpg + πŸ“„ logo_tromso-golfklubb.png + πŸ“„ main_stjordal-golfklubb.jpg + πŸ“„ slide_frosta-golfklubb_3.jpg + πŸ“„ main_nordvegen-golfklubb.jpg + πŸ“„ slide_norsjo-golfklubb_1.jpg + πŸ“„ logo_gumoy-golf.png + πŸ“„ slide_gronmo-golfklubb_2.jpg + πŸ“„ slide_hardanger-golfklubb_1.jpg + πŸ“„ main_oppdal-golfklubb.jpg + πŸ“„ slide_trondheim-golfklubb_0.jpg + πŸ“„ main_lillestrom-golfklubb.jpg + πŸ“„ slide_gjersjoen-golfklubb_0.jpg + πŸ“„ slide_soon-golfklubb_1.jpg + πŸ“„ logo_vestfold-golfklubb.jpg + πŸ“„ slide_stavanger-golfklubb_1.jpg + πŸ“„ main_norefjell-golfklubb.jpg + πŸ“„ main_kongsberg-golfklubb.jpg + πŸ“„ slide_gjovik-og-toten-golfklubb_2.jpg + πŸ“„ logo_veierland-golfklubb.jpg + πŸ“„ slide_soon-golfklubb_0.jpg + πŸ“„ slide_egersund-golfklubb_6.jpg + πŸ“„ slide_norsjo-golfklubb_0.jpg + πŸ“„ slide_kristiansand-golfklubb_0.jpg + πŸ“„ main_hurum-golfklubb.jpg + πŸ“„ main_sola-golfklubb-forus.jpg + πŸ“„ logo_mork-golfklubb.jpg + πŸ“„ logo_molde-golfklubb.png + πŸ“„ main_kongsvingers-golfklubb.jpg + πŸ“„ main_sunndal-golfklubb.jpg + πŸ“„ logo_utsikten-golfklubb.jpg + πŸ“„ logo_austratt-golfklubb.jpg + πŸ“„ logo_selje-golfklubb.jpg + πŸ“„ main_stord-golfklubb.jpg + πŸ“„ logo_sorknes-golfklubb.jpg + πŸ“„ slide_krokhol-golfklubb_3.jpg + πŸ“„ slide_hovden-golfklubb_4.jpg + πŸ“„ main_preikestolen.jpg + πŸ“„ logo_aurskog-golfpark.png + πŸ“„ main_aurskog-golfpark.jpg + πŸ“„ slide_mork-golfklubb_1.jpg + πŸ“„ logo_roros-golfklubb.jpg + πŸ“„ main_harstad-golfklubb.jpg + πŸ“„ main_helgeland-golfklubb.jpg + πŸ“„ slide_ogna-golfklubb_0.jpg + πŸ“„ slide_kvinnherad-golfklubb_1.jpg + πŸ“„ logo_haugesund-golfklubb.png + πŸ“„ slide_bjaavann-golfklubb_0.jpg + πŸ“„ slide_hakadal-golfklubb_3.jpg + πŸ“„ slide_ostmarka-golfklubb_6.jpg + πŸ“„ slide_bleik-golfstrombane_11.jpg + πŸ“„ logo_moss-rygge-golfklubb.png + πŸ“„ slide_borregaard-golfklubb_2.jpg + πŸ“„ main_lofoten-golfklubb.jpg + πŸ“„ slide_bleik-golfstrombane_5.jpg + πŸ“„ logo_oustoen-country-club.jpg + πŸ“„ main_oslo-golfklubb.jpg + πŸ“„ slide_gjovik-og-toten-golfklubb_0.jpg + πŸ“„ slide_frosta-golfklubb_1.jpg + πŸ“„ slide_ostmarka-golfklubb_7.jpg + πŸ“„ slide_helgeland-golfklubb_3.jpg + πŸ“„ logo_vradal-golfklubb.png + πŸ“„ main_salten-golfklubb-bodo-golfpark.jpg + πŸ“„ main_rygge-flystasjon-golf-klubb.jpg + πŸ“„ slide_trondheim-golfklubb_4.jpg + πŸ“„ main_meland-golfklubb.jpg + πŸ“„ slide_sotra-golfklubb_1.jpg + πŸ“„ main_sandane-golfklubb.jpg + πŸ“„ slide_baerum-golfklubb_0.jpg + πŸ“„ slide_egersund-golfklubb_3.jpg + πŸ“„ slide_frosta-golfklubb_5.jpg + πŸ“„ logo_sandefjord-golfklubb.png + πŸ“„ slide_egersund-golfklubb_4.jpg + πŸ“„ slide_sandane-golfklubb_1.jpg + πŸ“„ slide_bleik-golfstrombane_2.jpg + πŸ“„ main_eiker-golfklubb.jpg + πŸ“„ main_tromso-golfklubb.jpg + πŸ“„ logo_tysnes-golfklubb.jpg + πŸ“„ slide_hallingdal-golfklubb_4.jpeg + πŸ“„ main_ringerike-golfklubb.jpg + πŸ“„ main_norsjo-golfklubb.jpg + πŸ“„ logo_byneset-golf.jpg + πŸ“„ logo_land-golfklubb.png + πŸ“„ slide_bleik-golfstrombane_8.jpg + πŸ“„ logo_losby-golfklubb.png + πŸ“„ logo_husoy-golfklubb.png + πŸ“„ main_nittedal-golfklubb.jpg + πŸ“„ logo_trondheim-par3golf-havstein.png + πŸ“„ main_mork-golfklubb.jpg + πŸ“„ slide_sleneset-golfklubb_1.jpg + πŸ“„ logo_ekholtbruket-golfklubb.jpg + πŸ“„ slide_hauger-golfklubb_1.jpg + πŸ“„ main_borre-golfklubb.jpg + πŸ“„ main_kragero-golfklubb.jpg + πŸ“„ main_lonne-golfklubb.jpg + πŸ“„ logo_namsos-golfklubb.jpg + πŸ“„ slide_hallingdal-golfklubb_0.jpeg + πŸ“„ slide_solum-golfklubb_0.jpg + πŸ“„ main_holtsmark-golfklubb.jpg + πŸ“„ main_oustoen-country-club.jpg + πŸ“„ slide_hakadal-golfklubb_12.jpg + πŸ“„ slide_gronmo-golfklubb_1.jpg + πŸ“„ main_mjosen-golfklubb.jpg + πŸ“„ logo_sunndal-golfklubb.jpg + πŸ“„ logo_larvik-golfklubb.jpg + πŸ“„ slide_ekholtbruket-golfklubb_0.jpg + πŸ“„ main_husoy-golfklubb.jpg + πŸ“„ logo_drammen-golfklubb.png + πŸ“„ logo_helgeland-golfklubb.png + πŸ“„ main_gjerdrum-golfklubb.jpg + πŸ“„ slide_sandefjord-golfklubb_2.jpg + πŸ“„ main_hvaler-golfklubb.jpg + πŸ“„ logo_sunnmore-golfklubb.jpg + πŸ“„ slide_hauger-golfklubb_0.jpg + πŸ“„ main_naeroysund-golfklubb.jpg + πŸ“„ slide_hakadal-golfklubb_5.jpg + πŸ“„ logo_bamble-golfklubb.png + πŸ“„ slide_sandane-golfklubb_0.jpg + πŸ“„ main_bodo-golfklubb.jpeg + πŸ“„ main_drammen-golfklubb.jpg + πŸ“„ slide_frosta-golfklubb_4.jpg + πŸ“„ slide_solum-golfklubb_3.jpg + πŸ“„ slide_bleik-golfstrombane_1.jpg + πŸ“„ main_trondheim-par3golf-havstein.jpg + πŸ“„ logo_kongsvingers-golfklubb.jpg + πŸ“„ slide_volda-golfklubb_3.jpeg + πŸ“„ slide_tyrifjord-golfklubb_3.jpg + πŸ“„ slide_hakadal-golfklubb_2.jpg + πŸ“„ slide_hovden-golfklubb_2.jpg + πŸ“„ slide_kongsvingers-golfklubb_4.jpg + πŸ“„ main_arendal-omegn-golfklubb.jpg + πŸ“„ logo_bjaavann-golfklubb.png + πŸ“„ main_sande-golfklubb.jpg + πŸ“„ main_ballerud-golfklubb.jpg + πŸ“„ logo_norefjell-golfklubb.png + πŸ“„ slide_kristiansand-golfklubb_1.jpg + πŸ“„ main_veierland-golfklubb.jpg + πŸ“„ logo_karmoy-golfklubb.png + πŸ“„ logo_lofoten-golfklubb.png + πŸ“„ slide_lofoten-golfklubb_0.jpg + πŸ“„ logo_hurum-golfklubb.png + πŸ“„ slide_holtsmark-golfklubb_3.jpg + πŸ“„ logo_hemsedal-golfklubb.jpg + πŸ“„ slide_borregaard-golfklubb_5.jpg + πŸ“„ logo_haugaland-golfklubb.png + πŸ“„ slide_bjornefjorden-golfklubb_1.jpg + πŸ“„ main_notteroy-golfklubb.jpg + πŸ“„ slide_kongsvingers-golfklubb_3.jpg + πŸ“„ slide_kongsvingers-golfklubb_2.jpg + πŸ“„ main_fana-golfklubb.jpg + πŸ“„ logo_giske-golfklubb.jpg + πŸ“„ slide_hakadal-golfklubb_11.jpg + πŸ“„ main_nes-golfklubb-09.jpg + πŸ“„ main_volda-golfklubb.jpeg + πŸ“„ logo_arendal-omegn-golfklubb.png + πŸ“„ slide_gjovik-og-toten-golfklubb_1.jpg + πŸ“„ main_soon-golfklubb.jpg + πŸ“„ logo_soon-golfklubb.png + πŸ“„ slide_gumoy-golf_2.jpg + πŸ“„ logo_alsten-golfklubb.png + πŸ“„ main_halden-golfklubb.jpg + πŸ“„ logo_narvik-golfklubb.jpg + πŸ“„ logo_randaberg-golfklubb.jpg + πŸ“„ slide_hakadal-golfklubb_10.jpg + πŸ“„ main_ostmarka-golfklubb.jpg + πŸ“„ logo_bleik-golfstrombane.jpg + πŸ“„ logo_hasvik-golfklubb.png + πŸ“„ main_floro-golfklubb.jpg + πŸ“„ main_gronmo-golfklubb.jpg + πŸ“„ slide_hakadal-golfklubb_7.jpg + πŸ“„ slide_egersund-golfklubb_8.jpg + πŸ“„ logo_atlungstad-golfklubb.png + πŸ“„ main_sunnmore-golfklubb.jpeg + πŸ“„ main_sleneset-golfklubb.jpg + πŸ“„ logo_lonne-golfklubb.png + πŸ“„ main_haugaland-golfklubb.jpg + πŸ“„ main_egersund-golfklubb.jpg + πŸ“„ slide_solum-golfklubb_5.jpg + πŸ“„ slide_gamle-fredrikstad-golfklubb_1.jpg + πŸ“„ main_sotra-golfklubb.jpg + πŸ“„ main_trysil-golfklubb.jpg + πŸ“„ logo_kongsberg-golfklubb.png + πŸ“„ slide_tromso-golfklubb_1.jpg + πŸ“„ logo_frosta-golfklubb.jpg + πŸ“„ slide_groruddalen-golfklubb_1.jpg + πŸ“„ logo_hafjell-golfklubb.png + πŸ“„ logo_sola-golfklubb-solastranden.png + πŸ“„ logo_holtsmark-golfklubb.png + πŸ“„ slide_naeroysund-golfklubb_1.jpg + πŸ“„ slide_groruddalen-golfklubb_0.jpg + πŸ“„ slide_naeroysund-golfklubb_4.jpg + πŸ“„ slide_kjekstad-golfklubb_2.jpg + πŸ“„ slide_gronmo-golfklubb_0.jpg + πŸ“„ slide_preikestolen_1.jpg + πŸ“„ logo_fana-golfklubb.jpg + πŸ“„ slide_sorknes-golfklubb_0.jpg + πŸ“„ slide_atlungstad-golfklubb_3.jpg + πŸ“„ main_losby-golfklubb.jpg + πŸ“„ slide_borregaard-golfklubb_4.jpg + πŸ“„ slide_hakadal-golfklubb_1.jpg + πŸ“„ main_sandnes-golfklubb.jpg + πŸ“„ slide_norsjo-golfklubb_2.jpg + πŸ“„ main_frosta-golfklubb.jpg + πŸ“„ slide_krokhol-golfklubb_0.jpg + πŸ“„ logo_askim-golfklubb.png + πŸ“„ main_stryn-golfklubb.jpg + πŸ“„ slide_lofoten-golfklubb_5.jpg + πŸ“„ logo_karasjok-golfklubb.jpg + πŸ“„ slide_krokhol-golfklubb_2.jpg + πŸ“„ slide_hakadal-golfklubb_14.jpg + πŸ“„ logo_gjovik-og-toten-golfklubb.png + πŸ“„ main_haugesund-golfklubb.jpg + πŸ“„ logo_stjordal-golfklubb.jpg + πŸ“„ logo_drobak-golfklubb.png + πŸ“„ main_atlungstad-golfklubb.jpg + πŸ“„ logo_trondheim-golfklubb.jpg + πŸ“„ logo_gjersjoen-golfklubb.png + πŸ“„ slide_lofoten-golfklubb_4.jpg + πŸ“„ slide_helgeland-golfklubb_7.jpg + πŸ“„ main_molde-golfklubb.jpg + πŸ“„ slide_tyrifjord-golfklubb_7.jpg + πŸ“„ logo_gjerdrum-golfklubb.png + πŸ“„ main_onsoy-golfklubb.jpg + πŸ“„ slide_trondheim-golfklubb_2.jpg + πŸ“„ slide_mork-golfklubb_0.jpg + πŸ“„ slide_frosta-golfklubb_2.jpg + πŸ“„ slide_giske-golfklubb_1.jpeg + πŸ“„ slide_moss-rygge-golfklubb_1.jpg + πŸ“„ slide_ostmarka-golfklubb_1.jpg + πŸ“„ slide_volda-golfklubb_0.jpeg + πŸ“„ logo_sotra-golfklubb.jpg + πŸ“„ main_mandal-golfklubb.jpg + πŸ“„ logo_grenland-og-omegn-golfklubb.jpg + πŸ“„ slide_volda-golfklubb_1.jpeg + πŸ“„ main_imjelt-pitch-putt.jpg + πŸ“„ logo_nittedal-golfklubb.png + πŸ“„ main_gumoy-golf.jpg + πŸ“„ logo_hauger-golfklubb.png + πŸ“„ slide_hakadal-golfklubb_4.jpg + πŸ“„ slide_ostmarka-golfklubb_4.jpg + πŸ“„ slide_hakadal-golfklubb_0.jpg + πŸ“„ main_surnadal-golfklubb.jpg + πŸ“„ logo_hof-golfklubb.png + πŸ“„ slide_sleneset-golfklubb_2.jpg + πŸ“„ logo_halden-golfklubb.png + πŸ“„ logo_naeroysund-golfklubb.png + πŸ“„ logo_elverum-golfklubb.png + πŸ“„ main_bamble-golfklubb.jpg + πŸ“„ logo_kragero-golfklubb.png + πŸ“„ logo_valdres-golfklubb.png + πŸ“„ slide_tyrifjord-golfklubb_1.jpg + πŸ“„ logo_grini-golfklubb.png + πŸ“„ logo_lindesnes-golfklubb.jpg + πŸ“„ slide_lofoten-golfklubb_6.jpg + πŸ“„ main_sauda-golfklubb.jpg + πŸ“„ main_moss-rygge-golfklubb.jpg + πŸ“„ main_dalane-golfklubb.jpg + πŸ“„ logo_skei-golfklubb.jpg + πŸ“„ logo_ostmarka-golfklubb.png + πŸ“„ slide_nesbyen-golfklubb_1.jpg + πŸ“„ logo_stryn-golfklubb.png + πŸ“„ main_vradal-golfklubb.jpg + πŸ“„ slide_hovden-golfklubb_0.jpg + πŸ“„ main_gjovik-og-toten-golfklubb.jpg + πŸ“„ logo_hvaler-golfklubb.png + πŸ“„ main_giske-golfklubb.jpeg + πŸ“„ slide_herdla-golfklubb_0.jpg + πŸ“„ main_ibestad-golfklubb.jpg + πŸ“„ slide_kongsvingers-golfklubb_0.jpg + πŸ“„ slide_atlungstad-golfklubb_1.jpg + πŸ“„ logo_nordfjord-golfklubb.png + πŸ“„ logo_eidskog-golfklubb.png + πŸ“„ main_hitra-golfklubb.jpg + πŸ“„ logo_dalane-golfklubb.jpg + πŸ“„ main_kjekstad-golfklubb.jpg + πŸ“„ logo_odda-golfklubb.jpg + πŸ“„ logo_ballerud-golfklubb.jpg + πŸ“„ main_klaebu-golfklubb.jpg + πŸ“„ main_hasvik-golfklubb.jpg + πŸ“„ logo_klaebu-golfklubb.png + πŸ“„ slide_bleik-golfstrombane_4.jpg + πŸ“„ logo_north-cape-golf-club.jpg + πŸ“„ slide_tyrifjord-golfklubb_0.jpg + πŸ“„ logo_moa-golfsenter.png + πŸ“„ main_herdla-golfklubb.jpg + πŸ“„ slide_gjersjoen-golfklubb_2.jpg + πŸ“„ logo_stiklestad-golfklubb.png + πŸ“„ slide_bleik-golfstrombane_10.jpg + πŸ“„ logo_hovden-golfklubb.jpg + πŸ“„ slide_frosta-golfklubb_0.jpg + πŸ“„ logo_sirdal-fjellgolf-klubb.png + πŸ“„ main_ekholtbruket-golfklubb.jpg + πŸ“„ slide_salten-golfklubb-bodo-golfpark_0.jpg + πŸ“„ main_askim-golfklubb.jpg + πŸ“„ slide_elverum-golfklubb_0.jpg + πŸ“„ slide_gamle-fredrikstad-golfklubb_0.jpg + πŸ“„ main_oppegard-golfklubb.jpg + πŸ“„ slide_sorknes-golfklubb_1.jpg + πŸ“„ main_drobak-golfklubb.jpg + πŸ“„ slide_kongsvingers-golfklubb_1.jpg + πŸ“„ slide_helgeland-golfklubb_0.jpg + πŸ“„ slide_vestfold-golfklubb_1.jpg + πŸ“„ slide_kongsberg-golfklubb_1.jpg + πŸ“„ slide_tjome-golfklubb_1.jpg + πŸ“„ slide_ekholtbruket-golfklubb_1.jpg + πŸ“„ main_bergen-golfklubb.jpg + πŸ“„ logo_salten-golfklubb-bodo-golfpark.jpg + πŸ“„ slide_egersund-golfklubb_1.jpg + πŸ“„ slide_trondheim-golfklubb_7.jpg + πŸ“„ logo_stranda-golfklubb.jpg + πŸ“„ main_eidskog-golfklubb.jpg + πŸ“„ logo_nes-golfklubb-09.jpg + πŸ“„ logo_ski-golfklubb.png + πŸ“„ logo_norsjo-golfklubb.png + πŸ“„ slide_kongsberg-golfklubb_3.jpg + πŸ“„ slide_ski-golfklubb_0.jpg + πŸ“„ main_sorknes-golfklubb.jpg + πŸ“„ main_laerdal-golfklubb.jpg + πŸ“„ slide_giske-golfklubb_0.jpeg + πŸ“„ main_ullensaker-golfklubb.jpg + πŸ“„ main_midt-troms-golfklubb.jpg + πŸ“„ main_tysnes-golfklubb.jpg + πŸ“„ logo_namdal-golfklubb.jpg + πŸ“„ logo_tyrifjord-golfklubb.png + πŸ“„ slide_bjornefjorden-golfklubb_0.jpg + πŸ“„ slide_hovden-golfklubb_5.jpg + πŸ“„ main_tyrifjord-golfklubb.jpg + πŸ“„ slide_sotra-golfklubb_0.jpg + πŸ“„ main_valdres-golfklubb.jpg + πŸ“„ logo_steinkjer-golfklubb.jpg + πŸ“„ main_krokhol-golfklubb.jpg + πŸ“„ main_varanger-golfklubb.jpg + πŸ“„ logo_meland-golfklubb.png + πŸ“„ slide_namdal-golfklubb_1.jpg + πŸ“„ main_selje-golfklubb.jpg + πŸ“ src/ + πŸ“„ struktur_dump.txt + πŸ“ components/ + πŸ“„ Header.tsx + πŸ“ config/ + πŸ“„ constants.ts + πŸ“ app/ + πŸ“„ FacilitySearch.tsx + πŸ“„ HeroSlider.tsx + πŸ“„ favicon.ico + πŸ“„ globals.css + πŸ“„ page.tsx + πŸ“„ layout.tsx + πŸ“ golfbaner/ + πŸ“ [slug]/ + πŸ“„ CourseDisplay.tsx + πŸ“„ page.tsx + πŸ“„ FacilityDetailView.tsx +πŸ“ kode_eksport_1/ + πŸ“„ frontend_src_components_Header_tsx.txt + πŸ“„ frontend_next-env_d_ts.txt + πŸ“„ frontend_src_app_layout_tsx.txt + πŸ“„ frontend_src_app_page_tsx.txt + πŸ“„ eksport_script_py.txt + πŸ“„ frontend_src_app_golfbaner_[slug]_page_tsx.txt + πŸ“„ frontend_src_app_golfbaner_[slug]_CourseDisplay_tsx.txt + πŸ“„ frontend_next_config_ts.txt + πŸ“„ frontend_src_app_golfbaner_[slug]_FacilityDetailView_tsx.txt + πŸ“„ frontend_src_app_HeroSlider_tsx.txt + πŸ“„ frontend_src_app_FacilitySearch_tsx.txt + πŸ“„ frontend_src_config_constants_ts.txt +πŸ“ backend/ + πŸ“„ scrape_nsg_3.py + πŸ“„ import_gallery.py + πŸ“„ scrape_golfamore1.2.py + πŸ“„ scrape_golfamore1.3.py + πŸ“„ requirements.txt + πŸ“„ import_wp.py + πŸ“„ main.py + πŸ“„ Dockerfile + πŸ“„ scrape_golfamore.py + πŸ“ public/ + πŸ“ media/ \ No newline at end of file diff --git a/frontend/public/media/main_austratt-golfklubb.jpg b/frontend/public/media/main_austratt-golfklubb.jpg new file mode 100644 index 0000000..3aac0b5 Binary files /dev/null and b/frontend/public/media/main_austratt-golfklubb.jpg differ diff --git a/frontend/public/media/slide_atlungstad-golfklubb_2.jpg b/frontend/public/media/slide_atlungstad-golfklubb_2.jpg new file mode 100644 index 0000000..152a53c Binary files /dev/null and b/frontend/public/media/slide_atlungstad-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_atlungstad-golfklubb_3.jpg b/frontend/public/media/slide_atlungstad-golfklubb_3.jpg new file mode 100644 index 0000000..9c6ff23 Binary files /dev/null and b/frontend/public/media/slide_atlungstad-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_bjaavann-golfklubb_3.jpg b/frontend/public/media/slide_bjaavann-golfklubb_3.jpg new file mode 100644 index 0000000..a899da9 Binary files /dev/null and b/frontend/public/media/slide_bjaavann-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_bleik-golfstrombane_10.jpg b/frontend/public/media/slide_bleik-golfstrombane_10.jpg new file mode 100644 index 0000000..c6bf323 Binary files /dev/null and b/frontend/public/media/slide_bleik-golfstrombane_10.jpg differ diff --git a/frontend/public/media/slide_bleik-golfstrombane_11.jpg b/frontend/public/media/slide_bleik-golfstrombane_11.jpg new file mode 100644 index 0000000..a1d3647 Binary files /dev/null and b/frontend/public/media/slide_bleik-golfstrombane_11.jpg differ diff --git a/frontend/public/media/slide_borregaard-golfklubb_4.jpg b/frontend/public/media/slide_borregaard-golfklubb_4.jpg new file mode 100644 index 0000000..5d4af03 Binary files /dev/null and b/frontend/public/media/slide_borregaard-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_borregaard-golfklubb_5.jpg b/frontend/public/media/slide_borregaard-golfklubb_5.jpg new file mode 100644 index 0000000..68d19d8 Binary files /dev/null and b/frontend/public/media/slide_borregaard-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_egersund-golfklubb_5.jpg b/frontend/public/media/slide_egersund-golfklubb_5.jpg new file mode 100644 index 0000000..20683a8 Binary files /dev/null and b/frontend/public/media/slide_egersund-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_egersund-golfklubb_6.jpg b/frontend/public/media/slide_egersund-golfklubb_6.jpg new file mode 100644 index 0000000..6b2c7a9 Binary files /dev/null and b/frontend/public/media/slide_egersund-golfklubb_6.jpg differ diff --git a/frontend/public/media/slide_egersund-golfklubb_8.jpg b/frontend/public/media/slide_egersund-golfklubb_8.jpg new file mode 100644 index 0000000..2fb8097 Binary files /dev/null and b/frontend/public/media/slide_egersund-golfklubb_8.jpg differ diff --git a/frontend/public/media/slide_giske-golfklubb_1.jpeg b/frontend/public/media/slide_giske-golfklubb_1.jpeg new file mode 100644 index 0000000..23c38bc Binary files /dev/null and b/frontend/public/media/slide_giske-golfklubb_1.jpeg differ diff --git a/frontend/public/media/slide_gjovik-og-toten-golfklubb_2.jpg b/frontend/public/media/slide_gjovik-og-toten-golfklubb_2.jpg new file mode 100644 index 0000000..b49b06c Binary files /dev/null and b/frontend/public/media/slide_gjovik-og-toten-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_groruddalen-golfklubb_2.jpg b/frontend/public/media/slide_groruddalen-golfklubb_2.jpg new file mode 100644 index 0000000..3bc1b5e Binary files /dev/null and b/frontend/public/media/slide_groruddalen-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_gumoy-golf_1.jpg b/frontend/public/media/slide_gumoy-golf_1.jpg new file mode 100644 index 0000000..c4d01e1 Binary files /dev/null and b/frontend/public/media/slide_gumoy-golf_1.jpg differ diff --git a/frontend/public/media/slide_gumoy-golf_2.jpg b/frontend/public/media/slide_gumoy-golf_2.jpg new file mode 100644 index 0000000..dd24125 Binary files /dev/null and b/frontend/public/media/slide_gumoy-golf_2.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_10.jpg b/frontend/public/media/slide_hakadal-golfklubb_10.jpg new file mode 100644 index 0000000..a4cda67 Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_10.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_12.jpg b/frontend/public/media/slide_hakadal-golfklubb_12.jpg new file mode 100644 index 0000000..e7da608 Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_12.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_14.jpg b/frontend/public/media/slide_hakadal-golfklubb_14.jpg new file mode 100644 index 0000000..8a35cd2 Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_14.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_3.jpg b/frontend/public/media/slide_hakadal-golfklubb_3.jpg new file mode 100644 index 0000000..e9c555b Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_4.jpg b/frontend/public/media/slide_hakadal-golfklubb_4.jpg new file mode 100644 index 0000000..0ab3aec Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_6.jpg b/frontend/public/media/slide_hakadal-golfklubb_6.jpg new file mode 100644 index 0000000..490f954 Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_6.jpg differ diff --git a/frontend/public/media/slide_hakadal-golfklubb_8.jpg b/frontend/public/media/slide_hakadal-golfklubb_8.jpg new file mode 100644 index 0000000..387ccd2 Binary files /dev/null and b/frontend/public/media/slide_hakadal-golfklubb_8.jpg differ diff --git a/frontend/public/media/slide_hardanger-golfklubb_1.jpg b/frontend/public/media/slide_hardanger-golfklubb_1.jpg new file mode 100644 index 0000000..c3d571c Binary files /dev/null and b/frontend/public/media/slide_hardanger-golfklubb_1.jpg differ diff --git a/frontend/public/media/slide_helgeland-golfklubb_5.jpg b/frontend/public/media/slide_helgeland-golfklubb_5.jpg new file mode 100644 index 0000000..21f89bc Binary files /dev/null and b/frontend/public/media/slide_helgeland-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_helgeland-golfklubb_6.jpg b/frontend/public/media/slide_helgeland-golfklubb_6.jpg new file mode 100644 index 0000000..6e24189 Binary files /dev/null and b/frontend/public/media/slide_helgeland-golfklubb_6.jpg differ diff --git a/frontend/public/media/slide_helgeland-golfklubb_8.jpg b/frontend/public/media/slide_helgeland-golfklubb_8.jpg new file mode 100644 index 0000000..8c28e32 Binary files /dev/null and b/frontend/public/media/slide_helgeland-golfklubb_8.jpg differ diff --git a/frontend/public/media/slide_hovden-golfklubb_4.jpg b/frontend/public/media/slide_hovden-golfklubb_4.jpg new file mode 100644 index 0000000..b3f8dab Binary files /dev/null and b/frontend/public/media/slide_hovden-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_hovden-golfklubb_5.jpg b/frontend/public/media/slide_hovden-golfklubb_5.jpg new file mode 100644 index 0000000..b5bffd1 Binary files /dev/null and b/frontend/public/media/slide_hovden-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_kjekstad-golfklubb_2.jpg b/frontend/public/media/slide_kjekstad-golfklubb_2.jpg new file mode 100644 index 0000000..a7b7eb6 Binary files /dev/null and b/frontend/public/media/slide_kjekstad-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_kongsberg-golfklubb_2.jpg b/frontend/public/media/slide_kongsberg-golfklubb_2.jpg new file mode 100644 index 0000000..7b6422e Binary files /dev/null and b/frontend/public/media/slide_kongsberg-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_kongsberg-golfklubb_3.jpg b/frontend/public/media/slide_kongsberg-golfklubb_3.jpg new file mode 100644 index 0000000..17b2071 Binary files /dev/null and b/frontend/public/media/slide_kongsberg-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_kongsberg-golfklubb_5.jpg b/frontend/public/media/slide_kongsberg-golfklubb_5.jpg new file mode 100644 index 0000000..223f269 Binary files /dev/null and b/frontend/public/media/slide_kongsberg-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_kongsvingers-golfklubb_2.jpg b/frontend/public/media/slide_kongsvingers-golfklubb_2.jpg new file mode 100644 index 0000000..d56c0d6 Binary files /dev/null and b/frontend/public/media/slide_kongsvingers-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_kongsvingers-golfklubb_3.jpg b/frontend/public/media/slide_kongsvingers-golfklubb_3.jpg new file mode 100644 index 0000000..3627885 Binary files /dev/null and b/frontend/public/media/slide_kongsvingers-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_kongsvingers-golfklubb_5.jpg b/frontend/public/media/slide_kongsvingers-golfklubb_5.jpg new file mode 100644 index 0000000..b0e1a7d Binary files /dev/null and b/frontend/public/media/slide_kongsvingers-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_lofoten-golfklubb_3.jpg b/frontend/public/media/slide_lofoten-golfklubb_3.jpg new file mode 100644 index 0000000..bd6d016 Binary files /dev/null and b/frontend/public/media/slide_lofoten-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_lofoten-golfklubb_4.jpg b/frontend/public/media/slide_lofoten-golfklubb_4.jpg new file mode 100644 index 0000000..50a9092 Binary files /dev/null and b/frontend/public/media/slide_lofoten-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_lofoten-golfklubb_6.jpg b/frontend/public/media/slide_lofoten-golfklubb_6.jpg new file mode 100644 index 0000000..f9671bb Binary files /dev/null and b/frontend/public/media/slide_lofoten-golfklubb_6.jpg differ diff --git a/frontend/public/media/slide_mork-golfklubb_1.jpg b/frontend/public/media/slide_mork-golfklubb_1.jpg new file mode 100644 index 0000000..e0803d7 Binary files /dev/null and b/frontend/public/media/slide_mork-golfklubb_1.jpg differ diff --git a/frontend/public/media/slide_sorknes-golfklubb_0.jpg b/frontend/public/media/slide_sorknes-golfklubb_0.jpg new file mode 100644 index 0000000..5e8f8d9 Binary files /dev/null and b/frontend/public/media/slide_sorknes-golfklubb_0.jpg differ diff --git a/frontend/public/media/slide_sorknes-golfklubb_1.jpg b/frontend/public/media/slide_sorknes-golfklubb_1.jpg new file mode 100644 index 0000000..c69833d Binary files /dev/null and b/frontend/public/media/slide_sorknes-golfklubb_1.jpg differ diff --git a/frontend/public/media/slide_sotra-golfklubb_2.jpg b/frontend/public/media/slide_sotra-golfklubb_2.jpg new file mode 100644 index 0000000..842934d Binary files /dev/null and b/frontend/public/media/slide_sotra-golfklubb_2.jpg differ diff --git a/frontend/public/media/slide_trondheim-golfklubb_4.jpg b/frontend/public/media/slide_trondheim-golfklubb_4.jpg new file mode 100644 index 0000000..87fbfb5 Binary files /dev/null and b/frontend/public/media/slide_trondheim-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_trondheim-golfklubb_5.jpg b/frontend/public/media/slide_trondheim-golfklubb_5.jpg new file mode 100644 index 0000000..6b3aa78 Binary files /dev/null and b/frontend/public/media/slide_trondheim-golfklubb_5.jpg differ diff --git a/frontend/public/media/slide_trondheim-golfklubb_7.jpg b/frontend/public/media/slide_trondheim-golfklubb_7.jpg new file mode 100644 index 0000000..0ad0bd6 Binary files /dev/null and b/frontend/public/media/slide_trondheim-golfklubb_7.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_0.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_0.jpg new file mode 100644 index 0000000..d3da1b0 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_0.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_1.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_1.jpg new file mode 100644 index 0000000..2915ae3 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_1.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_3.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_3.jpg new file mode 100644 index 0000000..e609824 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_3.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_4.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_4.jpg new file mode 100644 index 0000000..97b66fb Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_4.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_6.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_6.jpg new file mode 100644 index 0000000..0e25d05 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_6.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_7.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_7.jpg new file mode 100644 index 0000000..9617900 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_7.jpg differ diff --git a/frontend/public/media/slide_tyrifjord-golfklubb_9.jpg b/frontend/public/media/slide_tyrifjord-golfklubb_9.jpg new file mode 100644 index 0000000..b2acd68 Binary files /dev/null and b/frontend/public/media/slide_tyrifjord-golfklubb_9.jpg differ diff --git a/frontend/public/media/slide_volda-golfklubb_2.jpeg b/frontend/public/media/slide_volda-golfklubb_2.jpeg new file mode 100644 index 0000000..7282d37 Binary files /dev/null and b/frontend/public/media/slide_volda-golfklubb_2.jpeg differ diff --git a/frontend/public/media/slide_volda-golfklubb_3.jpeg b/frontend/public/media/slide_volda-golfklubb_3.jpeg new file mode 100644 index 0000000..8e9401a Binary files /dev/null and b/frontend/public/media/slide_volda-golfklubb_3.jpeg differ diff --git a/frontend/src/app/FacilitySearch.tsx b/frontend/src/app/FacilitySearch.tsx index e6687fa..feeff9e 100644 --- a/frontend/src/app/FacilitySearch.tsx +++ b/frontend/src/app/FacilitySearch.tsx @@ -16,74 +16,75 @@ function getDistance(lat1: number, lon1: number, lat2: number, lon2: number) { export default function FacilitySearch({ initialFacilities }: { initialFacilities: any[] }) { const [searchQuery, setSearchQuery] = useState(""); const [userLocation, setUserLocation] = useState<{ lat: number, lng: number } | null>(null); - const [mounted, setMounted] = useState(false); + const [sortMethod, setSortMethod] = useState<'dist' | 'alpha'>('alpha'); useEffect(() => { - setMounted(true); if ("geolocation" in navigator) { - navigator.geolocation.getCurrentPosition(p => setUserLocation({ lat: p.coords.latitude, lng: p.coords.longitude })); + navigator.geolocation.getCurrentPosition(p => { + setUserLocation({ lat: p.coords.latitude, lng: p.coords.longitude }); + setSortMethod('dist'); + }); } }, []); const processed = useMemo(() => { - if (!mounted || !Array.isArray(initialFacilities)) return []; + if (!Array.isArray(initialFacilities)) return []; const words = searchQuery.toLowerCase().trim().split(/\s+/).filter(w => w.length > 0); return initialFacilities.map(f => { - // Skuddsikker status-vask - const raw = Array.isArray(f.course_statuses) ? f.course_statuses : []; - const statuses = raw.filter((s: any) => s && s.status && s.status !== 'finnes_ingen_bane_to' && s.name !== 'Bane 2'); - const dist = userLocation && f.lat && f.lng ? getDistance(userLocation.lat, userLocation.lng, f.lat, f.lng) : Infinity; - const searchBlob = `${f.name} ${f.city} ${f.county} ${statuses.map((s:any) => s.name + s.status).join(" ")}`.toLowerCase(); - const matches = words.every(w => searchBlob.includes(w)); + const hasNSG = f.nsg_data && Object.keys(f.nsg_data).length > 0; + const hasGolfamore = f.golfamore && (f.golfamore_data?.terms || f.golfamore === true); + + const blob = `${f.name} ${f.city} ${f.county} ${hasNSG ? 'nsg seniorgolf' : ''} ${hasGolfamore ? 'golfamore' : ''}`.toLowerCase(); + const matches = words.every(w => blob.includes(w)); - return { ...f, dist, statuses, matches }; + return { ...f, dist, hasNSG, hasGolfamore, matches }; }) .filter(f => f.matches) - .sort((a, b) => (userLocation && a.dist !== b.dist ? a.dist - b.dist : (a.name || "").localeCompare(b.name || "", 'nb'))); - }, [searchQuery, initialFacilities, userLocation, mounted]); - - if (!mounted) return null; + .sort((a, b) => { + if (sortMethod === 'dist' && a.dist !== b.dist) return a.dist - b.dist; + return a.name.localeCompare(b.name, 'nb'); + }); + }, [searchQuery, initialFacilities, userLocation, sortMethod]); return ( -
-
- - {userLocation ? "GPS AKTIV" : "SORTERER ALFABETISK"} β€’ {processed.length} BANER - +
+
+
- setSearchQuery(e.target.value)} /> + setSearchQuery(e.target.value)} /> -
+
{processed.map((f: any) => ( - -
- {f.name} - {f.dist !== Infinity &&
{Math.round(f.dist)} km unna
} -
- {f.statuses.map((s: any, idx: number) => { - const raw = (s.status || "ukjent").toLowerCase(); - let color = "bg-gray-500 text-white"; - if (raw.includes('aapen') && !raw.includes('vinter')) color = "bg-green-600 text-white"; - else if (raw.includes('vinter')) color = "bg-emerald-400 text-gray-900"; - else if (raw.includes('snart')) color = "bg-yellow-500 text-gray-900"; - else if (raw.includes('stengt')) color = "bg-red-600 text-white"; - return
{f.statuses.length > 1 ? `${s.name}: ${STATUS_MAP[raw] || raw}` : (STATUS_MAP[raw] || raw)}
; + +
+ {f.name} +
+ {(Array.isArray(f.course_statuses) ? f.course_statuses : []).slice(0, 1).map((s: any, idx: number) => { + const raw = (s.status || "").toLowerCase(); + let color = "bg-gray-500"; + if (raw === 'aapen') color = "bg-[#8bc34a]"; + else if (raw.includes('vinter')) color = "bg-emerald-600"; + else if (raw.includes('snart')) color = "bg-amber-500"; + else if (raw === 'stengt') color = "bg-red-600"; + return
{STATUS_MAP[s.status] || s.status}
; })}
+
+ {f.hasNSG &&
NSG
} + {f.hasGolfamore &&
G
} +
-
-

{f.name}

-

{f.city}{f.county ? ` β€’ ${f.county}` : ''}

-
-
- {f.amenities?.antall_hull || f.holes || '--'} Hull - {f.golfamore &&
G
} - {f.nsg_data?.url &&
S
} -
- Se bane β†’ +
+

{f.name}

+

{f.city} β€’ {f.county}

+
+ {f.amenities?.antall_hull || '--'} Hull + {f.banetype || 'Park/Skog'}
@@ -91,4 +92,4 @@ export default function FacilitySearch({ initialFacilities }: { initialFacilitie
); -} +} \ No newline at end of file diff --git a/frontend/src/app/HeroSlider.tsx b/frontend/src/app/HeroSlider.tsx index effc58f..515ce80 100644 --- a/frontend/src/app/HeroSlider.tsx +++ b/frontend/src/app/HeroSlider.tsx @@ -1,52 +1,130 @@ "use client"; -import { FALLBACK_IMAGE } from "@/config/constants"; -import { useState, useEffect } from 'react'; +/** + * TEE OFF SYSTEM INSTRUCTIONS - HERO SLIDER v2.4 + * --------------------------------------------------------------------------- + * REGEL 1: Kun baner med status 'aapen', 'aapner_snart', 'stenger_snart' + * eller 'aapen_med_vintergreener' skal prioriteres. + * REGEL 2: Baner med status 'nedlagt' eller 'under_utvikling' skal ALDRI vises. + * REGEL 3: Baner med generiske bilder (inneholder 'standard') skal ALDRI vises. + * REGEL 4: MANUELL EKSKLUDERING: Slugs i MANUAL_EXCLUSION_LIST skal aldri vises. + * REGEL 5: Slideren skal vise nΓΈyaktig 5 baner. + * REGEL 6: Maks hΓΈyde er lΓ₯st til 624px. Ingen badges. + * REGEL 7: Typografi: Nedjustert fontstΓΈrrelse (4xl mobil / 7xl desktop) for eleganse. + * REGEL 8: Utvalget skal vΓ¦re stabilt i Γ©n time (Hourly Seed) fΓΈr det refreshes. + * --------------------------------------------------------------------------- + */ + +import { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; +const MANUAL_EXCLUSION_LIST = [ + 'alsten-golfklubb', 'askim-golfklubb', 'bergen-golfklubb', 'eidskog-golfklubb', + 'eiker-golfklubb', 'floro-golfklubb', 'garder-golfklubb', 'hafjell-golfklubb', + 'halden-golfklubb', 'haugesund-golfklubb', 'hinnoy-golfklubb', 'hitra-golfklubb', + 'hurum-golfklubb', 'imjelt-pitch-putt', 'karmoy-golfklubb', 'kristiansund-og-omegn-golfklubb', + 'lommedalen-golfklubb', 'laerdal-golfklubb', 'moa-golfsenter', 'modum-golfklubb', + 'nes-golfklubb-09', 'nittedal-golfklubb', 'selbu-golfklubb', 'stryn-golfklubb', + 'sunnfjord-golfklubb', 'tysnes-golfklubb', 'vanylven-golfklubb', 'vesteralen-golfklubb', + 'vestlia-golf' +]; + export default function HeroSlider({ facilities }: { facilities: any[] }) { - const [currentIndex, setCurrentIndex] = useState(0); - const [sliderItems, setSliderItems] = useState([]); + const [currentIndex, setCurrentSlide] = useState(0); - useEffect(() => { - if (!Array.isArray(facilities)) return; + const sliderItems = useMemo(() => { + if (!Array.isArray(facilities) || facilities.length === 0) return []; - const filtered = facilities.filter(f => { - // Robust status-sjekk + const preferredStatuses = ['aapen', 'aapner_snart', 'stenger_snart', 'aapen_med_vintergreener']; + const forbiddenStatuses = ['nedlagt', 'under_utvikling']; + + const validCandidates = facilities.filter(f => { + if (MANUAL_EXCLUSION_LIST.includes(f.slug)) return false; + const img = f.image_url || ""; + if (!img || img.toLowerCase().includes('standard') || img.length < 5) return false; + const statuses = Array.isArray(f.course_statuses) ? f.course_statuses : []; - const interesting = ['aapen', 'aapen_med_vintergreener', 'aapner_snart', 'stenger_snart', 'stengt']; - const hasStatus = statuses.some((s: any) => s && s.status && interesting.includes(s.status)); - - // Bilde-sjekk - const img = f.front_image_url || f.image_url || ""; - const isReal = img && !img.includes('Toppbilde-standard.jpg') && String(img).startsWith('http'); - - return hasStatus && isReal; + const isForbidden = statuses.some((s: any) => + forbiddenStatuses.includes((s.status || "").toLowerCase()) + ); + return !isForbidden; }); - setSliderItems(filtered.sort(() => 0.5 - Math.random()).slice(0, 5)); + const highPriority = validCandidates.filter(f => { + const statuses = Array.isArray(f.course_statuses) ? f.course_statuses : []; + return statuses.some((s: any) => preferredStatuses.includes((s.status || "").toLowerCase())); + }); + + const fallbackPool = validCandidates.filter(f => !highPriority.includes(f)); + const now = new Date(); + const hourlySeed = parseInt(`${now.getFullYear()}${now.getMonth()}${now.getDate()}${now.getHours()}`); + + const seededShuffle = (arr: any[]) => { + return [...arr].sort((a, b) => ((a.id * hourlySeed) % 100) - ((b.id * hourlySeed) % 100)); + }; + + let selection = seededShuffle(highPriority); + if (selection.length < 5) { + selection = [...selection, ...seededShuffle(fallbackPool)].slice(0, 5); + } else { + selection = selection.slice(0, 5); + } + return selection; }, [facilities]); useEffect(() => { if (sliderItems.length <= 1) return; - const t = setInterval(() => setCurrentIndex(p => (p + 1) % sliderItems.length), 6000); - return () => clearInterval(t); - }, [sliderItems]); + const interval = setInterval(() => setCurrentSlide((p) => (p + 1) % sliderItems.length), 8000); + return () => clearInterval(interval); + }, [sliderItems.length]); if (sliderItems.length === 0) return null; return ( -
+
{sliderItems.map((f, i) => ( -
-
- {f.name} -
-

{f.name}

-

{f.city} β€’ {f.county}

- Se golfanlegget -
+
+ +
+ + {f.name} + +
+
+
+ {/* FONT NEDJUSTERT FRA text-6xl md:text-9xl TIL text-4xl md:text-7xl */} +

+ {f.name} +

+

+ {f.county} β€’ {f.city} +

+
+
+
+
))} -
+ +
+ {sliderItems.map((_, i) => ( +
+
); -} +} \ No newline at end of file diff --git a/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx b/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx index 9adb989..269c07c 100644 --- a/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx +++ b/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx @@ -5,8 +5,7 @@ import { STATUS_MAP, FALLBACK_IMAGE } from "@/config/constants"; import Link from 'next/link'; import CourseDisplay from './CourseDisplay'; -// --- UNIFORME MONOKROME IKONER (#11280f) --- -// FIKSET: Lagt til React Fragments <>... rundt ikoner med flere stier +// --- MONOKROME SVG IKONER (#11280f) --- const Icon = ({ children, className = "w-5 h-5" }: { children: React.ReactNode, className?: string }) => ( {children} ); @@ -33,11 +32,9 @@ export default function FacilityDetailView({ facility }: { facility: any }) { const activeCourses = (facility.courses || []).filter((c: any) => c.holes && c.holes.length > 0); const amenities = facility.amenities || {}; const gallery = Array.isArray(facility.gallery) && facility.gallery.length > 0 ? facility.gallery : [facility.image_url || FALLBACK_IMAGE]; - const socialLinks = Array.isArray(facility.social_links) ? facility.social_links : []; const shotzoom = Array.isArray(facility.shotzoom) ? facility.shotzoom : []; const linkClass = "text-orange-600 hover:underline transition-colors font-bold"; - // Slider Logic useEffect(() => { if (gallery.length <= 1) return; const timer = setInterval(() => setCurrentSlide((p) => (p + 1) % gallery.length), 5000); @@ -55,35 +52,28 @@ export default function FacilityDetailView({ facility }: { facility: any }) { if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.pageYOffset - 80, behavior: 'smooth' }); }; + const formatDate = (d: string) => d ? new Date(d).toLocaleDateString('nb-NO', { day: 'numeric', month: 'long', year: 'numeric' }) : null; + const weatherImg = facility.weather_url?.replace("/graf/dag/", "/innhold/").replace(/\/$/, "") + "/meteogram.svg"; return (
- {/* HERO / SLIDER */}
{gallery.map((img: string, i: number) => ( ))}
- {/* TOP-RIGHT STATUS */}
{activeCourses.map((c: any) => ( - - {activeCourses.length > 1 ? `${c.name}: ${STATUS_MAP[c.status] || c.status}` : STATUS_MAP[c.status] || c.status} - + {STATUS_MAP[c.status] || c.status} ))}
- {facility.status_updated_at && ( - - Sist oppdatert: {new Date(facility.status_updated_at).toLocaleDateString('nb-NO', { day: 'numeric', month: 'long', year: 'numeric' })} - - )} + {facility.status_updated_at && Sist oppdatert: {formatDate(facility.status_updated_at)}}
- {/* BOTTOM-RIGHT BUTTONS */}
{facility.website_url && } {facility.golfbox_booking_url && } @@ -92,13 +82,9 @@ export default function FacilityDetailView({ facility }: { facility: any }) { {facility.weather_url && }
-
- {facility.logo_url && ( -
- -
- )} -

{facility.name}

+
+ {facility.logo_url &&
} +

{facility.name}

{facility.county} β€’ {facility.city}

@@ -117,7 +103,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
- {/* RAD 1: INTRO (22%) */}

Kontakt & Adresse

@@ -126,19 +111,18 @@ export default function FacilityDetailView({ facility }: { facility: any }) { {facility.phone || 'Ikke oppgitt'} {facility.email || 'Ikke oppgitt'} {facility.address}
{facility.city}
-
+
{facility.facebook_url && } {facility.instagram_url && }
-
- {facility.footnote &&
{facility.footnote}
} -
+
+ {facility.footnote &&
{facility.footnote}
} +
- {/* RAD 2: VÆR */}

Vær for {facility.name}

@@ -146,16 +130,15 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
- {/* RAD 3: DETALJER */}

Andre ressurser

-
- {facility.golfbox_booking_url && Book Starttid→} - {facility.golfbox_tournament_url && Turneringer→} - {facility.baneguide_url && Baneguide→} - {facility.flyfoto_url && Flyfoto→} - {facility.webcam_url && Webkamera→} +
+ {facility.golfbox_booking_url && Book Starttidβ†’} + {facility.golfbox_tournament_url && Turneringerβ†’} + {facility.baneguide_url && Baneguideβ†’} + {facility.flyfoto_url && Flyfotoβ†’} + {facility.webcam_url && Webkameraβ†’} {shotzoom.map((sz: any, i: number) => ( πŸ“Š Statistikk: {sz.shotzoom_beskrivelse?.replace(' ', ' ')}β†’ ))} @@ -170,6 +153,7 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
Lengde:{facility.length_meters ? `${facility.length_meters}m` : '--'}
Sesong:{facility.season || '--'}
ByggeΓ₯r:{facility.established_year || '--'}
+
Banetype:{facility.banetype || 'Park/Skog'}
Arkitekt:{facility.architect || '--'}
@@ -188,7 +172,6 @@ export default function FacilityDetailView({ facility }: { facility: any }) {
- {/* RAD 4: KART & VIDEO */}

Kart