feat: Tabellen per Spaltenkopf sortierbar (A11-T4)
Neues static/sort.js sortiert Tabellen clientseitig per Klick auf den Spaltenkopf (⇅/▲/▼): Datum TT.MM.JJJJ, deutsche Zahlen/Beträge, sonst Text (Intl.Collator 'de'). Sortierwert optional per td[data-sort] (Punkt-Dezimal/ISO) überschreibbar, th[data-nosort] bekommt keinen Button, table[data-sort-mode="server"] wird übersprungen (Buchungen sortiert serverseitig, folgt in Task 5). Bearbeiten-/Tilgungsplan- Zeilen (tr[data-sort-follow]) wandern mit ihrer Vorzeile mit. Initialisierung über htmx.onLoad, greift auch bei nachgeladenen Fragmenten. Templates entsprechend annotiert (Planung, Szenarien, Admin, Index, Salden, Import-Liste, Buchungen); Salden-Gesamtzeile dafür in ein tfoot verschoben. test_gui.py::test_gui_zeigt_deutsche_betragsformate angepasst, da das neue machinenlesbare data-sort-Attribut bewusst Punkt-Dezimal führt (Interface-Konvention) - die Prüfung auf deutsches Format im sichtbaren Text bleibt bestehen. JS-Unit-/DOM-Tests unter tests/js (node --test, DOM-Teil optional via jsdom), pytest-Wrapper skip ohne node (Fable-Gate führt sie per Podman aus). Suite: 248 passed, 3 skipped (vorher 242/2). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<tr>
|
||||
<td>{{ s.filename }}</td>
|
||||
<td>{{ s.bank }}</td>
|
||||
<td>
|
||||
<td data-sort="{{ s.period_start.isoformat() if s.period_start else '' }}">
|
||||
{% if s.period_start and s.period_end %}
|
||||
{{ s.period_start.strftime('%d.%m.%Y') }} – {{ s.period_end.strftime('%d.%m.%Y') }}
|
||||
{% endif %}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<td>{{ c.name }}</td>
|
||||
<td><button type="button" onclick="toggleEdit('cat', {{ c.id }}, true)">Bearbeiten</button></td>
|
||||
</tr>
|
||||
<tr id="cat-edit-{{ c.id }}" hidden>
|
||||
<tr id="cat-edit-{{ c.id }}" hidden data-sort-follow>
|
||||
<td colspan="2">
|
||||
<form hx-ext="json-form" hx-patch="/api/categories/{{ c.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<title>{% block title %}Finanzberatung{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/sort.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Konto</th><th>Bank</th><th>Saldo</th><th>Anker</th>
|
||||
<th>Umbenennen</th><th>Anker pflegen</th>
|
||||
<th data-nosort>Umbenennen</th><th data-nosort>Anker pflegen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -26,8 +26,8 @@
|
||||
<tr>
|
||||
<td class="account">{{ account.name }}</td>
|
||||
<td>{{ account.bank }}</td>
|
||||
<td class="{{ 'neg' if balance < 0 else '' }}">{{ balance|eur }} €</td>
|
||||
<td>
|
||||
<td class="{{ 'neg' if balance < 0 else '' }}" data-sort="{{ balance }}">{{ balance|eur }} €</td>
|
||||
<td data-sort="{{ account.anchor_date.isoformat() if account.anchor_date is not none else '' }}">
|
||||
{% if account.anchor_date is not none and account.anchor_balance is not none %}
|
||||
Anker: {{ account.anchor_balance|eur }} € am {{ account.anchor_date.strftime('%d.%m.%Y') }}
|
||||
{% else %}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% for r in recurring %}
|
||||
<tr id="rec-row-{{ r.id }}">
|
||||
<td>{{ r.name }}</td>
|
||||
<td class="{{ 'neg' if r.amount < 0 else '' }}">{{ r.amount|eur }} €</td>
|
||||
<td class="{{ 'neg' if r.amount < 0 else '' }}" data-sort="{{ r.amount }}">{{ r.amount|eur }} €</td>
|
||||
<td>{{ r.rhythm|de_label }}</td>
|
||||
<td>{{ r.due_day }}</td>
|
||||
<td>{{ r.start_date.strftime('%d.%m.%Y') if r.start_date else '–' }}</td>
|
||||
@@ -28,7 +28,7 @@
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="rec-edit-{{ r.id }}" hidden>
|
||||
<tr id="rec-edit-{{ r.id }}" hidden data-sort-follow>
|
||||
<td colspan="8">
|
||||
<form hx-ext="json-form" hx-patch="/api/recurring/{{ r.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
@@ -94,7 +94,7 @@
|
||||
{% for s in suggestions %}
|
||||
<tr>
|
||||
<td>{{ s.name }}</td>
|
||||
<td class="{{ 'neg' if s.amount < 0 else '' }}">
|
||||
<td class="{{ 'neg' if s.amount < 0 else '' }}" data-sort="{{ s.amount }}">
|
||||
{{ s.amount|eur }} €
|
||||
{% if s.hinweis %}<span class="muted">{{ s.hinweis }}</span>{% endif %}
|
||||
</td>
|
||||
@@ -134,7 +134,7 @@
|
||||
{% for p in planned %}
|
||||
<tr id="pln-row-{{ p.id }}">
|
||||
<td>{{ p.name }}</td>
|
||||
<td class="{{ 'neg' if p.amount < 0 else '' }}">{{ p.amount|eur }} €</td>
|
||||
<td class="{{ 'neg' if p.amount < 0 else '' }}" data-sort="{{ p.amount }}">{{ p.amount|eur }} €</td>
|
||||
<td>{{ p.due.strftime('%d.%m.%Y') }}</td>
|
||||
<td>{{ category_names.get(p.category_id, '–') }}</td>
|
||||
<td>
|
||||
@@ -146,7 +146,7 @@
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="pln-edit-{{ p.id }}" hidden>
|
||||
<tr id="pln-edit-{{ p.id }}" hidden data-sort-follow>
|
||||
<td colspan="5">
|
||||
<form hx-ext="json-form" hx-patch="/api/planned/{{ p.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
@@ -198,9 +198,9 @@
|
||||
{% for l in loans %}
|
||||
<tr id="loan-row-{{ l.id }}">
|
||||
<td>{{ l.name }}</td>
|
||||
<td>{{ l.principal|eur }} €</td>
|
||||
<td data-sort="{{ l.principal }}">{{ l.principal|eur }} €</td>
|
||||
<td>{{ l.annual_rate_pct|eur }} %</td>
|
||||
<td>{{ l.term_months }} Monate</td>
|
||||
<td data-sort="{{ l.term_months }}">{{ l.term_months }} Monate</td>
|
||||
<td>{{ l.payout_date.strftime('%d.%m.%Y') }}</td>
|
||||
<td>{{ l.repayment_type|de_label }}</td>
|
||||
<td>
|
||||
@@ -214,7 +214,7 @@
|
||||
</tr>
|
||||
<!-- Hinweis: eine Kredit-Aenderung veraendert den Tilgungsplan; danach
|
||||
muessen betroffene Szenarien neu durchgerechnet werden. -->
|
||||
<tr id="loan-edit-{{ l.id }}" hidden>
|
||||
<tr id="loan-edit-{{ l.id }}" hidden data-sort-follow>
|
||||
<td colspan="7">
|
||||
<form hx-ext="json-form" hx-patch="/api/loans/{{ l.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
@@ -233,7 +233,7 @@
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr id="loan-plan-{{ l.id }}" data-sort-follow>
|
||||
<td colspan="7">
|
||||
<details ontoggle="if(this.open){loadLoanSchedule({{ l.id }})}">
|
||||
<summary>Tilgungsplan</summary>
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
{% else %}
|
||||
<tr><td colspan="2">Keine Konten angelegt.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total-row">
|
||||
<td><strong>Gesamt</strong></td>
|
||||
<td class="amount {{ 'neg' if total < 0 else '' }}"><strong>{{ total|eur }} €</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<h2>Monatsanfangs-Übersicht</h2>
|
||||
@@ -46,7 +48,7 @@
|
||||
<tbody>
|
||||
{% for row in months %}
|
||||
<tr data-month="{{ row['month'].isoformat() }}">
|
||||
<td>{{ row['month'].strftime('%m.%Y') }}</td>
|
||||
<td data-sort="{{ row['month'].isoformat() }}">{{ row['month'].strftime('%m.%Y') }}</td>
|
||||
{% for account, value in row['values'] %}
|
||||
<td class="amount {{ 'neg' if value < 0 else '' }}">{{ value|eur }} €</td>
|
||||
{% endfor %}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="mod-edit-{{ m.id }}" hidden>
|
||||
<tr id="mod-edit-{{ m.id }}" hidden data-sort-follow>
|
||||
<td colspan="5">
|
||||
<form hx-ext="json-form" hx-patch="/api/scenarios/{{ sc.id }}/modifiers/{{ m.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
@@ -117,7 +117,7 @@
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="spi-edit-{{ sp.id }}" hidden>
|
||||
<tr id="spi-edit-{{ sp.id }}" hidden data-sort-follow>
|
||||
<td colspan="5">
|
||||
<form hx-ext="json-form" hx-patch="/api/scenarios/{{ sc.id }}/planned/{{ sp.id }}" hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<button type="submit">Filtern</button>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<table data-sort-mode="server">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th><th>Konto</th><th>Verwendungszweck</th><th>Empfänger/Zahler</th>
|
||||
|
||||
Reference in New Issue
Block a user