From 964b48abf1ae7f6a48b13f69c6311c63861c3f43 Mon Sep 17 00:00:00 2001 From: "D. Rimron-Soutter" Date: Sat, 10 Jan 2026 18:26:34 +0000 Subject: [PATCH] Add entry ports and scores Surface ports, remakes, scores, and notes on entry detail. Signed-off-by: codex@lucy.xalior.com --- src/app/zxdb/entries/[id]/EntryDetail.tsx | 160 ++++++++++++++++++++++ src/server/repo/zxdb.ts | 154 +++++++++++++++++++++ 2 files changed, 314 insertions(+) diff --git a/src/app/zxdb/entries/[id]/EntryDetail.tsx b/src/app/zxdb/entries/[id]/EntryDetail.tsx index baf9daa..56bcceb 100644 --- a/src/app/zxdb/entries/[id]/EntryDetail.tsx +++ b/src/app/zxdb/entries/[id]/EntryDetail.tsx @@ -36,6 +36,34 @@ export type EntryDetailData = { link: string | null; comments: string | null; }[]; + ports?: { + id: number; + title: string | null; + platform: { id: number; name: string | null }; + isOfficial: boolean; + linkSystem: string | null; + }[]; + remakes?: { + id: number; + title: string; + fileLink: string; + fileDate: string | null; + fileSize: number | null; + authors: string | null; + platforms: string | null; + remakeYears: string | null; + remakeStatus: string | null; + }[]; + scores?: { + website: { id: number; name: string | null }; + score: number; + votes: number; + }[]; + notes?: { + id: number; + type: { id: string; name: string | null }; + text: string; + }[]; origins?: { type: { id: string; name: string | null }; libraryTitle: string; @@ -491,6 +519,138 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
+
+
Ports
+ {(!data.ports || data.ports.length === 0) &&
No ports recorded
} + {data.ports && data.ports.length > 0 && ( +
+ + + + + + + + + + + {data.ports.map((p) => ( + + + + + + + ))} + +
TitlePlatformOfficialLink
{p.title ?? -}{p.platform.name ?? `#${p.platform.id}`}{p.isOfficial ? "Yes" : "No"} + {p.linkSystem ? ( + Link + ) : ( + - + )} +
+
+ )} +
+ +
+ +
+
Remakes
+ {(!data.remakes || data.remakes.length === 0) &&
No remakes recorded
} + {data.remakes && data.remakes.length > 0 && ( +
+ + + + + + + + + + + + {data.remakes.map((r) => ( + + + + + + + + ))} + +
TitlePlatformsYearsFileNotes
{r.title}{r.platforms ?? -}{r.remakeYears ?? -} + {r.fileLink ? ( + File + ) : ( + - + )} + {r.remakeStatus ?? r.authors ?? -}
+
+ )} +
+ +
+ +
+
Scores
+ {(!data.scores || data.scores.length === 0) &&
No scores recorded
} + {data.scores && data.scores.length > 0 && ( +
+ + + + + + + + + + {data.scores.map((s, idx) => ( + + + + + + ))} + +
WebsiteScoreVotes
{s.website.name ?? `#${s.website.id}`}{s.score}{s.votes}
+
+ )} +
+ +
+ +
+
Notes
+ {(!data.notes || data.notes.length === 0) &&
No notes recorded
} + {data.notes && data.notes.length > 0 && ( +
+ + + + + + + + + {data.notes.map((n) => ( + + + + + ))} + +
TypeText
{n.type.name ?? n.type.id}{n.text}
+
+ )} +
+ +
+ {/* Aliases (alternative titles) */}
Aliases
diff --git a/src/server/repo/zxdb.ts b/src/server/repo/zxdb.ts index 371cae4..7ec2782 100644 --- a/src/server/repo/zxdb.ts +++ b/src/server/repo/zxdb.ts @@ -39,6 +39,12 @@ import { tags, tagtypes, members, + ports, + platforms, + remakes, + scores, + notes, + notetypes, webrefs, websites, magazines, @@ -319,6 +325,34 @@ export interface EntryDetail { link: string | null; comments: string | null; }[]; + ports?: { + id: number; + title: string | null; + platform: { id: number; name: string | null }; + isOfficial: boolean; + linkSystem: string | null; + }[]; + remakes?: { + id: number; + title: string; + fileLink: string; + fileDate: string | null; + fileSize: number | null; + authors: string | null; + platforms: string | null; + remakeYears: string | null; + remakeStatus: string | null; + }[]; + scores?: { + website: { id: number; name: string | null }; + score: number; + votes: number; + }[]; + notes?: { + id: number; + type: { id: string; name: string | null }; + text: string; + }[]; origins?: { type: { id: string; name: string | null }; libraryTitle: string; @@ -627,6 +661,37 @@ export async function getEntryById(id: number): Promise { link: string | null; comments: string | null; }[] = []; + let portRows: { + id: number | string; + title: string | null; + platformId: number | string; + platformName: string | null; + isOfficial: number | boolean; + linkSystem: string | null; + }[] = []; + let remakeRows: { + id: number | string; + title: string; + fileLink: string; + fileDate: string | null; + fileSize: number | string | null; + authors: string | null; + platforms: string | null; + remakeYears: string | null; + remakeStatus: string | null; + }[] = []; + let scoreRows: { + websiteId: number | string; + websiteName: string | null; + score: number | string; + votes: number | string; + }[] = []; + let noteRows: { + id: number | string; + notetypeId: string; + notetypeName: string | null; + text: string; + }[] = []; try { aliasRows = await db .select({ releaseSeq: aliases.releaseSeq, languageId: aliases.languageId, title: aliases.title }) @@ -741,6 +806,67 @@ export async function getEntryById(id: number): Promise { ); tagRows = rows as typeof tagRows; } catch {} + try { + const rows = await db + .select({ + id: ports.id, + title: ports.title, + platformId: ports.platformId, + platformName: platforms.name, + isOfficial: ports.isOfficial, + linkSystem: ports.linkSystem, + }) + .from(ports) + .leftJoin(platforms, eq(platforms.id, ports.platformId)) + .where(eq(ports.entryId, id)) + .orderBy(asc(ports.title)); + portRows = rows as typeof portRows; + } catch {} + try { + const rows = await db + .select({ + id: remakes.id, + title: remakes.title, + fileLink: remakes.fileLink, + fileDate: remakes.fileDate, + fileSize: remakes.fileSize, + authors: remakes.authors, + platforms: remakes.platforms, + remakeYears: remakes.remakeYears, + remakeStatus: remakes.remakeStatus, + }) + .from(remakes) + .where(eq(remakes.entryId, id)) + .orderBy(asc(remakes.title)); + remakeRows = rows as typeof remakeRows; + } catch {} + try { + const rows = await db + .select({ + websiteId: websites.id, + websiteName: websites.name, + score: scores.score, + votes: scores.votes, + }) + .from(scores) + .innerJoin(websites, eq(websites.id, scores.websiteId)) + .where(eq(scores.entryId, id)); + scoreRows = rows as typeof scoreRows; + } catch {} + try { + const rows = await db + .select({ + id: notes.id, + notetypeId: notes.notetypeId, + notetypeName: notetypes.name, + text: notes.text, + }) + .from(notes) + .leftJoin(notetypes, eq(notetypes.id, notes.notetypeId)) + .where(eq(notes.entryId, id)) + .orderBy(asc(notes.id)); + noteRows = rows as typeof noteRows; + } catch {} return { id: base.id, @@ -781,6 +907,34 @@ export async function getEntryById(id: number): Promise { link: t.link ?? null, comments: t.comments ?? null, })), + ports: portRows.map((p) => ({ + id: Number(p.id), + title: p.title ?? null, + platform: { id: Number(p.platformId), name: p.platformName ?? null }, + isOfficial: !!p.isOfficial, + linkSystem: p.linkSystem ?? null, + })), + remakes: remakeRows.map((r) => ({ + id: Number(r.id), + title: r.title, + fileLink: r.fileLink, + fileDate: r.fileDate ?? null, + fileSize: r.fileSize != null ? Number(r.fileSize) : null, + authors: r.authors ?? null, + platforms: r.platforms ?? null, + remakeYears: r.remakeYears ?? null, + remakeStatus: r.remakeStatus ?? null, + })), + scores: scoreRows.map((s) => ({ + website: { id: Number(s.websiteId), name: s.websiteName ?? null }, + score: Number(s.score), + votes: Number(s.votes), + })), + notes: noteRows.map((n) => ({ + id: Number(n.id), + type: { id: n.notetypeId, name: n.notetypeName ?? null }, + text: n.text, + })), origins: originRows.map((o) => ({ type: { id: o.origintypeId, name: o.origintypeName ?? null }, libraryTitle: o.libraryTitle,