135 lines
4.9 KiB
TypeScript
135 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useDeferredValue, useMemo, useState } from "react";
|
|
import type { FacilityRecord } from "@/app/facilityData";
|
|
|
|
type SortKey = "name" | "ngf_number" | "county";
|
|
|
|
type ClubNumbersTableProps = {
|
|
facilities: FacilityRecord[];
|
|
};
|
|
|
|
function normalizeComparable(value: string | number | null | undefined) {
|
|
return String(value ?? "").trim().toLowerCase();
|
|
}
|
|
|
|
export default function ClubNumbersTable({ facilities }: ClubNumbersTableProps) {
|
|
const [query, setQuery] = useState("");
|
|
const [sortKey, setSortKey] = useState<SortKey>("name");
|
|
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
|
const deferredQuery = useDeferredValue(query);
|
|
|
|
const rows = useMemo(() => {
|
|
const filtered = facilities.filter((facility) => {
|
|
if (!facility.slug || facility.ngf_number === null || facility.ngf_number === undefined) {
|
|
return false;
|
|
}
|
|
|
|
const haystack = [facility.name, facility.county, facility.city, facility.ngf_number]
|
|
.map((value) => normalizeComparable(value))
|
|
.join(" ");
|
|
|
|
return haystack.includes(normalizeComparable(deferredQuery));
|
|
});
|
|
|
|
filtered.sort((left, right) => {
|
|
const leftValue = normalizeComparable(left[sortKey]);
|
|
const rightValue = normalizeComparable(right[sortKey]);
|
|
const comparison = leftValue.localeCompare(rightValue, "nb-NO", { numeric: true });
|
|
return sortDirection === "asc" ? comparison : -comparison;
|
|
});
|
|
|
|
return filtered;
|
|
}, [deferredQuery, facilities, sortDirection, sortKey]);
|
|
|
|
const toggleSort = (key: SortKey) => {
|
|
if (sortKey === key) {
|
|
setSortDirection((current) => (current === "asc" ? "desc" : "asc"));
|
|
return;
|
|
}
|
|
|
|
setSortKey(key);
|
|
setSortDirection("asc");
|
|
};
|
|
|
|
const renderSortLabel = (key: SortKey, label: string) => (
|
|
<button
|
|
type="button"
|
|
onClick={() => toggleSort(key)}
|
|
className="flex items-center gap-2 text-left text-[11px] font-black uppercase tracking-[0.18em] text-[#112015]"
|
|
>
|
|
<span>{label}</span>
|
|
<span aria-hidden="true" className="text-[#6A766C]">
|
|
{sortKey === key ? (sortDirection === "asc" ? "↑" : "↓") : "↕"}
|
|
</span>
|
|
</button>
|
|
);
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">NGF-nummer</p>
|
|
<p className="mt-3 text-sm leading-6 text-[#536256]">
|
|
Klikk på klubbnavnet for å åpne den respektive baneprofilen.
|
|
</p>
|
|
</div>
|
|
|
|
<label className="flex flex-col gap-2">
|
|
<span className="text-[11px] font-black uppercase tracking-[0.18em] text-[#6A766C]">
|
|
Søk
|
|
</span>
|
|
<input
|
|
type="search"
|
|
value={query}
|
|
onChange={(event) => setQuery(event.target.value)}
|
|
placeholder="Søk på klubb, sted eller NGF-nummer"
|
|
className="w-full min-w-[18rem] rounded-[1rem] border border-[#112015]/12 bg-white px-4 py-3 text-sm font-bold text-[#112015] outline-none transition focus:border-[#8BC34A]"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="overflow-hidden rounded-[1.75rem] border border-[#112015]/8 bg-white">
|
|
<div className="overflow-x-auto">
|
|
<table className="min-w-full border-collapse">
|
|
<thead className="bg-[#F4F7EE]">
|
|
<tr>
|
|
<th className="px-4 py-4 text-left">{renderSortLabel("name", "Klubb")}</th>
|
|
<th className="px-4 py-4 text-left">{renderSortLabel("ngf_number", "NGF-nummer")}</th>
|
|
<th className="px-4 py-4 text-left">{renderSortLabel("county", "Fylke")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.map((facility) => (
|
|
<tr key={facility.slug} className="border-t border-[#112015]/8 align-top">
|
|
<td className="px-4 py-4">
|
|
<Link
|
|
href={`/golfbaner/${facility.slug}`}
|
|
className="text-sm font-black text-[#112015] underline underline-offset-4"
|
|
>
|
|
{facility.name}
|
|
</Link>
|
|
{facility.city ? (
|
|
<p className="mt-1 text-xs font-bold text-[#6A766C]">{facility.city}</p>
|
|
) : null}
|
|
</td>
|
|
<td className="px-4 py-4 text-sm font-bold text-[#334238]">
|
|
{facility.ngf_number || "—"}
|
|
</td>
|
|
<td className="px-4 py-4 text-sm font-bold text-[#334238]">
|
|
{facility.county || "—"}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-sm font-bold text-[#5A685C]">
|
|
{rows.length} klubber/anlegg med NGF-nummer
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|