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:
@@ -285,6 +285,7 @@ class AuditLogPlugin(BasePlugin):
|
||||
"ende": offset + len(eintraege),
|
||||
"url_zurueck": _seiten_url(seite - 1),
|
||||
"url_weiter": _seiten_url(seite + 1),
|
||||
"seiten_url": _seiten_url,
|
||||
"url_ohne_seite": f"/audit-log?{basis}" if basis else "/audit-log",
|
||||
"benutzer_auswahl": benutzer_auswahl,
|
||||
"aktionen_auswahl": aktionen_auswahl,
|
||||
|
||||
@@ -117,16 +117,7 @@
|
||||
0 Einträge
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if gesamt_seiten > 1 %}
|
||||
<div class="flex items-center gap-2">
|
||||
{% if seite > 1 %}
|
||||
<a href="{{ url_zurueck }}" class="border border-slate-300 hover:bg-slate-50 rounded-lg px-3 py-1">← Zurück</a>
|
||||
{% endif %}
|
||||
<span>Seite {{ seite }} von {{ gesamt_seiten }}</span>
|
||||
{% if seite < gesamt_seiten %}
|
||||
<a href="{{ url_weiter }}" class="border border-slate-300 hover:bg-slate-50 rounded-lg px-3 py-1">Weiter →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% from "seitennavigation.html" import seitennavigation %}
|
||||
{{ seitennavigation(seiten_url, seite, gesamt_seiten) }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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äge …</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -24,7 +24,8 @@ der Eintrage-Vorgang bricht.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from urllib.parse import quote
|
||||
from math import ceil
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
from fastapi import Depends, Form, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
@@ -299,13 +300,22 @@ class PlanungPlugin(BasePlugin):
|
||||
gesamt = db.scalar(
|
||||
select(func.count()).select_from(abfrage.subquery())
|
||||
) or 0
|
||||
gesamt_seiten = max(1, ceil(gesamt / pro_seite))
|
||||
seite_aktuell = min(max(seite_nr, 1), gesamt_seiten)
|
||||
eintraege = db.scalars(
|
||||
abfrage.order_by(
|
||||
Planungseintrag.aktualisiert_am.desc(), Planungseintrag.id.desc()
|
||||
)
|
||||
.offset((max(seite_nr, 1) - 1) * pro_seite)
|
||||
.offset((seite_aktuell - 1) * pro_seite)
|
||||
.limit(pro_seite)
|
||||
).all()
|
||||
|
||||
def _seiten_url(nr: int) -> str:
|
||||
parameter = {"seite_nr": str(nr)}
|
||||
if status in GUELTIGE_STATUS:
|
||||
parameter["status"] = status
|
||||
return f"/planung?{urlencode(parameter)}"
|
||||
|
||||
namen = {
|
||||
benutzer.id: (benutzer.display_name or benutzer.username)
|
||||
for benutzer in db.scalars(select(User)).all()
|
||||
@@ -326,8 +336,9 @@ class PlanungPlugin(BasePlugin):
|
||||
"meldung": meldung[:300],
|
||||
"fehler": fehler[:300],
|
||||
"anzahl": gesamt,
|
||||
"hat_weitere": max(seite_nr, 1) * pro_seite < gesamt,
|
||||
"naechste_seite": max(seite_nr, 1) + 1,
|
||||
"seite": seite_aktuell,
|
||||
"gesamt_seiten": gesamt_seiten,
|
||||
"seiten_url": _seiten_url,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -217,13 +217,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if hat_weitere %}
|
||||
<div id="planung-lade-mehr"
|
||||
hx-get="/planung?seite_nr={{ naechste_seite }}&status={{ status_filter }}"
|
||||
hx-trigger="revealed delay:150ms" hx-swap="outerHTML">
|
||||
<div class="py-6 text-center text-sm text-slate-400">Lade weitere Einträge …</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% from "seitennavigation.html" import seitennavigation %}
|
||||
{{ seitennavigation(seiten_url, seite, gesamt_seiten) }}
|
||||
<!-- Lösch-Bestätigungsdialog (Progressive Enhancement: ohne JavaScript
|
||||
sendet das Formular sofort, mit Alpine öffnet sich dieser Dialog). -->
|
||||
<div x-show="loeschFormular !== null" x-cloak
|
||||
|
||||
Reference in New Issue
Block a user