kennzeichen-seite/frontend/app/kennzeichen/page.tsx
2026-05-20 20:47:07 +02:00

121 lines
5.5 KiB
TypeScript

export const dynamic = 'force-dynamic';
import { pb, COUNTRY_LABELS, COUNTRY_FLAGS, type Kennzeichen } from "@/lib/pb";
import KennzeichenFilter from "./KennzeichenFilter";
interface Props { searchParams: { land?: string; q?: string; seite?: string; historisch?: string } }
export const metadata = { title: "Datenbank" };
async function getData(land?: string, q?: string, page = 1, historisch = false) {
try {
const safe = (s: string) => s.replace(/["\\]/g, "");
const filters: string[] = [`country!="global"`];
if (land) filters.push(`country="${safe(land)}"`);
if (!historisch) filters.push(`active=true`);
if (q) { const s = safe(q); filters.push(`(code~"${s}" || name~"${s}" || region~"${s}")`); }
return pb.collection("kennzeichen").getList<Kennzeichen>(page, 100, {
filter: filters.join(" && ") || undefined, sort: "code",
});
} catch { return null; }
}
async function getCountryCounts() {
try {
const all = await pb.collection("kennzeichen").getFullList<Kennzeichen>({ fields: "country", filter: 'active=true && country!="global"' });
const counts: Record<string, number> = {};
for (const k of all) counts[k.country] = (counts[k.country] ?? 0) + 1;
return counts;
} catch { return {}; }
}
function Pagination({ page, totalPages, land, q }: { page: number; totalPages: number; land?: string; q?: string }) {
if (totalPages <= 1) return null;
const makeHref = (p: number) => {
const params = new URLSearchParams();
if (land) params.set("land", land);
if (q) params.set("q", q);
params.set("seite", String(p));
return `?${params.toString()}`;
};
const pages: (number | string)[] = [];
for (let p = 1; p <= totalPages; p++) {
if (p === 1 || p === totalPages || Math.abs(p - page) <= 2) {
pages.push(p);
} else if (pages[pages.length - 1] !== "…") {
pages.push("…");
}
}
return (
<div className="flex gap-2 mt-6 justify-center flex-wrap">
{page > 1 && (
<a href={makeHref(page - 1)} className="w-9 h-9 flex items-center justify-center rounded-md text-sm border border-[var(--warm)] hover:border-[var(--ink)]"></a>
)}
{pages.map((p, i) =>
p === "…" ? (
<span key={`e${i}`} className="w-9 h-9 flex items-center justify-center text-[var(--muted)]"></span>
) : (
<a key={p} href={makeHref(p as number)}
className={`w-9 h-9 flex items-center justify-center rounded-md text-sm border transition-colors ${p === page ? "bg-[var(--ink)] text-[var(--paper)] border-[var(--ink)]" : "border-[var(--warm)] hover:border-[var(--ink)]"}`}>
{p}
</a>
)
)}
{page < totalPages && (
<a href={makeHref(page + 1)} className="w-9 h-9 flex items-center justify-center rounded-md text-sm border border-[var(--warm)] hover:border-[var(--ink)]"></a>
)}
</div>
);
}
export default async function KennzeichenPage({ searchParams }: Props) {
const land = searchParams.land;
const q = searchParams.q;
const page = parseInt(searchParams.seite ?? "1");
const historisch = searchParams.historisch === "1";
const [data, counts] = await Promise.all([getData(land, q, page, historisch), getCountryCounts()]);
const items = data?.items ?? [];
return (
<div className="max-w-6xl mx-auto px-6 py-12">
<div className="mb-10">
<div className="text-xs font-mono text-[var(--muted)] tracking-widest uppercase mb-2">Datenbank</div>
<h1 style={{ fontFamily: "'Syne',sans-serif", fontWeight: 800, fontSize: "2.25rem", letterSpacing: "-0.03em" }} className="text-[var(--ink)]">Kennzeichen</h1>
{data && <p className="text-[var(--muted)] mt-1">{data.totalItems.toLocaleString("de")} Einträge</p>}
</div>
<KennzeichenFilter
countries={Object.entries(COUNTRY_LABELS).map(([key, label]) => ({ key, label, flag: COUNTRY_FLAGS[key] ?? "🌍", count: counts[key] ?? 0 }))}
activeLand={land} activeQ={q} historisch={historisch}
/>
<div className="border border-[var(--warm)] rounded-lg overflow-hidden bg-white/40 mt-6">
{items.length === 0 ? (
<div className="text-center py-20 text-[var(--muted)]">Keine Kennzeichen gefunden.</div>
) : (
<div className="overflow-x-auto">
<table className="data-table">
<thead>
<tr>
<th>Kürzel</th><th>Ort / Kreis</th><th>Herleitung</th>
<th className="hidden md:table-cell">Region</th>
<th className="hidden lg:table-cell">Punkte</th>
<th className="hidden lg:table-cell">Bemerkung</th>
</tr>
</thead>
<tbody>
{items.map((kz) => (
<tr key={kz.id}>
<td><span className="kz-badge">{kz.code}</span></td>
<td className="font-medium">{kz.name}</td>
<td className="text-[var(--muted)] text-sm">{kz.derivation}</td>
<td className="text-[var(--muted)] text-sm hidden md:table-cell">{kz.region}</td>
<td className="text-[var(--muted)] text-sm hidden lg:table-cell font-mono">{kz.points ?? "—"}</td>
<td className="text-[var(--muted)] text-sm hidden lg:table-cell max-w-xs truncate">{kz.note}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{data && <Pagination page={page} totalPages={data.totalPages} land={land} q={q} />}
</div>
);
}