{/* breadcrumb */}
-
+
{t({ en: "Catalog", sv: "Katalog" })}
/
diff --git a/app/katalog/page.tsx b/app/katalog/page.tsx
index c8b3812..09eaf89 100644
--- a/app/katalog/page.tsx
+++ b/app/katalog/page.tsx
@@ -1,8 +1,8 @@
"use client";
import Link from "next/link";
-import { useRouter } from "next/navigation";
-import { useEffect, useMemo, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { useT } from "@/components/locale-provider";
import { Header } from "@/components/header";
import { useReveal } from "@/hooks/use-reveal";
@@ -37,14 +37,56 @@ function level(c: number): ProvLevel {
return 0;
}
+/**
+ * Filter + sök lever i URL:en (kernel-konventionen: URL state är kanoniskt och
+ * återställbart) — "Tillbaka" från en detaljsida återställer exakt vy, och en
+ * delad länk öppnar samma filtrering. pages-komponenten wrappar i Suspense
+ * eftersom useSearchParams kräver det vid prerender.
+ */
export default function CatalogPage() {
+ return (
+
+
+
+ );
+}
+
+function CatalogInner() {
const t = useT();
const ref = useReveal();
const router = useRouter();
+ const searchParams = useSearchParams();
const [items, setItems] = useState(null);
const [err, setErr] = useState(false);
- const [filter, setFilter] = useState("all");
- const [q, setQ] = useState("");
+ const filter = searchParams.get("filter") ?? "all";
+ const q = searchParams.get("q") ?? "";
+ // Lokalt utkast för sökfältet (skrivtick), synkat debouncerat till URL:en.
+ const [qDraft, setQDraft] = useState(q);
+ const urlSync = useRef | null>(null);
+ useEffect(() => { setQDraft(q); }, [q]);
+
+ function applyState(next: { q?: string; filter?: string }) {
+ const params = new URLSearchParams();
+ const nq = (next.q ?? q).trim().slice(0, 120);
+ const nf = next.filter ?? filter;
+ if (nq) params.set("q", nq);
+ if (nf && nf !== "all") params.set("filter", nf);
+ const qs = params.toString();
+ router.replace(qs ? `/katalog?${qs}` : "/katalog", { scroll: false });
+ }
+
+ function setFilterNext(next: string) {
+ if (urlSync.current) clearTimeout(urlSync.current);
+ applyState({ filter: next });
+ }
+
+ function setQNext(next: string) {
+ setQDraft(next);
+ if (urlSync.current) clearTimeout(urlSync.current);
+ urlSync.current = setTimeout(() => applyState({ q: next }), 250);
+ }
+
+ useEffect(() => () => { if (urlSync.current) clearTimeout(urlSync.current); }, []);
function load() {
setErr(false);
@@ -89,7 +131,7 @@ export default function CatalogPage() {
{FILTERS.map((f) => (
setQ(e.target.value)}
+ value={qDraft}
+ onChange={(e) => setQNext(e.target.value)}
placeholder={t({ en: "Search datasets…", sv: "Sök dataset…" })}
aria-label={t({ en: "Search datasets", sv: "Sök dataset" })}
className="w-full max-w-xs border border-border bg-input px-3 py-2 font-mono text-xs outline-none focus:border-foreground sm:w-auto"