Add ZXDB origins and label types

Show entry origins data and display label type names
in label detail view.

Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
2026-01-10 18:12:30 +00:00
parent e2f6aac856
commit fb206734db
3 changed files with 125 additions and 2 deletions

View File

@@ -22,6 +22,14 @@ export type EntryDetailData = {
linkSite?: string | null; linkSite?: string | null;
comments?: string | null; comments?: string | null;
}[]; }[];
origins?: {
type: { id: string; name: string | null };
libraryTitle: string;
publication: string | null;
containerId: number | null;
issueId: number | null;
date: { year: number | null; month: number | null; day: number | null };
}[];
// extra fields for richer details // extra fields for richer details
maxPlayers?: number; maxPlayers?: number;
availabletypeId?: string | null; availabletypeId?: string | null;
@@ -340,6 +348,53 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
<hr /> <hr />
<div>
<h5>Origins</h5>
{(!data.origins || data.origins.length === 0) && <div className="text-secondary">No origins recorded</div>}
{data.origins && data.origins.length > 0 && (
<div className="table-responsive">
<table className="table table-sm table-striped align-middle">
<thead>
<tr>
<th>Type</th>
<th>Title</th>
<th>Publication</th>
<th style={{ width: 140 }}>Issue</th>
<th style={{ width: 140 }}>Date</th>
</tr>
</thead>
<tbody>
{data.origins.map((o, idx) => {
const dateParts = [o.date.year, o.date.month, o.date.day]
.filter((v) => typeof v === "number" && Number.isFinite(v))
.map((v, i) => (i === 0 ? String(v) : String(v).padStart(2, "0")));
const dateText = dateParts.length ? dateParts.join("/") : "-";
return (
<tr key={`${o.type.id}-${idx}`}>
<td>{o.type.name ?? o.type.id}</td>
<td>{o.libraryTitle}</td>
<td>{o.publication ?? <span className="text-secondary">-</span>}</td>
<td>
{o.issueId ? (
<Link href={`/zxdb/issues/${o.issueId}`}>#{o.issueId}</Link>
) : o.containerId ? (
<span>#{o.containerId}</span>
) : (
<span className="text-secondary">-</span>
)}
</td>
<td>{dateText}</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
<hr />
{/* Aliases (alternative titles) */} {/* Aliases (alternative titles) */}
<div> <div>
<h5>Aliases</h5> <h5>Aliases</h5>

View File

@@ -9,6 +9,7 @@ type Label = {
id: number; id: number;
name: string; name: string;
labeltypeId: string | null; labeltypeId: string | null;
labeltypeName: string | null;
permissions: { permissions: {
website: { id: number; name: string; link?: string | null }; website: { id: number; name: string; link?: string | null };
type: { id: string; name: string | null }; type: { id: string; name: string | null };
@@ -49,7 +50,11 @@ export default function LabelDetailClient({ id, initial, initialTab, initialQ }:
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2"> <div className="d-flex align-items-center justify-content-between flex-wrap gap-2">
<h1 className="mb-0">{initial.label.name}</h1> <h1 className="mb-0">{initial.label.name}</h1>
<div> <div>
<span className="badge text-bg-light">{initial.label.labeltypeId ?? "?"}</span> <span className="badge text-bg-light">
{initial.label.labeltypeName
? `${initial.label.labeltypeName} (${initial.label.labeltypeId ?? "?"})`
: (initial.label.labeltypeId ?? "?")}
</span>
</div> </div>
</div> </div>

View File

@@ -8,6 +8,7 @@ import {
searchByAliases, searchByAliases,
searchByOrigins, searchByOrigins,
labels, labels,
labeltypes,
authors, authors,
publishers, publishers,
languages, languages,
@@ -31,6 +32,7 @@ import {
licensors, licensors,
permissions, permissions,
permissiontypes, permissiontypes,
origintypes,
webrefs, webrefs,
websites, websites,
magazines, magazines,
@@ -297,6 +299,14 @@ export interface EntryDetail {
linkSite?: string | null; linkSite?: string | null;
comments?: string | null; comments?: string | null;
}[]; }[];
origins?: {
type: { id: string; name: string | null };
libraryTitle: string;
publication: string | null;
containerId: number | null;
issueId: number | null;
date: { year: number | null; month: number | null; day: number | null };
}[];
// Additional entry fields for richer details // Additional entry fields for richer details
maxPlayers?: number; maxPlayers?: number;
availabletypeId?: string | null; availabletypeId?: string | null;
@@ -559,6 +569,17 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
linkSite: string | null; linkSite: string | null;
comments: string | null; comments: string | null;
}[] = []; }[] = [];
let originRows: {
libraryTitle: string;
origintypeId: string;
origintypeName: string | null;
containerId: number | string | null;
issueId: number | string | null;
dateYear: number | string | null;
dateMonth: number | string | null;
dateDay: number | string | null;
publication: string | null;
}[] = [];
try { try {
aliasRows = await db aliasRows = await db
.select({ releaseSeq: aliases.releaseSeq, languageId: aliases.languageId, title: aliases.title }) .select({ releaseSeq: aliases.releaseSeq, languageId: aliases.languageId, title: aliases.title })
@@ -591,6 +612,24 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
.where(eq(relatedlicenses.entryId, id)); .where(eq(relatedlicenses.entryId, id));
licenseRows = rows as typeof licenseRows; licenseRows = rows as typeof licenseRows;
} catch {} } catch {}
try {
const rows = await db
.select({
libraryTitle: searchByOrigins.libraryTitle,
origintypeId: searchByOrigins.origintypeId,
origintypeName: origintypes.name,
containerId: searchByOrigins.containerId,
issueId: searchByOrigins.issueId,
dateYear: searchByOrigins.dateYear,
dateMonth: searchByOrigins.dateMonth,
dateDay: searchByOrigins.dateDay,
publication: searchByOrigins.publication,
})
.from(searchByOrigins)
.leftJoin(origintypes, eq(origintypes.id, searchByOrigins.origintypeId))
.where(eq(searchByOrigins.entryId, id));
originRows = rows as typeof originRows;
} catch {}
return { return {
id: base.id, id: base.id,
@@ -610,6 +649,18 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
linkSite: l.linkSite ?? null, linkSite: l.linkSite ?? null,
comments: l.comments ?? null, comments: l.comments ?? null,
})), })),
origins: originRows.map((o) => ({
type: { id: o.origintypeId, name: o.origintypeName ?? null },
libraryTitle: o.libraryTitle,
publication: o.publication ?? null,
containerId: o.containerId != null ? Number(o.containerId) : null,
issueId: o.issueId != null ? Number(o.issueId) : null,
date: {
year: o.dateYear != null ? Number(o.dateYear) : null,
month: o.dateMonth != null ? Number(o.dateMonth) : null,
day: o.dateDay != null ? Number(o.dateDay) : null,
},
})),
maxPlayers: (base.maxPlayers) ?? undefined, maxPlayers: (base.maxPlayers) ?? undefined,
availabletypeId: (base.availabletypeId) ?? undefined, availabletypeId: (base.availabletypeId) ?? undefined,
withoutLoadScreen: (base.withoutLoadScreen) ?? undefined, withoutLoadScreen: (base.withoutLoadScreen) ?? undefined,
@@ -651,6 +702,7 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
// ----- Labels ----- // ----- Labels -----
export interface LabelDetail extends LabelSummary { export interface LabelDetail extends LabelSummary {
labeltypeName: string | null;
permissions: { permissions: {
website: { id: number; name: string; link?: string | null }; website: { id: number; name: string; link?: string | null };
type: { id: string; name: string | null }; type: { id: string; name: string | null };
@@ -709,7 +761,17 @@ export async function searchLabels(params: LabelSearchParams): Promise<PagedResu
} }
export async function getLabelById(id: number): Promise<LabelDetail | null> { export async function getLabelById(id: number): Promise<LabelDetail | null> {
const rows = await db.select().from(labels).where(eq(labels.id, id)).limit(1); const rows = await db
.select({
id: labels.id,
name: labels.name,
labeltypeId: labels.labeltypeId,
labeltypeName: labeltypes.name,
})
.from(labels)
.leftJoin(labeltypes, eq(labeltypes.id, labels.labeltypeId))
.where(eq(labels.id, id))
.limit(1);
const base = rows[0]; const base = rows[0];
if (!base) return null; if (!base) return null;
@@ -770,6 +832,7 @@ export async function getLabelById(id: number): Promise<LabelDetail | null> {
id: base.id, id: base.id,
name: base.name, name: base.name,
labeltypeId: base.labeltypeId, labeltypeId: base.labeltypeId,
labeltypeName: base.labeltypeName ?? null,
permissions: permissionRows.map((p) => ({ permissions: permissionRows.map((p) => ({
website: { id: Number(p.websiteId), name: p.websiteName, link: p.websiteLink ?? null }, website: { id: Number(p.websiteId), name: p.websiteName, link: p.websiteLink ?? null },
type: { id: p.permissiontypeId, name: p.permissiontypeName ?? null }, type: { id: p.permissiontypeId, name: p.permissiontypeName ?? null },