diff --git a/frontend/src/app/FacilitySearch.tsx b/frontend/src/app/FacilitySearch.tsx index a0c501f..8b4bfdf 100755 --- a/frontend/src/app/FacilitySearch.tsx +++ b/frontend/src/app/FacilitySearch.tsx @@ -71,13 +71,16 @@ const AREA_GROUPS: Record = { "oslo-og-akershus": ["akershus", "oslo", "viken"], }; +const COUNTY_FILTER_ALIASES: Record = { + trondelag: ["trondelag", "nord-trondelag", "sor-trondelag"], +}; + const HIERARCHICAL_AREA_OPTIONS = [ { value: "", label: "Hele Norge" }, { value: "region:nord-norge", label: "Nord-Norge" }, { value: "county:finnmark", label: "\u00A0\u00A0\u00A0Finnmark" }, { value: "county:troms", label: "\u00A0\u00A0\u00A0Troms" }, { value: "county:nordland", label: "\u00A0\u00A0\u00A0Nordland" }, - { value: "region:midt-norge", label: "Midt-Norge" }, { value: "county:nord-trondelag", label: "\u00A0\u00A0\u00A0Nord-Trøndelag" }, { value: "county:sor-trondelag", label: "\u00A0\u00A0\u00A0Sør-Trøndelag" }, { value: "county:trondelag", label: "\u00A0\u00A0\u00A0Trøndelag" }, @@ -86,7 +89,6 @@ const HIERARCHICAL_AREA_OPTIONS = [ { value: "county:sogn-og-fjordane", label: "\u00A0\u00A0\u00A0Sogn og Fjordane" }, { value: "county:hordaland", label: "\u00A0\u00A0\u00A0Hordaland" }, { value: "county:rogaland", label: "\u00A0\u00A0\u00A0Rogaland" }, - { value: "county:vestland", label: "\u00A0\u00A0\u00A0Vestland" }, { value: "region:sorlandet", label: "Sørlandet" }, { value: "county:vest-agder", label: "\u00A0\u00A0\u00A0Vest-Agder" }, { value: "county:aust-agder", label: "\u00A0\u00A0\u00A0Aust-Agder" }, @@ -129,6 +131,13 @@ const STATUS_CLASSES: Record = { const normalizeText = (value: unknown) => String(value ?? "") + .replace(/[æøå]/gi, (char) => { + const normalized = char.toLowerCase(); + if (normalized === "æ") return "ae"; + if (normalized === "ø") return "o"; + if (normalized === "å") return "a"; + return normalized; + }) .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") @@ -215,13 +224,6 @@ const buildMapUrl = (lat?: number | null, lng?: number | null) => { return `https://www.google.com/maps/search/?api=1&query=${lat},${lng}`; }; -const toPlainText = (value: string | null | undefined) => - String(value || "") - .replace(/<[^>]+>/g, " ") - .replace(/ /gi, " ") - .replace(/\s+/g, " ") - .trim(); - const escapeHtml = (value: string) => value .replace(/&/g, "&") @@ -473,13 +475,15 @@ export default function FacilitySearch({ .filter((word) => word && !stopWords.has(word)); const selectedArea = areaFilter.replace(/^(region:|county:)/, ""); + const countyAliases = COUNTY_FILTER_ALIASES[selectedArea]; const matchesSearch = words.every((word) => searchBlob.includes(word)); const matchesArea = !areaFilter || (areaFilter.startsWith("region:") && (regions.includes(selectedArea) || (AREA_GROUPS[selectedArea] ? AREA_GROUPS[selectedArea].includes(countySlug) : false))) || - (areaFilter.startsWith("county:") && countySlug === selectedArea); + (areaFilter.startsWith("county:") && + (countyAliases ? countyAliases.includes(countySlug) : countySlug === selectedArea)); const matchesStatus = !statusFilter || normalizedStatuses.includes(statusFilter); const matchesHoles = matchesHoleFilter(holeValue, holeFilter); const matchesSpecial = matchesSpecialFilter(specialFilter, { diff --git a/frontend/src/app/facilityData.ts b/frontend/src/app/facilityData.ts index 5ad334b..887089b 100755 --- a/frontend/src/app/facilityData.ts +++ b/frontend/src/app/facilityData.ts @@ -71,13 +71,16 @@ export const AREA_GROUPS: Record = { "oslo-og-akershus": ["akershus", "oslo", "viken"], }; +const COUNTY_FILTER_ALIASES: Record = { + trondelag: ["trondelag", "nord-trondelag", "sor-trondelag"], +}; + export const HIERARCHICAL_AREA_OPTIONS = [ { value: "", label: "Hele Norge", slug: "norge" }, { value: "region:nord-norge", label: "Nord-Norge", slug: "nord-norge" }, { value: "county:finnmark", label: "Finnmark", slug: "finnmark" }, { value: "county:troms", label: "Troms", slug: "troms" }, { value: "county:nordland", label: "Nordland", slug: "nordland" }, - { value: "region:midt-norge", label: "Midt-Norge", slug: "midt-norge" }, { value: "county:nord-trondelag", label: "Nord-Trøndelag", slug: "nord-trondelag" }, { value: "county:sor-trondelag", label: "Sør-Trøndelag", slug: "sor-trondelag" }, { value: "county:trondelag", label: "Trøndelag", slug: "trondelag" }, @@ -86,7 +89,6 @@ export const HIERARCHICAL_AREA_OPTIONS = [ { value: "county:sogn-og-fjordane", label: "Sogn og Fjordane", slug: "sogn-og-fjordane" }, { value: "county:hordaland", label: "Hordaland", slug: "hordaland" }, { value: "county:rogaland", label: "Rogaland", slug: "rogaland" }, - { value: "county:vestland", label: "Vestland", slug: "vestland" }, { value: "region:sorlandet", label: "Sørlandet", slug: "sorlandet" }, { value: "county:vest-agder", label: "Vest-Agder", slug: "vest-agder" }, { value: "county:aust-agder", label: "Aust-Agder", slug: "aust-agder" }, @@ -118,6 +120,13 @@ export const STATUS_ICON_PATHS: Record = { export const normalizeText = (value: unknown) => String(value ?? "") + .replace(/[æøå]/gi, (char) => { + const normalized = char.toLowerCase(); + if (normalized === "æ") return "ae"; + if (normalized === "ø") return "o"; + if (normalized === "å") return "a"; + return normalized; + }) .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") @@ -264,7 +273,8 @@ export const matchesAreaFilter = (facility: EnrichedFacility, areaFilter: string ); } if (areaFilter.startsWith("county:")) { - return facility.countySlug === selectedArea; + const aliases = COUNTY_FILTER_ALIASES[selectedArea]; + return aliases ? aliases.includes(facility.countySlug) : facility.countySlug === selectedArea; } return true; }; diff --git a/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx b/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx index c358f5e..334a9e9 100644 --- a/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx +++ b/frontend/src/app/golfbaner/[slug]/FacilityDetailView.tsx @@ -17,7 +17,7 @@ import { useState, useEffect } from 'react'; import { Icon as LeafletIcon } from "leaflet"; import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet"; import { STATUS_MAP, FALLBACK_IMAGE } from "@/config/constants"; -import { STATUS_ICON_PATHS, buildMapUrl, getPrimaryStatus, parseJson as parseSharedJson } from "@/app/facilityData"; +import { STATUS_ICON_PATHS, buildMapUrl, getPrimaryStatus, parseJson as parseSharedJson, slugify } from "@/app/facilityData"; import Link from 'next/link'; import CourseDisplay from './CourseDisplay'; @@ -198,8 +198,28 @@ export default function FacilityDetailView({ facility }: { facility: any }) { ); const sidebarLinkClass = "group flex items-center gap-4 text-[#11280f] transition-colors hover:text-[#ff5722]"; - const resourceBtnClass = "btn-panel p-5"; - const sectionNavButtonClass = "btn btn-sm btn-secondary whitespace-nowrap"; + const sidebarLinkTextClass = "transition-colors group-hover:text-[#ff5722]"; + const detailMetaLinkClass = + "text-[10px] font-black uppercase tracking-widest text-[#7ca982] transition-colors hover:text-[#ff5722]"; + const resourceBtnClass = "btn-panel group w-full p-5"; + const sectionNavButtonClass = + "btn btn-secondary h-10 shrink-0 whitespace-nowrap px-3 text-[9px] tracking-[0.12em] md:h-auto md:px-4 md:text-[11px] md:tracking-[0.16em]"; + const mobileSectionNavButtonClass = + "whitespace-nowrap text-[8px] font-black uppercase tracking-[0.04em] text-gray-500 transition-colors hover:text-[#11280f]"; + const placeSlug = slugify(facility.county || "") || "norge"; + const sectionNavItems = [ + { id: 'intro', label: 'Info', showOnMobile: false }, + { id: 'weather', label: 'Vær', showOnMobile: false }, + { id: 'details', label: 'Detaljer', showOnMobile: true }, + mapUrl ? { id: 'map', label: 'Kart', showOnMobile: true } : null, + facility.video_url ? { id: 'video', label: 'Video', showOnMobile: true } : null, + { id: 'prices', label: 'Priser', showOnMobile: true }, + hasVtg ? { id: 'vtg', label: 'VTG', showOnMobile: true } : null, + { id: 'scorecards', label: 'Scorekort', showOnMobile: true }, + ].filter( + (item): item is { id: string; label: string; showOnMobile: boolean } => Boolean(item) + ); + const mobileSectionNavItems = sectionNavItems.filter((item) => item.showOnMobile); useEffect(() => { if (gallery.length <= 1) return; @@ -267,16 +287,21 @@ export default function FacilityDetailView({ facility }: { facility: any }) { {/* 2. STICKY NAV */} -