"use client"; /** * TEE OFF SYSTEM INSTRUCTIONS - FACILITY CARDS v3.8 (BLOB SEARCH) * --------------------------------------------------------------------------- * REGEL 1: Status-badge SKAL vises øverst til venstre FOR ALLE BANER. * Bruk STATUS_MAP for tekst. * REGEL 2: DATA-PARSING: Bruk parseJson() for 'course_statuses', 'amenities' og 'nsg_data'. * REGEL 3: Avstand-pillen skal ha fargen #2d3319 (Mørk oliven) med hvit tekst. * REGEL 4: NSG (Blå 'N') og Golfamore (Oransje 'G') sirkler skal ha hvit kant (border-2). * REGEL 5: Bunnen: Antall Hull (grønn pill), Banetype (grå pill), og Ikon-sirkler. * REGEL 6: Viser dato (f.eks "05. mars 2026") rett til høyre for øverste status-pille. * REGEL 7: Natural Language Search bruker en "Search Blob" for å støtte delvise * ord og skrivefeil slik at listen ikke tømmes mens brukeren skriver. * --------------------------------------------------------------------------- */ import { STATUS_MAP, REGIONS } from "@/config/constants"; import { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; function getDistance(lat1: number, lon1: number, lat2: number, lon2: number) { try { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } catch (e) { return Infinity; } } export default function FacilitySearch({ initialFacilities }: { initialFacilities: any[] }) { const [searchQuery, setSearchQuery] = useState(""); const [userLocation, setUserLocation] = useState<{ lat: number, lng: number } | null>(null); const [sortMethod, setSortMethod] = useState<'dist' | 'alpha'>('alpha'); useEffect(() => { if ("geolocation" in navigator) { navigator.geolocation.getCurrentPosition(p => { setUserLocation({ lat: p.coords.latitude, lng: p.coords.longitude }); setSortMethod('dist'); }); } }, []); const processed = useMemo(() => { if (!Array.isArray(initialFacilities)) return []; // Fyllord som fjernes slik at "Åpne baner i Oslo" blir til søkeordene ["åpne", "oslo"] const stopWords = new Set(["i", "på", "for", "med", "av", "og"]); return initialFacilities.map(f => { // --- ROBUST DATA-PARSING --- const parseJson = (val: any, fallback: any) => { if (!val) return fallback; if (typeof val === 'object') return val; try { return JSON.parse(val); } catch (e) { return fallback; } }; const rawStatuses = parseJson(f.course_statuses, []); const sArr = Array.isArray(rawStatuses) && rawStatuses.length > 0 ? rawStatuses : [{ status: 'ukjent', name: 'Hovedbane' }]; const amenities = parseJson(f.amenities, {}); const nsgData = parseJson(f.nsg_data, {}); const dist = userLocation && f.lat && f.lng ? getDistance(userLocation.lat, userLocation.lng, f.lat, f.lng) : Infinity; const hasNSG = nsgData && Object.keys(nsgData).length > 0; const hasGolfamore = f.golfamore === true; // --- THE SEARCH BLOB --- // Vi starter med å legge navn, by og fylke i en stor, usynlig tekststreng let searchableText = `${f.name} ${f.city} ${f.county}`.toLowerCase(); // 1. Injiser statuser i tekststrengen const hasOpen = sArr.some((c: any) => (c.status || "") === 'aapen'); const hasClosed = sArr.some((c: any) => (c.status || "") === 'stengt'); const hasWinter = sArr.some((c: any) => (c.status || "") === 'aapen_med_vintergreener'); const hasNedlagt = sArr.some((c: any) => (c.status || "") === 'nedlagt'); if (hasOpen) searchableText += " åpen åpne aapen"; if (hasClosed) searchableText += " stengt stengte"; if (hasWinter) searchableText += " vinter vintergreener vinterbane"; if (hasNedlagt) searchableText += " nedlagt nedlagte"; // 2. Injiser spesial-tags if (hasNSG) searchableText += " nsg norsk seniorgolf"; if (hasGolfamore) searchableText += " golfamore amore"; // 3. Injiser landsdel (f.eks. hvis fylket er Akershus, legger vi til "østlandet") const fylke = (f.county || "").toLowerCase(); Object.entries(REGIONS).forEach(([regionName, counties]) => { if (counties.includes(fylke)) { searchableText += ` ${regionName}`; } }); // Splitter brukerens søk inn i enkeltord og fjerner stopWords + ordene "bane"/"baner" const words = searchQuery .toLowerCase() .trim() .split(/\s+/) .filter(w => w.length > 0 && !stopWords.has(w) && w !== "bane" && w !== "baner"); // Sjekker at ALLE ordene brukeren har skrevet, finnes et sted i "Search Blob"-en const matches = words.every(w => searchableText.includes(w)); return { ...f, statuses: sArr, amenities, dist, hasNSG, hasGolfamore, matches }; }) .filter(f => f.matches) .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 (
{f.city} • {f.county}