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> <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}> <div>
<div className="col-sm-8 col-md-6 col-lg-4"> <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)} /> <input className="form-control" placeholder="Search genres…" value={q} onChange={(e) => setQ(e.target.value)} />
</div> </div>
<div className="col-auto"> <div className="d-grid">
<button className="btn btn-primary">Search</button> <button className="btn btn-primary">Search</button>
</div> </div>
</form> </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="alert alert-warning">No genres found.</div>}
{data && data.items.length > 0 && ( {data && data.items.length > 0 && (
<div className="table-responsive"> <div className="table-responsive">
@@ -74,6 +88,7 @@ export default function GenresSearch({ initial, initialQ }: { initial?: Paged<Ge
</div> </div>
)} )}
</div> </div>
</div>
<div className="d-flex align-items-center gap-2 mt-2"> <div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span> <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> <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<form className="row gy-2 gx-2 align-items-center" onSubmit={submit}> <div>
<div className="col-sm-8 col-md-6 col-lg-4"> <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)} /> <input className="form-control" placeholder="Search labels…" value={q} onChange={(e) => setQ(e.target.value)} />
</div> </div>
<div className="col-auto"> <div className="d-grid">
<button className="btn btn-primary">Search</button> <button className="btn btn-primary">Search</button>
</div> </div>
</form> </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="alert alert-warning">No labels found.</div>}
{data && data.items.length > 0 && ( {data && data.items.length > 0 && (
<div className="table-responsive"> <div className="table-responsive">
@@ -80,6 +94,7 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
</div> </div>
)} )}
</div> </div>
</div>
<div className="d-flex align-items-center gap-2 mt-2"> <div className="d-flex align-items-center gap-2 mt-2">
<span>Page {data?.page ?? 1} / {totalPages}</span> <span>Page {data?.page ?? 1} / {totalPages}</span>

View File

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

View File

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