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>
229 lines
3.3 KiB
CSS
229 lines
3.3 KiB
CSS
* { box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: system-ui, sans-serif;
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
padding: 1rem;
|
|
color: #222;
|
|
background: #fff;
|
|
}
|
|
|
|
nav {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
border-bottom: 1px solid #ccc;
|
|
padding-bottom: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
background: #fff;
|
|
}
|
|
|
|
nav a {
|
|
text-decoration: none;
|
|
color: #06c;
|
|
}
|
|
|
|
nav form {
|
|
margin-left: auto;
|
|
}
|
|
|
|
table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
th, td {
|
|
border: 1px solid #ddd;
|
|
padding: 0.4rem 0.6rem;
|
|
text-align: left;
|
|
}
|
|
|
|
th {
|
|
background: #f2f2f2;
|
|
}
|
|
|
|
tr:nth-child(even) {
|
|
background: #fafafa;
|
|
}
|
|
|
|
.neg {
|
|
color: #b00;
|
|
}
|
|
|
|
.amount {
|
|
text-align: right;
|
|
}
|
|
|
|
td.account {
|
|
word-break: break-all;
|
|
max-width: 12rem;
|
|
}
|
|
|
|
.warning {
|
|
background: #fff3cd;
|
|
border: 1px solid #e0c26b;
|
|
padding: 0.75rem 1rem;
|
|
margin-bottom: 1rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.success {
|
|
background: #d4edda;
|
|
border: 1px solid #7bc088;
|
|
padding: 0.75rem 1rem;
|
|
margin-bottom: 1rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.total-balance {
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
fieldset {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
form.filter-form {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
align-items: end;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
form.filter-form label {
|
|
display: flex;
|
|
flex-direction: column;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
iframe.grafana {
|
|
width: 100%;
|
|
height: 480px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
|
|
button {
|
|
cursor: pointer;
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.45;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.15rem 0.6rem;
|
|
border-radius: 999px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.badge-warning {
|
|
background: #fff3cd;
|
|
color: #7a5c00;
|
|
}
|
|
|
|
.badge-error {
|
|
background: #fdecea;
|
|
color: #b00020;
|
|
}
|
|
|
|
.badge-ok {
|
|
background: #e6f4ea;
|
|
color: #1e7e34;
|
|
}
|
|
|
|
#dropzone {
|
|
border: 2px dashed #999;
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
margin-bottom: 0.5rem;
|
|
color: #555;
|
|
}
|
|
|
|
#dropzone.dragover {
|
|
background: #f0f6ff;
|
|
border-color: #06c;
|
|
}
|
|
|
|
.inline-form {
|
|
display: inline-block;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.muted {
|
|
color: #888;
|
|
}
|
|
|
|
section.planning-section {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
section.planning-section fieldset {
|
|
margin-top: 0.75rem;
|
|
}
|
|
|
|
.version {
|
|
margin-top: 2rem;
|
|
color: #888;
|
|
text-align: center;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* Ausbaustufe 7: Formular-Paare als Einheit, gesperrte Felder erkennbar. */
|
|
.entry-form label,
|
|
.planning-section form label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
margin: 0 1.25rem 0.6rem 0;
|
|
white-space: nowrap;
|
|
}
|
|
.entry-form select,
|
|
.planning-section form select {
|
|
max-width: 14rem;
|
|
}
|
|
.entry-form .entry-art {
|
|
display: block;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
.entry-form .entry-art > label { font-weight: bold; }
|
|
.entry-form .entry-art .muted { margin-left: 0.5rem; }
|
|
input:disabled, select:disabled {
|
|
background: #e3e3e3;
|
|
color: #8a8a8a;
|
|
cursor: not-allowed;
|
|
}
|
|
label:has(input:disabled), label:has(select:disabled) {
|
|
opacity: 0.55;
|
|
}
|
|
.project-block {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
/* Sortier-Buttons in Spaltenköpfen (Ausbaustufe 11, static/sort.js). */
|
|
.sort-btn {
|
|
border: none;
|
|
background: none;
|
|
padding: 0 0.2rem;
|
|
margin: 0;
|
|
font-size: 0.8em;
|
|
color: #888;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
}
|
|
.sort-btn.active {
|
|
color: inherit;
|
|
font-weight: bold;
|
|
}
|