Nye-TeeOff/frontend/src/components/ClubNumbersTable.tsx

136 lines
4.9 KiB
TypeScript
Raw Normal View History

"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 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>
);
}