fix: tolerante GUI-Filter, sichtbare Bedienelemente, fixierte Navigation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 17:09:35 +02:00
parent e7855a7bda
commit 06392c0fec
5 changed files with 92 additions and 6 deletions

View File

@@ -62,6 +62,28 @@ def _latest_projection(session: Session) -> ProjectionResult | None:
).scalars().first()
def _opt_int(value: str | None) -> int | None:
"""Wandelt einen GUI-Query-Parameter tolerant in int|None um. HTML-Formulare
senden bei nicht ausgefüllten Feldern leere Strings statt gar keinen
Parameter - das darf nie zu einem 422 fuehren, sondern degradiert zu None."""
if value is None or not value.strip():
return None
try:
return int(value)
except ValueError:
return None
def _opt_date(value: str | None) -> date | None:
"""Wie _opt_int, nur fuer date-Felder (ISO-Format aus <input type=date>)."""
if value is None or not value.strip():
return None
try:
return date.fromisoformat(value)
except ValueError:
return None
@router.get("/", dependencies=[Depends(gui_session)])
def index(request: Request, session: Session = Depends(get_session)):
accounts = session.execute(select(Account)).scalars().all()
@@ -77,15 +99,23 @@ def index(request: Request, session: Session = Depends(get_session)):
@router.get("/buchungen", dependencies=[Depends(gui_session)])
def buchungen(
request: Request,
account_id: int | None = None,
date_from: date | None = None,
date_to: date | None = None,
category_id: int | None = None,
account_id: str | None = None,
date_from: str | None = None,
date_to: str | None = None,
category_id: str | None = None,
q: str | None = None,
status: str = "confirmed",
page: int = 1,
session: Session = Depends(get_session),
):
# HTML-Filterformular sendet leere Strings fuer nicht ausgefuellte Felder -
# daher hier bewusst str|None statt int|None/date|None und tolerante
# Konvertierung, statt FastAPI vor dem Handler mit 422 abbrechen zu lassen.
# Die API-Routen (/api/transactions) bleiben strikt typisiert.
account_id = _opt_int(account_id)
date_from = _opt_date(date_from)
date_to = _opt_date(date_to)
category_id = _opt_int(category_id)
page = max(1, page)
total = count_transactions(
account_id=account_id,

View File

@@ -16,6 +16,10 @@ nav {
border-bottom: 1px solid #ccc;
padding-bottom: 0.5rem;
margin-bottom: 1rem;
position: sticky;
top: 0;
z-index: 10;
background: #fff;
}
nav a {
@@ -102,6 +106,11 @@ button {
cursor: pointer;
}
button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.badge {
display: inline-block;
padding: 0.15rem 0.6rem;

View File

@@ -39,19 +39,24 @@
hx-on::after-request="if(event.detail.successful){htmx.trigger(document.getElementById('imports'), 'refresh')}">
<button type="submit">Verwerfen</button>
</form>
<button type="button" class="inline-form" disabled title="Nur für bestätigte Importe">Import zurückrollen</button>
{% elif s.status == "confirmed" %}
<span class="muted">Bestätigt — Buchungen übernommen</span>
<button type="button" class="inline-form" disabled title="Nur für Entwürfe">Übernehmen</button>
<button type="button" class="inline-form" disabled title="Nur für Entwürfe">Verwerfen</button>
<form class="inline-form" hx-post="/api/imports/{{ s.id }}/rollback" hx-swap="none"
hx-confirm="Diesen Import und ALLE zugehörigen Buchungen unwiderruflich löschen? Salden und Projektionen ändern sich."
hx-on::after-request="if(event.detail.successful){htmx.trigger(document.getElementById('imports'), 'refresh')}">
<button type="submit">Import zurückrollen</button>
</form>
{% else %}
<button type="button" class="inline-form" disabled title="Nur für Entwürfe">Übernehmen</button>
<form class="inline-form" hx-delete="/api/imports/{{ s.id }}" hx-swap="none"
hx-confirm="Import „{{ s.filename }}“ wirklich verwerfen?"
hx-on::after-request="if(event.detail.successful){htmx.trigger(document.getElementById('imports'), 'refresh')}">
<button type="submit">Verwerfen</button>
</form>
<button type="button" class="inline-form" disabled title="Nur für bestätigte Importe">Import zurückrollen</button>
{% endif %}
</td>
</tr>

View File

@@ -53,9 +53,9 @@
</form>
</fieldset>
{% if suggestions %}
<fieldset>
<legend>Vorschläge aus Buchungen</legend>
{% if suggestions %}
<table>
<thead>
<tr><th>Name</th><th>Betrag</th><th>Rhythmus</th><th>Fälligkeitstag</th><th></th></tr>
@@ -82,8 +82,10 @@
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">Keine Vorschläge — erkannt werden Serien aus mindestens 3 Monaten gleichartiger Buchungen.</p>
{% endif %}
</fieldset>
{% endif %}
</section>
<section class="planning-section">