Unify ZXDB list layouts

Apply sidebar filter layout to label/genre/language/machine
lists and restructure release detail into a two-column view.

Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
2026-01-10 22:05:28 +00:00
parent 6f7ffa899d
commit e94492eab6
5 changed files with 503 additions and 442 deletions

View File

@@ -39,17 +39,31 @@ export default function GenresSearch({ initial, initialQ }: { initial?: Paged<Ge
]}
/>
<h1>Genres</h1>
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}>
<div className="col-sm-8 col-md-6 col-lg-4">
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h1 className="mb-1">Genres</h1>
<div className="text-secondary">{data?.total.toLocaleString() ?? "0"} results</div>
</div>
</div>
<div className="row g-3">
<div className="col-lg-3">
<div className="card shadow-sm">
<div className="card-body">
<form className="d-flex flex-column gap-2" onSubmit={submit}>
<div>
<label className="form-label small text-secondary">Search</label>
<input className="form-control" placeholder="Search genres…" value={q} onChange={(e) => setQ(e.target.value)} />
</div>
<div className="col-auto">
<div className="d-grid">
<button className="btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<div className="mt-3">
<div className="col-lg-9">
{data && data.items.length === 0 && <div className="alert alert-warning">No genres found.</div>}
{data && data.items.length > 0 && (
<div className="table-responsive">
@@ -74,6 +88,7 @@ export default function GenresSearch({ initial, initialQ }: { initial?: Paged<Ge
</div>
)}
</div>
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span>

View File

@@ -41,17 +41,31 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
]}
/>
<h1>Labels</h1>
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}>
<div className="col-sm-8 col-md-6 col-lg-4">
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h1 className="mb-1">Labels</h1>
<div className="text-secondary">{data?.total.toLocaleString() ?? "0"} results</div>
</div>
</div>
<div className="row g-3">
<div className="col-lg-3">
<div className="card shadow-sm">
<div className="card-body">
<form className="d-flex flex-column gap-2" onSubmit={submit}>
<div>
<label className="form-label small text-secondary">Search</label>
<input className="form-control" placeholder="Search labels…" value={q} onChange={(e) => setQ(e.target.value)} />
</div>
<div className="col-auto">
<div className="d-grid">
<button className="btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<div className="mt-3">
<div className="col-lg-9">
{data && data.items.length === 0 && <div className="alert alert-warning">No labels found.</div>}
{data && data.items.length > 0 && (
<div className="table-responsive">
@@ -80,6 +94,7 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
</div>
)}
</div>
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span>

View File

@@ -39,17 +39,31 @@ export default function LanguagesSearch({ initial, initialQ }: { initial?: Paged
]}
/>
<h1>Languages</h1>
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}>
<div className="col-sm-8 col-md-6 col-lg-4">
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h1 className="mb-1">Languages</h1>
<div className="text-secondary">{data?.total.toLocaleString() ?? "0"} results</div>
</div>
</div>
<div className="row g-3">
<div className="col-lg-3">
<div className="card shadow-sm">
<div className="card-body">
<form className="d-flex flex-column gap-2" onSubmit={submit}>
<div>
<label className="form-label small text-secondary">Search</label>
<input className="form-control" placeholder="Search languages…" value={q} onChange={(e) => setQ(e.target.value)} />
</div>
<div className="col-auto">
<div className="d-grid">
<button className="btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<div className="mt-3">
<div className="col-lg-9">
{data && data.items.length === 0 && <div className="alert alert-warning">No languages found.</div>}
{data && data.items.length > 0 && (
<div className="table-responsive">
@@ -74,6 +88,7 @@ export default function LanguagesSearch({ initial, initialQ }: { initial?: Paged
</div>
)}
</div>
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span>

View File

@@ -41,17 +41,31 @@ export default function MachineTypesSearch({ initial, initialQ }: { initial?: Pa
]}
/>
<h1>Machine Types</h1>
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}>
<div className="col-sm-8 col-md-6 col-lg-4">
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h1 className="mb-1">Machine Types</h1>
<div className="text-secondary">{data?.total.toLocaleString() ?? "0"} results</div>
</div>
</div>
<div className="row g-3">
<div className="col-lg-3">
<div className="card shadow-sm">
<div className="card-body">
<form className="d-flex flex-column gap-2" onSubmit={submit}>
<div>
<label className="form-label small text-secondary">Search</label>
<input className="form-control" placeholder="Search machine types…" value={q} onChange={(e) => setQ(e.target.value)} />
</div>
<div className="col-auto">
<div className="d-grid">
<button className="btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<div className="mt-3">
<div className="col-lg-9">
{data && data.items.length === 0 && <div className="alert alert-warning">No machine types found.</div>}
{data && data.items.length > 0 && (
<div className="table-responsive">
@@ -76,6 +90,7 @@ export default function MachineTypesSearch({ initial, initialQ }: { initial?: Pa
</div>
)}
</div>
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span>

View File

@@ -191,29 +191,26 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</Link>
</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">Release Summary</h5>
<div className="table-responsive">
<table className="table table-striped table-hover align-middle">
<thead>
<tr>
<th style={{ width: 220 }}>Field</th>
<th>Value</th>
</tr>
</thead>
<table className="table table-sm table-striped align-middle mb-0">
<tbody>
<tr>
<td>Entry</td>
<th style={{ width: 160 }}>Entry</th>
<td>
<Link href={`/zxdb/entries/${data.entry.id}`}>#{data.entry.id}</Link>
</td>
</tr>
<tr>
<td>Release Sequence</td>
<th>Release Sequence</th>
<td>#{data.release.releaseSeq}</td>
</tr>
<tr>
<td>Release Date</td>
<th>Release Date</th>
<td>
{data.release.year != null ? (
<span>
@@ -227,7 +224,7 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</td>
</tr>
<tr>
<td>Currency</td>
<th>Currency</th>
<td>
{data.release.currency.id ? (
<span>{data.release.currency.id} {data.release.currency.name ? `(${data.release.currency.name})` : ""}</span>
@@ -237,41 +234,42 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</td>
</tr>
<tr>
<td>Price</td>
<th>Price</th>
<td>{formatCurrency(data.release.prices.release, data.release.currency)}</td>
</tr>
<tr>
<td>Budget Price</td>
<th>Budget Price</th>
<td>{formatCurrency(data.release.prices.budget, data.release.currency)}</td>
</tr>
<tr>
<td>Microdrive Price</td>
<th>Microdrive Price</th>
<td>{formatCurrency(data.release.prices.microdrive, data.release.currency)}</td>
</tr>
<tr>
<td>Disk Price</td>
<th>Disk Price</th>
<td>{formatCurrency(data.release.prices.disk, data.release.currency)}</td>
</tr>
<tr>
<td>Cartridge Price</td>
<th>Cartridge Price</th>
<td>{formatCurrency(data.release.prices.cartridge, data.release.currency)}</td>
</tr>
<tr>
<td>Book ISBN</td>
<th>Book ISBN</th>
<td>{data.release.book.isbn ?? <span className="text-secondary">-</span>}</td>
</tr>
<tr>
<td>Book Pages</td>
<th>Book Pages</th>
<td>{data.release.book.pages ?? <span className="text-secondary">-</span>}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<hr />
<div>
<h5>Other Releases</h5>
<div className="card shadow-sm">
<div className="card-body">
<h5 className="card-title">Other Releases</h5>
{otherReleases.length === 0 && <div className="text-secondary">No other releases</div>}
{otherReleases.length > 0 && (
<div className="d-flex flex-wrap gap-2">
@@ -287,11 +285,13 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</div>
)}
</div>
</div>
</div>
<hr />
<div>
<h5>Places (Magazines)</h5>
<div className="col-lg-8">
<div className="card shadow-sm mb-3">
<div className="card-body">
<h5 className="card-title">Places (Magazines)</h5>
{magazineGroups.length === 0 && <div className="text-secondary">No magazine references</div>}
{magazineGroups.length > 0 && (
<div className="d-flex flex-column gap-3">
@@ -349,11 +349,11 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</div>
)}
</div>
</div>
<hr />
<div>
<h5>Downloads</h5>
<div className="card shadow-sm mb-3">
<div className="card-body">
<h5 className="card-title">Downloads</h5>
{data.downloads.length === 0 && <div className="text-secondary">No downloads</div>}
{data.downloads.length > 0 && (
<div className="table-responsive">
@@ -412,11 +412,11 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</div>
)}
</div>
</div>
<hr />
<div>
<h5>Scraps / Media</h5>
<div className="card shadow-sm mb-3">
<div className="card-body">
<h5 className="card-title">Scraps / Media</h5>
{data.scraps.length === 0 && <div className="text-secondary">No scraps</div>}
{data.scraps.length > 0 && (
<div className="table-responsive">
@@ -477,11 +477,11 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</div>
)}
</div>
</div>
<hr />
<div>
<h5>Issue Files</h5>
<div className="card shadow-sm mb-3">
<div className="card-body">
<h5 className="card-title">Issue Files</h5>
{data.files.length === 0 && <div className="text-secondary">No files linked</div>}
{data.files.length > 0 && (
<div className="table-responsive">
@@ -519,8 +519,9 @@ export default function ReleaseDetailClient({ data }: { data: ReleaseDetailData
</div>
)}
</div>
<hr />
</div>
</div>
</div>
<div className="d-flex align-items-center gap-2">
<Link className="btn btn-sm btn-outline-secondary" href={`/zxdb/releases/${data.entry.id}/${data.release.releaseSeq}`}>Permalink</Link>