FEAT: Normalize Anke B./Anke Busch contact, and save other infinite scroll layout updates

This commit is contained in:
2026-06-13 08:11:17 +02:00
parent d189da954f
commit f3f4534392
5 changed files with 84 additions and 105 deletions

View File

@@ -13,7 +13,8 @@ import {
syncRequests,
type RequestStatus,
} from '../api/requests'
import { useApi, useMutation } from '../hooks/useApi'
import { useMutation } from '../hooks/useApi'
import { useInfiniteList, useInfiniteSentinel } from '../hooks/useInfiniteList'
import { formatDateTime } from '../format/labels'
import { FilterPanel } from '../components/FilterPanel'
import './anfragen.css'
@@ -31,19 +32,20 @@ export function StatusBadge({ status }: { status: RequestStatus }) {
export default function AnfragenPage() {
const t = de.pages.anfragen
const [status, setStatus] = useState<RequestStatus | ''>('')
const [page, setPage] = useState(1)
const [notice, setNotice] = useState<string | null>(null)
const requests = useApi(
() =>
const requests = useInfiniteList(
(page) =>
listRequests({
page,
pageSize: PAGE_SIZE,
orderBy: 'receivedAt desc',
orderBy: 'receivedAt desc,id',
filter: status === '' ? undefined : `status==${status}`,
}),
[page, status],
`${status}`,
)
const sentinelRef = useInfiniteSentinel(requests)
const { items, total, loading } = requests
const sync = useMutation(() => syncRequests())
async function onSync() {
@@ -63,7 +65,7 @@ export default function AnfragenPage() {
<header className="page-head">
<div>
<h2>{t.title}</h2>
{requests.data && <p className="muted">{t.countText(requests.data.totalCount)}</p>}
{!loading && <p className="muted">{t.countText(total)}</p>}
</div>
<div className="head-actions">
<button type="button" className="btn btn--primary" onClick={onSync} disabled={sync.pending}>
@@ -78,16 +80,13 @@ export default function AnfragenPage() {
{/* Status-Filter */}
<FilterPanel
activeCount={status !== '' ? 1 : 0}
onReset={() => { setStatus(''); setPage(1) }}
onReset={() => setStatus('')}
>
<label className="field">
<span>{t.detail.statusLabel}</span>
<select
value={status}
onChange={(e) => {
setStatus(e.target.value as RequestStatus | '')
setPage(1)
}}
onChange={(e) => setStatus(e.target.value as RequestStatus | '')}
>
<option value="">{t.filterAll}</option>
{REQUEST_STATUSES.map((s) => (
@@ -99,7 +98,7 @@ export default function AnfragenPage() {
</label>
</FilterPanel>
{requests.loading && <p className="muted">{de.common.loading}</p>}
{loading && <p className="muted">{de.common.loading}</p>}
{requests.error && (
<div className="alert alert--error">
<span>{requests.error}</span>
@@ -109,13 +108,13 @@ export default function AnfragenPage() {
</div>
)}
{requests.data && requests.data.items.length === 0 && (
{!loading && items.length === 0 && (
<p className="muted">{status === '' ? t.empty : t.emptyFiltered}</p>
)}
{requests.data && requests.data.items.length > 0 && (
{items.length > 0 && (
<ul className="card-list">
{requests.data.items.map((r) => (
{items.map((r) => (
<li key={r.id}>
<Link to={`/anfragen/${r.id}`} className="gerbil-card anfrage-card">
<span className="gerbil-card__name">{r.fromName ?? r.fromAddress}</span>
@@ -130,24 +129,12 @@ export default function AnfragenPage() {
</ul>
)}
{requests.data && requests.data.totalCount > PAGE_SIZE && (
<nav className="pager" aria-label="Seitennavigation">
<button type="button" className="btn" disabled={page <= 1} onClick={() => setPage(page - 1)}>
{de.common.previous}
</button>
<span>
{de.common.page} {page} {de.common.of}{' '}
{Math.max(1, Math.ceil(requests.data.totalCount / PAGE_SIZE))}
</span>
<button
type="button"
className="btn"
disabled={page >= Math.ceil(requests.data.totalCount / PAGE_SIZE)}
onClick={() => setPage(page + 1)}
>
{de.common.next}
</button>
</nav>
{/* Infinite-scroll sentinel + "loading more" indicator. */}
{requests.hasMore && <div ref={sentinelRef} aria-hidden="true" style={{ height: 1 }} />}
{requests.loadingMore && (
<p className="muted" style={{ textAlign: 'center', padding: '0.75rem 0' }}>
{de.common.loading}
</p>
)}
</section>
)