Fix ZXDB pagination counters and navigation

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>
This commit is contained in:
2025-12-12 16:11:12 +00:00
parent 54cfe4f175
commit 3ef3a16bc0
13 changed files with 238 additions and 143 deletions

View File

@@ -30,7 +30,7 @@ export default function ZxdbExplorer({
initialMachines?: { id: number; name: string }[];
}) {
const [q, setQ] = useState("");
const [page, setPage] = useState(1);
const [page, setPage] = useState(initial?.page ?? 1);
const [loading, setLoading] = useState(false);
const [data, setData] = useState<Paged<Item> | null>(initial ?? null);
const [genres, setGenres] = useState<{ id: number; name: string }[]>(initialGenres ?? []);
@@ -39,7 +39,7 @@ export default function ZxdbExplorer({
const [genreId, setGenreId] = useState<number | "">("");
const [languageId, setLanguageId] = useState<string | "">("");
const [machinetypeId, setMachinetypeId] = useState<number | "">("");
const [sort, setSort] = useState<"title" | "id_desc">("title");
const [sort, setSort] = useState<"title" | "id_desc">("id_desc");
const pageSize = 20;
const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]);
@@ -69,8 +69,27 @@ export default function ZxdbExplorer({
}
useEffect(() => {
// Avoid immediate client fetch on first paint if server provided initial data
if (initial && page === 1 && q === "" && genreId === "" && languageId === "" && machinetypeId === "" && sort === "title") {
// When navigating via Next.js Links that change ?page=, SSR provides new `initial`.
// Sync local state from new SSR payload so the list and counter update immediately
// without an extra client fetch.
if (initial) {
setData(initial);
setPage(initial.page);
}
}, [initial]);
useEffect(() => {
// Avoid immediate client fetch on first paint if server provided initial data for this exact state
const initialPage = initial?.page ?? 1;
if (
initial &&
page === initialPage &&
q === "" &&
genreId === "" &&
languageId === "" &&
machinetypeId === "" &&
sort === "id_desc"
) {
return;
}
fetchData(q, page);
@@ -185,23 +204,25 @@ export default function ZxdbExplorer({
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<button
className="btn btn-outline-secondary"
onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={loading || page <= 1}
>
Prev
</button>
<span>
Page {data?.page ?? page} / {totalPages}
Page {data?.page ?? 1} / {totalPages}
</span>
<button
className="btn btn-outline-secondary"
onClick={() => setPage((p) => p + 1)}
disabled={loading || (data ? data.page >= totalPages : false)}
>
Next
</button>
<div className="ms-auto d-flex gap-2">
<Link
className={`btn btn-outline-secondary ${!data || (data.page <= 1) ? "disabled" : ""}`}
aria-disabled={!data || data.page <= 1}
href={`/zxdb?page=${Math.max(1, (data?.page ?? 1) - 1)}`}
>
Prev
</Link>
<Link
className={`btn btn-outline-secondary ${!data || (data.page >= totalPages) ? "disabled" : ""}`}
aria-disabled={!data || data.page >= totalPages}
href={`/zxdb?page=${Math.min(totalPages, (data?.page ?? 1) + 1)}`}
>
Next
</Link>
</div>
</div>
<hr />