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>
89 lines
3.3 KiB
HTML
89 lines
3.3 KiB
HTML
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<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>
|
|
<a href="/">Übersicht</a>
|
|
<a href="/import">Import</a>
|
|
<a href="/buchungen">Buchungen</a>
|
|
<a href="/salden">Salden</a>
|
|
<a href="/planung">Planung</a>
|
|
<a href="/szenarien">Szenarien</a>
|
|
<a href="/admin">Admin</a>
|
|
<a href="/hilfe">Hilfe</a>
|
|
<a href="{{ grafana_public_base(request) }}" target="_blank" rel="noopener">Grafana</a>
|
|
<form method="post" action="/logout">
|
|
<button type="submit">Logout</button>
|
|
</form>
|
|
</nav>
|
|
<main>
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
<script>
|
|
document.body.addEventListener('htmx:responseError', function (e) {
|
|
alert('Fehler: ' + (e.detail.xhr.responseText || e.detail.xhr.status));
|
|
});
|
|
|
|
// Minimale JSON-Kodierung für htmx-Formulare/Selects, die gegen die JSON-APIs
|
|
// (POST/PATCH /api/...) senden. Felder mit data-type="int" werden als Zahl
|
|
// kodiert, leere Werte als null, alles andere bleibt String.
|
|
htmx.defineExtension('json-form', {
|
|
onEvent: function (name, evt) {
|
|
if (name === 'htmx:configRequest') {
|
|
evt.detail.headers['Content-Type'] = 'application/json';
|
|
}
|
|
},
|
|
encodeParameters: function (xhr, parameters, elt) {
|
|
var out = {};
|
|
for (var k in parameters) {
|
|
var v = parameters[k];
|
|
var field = elt.matches && elt.matches('[name="' + k + '"]')
|
|
? elt
|
|
: elt.querySelector('[name="' + k + '"]');
|
|
var kind = field && field.dataset ? field.dataset.type : null;
|
|
if (v === '') {
|
|
out[k] = null;
|
|
} else if (kind === 'int') {
|
|
out[k] = parseInt(v, 10);
|
|
} else if (kind === 'amount') {
|
|
// Deutsche Betragseingabe: "1.234,56" -> "1234.56". Ohne Komma
|
|
// bleibt der Wert unveraendert (Punkt-Eingaben funktionieren weiter).
|
|
var s = String(v).replace(/[\s€]/g, '');
|
|
if (s.indexOf(',') !== -1) {
|
|
s = s.replace(/\./g, '').replace(',', '.');
|
|
}
|
|
out[k] = s;
|
|
} else {
|
|
out[k] = v;
|
|
}
|
|
}
|
|
// Checkboxen mit data-type="bool" werden unabhaengig vom parameters-Objekt
|
|
// ausgelesen, weil unmarkierte Checkboxen beim Serialisieren komplett
|
|
// fehlen wuerden (kein Feld => kein "false" moeglich).
|
|
if (elt.querySelectorAll) {
|
|
elt.querySelectorAll('[data-type="bool"]').forEach(function (field) {
|
|
out[field.name] = field.checked;
|
|
});
|
|
}
|
|
xhr.overrideMimeType('text/json');
|
|
return JSON.stringify(out);
|
|
}
|
|
});
|
|
|
|
function toggleEdit(prefix, id, editing) {
|
|
// UX-Regel: Zeilen werden versteckt (hidden), nie aus dem DOM entfernt.
|
|
document.getElementById(prefix + '-row-' + id).hidden = editing;
|
|
document.getElementById(prefix + '-edit-' + id).hidden = !editing;
|
|
}
|
|
</script>
|
|
<footer class="version">Finanzberatungs-Tool v{{ app_version }}</footer>
|
|
</body>
|
|
</html>
|