Implement URL-driven pagination and correct total counts across ZXDB: - Root /zxdb: SSR reads ?page; client syncs to SSR; Prev/Next as Links. - Sub-index pages (genres, languages, machinetypes): parse ?page on server; use SSR props in clients; Prev/Next via Links. - Labels browse (/zxdb/labels): dynamic SSR, reads ?q & ?page; typed count(*); client syncs to SSR; Prev/Next preserve q. - Label detail (/zxdb/labels/[id]): tab-aware Prev/Next Links; counters from server. - Repo: replace raw counts with typed Drizzle count(*) for reliable totals. Signed-off-by: Junie <Junie@lucy.xalior.com>
63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useMemo } from "react";
|
|
|
|
type Item = { id: number; title: string; isXrated: number; machinetypeId: number | null; languageId: string | null };
|
|
type Paged<T> = { items: T[]; page: number; pageSize: number; total: number };
|
|
|
|
export default function LanguageDetailClient({ id, initial }: { id: string; initial: Paged<Item> }) {
|
|
const totalPages = useMemo(() => Math.max(1, Math.ceil(initial.total / initial.pageSize)), [initial]);
|
|
|
|
return (
|
|
<div className="container">
|
|
<h1>Language {id}</h1>
|
|
{initial && initial.items.length === 0 && <div className="alert alert-warning">No entries.</div>}
|
|
{initial && initial.items.length > 0 && (
|
|
<div className="table-responsive">
|
|
<table className="table table-striped table-hover align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th style={{ width: 80 }}>ID</th>
|
|
<th>Title</th>
|
|
<th style={{ width: 120 }}>Machine</th>
|
|
<th style={{ width: 80 }}>Lang</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{initial.items.map((it) => (
|
|
<tr key={it.id}>
|
|
<td>{it.id}</td>
|
|
<td><Link href={`/zxdb/entries/${it.id}`}>{it.title}</Link></td>
|
|
<td>{it.machinetypeId ?? "-"}</td>
|
|
<td>{it.languageId ?? "-"}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
|
|
<div className="d-flex align-items-center gap-2 mt-2">
|
|
<span>Page {initial.page} / {totalPages}</span>
|
|
<div className="ms-auto d-flex gap-2">
|
|
<Link
|
|
className={`btn btn-sm btn-outline-secondary ${initial.page <= 1 ? "disabled" : ""}`}
|
|
aria-disabled={initial.page <= 1}
|
|
href={`/zxdb/languages/${id}?page=${Math.max(1, initial.page - 1)}`}
|
|
>
|
|
Prev
|
|
</Link>
|
|
<Link
|
|
className={`btn btn-sm btn-outline-secondary ${initial.page >= totalPages ? "disabled" : ""}`}
|
|
aria-disabled={initial.page >= totalPages}
|
|
href={`/zxdb/languages/${id}?page=${Math.min(totalPages, initial.page + 1)}`}
|
|
>
|
|
Next
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|