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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user