Files
bricktrack/src/components/katalog/pagination.tsx
T
unknownandClaude Opus 5 856800b4e4 feat(katalog): set-katalog mit filtern, detailseite und uploads
Kachelliste mit Suche ueber Setnummer, Name und EAN sowie Filtern nach
Hersteller, Thema, Jahr und Besitzstatus. Besitz kommt aus genau einer
Stelle: src/lib/ownership.ts definiert "hat er nicht" als Katalogeintrag
ohne collection_item mit Status ungleich sold, als korrelierte
Subquery, damit mehrere Exemplare die Liste nicht verdoppeln.

Detailseite mit Metadaten, Anleitungen (PDF-Upload plus pdf.js-Viewer
ohne CDN, oder externer Link), Fotos, Sammlungseintraegen und den
Aktionen "Zur Sammlung" und "Auf Wunschliste".

Upload-Pipeline prueft Magic Bytes statt des gemeldeten MIME-Typs,
erzeugt Dateinamen serverseitig, wandelt Bilder in WebP inklusive
Thumbnail und liefert Dateien ueber /api/files aus - Set-Bilder
oeffentlich, Fotos und PDFs nur mit Session.

Dazu das Seed-Skript mit 3 Herstellern, 6 Themen, 10 Sets und
4 Sammlungseintraegen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:59:26 +02:00

55 lines
1.6 KiB
TypeScript

'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
export function Pagination({
page,
total,
pageSize,
}: {
page: number;
total: number;
pageSize: number;
}) {
const pathname = usePathname();
const searchParams = useSearchParams();
const lastPage = Math.max(1, Math.ceil(total / pageSize));
if (lastPage <= 1) return null;
function hrefFor(target: number) {
const params = new URLSearchParams(searchParams.toString());
if (target <= 1) params.delete('seite');
else params.set('seite', String(target));
const query = params.toString();
return query ? `${pathname}?${query}` : pathname;
}
return (
<nav className="flex items-center justify-between gap-3" aria-label="Seiten">
<Button asChild variant="outline" disabled={page <= 1}>
<Link href={hrefFor(page - 1)} aria-disabled={page <= 1} className={page <= 1 ? 'pointer-events-none opacity-50' : ''}>
<ChevronLeft />
Zurück
</Link>
</Button>
<span className="text-sm text-muted-foreground">
Seite {page} von {lastPage}
</span>
<Button asChild variant="outline" disabled={page >= lastPage}>
<Link
href={hrefFor(page + 1)}
aria-disabled={page >= lastPage}
className={page >= lastPage ? 'pointer-events-none opacity-50' : ''}
>
Weiter
<ChevronRight />
</Link>
</Button>
</nav>
);
}