Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
109 lines
4.3 KiB
HTML
109 lines
4.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?v={{ app_version }}">
|
|
<script src="/static/htmx.min.js?v={{ app_version }}"></script>
|
|
<script src="/static/sort.js?v={{ app_version }}"></script>
|
|
<script src="/static/doppelzaehlung.js?v={{ app_version }}"></script>
|
|
<script src="/static/szenarien.js?v={{ app_version }}"></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="/aufteilung">Aufteilung</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>
|
|
<dialog id="doppelzaehlung-dialog">
|
|
<h2>Mögliche Doppelzählung</h2>
|
|
<p class="dz-meldung"></p>
|
|
<ul class="dz-konflikte"></ul>
|
|
<p>Trotzdem speichern?</p>
|
|
<button type="button" id="dz-abbrechen" autofocus>Abbrechen</button>
|
|
<button type="button" id="dz-bestaetigen">Trotzdem speichern</button>
|
|
</dialog>
|
|
<script>
|
|
document.body.addEventListener('htmx:responseError', function (e) {
|
|
// Mögliche Doppelzählung Kredit/Fixposten: eigenes Warnfenster statt alert.
|
|
if (window.FBDoppel && window.FBDoppel.handle(e)) { return; }
|
|
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;
|
|
});
|
|
}
|
|
// Bestätigte Doppelzählung (Warnfenster „Trotzdem speichern"):
|
|
if (elt.getAttribute && elt.getAttribute('data-doppelzaehlung-bestaetigt') === '1') {
|
|
out.doppelzaehlung_bestaetigt = true;
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (window.FBDoppel) { window.FBDoppel.init(document); }
|
|
if (window.FBSzenario) { window.FBSzenario.init(document); }
|
|
</script>
|
|
<footer class="version">Finanzberatungs-Tool v{{ app_version }}</footer>
|
|
</body>
|
|
</html>
|