diff --git a/src/app/zxdb/entries/[id]/EntryDetail.tsx b/src/app/zxdb/entries/[id]/EntryDetail.tsx
index 609c3b1..baf9daa 100644
--- a/src/app/zxdb/entries/[id]/EntryDetail.tsx
+++ b/src/app/zxdb/entries/[id]/EntryDetail.tsx
@@ -22,6 +22,20 @@ export type EntryDetailData = {
linkSite?: string | null;
comments?: string | null;
}[];
+ relations?: {
+ direction: "from" | "to";
+ type: { id: string; name: string | null };
+ entry: { id: number; title: string | null };
+ }[];
+ tags?: {
+ id: number;
+ name: string;
+ type: { id: string; name: string | null };
+ category: { id: number | null; name: string | null };
+ memberSeq: number | null;
+ link: string | null;
+ comments: string | null;
+ }[];
origins?: {
type: { id: string; name: string | null };
libraryTitle: string;
@@ -403,6 +417,80 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
+
+
Relations
+ {(!data.relations || data.relations.length === 0) &&
No relations recorded
}
+ {data.relations && data.relations.length > 0 && (
+
+
+
+
+ | Direction |
+ Type |
+ Entry |
+
+
+
+ {data.relations.map((r, idx) => (
+
+ | {r.direction === "from" ? "From" : "To"} |
+ {r.type.name ?? r.type.id} |
+
+
+ {r.entry.title ?? `Entry #${r.entry.id}`}
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+
+
+
+
Tags / Members
+ {(!data.tags || data.tags.length === 0) &&
No tags recorded
}
+ {data.tags && data.tags.length > 0 && (
+
+
+
+
+ | Tag |
+ Type |
+ Category |
+ Member Seq |
+ Links |
+
+
+
+ {data.tags.map((t) => (
+
+ | {t.name} |
+ {t.type.name ?? t.type.id} |
+ {t.category.name ?? (t.category.id != null ? `#${t.category.id}` : "-")} |
+ {t.memberSeq ?? -} |
+
+
+ {t.link && (
+ Link
+ )}
+ {t.comments && {t.comments}}
+ {!t.link && !t.comments && -}
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+
+
{/* Aliases (alternative titles) */}
Aliases
diff --git a/src/server/repo/zxdb.ts b/src/server/repo/zxdb.ts
index 2e452a7..371cae4 100644
--- a/src/server/repo/zxdb.ts
+++ b/src/server/repo/zxdb.ts
@@ -14,6 +14,7 @@ import {
languages,
machinetypes,
genretypes,
+ categories,
files,
filetypes,
releases,
@@ -33,6 +34,11 @@ import {
permissions,
permissiontypes,
origintypes,
+ relations,
+ relationtypes,
+ tags,
+ tagtypes,
+ members,
webrefs,
websites,
magazines,
@@ -299,6 +305,20 @@ export interface EntryDetail {
linkSite?: string | null;
comments?: string | null;
}[];
+ relations?: {
+ direction: "from" | "to";
+ type: { id: string; name: string | null };
+ entry: { id: number; title: string | null };
+ }[];
+ tags?: {
+ id: number;
+ name: string;
+ type: { id: string; name: string | null };
+ category: { id: number | null; name: string | null };
+ memberSeq: number | null;
+ link: string | null;
+ comments: string | null;
+ }[];
origins?: {
type: { id: string; name: string | null };
libraryTitle: string;
@@ -583,6 +603,30 @@ export async function getEntryById(id: number): Promise {
dateDay: number | string | null;
publication: string | null;
}[] = [];
+ let relationOutRows: {
+ relationtypeId: string;
+ relationtypeName: string | null;
+ relatedId: number | string;
+ relatedTitle: string | null;
+ }[] = [];
+ let relationInRows: {
+ relationtypeId: string;
+ relationtypeName: string | null;
+ relationtypeReciprocal: string | null;
+ relatedId: number | string;
+ relatedTitle: string | null;
+ }[] = [];
+ let tagRows: {
+ id: number | string;
+ name: string;
+ tagtypeId: string;
+ tagtypeName: string | null;
+ categoryId: number | string | null;
+ categoryName: string | null;
+ memberSeq: number | string | null;
+ link: string | null;
+ comments: string | null;
+ }[] = [];
try {
aliasRows = await db
.select({ releaseSeq: aliases.releaseSeq, languageId: aliases.languageId, title: aliases.title })
@@ -644,6 +688,59 @@ export async function getEntryById(id: number): Promise {
);
originRows = rows as typeof originRows;
} catch {}
+ try {
+ const rows = await db
+ .select({
+ relationtypeId: relations.relationtypeId,
+ relationtypeName: relationtypes.name,
+ relatedId: relations.originalId,
+ relatedTitle: entries.title,
+ })
+ .from(relations)
+ .innerJoin(relationtypes, eq(relationtypes.id, relations.relationtypeId))
+ .leftJoin(entries, eq(entries.id, relations.originalId))
+ .where(eq(relations.entryId, id));
+ relationOutRows = rows as typeof relationOutRows;
+ } catch {}
+ try {
+ const rows = await db
+ .select({
+ relationtypeId: relations.relationtypeId,
+ relationtypeName: relationtypes.name,
+ relationtypeReciprocal: relationtypes.reciprocal,
+ relatedId: relations.entryId,
+ relatedTitle: entries.title,
+ })
+ .from(relations)
+ .innerJoin(relationtypes, eq(relationtypes.id, relations.relationtypeId))
+ .leftJoin(entries, eq(entries.id, relations.entryId))
+ .where(eq(relations.originalId, id));
+ relationInRows = rows as typeof relationInRows;
+ } catch {}
+ try {
+ const rows = await db
+ .select({
+ id: tags.id,
+ name: tags.name,
+ tagtypeId: tags.tagtypeId,
+ tagtypeName: tagtypes.name,
+ categoryId: members.categoryId,
+ categoryName: categories.name,
+ memberSeq: members.memberSeq,
+ link: tags.link,
+ comments: tags.comments,
+ })
+ .from(members)
+ .innerJoin(tags, eq(tags.id, members.tagId))
+ .leftJoin(tagtypes, eq(tagtypes.id, tags.tagtypeId))
+ .leftJoin(categories, eq(categories.id, members.categoryId))
+ .where(eq(members.entryId, id))
+ .orderBy(
+ asc(tags.name),
+ asc(members.memberSeq)
+ );
+ tagRows = rows as typeof tagRows;
+ } catch {}
return {
id: base.id,
@@ -663,6 +760,27 @@ export async function getEntryById(id: number): Promise {
linkSite: l.linkSite ?? null,
comments: l.comments ?? null,
})),
+ relations: [
+ ...relationOutRows.map((r) => ({
+ direction: "from" as const,
+ type: { id: r.relationtypeId, name: r.relationtypeName ?? null },
+ entry: { id: Number(r.relatedId), title: r.relatedTitle ?? null },
+ })),
+ ...relationInRows.map((r) => ({
+ direction: "to" as const,
+ type: { id: r.relationtypeId, name: r.relationtypeReciprocal ?? r.relationtypeName ?? null },
+ entry: { id: Number(r.relatedId), title: r.relatedTitle ?? null },
+ })),
+ ],
+ tags: tagRows.map((t) => ({
+ id: Number(t.id),
+ name: t.name,
+ type: { id: t.tagtypeId, name: t.tagtypeName ?? null },
+ category: { id: t.categoryId != null ? Number(t.categoryId) : null, name: t.categoryName ?? null },
+ memberSeq: t.memberSeq != null ? Number(t.memberSeq) : null,
+ link: t.link ?? null,
+ comments: t.comments ?? null,
+ })),
origins: originRows.map((o) => ({
type: { id: o.origintypeId, name: o.origintypeName ?? null },
libraryTitle: o.libraryTitle,