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

158 lines
6.6 KiB
TypeScript

export const dynamic = 'force-dynamic';
import { pb, type Diplomatenkennzeichen } from "@/lib/pb";
export const metadata = { title: "Diplomatenkennzeichen" };
const COUNTRY_NAMES: Record<string, string> = {
germany: "Deutschland", austria: "Österreich",
switzerland: "Schweiz", luxembourg: "Luxemburg",
};
const COUNTRY_FLAGS: Record<string, string> = {
germany: "🇩🇪", austria: "🇦🇹", switzerland: "🇨🇭", luxembourg: "🇱🇺",
};
const TYPE_LABELS: Record<string, string> = {
embassy: "Botschaft", consulate: "Konsulat", io: "Intern. Organisation",
};
const TYPE_COLORS: Record<string, string> = {
embassy: "bg-blue-50 text-blue-800 border-blue-200",
consulate: "bg-amber-50 text-amber-800 border-amber-200",
io: "bg-emerald-50 text-emerald-800 border-emerald-200",
};
interface Props { searchParams: { land?: string } }
async function getData(baseCountry: string) {
try {
// Ohne sort um 400 zu vermeiden, sortieren wir client-side
return pb.collection("diplomatenkennzeichen").getFullList<Diplomatenkennzeichen>({
filter: `base_country="${baseCountry}"`,
});
} catch { return []; }
}
async function getAvailableCountries(): Promise<string[]> {
try {
const all = await pb.collection("diplomatenkennzeichen").getFullList<Diplomatenkennzeichen>({
fields: "base_country",
});
return [...new Set(all.map((i: any) => i.base_country).filter(Boolean))].sort() as string[];
} catch { return ["germany"]; }
}
function sortByCode(a: Diplomatenkennzeichen, b: Diplomatenkennzeichen) {
const parseCode = (c: string) => {
const m = c.match(/^(?:0-)?(\d+)$/);
return m ? parseInt(m[1]) : -1;
};
const na = parseCode(a.code);
const nb = parseCode(b.code);
if (na >= 0 && nb >= 0) return na - nb;
if (na >= 0) return -1;
if (nb >= 0) return 1;
return a.code.localeCompare(b.code);
}
export default async function DiplomatenPage({ searchParams }: Props) {
const countries = await getAvailableCountries();
const activeLand = searchParams.land ?? countries[0] ?? "germany";
const rawItems = await getData(activeLand);
const items = [...rawItems].sort(sortByCode);
const grouped = items.reduce<Record<string, Diplomatenkennzeichen[]>>((acc, item) => {
const t = item.type ?? "embassy";
if (!acc[t]) acc[t] = [];
acc[t].push(item);
return acc;
}, {});
return (
<div className="max-w-6xl mx-auto px-6 py-12">
<div className="mb-8">
<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)]">Diplomatenkennzeichen</h1>
<p className="text-[var(--muted)] mt-2 max-w-2xl">
Sonderkennzeichen für Botschaften, Konsulate und internationale Organisationen.
</p>
</div>
{/* Länder-Tabs */}
<div className="flex gap-2 flex-wrap mb-8">
{countries.map(c => (
<a key={c} href={`?land=${c}`}
className={`flex items-center gap-2 px-4 py-2 rounded-lg border text-sm transition-colors ${
c === activeLand
? "bg-[var(--ink)] text-[var(--paper)] border-[var(--ink)]"
: "border-[var(--warm)] text-[var(--muted)] hover:border-[var(--ink)] hover:text-[var(--ink)]"
}`}
style={{ fontFamily: "'Syne',sans-serif", fontWeight: 500 }}>
<span>{COUNTRY_FLAGS[c] ?? "🌍"}</span>
<span>{COUNTRY_NAMES[c] ?? c}</span>
</a>
))}
</div>
<div className="mb-6 flex gap-3 flex-wrap">
{Object.entries(TYPE_LABELS).map(([key, label]) => (
<span key={key} className={`px-2.5 py-1 rounded border text-xs font-medium ${TYPE_COLORS[key]}`}>
{label}: {grouped[key]?.length ?? 0}
</span>
))}
<span className="text-sm text-[var(--muted)] self-center"> {items.length} Einträge gesamt</span>
</div>
{items.length === 0 ? (
<div className="text-center py-20 text-[var(--muted)]">Keine Daten für dieses Land.</div>
) : (
Object.entries(TYPE_LABELS).map(([type, typeLabel]) => {
const entries = grouped[type];
if (!entries?.length) return null;
return (
<div key={type} className="mb-10">
<h2 className="text-base mb-3 flex items-center gap-2"
style={{ fontFamily: "'Syne',sans-serif", fontWeight: 700 }}>
<span className={`px-2.5 py-0.5 rounded border text-xs ${TYPE_COLORS[type]}`}>{typeLabel}</span>
<span className="text-[var(--muted)] text-sm font-normal">{entries.length} Einträge</span>
</h2>
<div className="border border-[var(--warm)] rounded-lg overflow-hidden bg-white/40">
<div className="overflow-x-auto">
<table className="data-table">
<thead>
<tr>
<th>Kürzel</th>
<th>Land / Organisation</th>
<th className="hidden md:table-cell">Stadt</th>
<th className="hidden lg:table-cell">Bemerkung</th>
</tr>
</thead>
<tbody>
{entries.map((d) => (
<tr key={d.id}>
<td><span className="kz-badge font-mono text-xs">{d.code}</span></td>
<td>
<div className="font-medium text-sm">{d.country}</div>
{d.organisation && <div className="text-xs text-[var(--muted)]">{d.organisation}</div>}
</td>
<td className="text-sm text-[var(--muted)] hidden md:table-cell">{d.city}</td>
<td className="text-sm text-[var(--muted)] hidden lg:table-cell">{d.note}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
})
)}
<div className="mt-8 p-5 border border-dashed border-[var(--warm)] rounded-lg text-center">
<p className="text-sm text-[var(--muted)] mb-2">Daten unvollständig oder fehlerhaft?</p>
<a href="https://git.denode.eu/denode/plates" target="_blank" rel="noopener"
className="text-sm text-[var(--accent)] hover:underline font-medium">
git.denode.eu/denode/plates
</a>
</div>
</div>
);
}