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>
84 lines
3.2 KiB
HTML
84 lines
3.2 KiB
HTML
{% 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 %}
|