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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user