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>
85 lines
3.2 KiB
HTML
85 lines
3.2 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Übersicht – Finanzberatung{% endblock %}
|
||
{% block content %}
|
||
<h1>Übersicht</h1>
|
||
|
||
{% if projection and projection.below_threshold_date %}
|
||
<div class="warning">
|
||
Achtung: Prognostizierter Saldo unterschreitet den Schwellenwert am
|
||
<strong>{{ projection.below_threshold_date.strftime('%d.%m.%Y') }}</strong>.
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="total-balance {{ 'neg' if total < 0 else '' }}">
|
||
Gesamtsaldo: {{ total|eur }} €
|
||
</div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Konto</th><th>Bank</th><th>Saldo</th><th>Anker</th>
|
||
<th data-nosort>Umbenennen</th><th data-nosort>Anker pflegen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for account, balance in balances %}
|
||
<tr>
|
||
<td class="account">{{ account.name }}</td>
|
||
<td>{{ account.bank }}</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 %}
|
||
kein Anker
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<form class="inline-form" hx-ext="json-form" hx-patch="/api/accounts/{{ account.id }}"
|
||
hx-swap="none" hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||
<input type="text" name="name" value="{{ account.name }}" required maxlength="100">
|
||
<button type="submit">Umbenennen</button>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<form class="inline-form" hx-ext="json-form" hx-patch="/api/accounts/{{ account.id }}"
|
||
hx-swap="none" hx-on::after-request="if(event.detail.successful){window.location.reload()}">
|
||
<input type="date" name="anchor_date"
|
||
value="{{ account.anchor_date.isoformat() if account.anchor_date is not none else '' }}" required>
|
||
<input type="text" name="anchor_balance" data-type="amount" placeholder="500,00"
|
||
value="{{ account.anchor_balance|eur if account.anchor_balance is not none else '' }}" required>
|
||
<button type="submit">Anker setzen</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="6">Keine Konten angelegt.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>Nächste anstehende Posten (30 Tage)</h2>
|
||
<table>
|
||
<thead>
|
||
<tr><th>Datum</th><th>Bezeichnung</th><th>Betrag</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for item in upcoming %}
|
||
<tr>
|
||
<td>{{ item.date.strftime('%d.%m.%Y') }}</td>
|
||
<td>{{ item.name }}</td>
|
||
<td class="{{ 'neg' if item.amount < 0 else '' }}">{{ item.amount|eur }} €</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="3">Keine anstehenden Posten.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>Grafana-Dashboard</h2>
|
||
<iframe class="grafana" src="{{ grafana_public_base(request) }}/d/finanzen/finanzen?orgId=1&kiosk"></iframe>
|
||
<p class="hint">Kein Diagramm sichtbar? Einmal in
|
||
<a href="{{ grafana_public_base(request) }}" target="_blank">Grafana anmelden</a>
|
||
(gleiches Passwort wie hier).</p>
|
||
{% endblock %}
|