Files
bin/finance/app/templates/admin.html
T
rdmsandClaude Opus 5.5 4458300f43 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>
2026-09-28 19:22:34 +02:00

84 lines
3.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Admin – Finanzberatung{% endblock %}
{% block content %}
<h1>Admin</h1>
<section class="admin-section">
<h2>Passwort ändern</h2>
<p class="muted">Gilt gemeinsam für GUI- und Grafana-Login. Bestehende Sitzungen bleiben nach der Änderung gültig.</p>
{% if error %}<div class="warning">{{ error }}</div>{% endif %}
{% if success %}<div class="success">{{ success }}</div>{% endif %}
<form method="post" action="/admin/passwort">
<label>Altes Passwort
<input type="password" name="alt" required>
</label>
<label>Neues Passwort (mind. 8 Zeichen)
<input type="password" name="neu" minlength="8" required>
</label>
<label>Neues Passwort wiederholen
<input type="password" name="neu2" minlength="8" required>
</label>
<button type="submit">Passwort ändern</button>
</form>
</section>
<section class="admin-section">
<h2>Regeln neu anwenden</h2>
<p class="muted">Wendet die aktuellen Kategorie-Regeln rückwirkend auf noch nicht kategorisierte, bestätigte Buchungen an.</p>
<button type="button" hx-post="/api/category-rules/apply" hx-swap="none"
hx-on::after-request="handleApplyRulesResult(event)">
Regeln neu anwenden
</button>
<p id="apply-rules-result"></p>
</section>
<section class="admin-section">
<h2>Kategorien</h2>
<p class="muted">Kategorien für Buchungen, Fixposten und Szenario-Modifikatoren.
Löschen ist bewusst nicht vorgesehen (Kategorien hängen an Buchungen und Regeln).</p>
<table>
<thead><tr><th>Name</th><th></th></tr></thead>
<tbody>
{% for c in categories %}
<tr id="cat-row-{{ c.id }}">
<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 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()}">
<label>Name <input type="text" name="name" value="{{ c.name }}" required maxlength="100"></label>
<button type="submit">Speichern</button>
<button type="button" onclick="toggleEdit('cat', {{ c.id }}, false)">Abbrechen</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="2">Noch keine Kategorien.</td></tr>
{% endfor %}
</tbody>
</table>
<fieldset>
<legend>Neue Kategorie anlegen</legend>
<form hx-ext="json-form" hx-post="/api/categories" hx-swap="none"
hx-on::after-request="if(event.detail.successful){window.location.reload()}">
<label>Name <input type="text" name="name" required maxlength="100"></label>
<button type="submit">Anlegen</button>
</form>
</fieldset>
</section>
<script>
function handleApplyRulesResult(event) {
var result = document.getElementById('apply-rules-result');
if (event.detail.successful) {
var data = JSON.parse(event.detail.xhr.responseText);
result.textContent = data.categorized + ' Buchung(en) neu kategorisiert.';
} else {
result.textContent = 'Fehler beim Anwenden der Regeln.';
}
}
</script>
{% endblock %}