P1: Einheitliche klassische Pagination (Neuheiten, Planung, Audit-Log)

- Infinite Scroll ersetzt durch Zurück/Weiter + Seitenzahlen-Fenster
  (funktioniert ohne JavaScript)
- Gemeinsames Makro seitennavigation.html (aria-current auf aktiver
  Seite, deaktivierte Buttons als span)
- Neuheiten: Pagination bleibt beim HTMX-Filter-Swap innerhalb von
  #neuheiten-liste konsistent; Filter/Sortierung/Jahr bleiben in den
  Seiten-URLs erhalten
- Audit-Log: gleiche Optik statt reiner Text-Links
This commit is contained in:
Flo Hartmann
2026-08-26 08:59:49 +00:00
parent 117b34653c
commit 7645f6a791
7 changed files with 104 additions and 34 deletions

View File

@@ -27,7 +27,8 @@ import logging
import os
import threading
from datetime import datetime, timedelta, timezone
from urllib.parse import quote
from math import ceil
from urllib.parse import quote, urlencode
from fastapi import Depends, Form, HTTPException, Request
from fastapi.responses import RedirectResponse
@@ -113,6 +114,7 @@ class NeuheitenPlugin(BasePlugin):
abwaerts = richtung == "ab" if sort in SORTIERBAR else False
spalte_sortiert = spalte.desc() if abwaerts else spalte.asc()
pro_seite = 50
seite_aktuell = max(seite_nr, 1)
with self.context.session_factory() as db:
abfrage = select(Neuheit)
@@ -148,9 +150,11 @@ class NeuheitenPlugin(BasePlugin):
) if j is not None
]
gesamt = db.scalar(select(func.count()).select_from(abfrage.subquery())) or 0
gesamt_seiten = max(1, ceil(gesamt / pro_seite))
seite_aktuell = min(seite_aktuell, gesamt_seiten)
eintraege = db.scalars(
abfrage.order_by(spalte_sortiert, Neuheit.id.asc())
.offset((max(seite_nr, 1) - 1) * pro_seite)
.offset((seite_aktuell - 1) * pro_seite)
.limit(pro_seite)
).all()
status_optionen = [
@@ -160,7 +164,18 @@ class NeuheitenPlugin(BasePlugin):
]
quellen_zeilen = self._quellen_uebersicht(db)
hat_weitere = max(seite_nr, 1) * pro_seite < gesamt
def _seiten_url(nr: int) -> str:
parameter = {
"q": q,
"status": status,
"sort": sort if sort in SORTIERBAR else STANDARD_SORTIERUNG[0],
"richtung": "ab" if abwaerts else "auf",
"seite_nr": str(nr),
}
if jahr is not None:
parameter["jahr"] = str(jahr)
return f"/neuheiten?{urlencode(parameter)}"
kontext = {
"user": user,
"titel": self.title,
@@ -182,8 +197,9 @@ class NeuheitenPlugin(BasePlugin):
"suchbegriffe": ", ".join(self._suchbegriffe) or "",
"sync_aktiv": self._scheduler is not None and self._scheduler.running,
"anzahl": gesamt,
"hat_weitere": hat_weitere,
"naechste_seite": max(seite_nr, 1) + 1,
"seite": seite_aktuell,
"gesamt_seiten": gesamt_seiten,
"seiten_url": _seiten_url,
"quellen_zeilen": quellen_zeilen,
"quellen_sync_aktiv": (
os.environ.get("SPIELE_NEUHEITEN_QUELLEN_SYNC_AKTIV", "1").strip()

View File

@@ -122,11 +122,6 @@
</tbody>
</table>
</div>
{% from "seitennavigation.html" import seitennavigation %}
{{ seitennavigation(seiten_url, seite, gesamt_seiten) }}
</div>
{% if hat_weitere %}
<div id="lade-mehr"
hx-get="/neuheiten?seite_nr={{ naechste_seite }}&q={{ q }}&status={{ status_filter }}&sort={{ sort }}&richtung={{ richtung }}"
hx-trigger="revealed delay:150ms" hx-swap="outerHTML">
<div class="py-6 text-center text-sm text-slate-400">Lade weitere Eintr&auml;ge&nbsp;&hellip;</div>
</div>
{% endif %}