FEAT-2: Becken pages - list (Gridify search+paging), detail w/ occupancy, form, delete w/ 409 conflict message
This commit is contained in:
109
gerbil-manager-web/src/pages/BeckenPage.tsx
Normal file
109
gerbil-manager-web/src/pages/BeckenPage.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
/** FEAT-2: Becken-Liste — Gridify-Namenssuche + Paging, Link auf Detailseite. */
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { de } from '../strings/de'
|
||||
import { listEnclosuresPaged } from '../api/enclosures'
|
||||
import { condition, type GridifyQuery } from '../api/gridify'
|
||||
import { useApi } from '../hooks/useApi'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export default function BeckenPage() {
|
||||
const t = de.pages.becken
|
||||
const [search, setSearch] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const filter = search.trim()
|
||||
? condition({ field: 'name', op: 'contains', value: search.trim() })
|
||||
: undefined
|
||||
const query: GridifyQuery = { filter, orderBy: 'name', page, pageSize: PAGE_SIZE }
|
||||
|
||||
const enclosures = useApi(() => listEnclosuresPaged(query), [filter, page])
|
||||
|
||||
const total = enclosures.data?.totalCount ?? 0
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||||
const items = enclosures.data?.items ?? []
|
||||
|
||||
return (
|
||||
<section className="page">
|
||||
<header className="page-head">
|
||||
<div>
|
||||
<h2>{t.title}</h2>
|
||||
{enclosures.data && (
|
||||
<p className="muted">
|
||||
{total} {t.countLabel}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Link to="/becken/neu" className="btn btn--primary">
|
||||
+ {t.newButton}
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<div className="filters">
|
||||
<input
|
||||
type="search"
|
||||
className="input"
|
||||
placeholder={t.searchPlaceholder}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
setPage(1)
|
||||
}}
|
||||
aria-label={t.fields.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{enclosures.loading && <p className="muted">{de.common.loading}</p>}
|
||||
{enclosures.error && (
|
||||
<div className="alert alert--error">
|
||||
<span>{enclosures.error}</span>
|
||||
<button type="button" className="btn" onClick={enclosures.reload}>
|
||||
{de.common.retry}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!enclosures.loading && !enclosures.error && items.length === 0 && (
|
||||
<p className="muted">{t.empty}</p>
|
||||
)}
|
||||
|
||||
{items.length > 0 && (
|
||||
<ul className="card-list">
|
||||
{items.map((e) => (
|
||||
<li key={e.id}>
|
||||
<Link to={`/becken/${e.id}`} className="gerbil-card">
|
||||
<span className="gerbil-card__name">{e.name}</span>
|
||||
<span className="gerbil-card__meta">{e.notes ?? ''}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<nav className="pager" aria-label="Seitennavigation">
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
disabled={page <= 1}
|
||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||
>
|
||||
{de.common.previous}
|
||||
</button>
|
||||
<span className="muted">
|
||||
{de.common.page} {page} {de.common.of} {totalPages}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||
>
|
||||
{de.common.next}
|
||||
</button>
|
||||
</nav>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user