Compare commits
1 commit
27fb01f619
...
8544648cc4
| Author | SHA1 | Date | |
|---|---|---|---|
| 8544648cc4 |
6 changed files with 19 additions and 68 deletions
|
|
@ -4,8 +4,8 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- pb_data:/pb/pb_data
|
- pb_data:/pb/pb_data
|
||||||
ports:
|
# ports:
|
||||||
- "4444:8090"
|
#- "4444:8090"
|
||||||
nextjs:
|
nextjs:
|
||||||
build: ./frontend
|
build: ./frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||||
<Nav />
|
<Nav />
|
||||||
<main className="min-h-screen">{children}</main>
|
<main className="min-h-screen">{children}</main>
|
||||||
<footer className="border-t border-[var(--warm)] mt-24 py-10 px-6">
|
<footer className="border-t border-[var(--warm)] mt-24 py-10 px-6">
|
||||||
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 text-sm text-[var(--muted)]">
|
<div className="max-w-6xl mx-auto flex items-center justify-between text-sm text-[var(--muted)]">
|
||||||
<span style={{ fontFamily: "'Syne', sans-serif", fontWeight: 600 }}>
|
<span style={{ fontFamily: "'Syne', sans-serif", fontWeight: 600 }}>
|
||||||
Kennzeichensammler
|
Kennzeichensammler
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
import { unstable_noStore as noStore } from 'next/cache';
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { pb, COUNTRY_LABELS, COUNTRY_FLAGS } from "@/lib/pb";
|
import { pb, COUNTRY_LABELS, COUNTRY_FLAGS } from "@/lib/pb";
|
||||||
|
|
||||||
async function getStats() {
|
async function getStats() {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
const [kz, diplo, gesehen] = await Promise.all([
|
const [kz, diplo, gesehen] = await Promise.all([
|
||||||
pb.collection("kennzeichen").getList(1, 1, { filter: "active=true" }),
|
pb.collection("kennzeichen").getList(1, 1, { filter: "active=true" }),
|
||||||
|
|
@ -22,7 +20,6 @@ async function getStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLasteSeen() {
|
async function getLasteSeen() {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
return pb.collection("gesehen").getList(1, 5, { sort: "-datum" });
|
return pb.collection("gesehen").getList(1, 5, { sort: "-datum" });
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -67,7 +64,7 @@ export default async function Home() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-3 gap-3 sm:gap-4 mb-20">
|
<div className="grid grid-cols-3 gap-4 mb-20">
|
||||||
{[
|
{[
|
||||||
{ label: "Kennzeichen", value: stats.kennzeichen.toLocaleString("de") },
|
{ label: "Kennzeichen", value: stats.kennzeichen.toLocaleString("de") },
|
||||||
{ label: "Diplomatenkz.", value: stats.diplomaten.toLocaleString("de") },
|
{ label: "Diplomatenkz.", value: stats.diplomaten.toLocaleString("de") },
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default async function SammlungPage() {
|
||||||
<h1 style={{ fontFamily:"'Syne',sans-serif", fontWeight:800, fontSize:"2.25rem", letterSpacing:"-0.03em" }} className="text-[var(--ink)]">Meine Sammlung</h1>
|
<h1 style={{ fontFamily:"'Syne',sans-serif", fontWeight:800, fontSize:"2.25rem", letterSpacing:"-0.03em" }} className="text-[var(--ink)]">Meine Sammlung</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[var(--warm)] rounded-lg p-8 bg-white/40 mb-8">
|
<div className="border border-[var(--warm)] rounded-lg p-8 bg-white/40 mb-8">
|
||||||
<div className="flex flex-wrap items-end justify-between gap-2 mb-4">
|
<div className="flex items-end justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:800, fontSize:"3rem", lineHeight:1 }} className="text-[var(--ink)]">{stats.total.toLocaleString("de")}</div>
|
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:800, fontSize:"3rem", lineHeight:1 }} className="text-[var(--ink)]">{stats.total.toLocaleString("de")}</div>
|
||||||
<div className="text-[var(--muted)] text-sm mt-1">von {stats.totalKz.toLocaleString("de")} Kennzeichen gesehen</div>
|
<div className="text-[var(--muted)] text-sm mt-1">von {stats.totalKz.toLocaleString("de")} Kennzeichen gesehen</div>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,30 @@
|
||||||
"use client";
|
"use client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useState } from "react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ href: "/", label: "Start" },
|
{ href: "/", label: "Start" },
|
||||||
{ href: "/kennzeichen", label: "Datenbank" },
|
{ href: "/kennzeichen", label: "Datenbank" },
|
||||||
{ href: "/diplomatenkennzeichen", label: "Diplomaten" },
|
{ href: "/diplomatenkennzeichen",label: "Diplomaten" },
|
||||||
{ href: "/sammlung", label: "Sammlung" },
|
{ href: "/sammlung", label: "Sammlung" },
|
||||||
{ href: "/blog", label: "Blog" },
|
{ href: "/blog", label: "Blog" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Nav() {
|
export default function Nav() {
|
||||||
const path = usePathname();
|
const path = usePathname();
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 border-b border-[var(--warm)] bg-[var(--paper)]/90 backdrop-blur-sm">
|
<header className="sticky top-0 z-50 border-b border-[var(--warm)] bg-[var(--paper)]/90 backdrop-blur-sm">
|
||||||
<div className="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
<div className="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
style={{ fontFamily: "'Syne', sans-serif", fontWeight: 700, fontSize: 15 }}
|
style={{ fontFamily: "'Syne', sans-serif", fontWeight: 700, fontSize: 15, letterSpacing: "0em" }}
|
||||||
className="text-[var(--ink)] hover:text-[var(--accent)] transition-colors"
|
className="text-[var(--ink)] hover:text-[var(--accent)] transition-colors"
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
>
|
>
|
||||||
Kennzeichensammler
|
Kennzeichensammler
|
||||||
</Link>
|
</Link>
|
||||||
|
<nav className="flex items-center gap-1">
|
||||||
{/* Desktop nav */}
|
|
||||||
<nav className="hidden sm:flex items-center gap-1">
|
|
||||||
{links.map((l) => (
|
{links.map((l) => (
|
||||||
<Link
|
<Link
|
||||||
key={l.href}
|
key={l.href}
|
||||||
|
|
@ -46,40 +41,7 @@ export default function Nav() {
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Mobile hamburger */}
|
|
||||||
<button
|
|
||||||
className="sm:hidden flex flex-col gap-1.5 p-2 -mr-2"
|
|
||||||
onClick={() => setOpen((o) => !o)}
|
|
||||||
aria-label="Menü"
|
|
||||||
>
|
|
||||||
<span className={clsx("block w-5 h-0.5 bg-[var(--ink)] transition-all origin-center", open && "rotate-45 translate-y-2")} />
|
|
||||||
<span className={clsx("block w-5 h-0.5 bg-[var(--ink)] transition-all", open && "opacity-0")} />
|
|
||||||
<span className={clsx("block w-5 h-0.5 bg-[var(--ink)] transition-all origin-center", open && "-rotate-45 -translate-y-2")} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile menu */}
|
|
||||||
{open && (
|
|
||||||
<div className="sm:hidden border-t border-[var(--warm)] bg-[var(--paper)] px-4 py-3 flex flex-col gap-1">
|
|
||||||
{links.map((l) => (
|
|
||||||
<Link
|
|
||||||
key={l.href}
|
|
||||||
href={l.href}
|
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
className={clsx(
|
|
||||||
"px-3 py-2.5 rounded-md text-sm transition-colors",
|
|
||||||
path === l.href
|
|
||||||
? "bg-[var(--ink)] text-[var(--paper)]"
|
|
||||||
: "text-[var(--muted)] hover:text-[var(--ink)] hover:bg-[var(--warm)]"
|
|
||||||
)}
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif", fontWeight: 500 }}
|
|
||||||
>
|
|
||||||
{l.label}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,13 @@ def import_backup(pb, backup_path, db_path):
|
||||||
if land in ("global", "global2", "laender"):
|
if land in ("global", "global2", "laender"):
|
||||||
kz_code = normalize_diplo_code(kz_code)
|
kz_code = normalize_diplo_code(kz_code)
|
||||||
|
|
||||||
|
# Duplikat prüfen
|
||||||
|
existing = pb.find_record("gesehen",
|
||||||
|
f'kennzeichen_code="{kz_code}" && land="{folder}"')
|
||||||
|
if existing:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
# Kennzeichen-Namen aus Referenz holen
|
# Kennzeichen-Namen aus Referenz holen
|
||||||
try:
|
try:
|
||||||
rcur.execute(f"SELECT ort FROM {land} WHERE id=?", (kennid,))
|
rcur.execute(f"SELECT ort FROM {land} WHERE id=?", (kennid,))
|
||||||
|
|
@ -393,21 +400,6 @@ def import_backup(pb, backup_path, db_path):
|
||||||
except:
|
except:
|
||||||
kz_name = ""
|
kz_name = ""
|
||||||
|
|
||||||
existing = pb.find_record("gesehen",
|
|
||||||
f'kennzeichen_code="{kz_code}" && land="{folder}"')
|
|
||||||
if existing:
|
|
||||||
# Datum aktualisieren wenn neue Sichtung neuer ist
|
|
||||||
if datum_iso > (existing.get("datum") or ""):
|
|
||||||
requests.patch(
|
|
||||||
f"{pb.url}/api/collections/gesehen/records/{existing['id']}",
|
|
||||||
headers=pb.h(),
|
|
||||||
json={"datum": datum_iso},
|
|
||||||
)
|
|
||||||
imported += 1
|
|
||||||
else:
|
|
||||||
skipped += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
pb.create("gesehen", {
|
pb.create("gesehen", {
|
||||||
"kennzeichen_code": kz_code,
|
"kennzeichen_code": kz_code,
|
||||||
"kennzeichen_name": kz_name,
|
"kennzeichen_name": kz_name,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue