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

@@ -68,7 +68,7 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
const whereExpr = whereClauses.length ? and(...whereClauses) : undefined;
const [items, [{ total }]] = await Promise.all([
const [items, countRows] = await Promise.all([
db
.select()
.from(entries)
@@ -76,16 +76,13 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
.orderBy(sort === "id_desc" ? desc(entries.id) : entries.title)
.limit(pageSize)
.offset(offset),
db.execute(
sql`select count(*) as total from ${entries} ${whereExpr ? sql`where ${whereExpr}` : sql``}`
) as Promise<any>,
db
.select({ total: sql<number>`count(*)` })
.from(entries)
.where(whereExpr as any) as unknown as Promise<{ total: number }[]>,
]);
return {
items: items as any,
page,
pageSize,
total: Number((total as any) ?? 0),
};
const total = Number(countRows?.[0]?.total ?? 0);
return { items: items as any, page, pageSize, total };
}
const pattern = `%${q.toLowerCase().replace(/[^a-z0-9]+/g, "")}%`;
@@ -201,11 +198,14 @@ export async function searchLabels(params: LabelSearchParams): Promise<PagedResu
const offset = (page - 1) * pageSize;
if (!q) {
const [items, [{ total }]] = await Promise.all([
const [items, countRows] = await Promise.all([
db.select().from(labels).orderBy(labels.name).limit(pageSize).offset(offset),
db.execute(sql`select count(*) as total from ${labels}`) as Promise<any>,
db
.select({ total: sql<number>`count(*)` })
.from(labels) as unknown as Promise<{ total: number }[]>,
]);
return { items: items as any, page, pageSize, total: Number(total ?? 0) };
const total = Number(countRows?.[0]?.total ?? 0);
return { items: items as any, page, pageSize, total };
}
// Using helper search_by_names for efficiency
@@ -313,7 +313,10 @@ export async function listMachinetypes() {
export async function entriesByGenre(genreId: number, page: number, pageSize: number): Promise<PagedResult<SearchResultItem>> {
const offset = (page - 1) * pageSize;
const [{ total }]: any = await db.execute(sql`select count(*) as total from ${entries} where ${entries.genretypeId} = ${genreId}`);
const countRows = (await db
.select({ total: sql<number>`count(*)` })
.from(entries)
.where(eq(entries.genretypeId, genreId as any))) as unknown as { total: number }[];
const items = await db
.select({ id: entries.id, title: entries.title, isXrated: entries.isXrated, machinetypeId: entries.machinetypeId, languageId: entries.languageId })
.from(entries)
@@ -321,12 +324,15 @@ export async function entriesByGenre(genreId: number, page: number, pageSize: nu
.orderBy(entries.title)
.limit(pageSize)
.offset(offset);
return { items: items as any, page, pageSize, total: Number(total ?? 0) };
return { items: items as any, page, pageSize, total: Number(countRows?.[0]?.total ?? 0) };
}
export async function entriesByLanguage(langId: string, page: number, pageSize: number): Promise<PagedResult<SearchResultItem>> {
const offset = (page - 1) * pageSize;
const [{ total }]: any = await db.execute(sql`select count(*) as total from ${entries} where ${entries.languageId} = ${langId}`);
const countRows = (await db
.select({ total: sql<number>`count(*)` })
.from(entries)
.where(eq(entries.languageId, langId as any))) as unknown as { total: number }[];
const items = await db
.select({ id: entries.id, title: entries.title, isXrated: entries.isXrated, machinetypeId: entries.machinetypeId, languageId: entries.languageId })
.from(entries)
@@ -334,12 +340,15 @@ export async function entriesByLanguage(langId: string, page: number, pageSize:
.orderBy(entries.title)
.limit(pageSize)
.offset(offset);
return { items: items as any, page, pageSize, total: Number(total ?? 0) };
return { items: items as any, page, pageSize, total: Number(countRows?.[0]?.total ?? 0) };
}
export async function entriesByMachinetype(mtId: number, page: number, pageSize: number): Promise<PagedResult<SearchResultItem>> {
const offset = (page - 1) * pageSize;
const [{ total }]: any = await db.execute(sql`select count(*) as total from ${entries} where ${entries.machinetypeId} = ${mtId}`);
const countRows = (await db
.select({ total: sql<number>`count(*)` })
.from(entries)
.where(eq(entries.machinetypeId, mtId as any))) as unknown as { total: number }[];
const items = await db
.select({ id: entries.id, title: entries.title, isXrated: entries.isXrated, machinetypeId: entries.machinetypeId, languageId: entries.languageId })
.from(entries)
@@ -347,7 +356,7 @@ export async function entriesByMachinetype(mtId: number, page: number, pageSize:
.orderBy(entries.title)
.limit(pageSize)
.offset(offset);
return { items: items as any, page, pageSize, total: Number(total ?? 0) };
return { items: items as any, page, pageSize, total: Number(countRows?.[0]?.total ?? 0) };
}
// ----- Facets for search -----