Nye-TeeOff/frontend/src/app/FacilitySearch.tsx

95 lines
5.6 KiB
TypeScript
Raw Normal View History

2026-02-26 09:20:51 +01:00
"use client";
import { STATUS_MAP } 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);
2026-02-27 08:53:14 +01:00
const [sortMethod, setSortMethod] = useState<'dist' | 'alpha'>('alpha');
2026-02-26 09:20:51 +01:00
useEffect(() => {
if ("geolocation" in navigator) {
2026-02-27 08:53:14 +01:00
navigator.geolocation.getCurrentPosition(p => {
setUserLocation({ lat: p.coords.latitude, lng: p.coords.longitude });
setSortMethod('dist');
});
2026-02-26 09:20:51 +01:00
}
}, []);
const processed = useMemo(() => {
2026-02-27 08:53:14 +01:00
if (!Array.isArray(initialFacilities)) return [];
2026-02-26 09:20:51 +01:00
const words = searchQuery.toLowerCase().trim().split(/\s+/).filter(w => w.length > 0);
return initialFacilities.map(f => {
const dist = userLocation && f.lat && f.lng ? getDistance(userLocation.lat, userLocation.lng, f.lat, f.lng) : Infinity;
2026-02-27 08:53:14 +01:00
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));
2026-02-26 09:20:51 +01:00
2026-02-27 08:53:14 +01:00
return { ...f, dist, hasNSG, hasGolfamore, matches };
2026-02-26 09:20:51 +01:00
})
.filter(f => f.matches)
2026-02-27 08:53:14 +01:00
.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]);
2026-02-26 09:20:51 +01:00
return (
2026-02-27 08:53:14 +01:00
<div className="max-w-[1400px] mx-auto px-6 py-12 relative z-40">
<div className="text-center mb-6">
<button onClick={() => setSortMethod(sortMethod === 'dist' ? 'alpha' : 'dist')} className="bg-white px-6 py-3 rounded-full shadow-md text-[10px] font-black text-[#8bc34a] uppercase tracking-widest border border-gray-100">
{sortMethod === 'dist' ? "📍 Nærmeste baner først" : "🔠 Alfabetisk visning"} {processed.length} baner
</button>
2026-02-26 09:20:51 +01:00
</div>
2026-02-27 08:53:14 +01:00
<input className="w-full p-8 rounded-[2.5rem] shadow-2xl mb-16 text-gray-900 border-none ring-1 ring-black/5 text-2xl outline-none focus:ring-4 focus:ring-[#8bc34a]/20 transition-all bg-white" placeholder='Søk baner, fylke eller "nsg"...' value={searchQuery} onChange={e => setSearchQuery(e.target.value)} />
2026-02-26 09:20:51 +01:00
2026-02-27 08:53:14 +01:00
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
2026-02-26 09:20:51 +01:00
{processed.map((f: any) => (
2026-02-27 08:53:14 +01:00
<Link href={`/golfbaner/${f.slug}`} key={f.id} className="bg-white rounded-[3rem] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-gray-100 flex flex-col group relative">
<div className="h-64 relative overflow-hidden bg-gray-100">
<img src={f.image_url || "/Toppbilde-standard.jpg"} className="w-full h-full object-cover transition duration-1000 group-hover:scale-105" alt={f.name} />
<div className="absolute top-6 left-6 flex flex-col gap-2">
{(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 <div key={idx} className={`${color} text-white px-4 py-2 rounded-xl text-[10px] font-black uppercase shadow-xl`}>{STATUS_MAP[s.status] || s.status}</div>;
2026-02-26 09:20:51 +01:00
})}
</div>
2026-02-27 08:53:14 +01:00
<div className="absolute top-6 right-6 flex gap-2">
{f.hasNSG && <div className="w-10 h-10 bg-blue-600 text-white rounded-xl flex items-center justify-center font-black text-xs shadow-2xl">NSG</div>}
{f.hasGolfamore && <div className="w-10 h-10 bg-[#ff5722] text-white rounded-xl flex items-center justify-center font-black text-xs shadow-2xl">G</div>}
</div>
2026-02-26 09:20:51 +01:00
</div>
2026-02-27 08:53:14 +01:00
<div className="p-10 flex flex-col flex-grow">
<h3 className="font-black text-3xl text-[#11280f] mb-2 group-hover:text-[#8bc34a] transition-colors leading-tight">{f.name}</h3>
<p className="text-gray-400 text-sm font-bold uppercase tracking-widest">{f.city} {f.county}</p>
<div className="pt-8 mt-auto border-t border-gray-50 flex items-center gap-3">
<span className="bg-[#f1f7ed] text-[#8bc34a] px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest">{f.amenities?.antall_hull || '--'} Hull</span>
<span className="bg-gray-50 text-gray-400 px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest border border-gray-100">{f.banetype || 'Park/Skog'}</span>
2026-02-26 09:20:51 +01:00
</div>
</div>
</Link>
))}
</div>
</div>
);
2026-02-27 08:53:14 +01:00
}