Revamp entry detail layout
Restructure entry detail into a two-column layout with summary/people cards and section cards on the right. Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
@@ -165,27 +165,24 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
{data.isXrated ? <span className="badge text-bg-danger">18+</span> : null}
|
{data.isXrated ? <span className="badge text-bg-danger">18+</span> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="row g-3 mt-2">
|
||||||
|
<div className="col-lg-4">
|
||||||
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">Entry Summary</h5>
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-striped table-hover align-middle">
|
<table className="table table-sm table-striped align-middle mb-0">
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style={{ width: 220 }}>Field</th>
|
|
||||||
<th>Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>ID</td>
|
<th style={{ width: 180 }}>ID</th>
|
||||||
<td>{data.id}</td>
|
<td>{data.id}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Title</td>
|
<th>Title</th>
|
||||||
<td>{data.title}</td>
|
<td>{data.title}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Machine</td>
|
<th>Machine</th>
|
||||||
<td>
|
<td>
|
||||||
{data.machinetype.id != null ? (
|
{data.machinetype.id != null ? (
|
||||||
data.machinetype.name ? (
|
data.machinetype.name ? (
|
||||||
@@ -199,7 +196,7 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Language</td>
|
<th>Language</th>
|
||||||
<td>
|
<td>
|
||||||
{data.language.id ? (
|
{data.language.id ? (
|
||||||
data.language.name ? (
|
data.language.name ? (
|
||||||
@@ -213,7 +210,7 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Genre</td>
|
<th>Genre</th>
|
||||||
<td>
|
<td>
|
||||||
{data.genre.id ? (
|
{data.genre.id ? (
|
||||||
data.genre.name ? (
|
data.genre.name ? (
|
||||||
@@ -228,43 +225,86 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</tr>
|
</tr>
|
||||||
{typeof data.maxPlayers !== "undefined" && (
|
{typeof data.maxPlayers !== "undefined" && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Max Players</td>
|
<th>Max Players</th>
|
||||||
<td>{data.maxPlayers}</td>
|
<td>{data.maxPlayers}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{typeof data.availabletypeId !== "undefined" && (
|
{typeof data.availabletypeId !== "undefined" && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Available Type</td>
|
<th>Available Type</th>
|
||||||
<td>{data.availabletypeId ?? <span className="text-secondary">-</span>}</td>
|
<td>{data.availabletypeId ?? <span className="text-secondary">-</span>}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{typeof data.withoutLoadScreen !== "undefined" && (
|
{typeof data.withoutLoadScreen !== "undefined" && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Without Load Screen</td>
|
<th>Without Load Screen</th>
|
||||||
<td>{data.withoutLoadScreen ? "Yes" : "No"}</td>
|
<td>{data.withoutLoadScreen ? "Yes" : "No"}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{typeof data.withoutInlay !== "undefined" && (
|
{typeof data.withoutInlay !== "undefined" && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Without Inlay</td>
|
<th>Without Inlay</th>
|
||||||
<td>{data.withoutInlay ? "Yes" : "No"}</td>
|
<td>{data.withoutInlay ? "Yes" : "No"}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{typeof data.issueId !== "undefined" && (
|
{typeof data.issueId !== "undefined" && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Issue</td>
|
<th>Issue</th>
|
||||||
<td>{data.issueId ? <span>#{data.issueId}</span> : <span className="text-secondary">-</span>}</td>
|
<td>{data.issueId ? <span>#{data.issueId}</span> : <span className="text-secondary">-</span>}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">People</h5>
|
||||||
|
<div className="row g-3">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="text-secondary small mb-1">Authors</div>
|
||||||
|
{data.authors.length === 0 && <div className="text-secondary">Unknown</div>}
|
||||||
|
{data.authors.length > 0 && (
|
||||||
|
<ul className="list-unstyled mb-0">
|
||||||
|
{data.authors.map((a) => (
|
||||||
|
<li key={a.id}>
|
||||||
|
<Link href={`/zxdb/labels/${a.id}`}>{a.name}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="text-secondary small mb-1">Publishers</div>
|
||||||
|
{data.publishers.length === 0 && <div className="text-secondary">Unknown</div>}
|
||||||
|
{data.publishers.length > 0 && (
|
||||||
|
<ul className="list-unstyled mb-0">
|
||||||
|
{data.publishers.map((p) => (
|
||||||
|
<li key={p.id}>
|
||||||
|
<Link href={`/zxdb/labels/${p.id}`}>{p.name}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Downloads (flat, by entry_id). Render only this flat section; do not render grouped downloads here. */}
|
<div className="card shadow-sm">
|
||||||
<div>
|
<div className="card-body d-flex flex-wrap gap-2">
|
||||||
<h5>Downloads</h5>
|
<Link className="btn btn-sm btn-outline-secondary" href={`/zxdb/entries/${data.id}`}>Permalink</Link>
|
||||||
|
<Link className="btn btn-sm btn-outline-primary" href="/zxdb">Back to Explorer</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-lg-8">
|
||||||
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">Downloads</h5>
|
||||||
{(!data.downloadsFlat || data.downloadsFlat.length === 0) && <div className="text-secondary">No downloads</div>}
|
{(!data.downloadsFlat || data.downloadsFlat.length === 0) && <div className="text-secondary">No downloads</div>}
|
||||||
{data.downloadsFlat && data.downloadsFlat.length > 0 && (
|
{data.downloadsFlat && data.downloadsFlat.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -326,42 +366,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div className="row g-4">
|
|
||||||
<div className="col-lg-6">
|
|
||||||
<h5>Authors</h5>
|
|
||||||
{data.authors.length === 0 && <div className="text-secondary">Unknown</div>}
|
|
||||||
{data.authors.length > 0 && (
|
|
||||||
<ul className="list-unstyled mb-0">
|
|
||||||
{data.authors.map((a) => (
|
|
||||||
<li key={a.id}>
|
|
||||||
<Link href={`/zxdb/labels/${a.id}`}>{a.name}</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="col-lg-6">
|
|
||||||
<h5>Publishers</h5>
|
|
||||||
{data.publishers.length === 0 && <div className="text-secondary">Unknown</div>}
|
|
||||||
{data.publishers.length > 0 && (
|
|
||||||
<ul className="list-unstyled mb-0">
|
|
||||||
{data.publishers.map((p) => (
|
|
||||||
<li key={p.id}>
|
|
||||||
<Link href={`/zxdb/labels/${p.id}`}>{p.name}</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Releases</h5>
|
||||||
<h5>Releases</h5>
|
|
||||||
{(!data.releases || data.releases.length === 0) && <div className="text-secondary">No releases recorded</div>}
|
{(!data.releases || data.releases.length === 0) && <div className="text-secondary">No releases recorded</div>}
|
||||||
{data.releases && data.releases.length > 0 && (
|
{data.releases && data.releases.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -388,11 +397,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Origins</h5>
|
||||||
<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="text-secondary">No origins recorded</div>}
|
||||||
{data.origins && data.origins.length > 0 && (
|
{data.origins && data.origins.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -442,11 +451,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Relations</h5>
|
||||||
<h5>Relations</h5>
|
|
||||||
{(!data.relations || data.relations.length === 0) && <div className="text-secondary">No relations recorded</div>}
|
{(!data.relations || data.relations.length === 0) && <div className="text-secondary">No relations recorded</div>}
|
||||||
{data.relations && data.relations.length > 0 && (
|
{data.relations && data.relations.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -475,11 +484,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Tags / Members</h5>
|
||||||
<h5>Tags / Members</h5>
|
|
||||||
{(!data.tags || data.tags.length === 0) && <div className="text-secondary">No tags recorded</div>}
|
{(!data.tags || data.tags.length === 0) && <div className="text-secondary">No tags recorded</div>}
|
||||||
{data.tags && data.tags.length > 0 && (
|
{data.tags && data.tags.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -516,11 +525,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Ports</h5>
|
||||||
<h5>Ports</h5>
|
|
||||||
{(!data.ports || data.ports.length === 0) && <div className="text-secondary">No ports recorded</div>}
|
{(!data.ports || data.ports.length === 0) && <div className="text-secondary">No ports recorded</div>}
|
||||||
{data.ports && data.ports.length > 0 && (
|
{data.ports && data.ports.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -553,11 +562,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Remakes</h5>
|
||||||
<h5>Remakes</h5>
|
|
||||||
{(!data.remakes || data.remakes.length === 0) && <div className="text-secondary">No remakes recorded</div>}
|
{(!data.remakes || data.remakes.length === 0) && <div className="text-secondary">No remakes recorded</div>}
|
||||||
{data.remakes && data.remakes.length > 0 && (
|
{data.remakes && data.remakes.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -592,11 +601,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Scores</h5>
|
||||||
<h5>Scores</h5>
|
|
||||||
{(!data.scores || data.scores.length === 0) && <div className="text-secondary">No scores recorded</div>}
|
{(!data.scores || data.scores.length === 0) && <div className="text-secondary">No scores recorded</div>}
|
||||||
{data.scores && data.scores.length > 0 && (
|
{data.scores && data.scores.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -621,11 +630,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Notes</h5>
|
||||||
<h5>Notes</h5>
|
|
||||||
{(!data.notes || data.notes.length === 0) && <div className="text-secondary">No notes recorded</div>}
|
{(!data.notes || data.notes.length === 0) && <div className="text-secondary">No notes recorded</div>}
|
||||||
{data.notes && data.notes.length > 0 && (
|
{data.notes && data.notes.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -648,12 +657,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
{/* Aliases (alternative titles) */}
|
<h5 className="card-title">Aliases</h5>
|
||||||
<div>
|
|
||||||
<h5>Aliases</h5>
|
|
||||||
{(!data.aliases || data.aliases.length === 0) && <div className="text-secondary">No aliases</div>}
|
{(!data.aliases || data.aliases.length === 0) && <div className="text-secondary">No aliases</div>}
|
||||||
{data.aliases && data.aliases.length > 0 && (
|
{data.aliases && data.aliases.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -680,11 +688,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Licenses</h5>
|
||||||
<h5>Licenses</h5>
|
|
||||||
{(!data.licenses || data.licenses.length === 0) && <div className="text-secondary">No licenses linked</div>}
|
{(!data.licenses || data.licenses.length === 0) && <div className="text-secondary">No licenses linked</div>}
|
||||||
{data.licenses && data.licenses.length > 0 && (
|
{data.licenses && data.licenses.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -721,12 +729,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm mb-3">
|
||||||
|
<div className="card-body">
|
||||||
{/* Web links (external references) */}
|
<h5 className="card-title">Web links</h5>
|
||||||
<div>
|
|
||||||
<h5>Web links</h5>
|
|
||||||
{(!data.webrefs || data.webrefs.length === 0) && <div className="text-secondary">No web links</div>}
|
{(!data.webrefs || data.webrefs.length === 0) && <div className="text-secondary">No web links</div>}
|
||||||
{data.webrefs && data.webrefs.length > 0 && (
|
{data.webrefs && data.webrefs.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -759,11 +766,11 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<div className="card shadow-sm">
|
||||||
|
<div className="card-body">
|
||||||
<div>
|
<h5 className="card-title">Files</h5>
|
||||||
<h5>Files</h5>
|
|
||||||
{(!data.files || data.files.length === 0) && <div className="text-secondary">No files linked</div>}
|
{(!data.files || data.files.length === 0) && <div className="text-secondary">No files linked</div>}
|
||||||
{data.files && data.files.length > 0 && (
|
{data.files && data.files.length > 0 && (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
@@ -801,14 +808,8 @@ export default function EntryDetailClient({ data }: { data: EntryDetailData | nu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
</div>
|
||||||
|
|
||||||
{/* Removed grouped releases/downloads section to avoid duplicate downloads UI. */}
|
|
||||||
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
|
||||||
<Link className="btn btn-sm btn-outline-secondary" href={`/zxdb/entries/${data.id}`}>Permalink</Link>
|
|
||||||
<Link className="btn btn-sm btn-outline-primary" href="/zxdb">Back to Explorer</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user