Improve ZXDB releases list
Link release titles to release detail, add magref count badges, and show other releases on release detail. Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
@@ -11,6 +11,7 @@ type Item = {
|
|||||||
releaseSeq: number;
|
releaseSeq: number;
|
||||||
entryTitle: string;
|
entryTitle: string;
|
||||||
year: number | null;
|
year: number | null;
|
||||||
|
magrefCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Paged<T> = {
|
type Paged<T> = {
|
||||||
@@ -337,6 +338,7 @@ export default function ReleasesExplorer({
|
|||||||
<th style={{width: 80}}>Entry ID</th>
|
<th style={{width: 80}}>Entry ID</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th style={{width: 140}}>Release #</th>
|
<th style={{width: 140}}>Release #</th>
|
||||||
|
<th style={{width: 110}}>Places</th>
|
||||||
<th style={{width: 100}}>Year</th>
|
<th style={{width: 100}}>Year</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -347,13 +349,24 @@ export default function ReleasesExplorer({
|
|||||||
<EntryLink id={it.entryId} />
|
<EntryLink id={it.entryId} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<EntryLink id={it.entryId} title={it.entryTitle} />
|
<div className="d-flex flex-column gap-1">
|
||||||
|
<Link href={`/zxdb/releases/${it.entryId}/${it.releaseSeq}`} className="link-underline link-underline-opacity-0">
|
||||||
|
{it.entryTitle || `Entry #${it.entryId}`}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Link href={`/zxdb/releases/${it.entryId}/${it.releaseSeq}`}>
|
<Link href={`/zxdb/releases/${it.entryId}/${it.releaseSeq}`}>
|
||||||
#{it.releaseSeq}
|
#{it.releaseSeq}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{it.magrefCount > 0 ? (
|
||||||
|
<span className="badge text-bg-secondary">{it.magrefCount}</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-secondary">-</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
<td>{it.year ?? <span className="text-secondary">-</span>}</td>
|
<td>{it.year ?? <span className="text-secondary">-</span>}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ type ReleaseDetailData = {
|
|||||||
title: string;
|
title: string;
|
||||||
issueId: number | null;
|
issueId: number | null;
|
||||||
};
|
};
|
||||||
|
entryReleases: Array<{
|
||||||
|
releaseSeq: number;
|
||||||
|
year: number | null;
|
||||||
|
}>;
|
||||||
release: {
|
release: {
|
||||||
entryId: number;
|
entryId: number;
|
||||||
releaseSeq: number;
|
releaseSeq: number;
|
||||||
@@ -167,6 +171,7 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
|
|||||||
if (!data) return <div className="alert alert-warning">Not found</div>;
|
if (!data) return <div className="alert alert-warning">Not found</div>;
|
||||||
|
|
||||||
const magazineGroups = groupMagazineRefs(data.magazineRefs);
|
const magazineGroups = groupMagazineRefs(data.magazineRefs);
|
||||||
|
const otherReleases = data.entryReleases.filter((r) => r.releaseSeq !== data.release.releaseSeq);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -265,6 +270,26 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h5>Other Releases</h5>
|
||||||
|
{otherReleases.length === 0 && <div className="text-secondary">No other releases</div>}
|
||||||
|
{otherReleases.length > 0 && (
|
||||||
|
<div className="d-flex flex-wrap gap-2">
|
||||||
|
{otherReleases.map((r) => (
|
||||||
|
<Link
|
||||||
|
key={r.releaseSeq}
|
||||||
|
className="badge text-bg-light text-decoration-none"
|
||||||
|
href={`/zxdb/releases/${data.entry.id}/${r.releaseSeq}`}
|
||||||
|
>
|
||||||
|
#{r.releaseSeq}{r.year != null ? ` · ${r.year}` : ""}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h5>Places (Magazines)</h5>
|
<h5>Places (Magazines)</h5>
|
||||||
{magazineGroups.length === 0 && <div className="text-secondary">No magazine references</div>}
|
{magazineGroups.length === 0 && <div className="text-secondary">No magazine references</div>}
|
||||||
|
|||||||
@@ -1103,6 +1103,7 @@ export interface ReleaseListItem {
|
|||||||
releaseSeq: number;
|
releaseSeq: number;
|
||||||
entryTitle: string;
|
entryTitle: string;
|
||||||
year: number | null;
|
year: number | null;
|
||||||
|
magrefCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchReleases(params: ReleaseSearchParams): Promise<PagedResult<ReleaseListItem>> {
|
export async function searchReleases(params: ReleaseSearchParams): Promise<PagedResult<ReleaseListItem>> {
|
||||||
@@ -1192,12 +1193,33 @@ export async function searchReleases(params: ReleaseSearchParams): Promise<Paged
|
|||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
|
||||||
|
const entryIds = Array.from(new Set(rowsQB.map((r) => Number(r.entryId)).filter((id) => Number.isFinite(id))));
|
||||||
|
const magrefCounts = new Map<number, number>();
|
||||||
|
if (entryIds.length > 0) {
|
||||||
|
try {
|
||||||
|
const values = entryIds.map((id) => sql`${id}`);
|
||||||
|
const rows = await db.execute(sql`
|
||||||
|
select ${searchByMagrefs.entryId} as entryId, count(*) as count
|
||||||
|
from ${searchByMagrefs}
|
||||||
|
where ${searchByMagrefs.entryId} in (${sql.join(values, sql`, `)})
|
||||||
|
group by ${searchByMagrefs.entryId}
|
||||||
|
`);
|
||||||
|
type CountRow = { entryId: number | string; count: number | string };
|
||||||
|
for (const row of rows as unknown as CountRow[]) {
|
||||||
|
magrefCounts.set(Number(row.entryId), Number(row.count));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Helper table might be missing; default to 0 counts.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure plain primitives
|
// Ensure plain primitives
|
||||||
const items: ReleaseListItem[] = rowsQB.map((r) => ({
|
const items: ReleaseListItem[] = rowsQB.map((r) => ({
|
||||||
entryId: Number(r.entryId),
|
entryId: Number(r.entryId),
|
||||||
releaseSeq: Number(r.releaseSeq),
|
releaseSeq: Number(r.releaseSeq),
|
||||||
entryTitle: r.entryTitle ?? "",
|
entryTitle: r.entryTitle ?? "",
|
||||||
year: r.year != null ? Number(r.year) : null,
|
year: r.year != null ? Number(r.year) : null,
|
||||||
|
magrefCount: magrefCounts.get(Number(r.entryId)) ?? 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { items, page, pageSize, total };
|
return { items, page, pageSize, total };
|
||||||
@@ -1209,6 +1231,10 @@ export interface ReleaseDetail {
|
|||||||
title: string;
|
title: string;
|
||||||
issueId: number | null;
|
issueId: number | null;
|
||||||
};
|
};
|
||||||
|
entryReleases: Array<{
|
||||||
|
releaseSeq: number;
|
||||||
|
year: number | null;
|
||||||
|
}>;
|
||||||
release: {
|
release: {
|
||||||
entryId: number;
|
entryId: number;
|
||||||
releaseSeq: number;
|
releaseSeq: number;
|
||||||
@@ -1316,6 +1342,15 @@ export async function getReleaseDetail(entryId: number, releaseSeq: number): Pro
|
|||||||
const base = rows[0];
|
const base = rows[0];
|
||||||
if (!base) return null;
|
if (!base) return null;
|
||||||
|
|
||||||
|
const entryReleaseRows = await db
|
||||||
|
.select({
|
||||||
|
releaseSeq: releases.releaseSeq,
|
||||||
|
year: releases.releaseYear,
|
||||||
|
})
|
||||||
|
.from(releases)
|
||||||
|
.where(eq(releases.entryId, entryId))
|
||||||
|
.orderBy(asc(releases.releaseSeq));
|
||||||
|
|
||||||
type DownloadRow = {
|
type DownloadRow = {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
link: string;
|
link: string;
|
||||||
@@ -1455,6 +1490,10 @@ export async function getReleaseDetail(entryId: number, releaseSeq: number): Pro
|
|||||||
title: base.entryTitle ?? "",
|
title: base.entryTitle ?? "",
|
||||||
issueId: base.issueId ?? null,
|
issueId: base.issueId ?? null,
|
||||||
},
|
},
|
||||||
|
entryReleases: entryReleaseRows.map((r) => ({
|
||||||
|
releaseSeq: Number(r.releaseSeq),
|
||||||
|
year: r.year != null ? Number(r.year) : null,
|
||||||
|
})),
|
||||||
release: {
|
release: {
|
||||||
entryId: Number(base.entryId),
|
entryId: Number(base.entryId),
|
||||||
releaseSeq: Number(base.releaseSeq),
|
releaseSeq: Number(base.releaseSeq),
|
||||||
|
|||||||
Reference in New Issue
Block a user