chore: commit pending ZXDB explorer changes prior to index perf work
Context - Housekeeping commit to capture all current ZXDB Explorer work before index-page performance optimizations. Includes - Server-rendered entry detail page with ISR and parallelized DB queries. - Node runtime for ZXDB API routes and params validation updates for Next 15. - ZXDB repository extensions (facets, label queries, category queries). - Cross-linking and Link-based prefetch across ZXDB UI. - Cache headers on low-churn list APIs. Notes - Follow-up commit will focus specifically on speeding up index pages via SSR initial data and ISR. Signed-off-by: Junie@lucy.xalior.com
This commit is contained in:
@@ -1,38 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } 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 GenreDetailClient({ id }: { id: number }) {
|
||||
const [data, setData] = useState<Paged<Item> | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const pageSize = 20;
|
||||
const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]);
|
||||
|
||||
async function load(p: number) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/zxdb/genres/${id}?page=${p}&pageSize=${pageSize}`, { cache: "no-store" });
|
||||
const json = (await res.json()) as Paged<Item>;
|
||||
setData(json);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load(page);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page]);
|
||||
export default function GenreDetailClient({ id, initial }: { id: number; initial: Paged<Item> }) {
|
||||
const [data] = useState<Paged<Item>>(initial);
|
||||
const [page] = useState(1);
|
||||
const totalPages = useMemo(() => Math.max(1, Math.ceil(data.total / data.pageSize)), [data]);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Genre #{id}</h1>
|
||||
{loading && <div>Loading…</div>}
|
||||
{data && data.items.length === 0 && !loading && <div className="alert alert-warning">No entries.</div>}
|
||||
{data && data.items.length === 0 && <div className="alert alert-warning">No entries.</div>}
|
||||
{data && data.items.length > 0 && (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped table-hover align-middle">
|
||||
@@ -48,7 +30,7 @@ export default function GenreDetailClient({ id }: { id: number }) {
|
||||
{data.items.map((it) => (
|
||||
<tr key={it.id}>
|
||||
<td>{it.id}</td>
|
||||
<td><a href={`/zxdb/entries/${it.id}`}>{it.title}</a></td>
|
||||
<td><Link href={`/zxdb/entries/${it.id}`}>{it.title}</Link></td>
|
||||
<td>{it.machinetypeId ?? "-"}</td>
|
||||
<td>{it.languageId ?? "-"}</td>
|
||||
</tr>
|
||||
@@ -59,9 +41,7 @@ export default function GenreDetailClient({ id }: { id: number }) {
|
||||
)}
|
||||
|
||||
<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}</span>
|
||||
<button className="btn btn-outline-secondary" onClick={() => setPage((p) => p + 1)} disabled={loading || (data ? data.page >= totalPages : false)}>Next</button>
|
||||
<span>Page {data.page} / {totalPages}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user