Refresh releases and magazines UI

Apply sidebar filter layout and header summary to releases
and magazines list pages.

Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
2026-01-10 21:56:46 +00:00
parent 84dee2710c
commit 6f7ffa899d
2 changed files with 210 additions and 161 deletions

View File

@@ -27,30 +27,58 @@ export default async function Page({
]}
/>
<h1 className="mb-3">Magazines</h1>
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h1 className="mb-1">Magazines</h1>
<div className="text-secondary">{data.total.toLocaleString()} results</div>
</div>
</div>
<form className="mb-3" action="/zxdb/magazines" method="get">
<div className="input-group">
<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" action="/zxdb/magazines" method="get">
<div>
<label className="form-label small text-secondary">Search</label>
<input type="text" className="form-control" name="q" placeholder="Search magazines..." defaultValue={q} />
<button className="btn btn-outline-secondary" type="submit">
<span className="bi bi-search" aria-hidden />
<span className="visually-hidden">Search</span>
</button>
</div>
<div className="d-grid">
<button className="btn btn-primary" type="submit">Search</button>
</div>
</form>
</div>
</div>
</div>
<div className="list-group">
<div className="col-lg-9">
<div className="table-responsive">
<table className="table table-striped table-hover align-middle">
<thead>
<tr>
<th>Title</th>
<th style={{ width: 140 }}>Language</th>
<th style={{ width: 120 }}>Issues</th>
</tr>
</thead>
<tbody>
{data.items.map((m) => (
<Link key={m.id} className="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href={`/zxdb/magazines/${m.id}`}>
<span>
{m.title}
<span className="text-secondary ms-2">({m.languageId})</span>
</span>
<tr key={m.id}>
<td>
<Link href={`/zxdb/magazines/${m.id}`}>{m.title}</Link>
</td>
<td>{m.languageId}</td>
<td>
<span className="badge bg-secondary rounded-pill" title="Issues">
{m.issueCount}
</span>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
<Pagination page={data.page} pageSize={data.pageSize} total={data.total} q={q} />

View File

@@ -238,9 +238,22 @@ export default function ReleasesExplorer({
]}
/>
<h1 className="mb-3">Releases</h1>
<form className="row gy-2 gx-2 align-items-center" onSubmit={onSubmit}>
<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">Releases</h1>
<div className="text-secondary">
{data ? `${data.total.toLocaleString()} results` : "Loading 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={onSubmit}>
<div>
<label className="form-label small text-secondary">Search title</label>
<input
type="text"
className="form-control"
@@ -249,84 +262,93 @@ export default function ReleasesExplorer({
onChange={(e) => setQ(e.target.value)}
/>
</div>
<div className="col-auto">
<div className="d-grid">
<button className="btn btn-primary" type="submit" disabled={loading}>Search</button>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">Year</label>
<input
type="number"
className="form-control"
placeholder="Year"
placeholder="Any"
value={year}
onChange={(e) => { setYear(e.target.value); setPage(1); }}
/>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">DL Language</label>
<select className="form-select" value={dLanguageId} onChange={(e) => { setDLanguageId(e.target.value); setPage(1); }}>
<option value="">DL Language</option>
<option value="">All languages</option>
{langs.map((l) => (
<option key={l.id} value={l.id}>{l.name}</option>
))}
</select>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">DL Machine</label>
<select className="form-select" value={dMachinetypeId} onChange={(e) => { setDMachinetypeId(e.target.value); setPage(1); }}>
<option value="">DL Machine</option>
<option value="">All machines</option>
{machines.map((m) => (
<option key={m.id} value={m.id}>{m.name}</option>
))}
</select>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">File type</label>
<select className="form-select" value={filetypeId} onChange={(e) => { setFiletypeId(e.target.value); setPage(1); }}>
<option value="">File type</option>
<option value="">All file types</option>
{filetypes.map((ft) => (
<option key={ft.id} value={ft.id}>{ft.name}</option>
))}
</select>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">Scheme</label>
<select className="form-select" value={schemetypeId} onChange={(e) => { setSchemetypeId(e.target.value); setPage(1); }}>
<option value="">Scheme</option>
<option value="">All schemes</option>
{schemes.map((s) => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
</select>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">Source</label>
<select className="form-select" value={sourcetypeId} onChange={(e) => { setSourcetypeId(e.target.value); setPage(1); }}>
<option value="">Source</option>
<option value="">All sources</option>
{sources.map((s) => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
</select>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">Case</label>
<select className="form-select" value={casetypeId} onChange={(e) => { setCasetypeId(e.target.value); setPage(1); }}>
<option value="">Case</option>
<option value="">All cases</option>
{cases.map((c) => (
<option key={c.id} value={c.id}>{c.name}</option>
))}
</select>
</div>
<div className="col-auto form-check ms-2">
<div className="form-check">
<input id="demoCheck" className="form-check-input" type="checkbox" checked={isDemo} onChange={(e) => { setIsDemo(e.target.checked); setPage(1); }} />
<label className="form-check-label" htmlFor="demoCheck">Demo only</label>
</div>
<div className="col-auto">
<div>
<label className="form-label small text-secondary">Sort</label>
<select className="form-select" value={sort} onChange={(e) => { setSort(e.target.value as typeof sort); setPage(1); }}>
<option value="year_desc">Sort: Newest</option>
<option value="year_asc">Sort: Oldest</option>
<option value="title">Sort: Title</option>
<option value="entry_id_desc">Sort: Entry ID</option>
<option value="year_desc">Newest</option>
<option value="year_asc">Oldest</option>
<option value="title">Title</option>
<option value="entry_id_desc">Entry ID</option>
</select>
</div>
{loading && (
<div className="col-auto text-secondary">Loading...</div>
)}
{loading && <div className="text-secondary small">Loading...</div>}
</form>
</div>
</div>
</div>
<div className="mt-3">
<div className="col-lg-9">
{data && data.items.length === 0 && !loading && (
<div className="alert alert-warning">No results.</div>
)}
@@ -335,11 +357,11 @@ export default function ReleasesExplorer({
<table className="table table-striped table-hover align-middle">
<thead>
<tr>
<th style={{width: 80}}>Entry ID</th>
<th style={{ width: 80 }}>Entry ID</th>
<th>Title</th>
<th style={{width: 140}}>Release #</th>
<th style={{width: 110}}>Places</th>
<th style={{width: 100}}>Year</th>
<th style={{ width: 140 }}>Release #</th>
<th style={{ width: 110 }}>Places</th>
<th style={{ width: 100 }}>Year</th>
</tr>
</thead>
<tbody>
@@ -375,11 +397,10 @@ export default function ReleasesExplorer({
</div>
)}
</div>
</div>
<div className="d-flex align-items-center gap-2 mt-2">
<span>
Page {data?.page ?? 1} / {totalPages}
</span>
<div className="d-flex align-items-center gap-2 mt-4">
<span>Page {data?.page ?? 1} / {totalPages}</span>
<div className="ms-auto d-flex gap-2">
<Link
className={`btn btn-outline-secondary ${!data || (data.page <= 1) ? "disabled" : ""}`}