From 43cd73afb73de0211c0fb2bda09a545351626917 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Tue, 18 Aug 2026 21:00:32 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Berichte:=20Tabellen=20so=20breit=20wie=20n?= =?UTF-8?q?=C3=B6tig,=20Zahlen=20rechtsb=C3=BCndig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Berichtstabellen nehmen nur noch den Platz, den ihre Spalten brauchen, und stehen links; erst bei mehr Inhalt wachsen sie bis zur vollen Breite. Im Scrollrahmen .breittabelle bleibt es beim bisherigen Verhalten, damit die zwölf Monatsspalten weiter waagerecht scrollen statt umzubrechen. Dabei kam heraus, dass die Zahlenspalten gar nicht rechtsbündig standen: .rechts und .betrag sind nur einfach spezifisch, das weiter oben stehende "table.liste th, table.liste td { text-align: left }" gewann. Für Berichte steht die Ausrichtung jetzt ausdrücklich, sodass die letzte Spalte ihre Beträge an der eigenen Kante ausrichtet. Nachgezogen: Der E2E-Test der eingeklappten Seitenleiste erwartete toBeHidden() für die Beschriftungen, die aber mit Absicht nur per clip-path aus dem Bild geschoben werden – für Playwright bleiben sie sichtbar. Geprüft wird jetzt, dass sie keinen Platz mehr einnehmen und der Verweis seinen zugänglichen Namen behält. Co-Authored-By: Claude Opus 5 --- Changes.md | 27 +++++++++++++++++++++++++++ public/static/app.css | 19 +++++++++++++++++++ tests/e2e/specs/auswertungen.spec.js | 26 ++++++++++++++++++++++++++ tests/e2e/specs/seitenleiste.spec.js | 28 +++++++++++++++++++++++----- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/Changes.md b/Changes.md index 79a0849..75bfd15 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,32 @@ # Änderungen +## Berichte: Tabellen so breit wie nötig, Zahlen rechtsbündig (18.08.2026) + +Ein Bericht mit zwei oder vier Spalten wurde bisher über die gesamte Fensterbreite gezogen – +zwischen Bezeichnung und Betrag klaffte ein leeres Feld, und das Auge musste die Zeile +zweimal überqueren. Die **Berichtstabellen nehmen jetzt nur den Platz, den ihre Spalten +brauchen** (`width: auto`) und stehen links; erst wenn der Inhalt mehr verlangt, wachsen sie +bis zur vollen Breite. Im Scrollrahmen `.breittabelle` (zwölf Monatsspalten) bleibt es beim +bisherigen Verhalten: Die Tabelle darf hinausragen und waagerecht scrollen, statt umzubrechen. + +Dabei fiel auf, dass die Zahlenspalten **gar nicht rechtsbündig** standen: `.rechts` und +`.betrag` sind nur einfach spezifisch, `table.liste th, table.liste td { text-align: left }` +weiter oben im Stilblatt ist spezifischer und gewann. Solange die Spalten über den Bildschirm +gezogen waren, fiel es kaum auf – in der schmalen Tabelle sofort. Für Berichte steht die +Ausrichtung jetzt ausdrücklich (`table.liste.bericht td.rechts`), sodass die letzte Spalte – +die Betragsspalte – ihre Zahlen rechtsbündig an ihrer eigenen Kante ausrichtet. Die übrigen +Listen (Kontenübersicht, Wertpapiere) sind unverändert geblieben. + +Der neue E2E-Test misst beides: dass der Summenbericht deutlich schmaler bleibt als sein +Rahmen, dass die letzte Spalte `text-align: right` hat und dass die Monatsansicht weiterhin +breiter wird. + +Nachgezogen: Der E2E-Test der eingeklappten Seitenleiste erwartete `toBeHidden()` für die +Beschriftungen. Die sind aber mit Absicht nur aus dem Bild geschoben (`clip-path`, 1×1 Pixel) +statt entfernt, damit Vorleseprogramme die Namen behalten – für Playwright bleibt so ein +Element „sichtbar“. Geprüft wird jetzt, dass die Beschriftung keinen Platz mehr einnimmt und +der Verweis trotzdem seinen zugänglichen Namen behält. + ## Seitenleiste: Marke mit Münzstapel, ein Klick schiebt sie auf Symbolbreite (18.08.2026) Oben in der Seitenleiste steht jetzt eine richtige **Marke**: das Bild `Coinstack.png` aus den diff --git a/public/static/app.css b/public/static/app.css index 63ad251..5a6c30e 100644 --- a/public/static/app.css +++ b/public/static/app.css @@ -1737,6 +1737,25 @@ body.schlicht { table.bericht { font-size: .9rem; } table.bericht td { padding: .28rem .7rem; } +/* Ein Bericht mit wenigen Spalten soll nicht über die ganze Breite gezogen + werden: Die Tabelle nimmt nur den Platz, den ihre Spalten brauchen, und + steht links. Die Zahlenspalten bleiben in sich rechtsbündig (.rechts), die + letzte schließt damit bündig mit dem Tabellenrand ab. */ +table.liste.bericht { + width: auto; + max-width: 100%; +} + +/* Im Scrollrahmen darf die Tabelle über die sichtbare Breite hinausgehen – + sonst quetschte max-width die zwölf Monatsspalten in Umbrüche. */ +.breittabelle table.liste.bericht { max-width: none; } + +/* `.rechts` allein reicht hier nicht: `table.liste th, td` setzt weiter oben + text-align auf links und ist spezifischer. Die Zahlenspalten – allen voran + die letzte – stehen deshalb ausdrücklich rechtsbündig. */ +table.liste.bericht th.rechts, +table.liste.bericht td.rechts { text-align: right; } + /* Abschnittsüberschrift „Einnahmen“ / „Ausgaben“. */ table.bericht tr.abschnittszeile th { text-align: left; diff --git a/tests/e2e/specs/auswertungen.spec.js b/tests/e2e/specs/auswertungen.spec.js index 2a8c7a6..4c6577a 100644 --- a/tests/e2e/specs/auswertungen.spec.js +++ b/tests/e2e/specs/auswertungen.spec.js @@ -145,6 +145,32 @@ test.describe('Auswertungen', () => { await expect(page.locator('.meldung-erfolg')).toContainText('gelöscht') }) + test('Schmale Berichte bleiben schmal, die Zahlenspalte bleibt rechtsbündig', async ({ page }) => { + // Zwei Spalten brauchen keine Bildschirmbreite: Die Tabelle steht links + // und ist deutlich schmaler als der Platz, den sie hätte. + await vorlage(page, 'summenbericht') + + const tabelle = page.locator('table.bericht') + const rahmen = page.locator('.breittabelle') + + const tabelleBreit = (await tabelle.boundingBox()).width + const rahmenBreit = (await rahmen.boundingBox()).width + expect(tabelleBreit).toBeLessThan(rahmenBreit * 0.9) + + // Letzte Spalte: Zahlen rechtsbündig in ihrer eigenen Spalte. + // Die erste Zeile eines Abschnitts ist seine Überschrift (th) – deshalb + // die erste echte Wertzeile. + const letzte = tabelle.locator('tbody tr > td:last-child').first() + await expect(letzte).toHaveCSS('text-align', 'right') + + // Zwölf Monatsspalten dürfen dagegen weiterhin über die Breite gehen + // und im Rahmen scrollen, statt umzubrechen. + await page.selectOption('select[name="aufteilung"]', 'monat') + await page.click('button:has-text("Anzeigen")') + expect((await tabelle.boundingBox()).width) + .toBeGreaterThan(tabelleBreit) + }) + test('CSV-Ausgabe kommt als Datei mit deutschem Zahlenformat', async ({ page }) => { await vorlage(page, 'summenbericht') diff --git a/tests/e2e/specs/seitenleiste.spec.js b/tests/e2e/specs/seitenleiste.spec.js index 89bfe34..72b8a66 100644 --- a/tests/e2e/specs/seitenleiste.spec.js +++ b/tests/e2e/specs/seitenleiste.spec.js @@ -14,6 +14,19 @@ async function anmelden(page) { await expect(page).toHaveURL(/\/$/) } +/** + * Eingeklappt werden die Beschriftungen nur aus dem Bild geschoben (`clip-path` + * auf 1×1 Pixel), nicht mit `display:none` entfernt – sonst verlören die + * Verweise ihren Namen für Vorleseprogramme. Für Playwright bleibt ein solches + * Element „sichtbar“; geprüft wird deshalb, dass es keinen Platz mehr einnimmt. + */ +async function istWeggeschoben(beschriftung) { + await expect.poll(async () => { + const kasten = await beschriftung.boundingBox() + return kasten === null ? 0 : Math.max(kasten.width, kasten.height) + }, { message: 'Beschriftung nimmt keinen Platz mehr ein' }).toBeLessThanOrEqual(1) +} + test.describe('Seitenleiste ein- und ausklappen', () => { test('Klick auf die Marke schiebt die Leiste auf Symbolbreite und zurück', async ({ page }) => { await anmelden(page) @@ -26,6 +39,7 @@ test.describe('Seitenleiste ein- und ausklappen', () => { await expect(symbol).toBeVisible() await expect(beschriftung).toBeVisible() const breitOffen = (await page.locator('.seitenleiste').boundingBox()).width + const hauptbereichOffen = (await page.locator('.hauptbereich').boundingBox()).width await marke.click() @@ -34,21 +48,25 @@ test.describe('Seitenleiste ein- und ausklappen', () => { // Das Markensymbol und die Symbole der Menüpunkte bleiben stehen, // nur die Beschriftungen verschwinden. await expect(symbol).toBeVisible() - await expect(beschriftung).toBeHidden() + await istWeggeschoben(beschriftung) await expect(page.locator('.seitenleiste .icon').first()).toBeVisible() + // Der Verweis behält dabei seinen Namen – nur das Auge verliert ihn. + await expect(page.locator('.seitenleiste a[href="/konten"]')) + .toHaveAccessibleName(/Kontenübersicht/) + const breitEingeklappt = (await page.locator('.seitenleiste').boundingBox()).width expect(breitEingeklappt).toBeLessThan(breitOffen) // Der Hauptbereich bekommt den gewonnenen Platz. - const hauptOffen = breitOffen - expect((await page.locator('.hauptbereich').boundingBox()).width).toBeGreaterThan(0) + expect((await page.locator('.hauptbereich').boundingBox()).width) + .toBeGreaterThan(hauptbereichOffen) await marke.click() await expect(rahmen).not.toHaveClass(/eingeklappt/) await expect(marke).toHaveAttribute('aria-expanded', 'true') await expect(beschriftung).toBeVisible() - expect((await page.locator('.seitenleiste').boundingBox()).width).toBe(hauptOffen) + expect((await page.locator('.seitenleiste').boundingBox()).width).toBe(breitOffen) }) test('Die Symbole führen eingeklappt zu denselben Seiten', async ({ page }) => { @@ -64,7 +82,7 @@ test.describe('Seitenleiste ein- und ausklappen', () => { // Der Zustand hält über den Seitenwechsel hinweg – ohne Aufblitzen, // weil der Server ihn schon aus dem Cookie kennt. await expect(page.locator('.rahmen')).toHaveClass(/eingeklappt/) - await expect(page.locator('.seitenleiste .beschriftung').first()).toBeHidden() + await istWeggeschoben(page.locator('.seitenleiste .beschriftung').first()) await page.locator('[data-seitenleiste-schalter]').click() await expect(page.locator('.rahmen')).not.toHaveClass(/eingeklappt/) From 6ca247a40d08a22b83c098251e7116222038712a Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Tue, 18 Aug 2026 21:04:19 +0200 Subject: [PATCH 2/3] Berichte: Tabelle mittig statt links ausgerichtet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linksbündig wirkte die schmale Tabelle verloren neben dem zentrierten Berichtskopf. Sie steht jetzt mittig unter Überschrift und Zeitraumzeile; die Zahlenspalten bleiben in sich rechtsbündig. Der E2E-Test prüft, dass links und rechts gleich viel Luft bleibt. Co-Authored-By: Claude Opus 5 --- Changes.md | 10 +++++----- public/static/app.css | 5 +++-- tests/e2e/specs/auswertungen.spec.js | 12 +++++++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Changes.md b/Changes.md index 75bfd15..d8a8c7d 100644 --- a/Changes.md +++ b/Changes.md @@ -5,8 +5,8 @@ Ein Bericht mit zwei oder vier Spalten wurde bisher über die gesamte Fensterbreite gezogen – zwischen Bezeichnung und Betrag klaffte ein leeres Feld, und das Auge musste die Zeile zweimal überqueren. Die **Berichtstabellen nehmen jetzt nur den Platz, den ihre Spalten -brauchen** (`width: auto`) und stehen links; erst wenn der Inhalt mehr verlangt, wachsen sie -bis zur vollen Breite. Im Scrollrahmen `.breittabelle` (zwölf Monatsspalten) bleibt es beim +brauchen** (`width: auto`) und stehen dabei mittig unter der Überschrift; erst wenn der +Inhalt mehr verlangt, wachsen sie bis zur vollen Breite. Im Scrollrahmen `.breittabelle` (zwölf Monatsspalten) bleibt es beim bisherigen Verhalten: Die Tabelle darf hinausragen und waagerecht scrollen, statt umzubrechen. Dabei fiel auf, dass die Zahlenspalten **gar nicht rechtsbündig** standen: `.rechts` und @@ -17,9 +17,9 @@ Ausrichtung jetzt ausdrücklich (`table.liste.bericht td.rechts`), sodass die le die Betragsspalte – ihre Zahlen rechtsbündig an ihrer eigenen Kante ausrichtet. Die übrigen Listen (Kontenübersicht, Wertpapiere) sind unverändert geblieben. -Der neue E2E-Test misst beides: dass der Summenbericht deutlich schmaler bleibt als sein -Rahmen, dass die letzte Spalte `text-align: right` hat und dass die Monatsansicht weiterhin -breiter wird. +Der neue E2E-Test misst alles drei: dass der Summenbericht deutlich schmaler bleibt als sein +Rahmen und links wie rechts gleich viel Luft behält, dass die letzte Spalte +`text-align: right` hat und dass die Monatsansicht weiterhin breiter wird. Nachgezogen: Der E2E-Test der eingeklappten Seitenleiste erwartete `toBeHidden()` für die Beschriftungen. Die sind aber mit Absicht nur aus dem Bild geschoben (`clip-path`, 1×1 Pixel) diff --git a/public/static/app.css b/public/static/app.css index 5a6c30e..f0d0644 100644 --- a/public/static/app.css +++ b/public/static/app.css @@ -1739,11 +1739,12 @@ table.bericht td { padding: .28rem .7rem; } /* Ein Bericht mit wenigen Spalten soll nicht über die ganze Breite gezogen werden: Die Tabelle nimmt nur den Platz, den ihre Spalten brauchen, und - steht links. Die Zahlenspalten bleiben in sich rechtsbündig (.rechts), die - letzte schließt damit bündig mit dem Tabellenrand ab. */ + steht dabei mittig unter der Überschrift. Die Zahlenspalten bleiben in sich + rechtsbündig (.rechts), die letzte schließt bündig mit dem Tabellenrand ab. */ table.liste.bericht { width: auto; max-width: 100%; + margin-inline: auto; } /* Im Scrollrahmen darf die Tabelle über die sichtbare Breite hinausgehen – diff --git a/tests/e2e/specs/auswertungen.spec.js b/tests/e2e/specs/auswertungen.spec.js index 4c6577a..769993f 100644 --- a/tests/e2e/specs/auswertungen.spec.js +++ b/tests/e2e/specs/auswertungen.spec.js @@ -153,9 +153,15 @@ test.describe('Auswertungen', () => { const tabelle = page.locator('table.bericht') const rahmen = page.locator('.breittabelle') - const tabelleBreit = (await tabelle.boundingBox()).width - const rahmenBreit = (await rahmen.boundingBox()).width - expect(tabelleBreit).toBeLessThan(rahmenBreit * 0.9) + const tabelleKasten = await tabelle.boundingBox() + const rahmenKasten = await rahmen.boundingBox() + const tabelleBreit = tabelleKasten.width + expect(tabelleBreit).toBeLessThan(rahmenKasten.width * 0.9) + + // … und steht dabei mittig: links und rechts bleibt gleich viel Luft. + const links = tabelleKasten.x - rahmenKasten.x + const rechts = (rahmenKasten.x + rahmenKasten.width) - (tabelleKasten.x + tabelleBreit) + expect(Math.abs(links - rechts)).toBeLessThanOrEqual(1) // Letzte Spalte: Zahlen rechtsbündig in ihrer eigenen Spalte. // Die erste Zeile eines Abschnitts ist seine Überschrift (th) – deshalb From a5e50f8529039dda8a832f371858efbee5f2eab2 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Tue, 18 Aug 2026 21:13:13 +0200 Subject: [PATCH 3/3] =?UTF-8?q?Listen:=20Zahlenspalten=20=C3=BCberall=20re?= =?UTF-8?q?chtsb=C3=BCndig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Klasse .rechts wurde in Listen von der spezifischeren Grundregel "table.liste th, table.liste td { text-align: left }" überstimmt – die Ausrichtung war also überall wirkungslos, nicht nur in den Berichten. Die Regel steht jetzt bei den Tabellen-Grundregeln und gilt für alle Listen: Kontenübersicht, Kontoblatt, Wertpapiere, Depotbuchungen, Kategorien und die Berichte. Die berichtseigene Sonderregel entfällt dadurch. Budgetplaner und Splittabellen bringen ihre eigene Ausrichtung mit und bleiben unberührt. Co-Authored-By: Claude Opus 5 --- Changes.md | 9 +++++---- public/static/app.css | 13 +++++++------ tests/e2e/specs/module.spec.js | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Changes.md b/Changes.md index d8a8c7d..67e0414 100644 --- a/Changes.md +++ b/Changes.md @@ -12,10 +12,11 @@ bisherigen Verhalten: Die Tabelle darf hinausragen und waagerecht scrollen, stat Dabei fiel auf, dass die Zahlenspalten **gar nicht rechtsbündig** standen: `.rechts` und `.betrag` sind nur einfach spezifisch, `table.liste th, table.liste td { text-align: left }` weiter oben im Stilblatt ist spezifischer und gewann. Solange die Spalten über den Bildschirm -gezogen waren, fiel es kaum auf – in der schmalen Tabelle sofort. Für Berichte steht die -Ausrichtung jetzt ausdrücklich (`table.liste.bericht td.rechts`), sodass die letzte Spalte – -die Betragsspalte – ihre Zahlen rechtsbündig an ihrer eigenen Kante ausrichtet. Die übrigen -Listen (Kontenübersicht, Wertpapiere) sind unverändert geblieben. +gezogen waren, fiel es kaum auf – in der schmalen Tabelle sofort. Die Ausrichtung steht jetzt +für **alle Listen** ausdrücklich (`table.liste th.rechts, table.liste td.rechts`): Beträge, +Salden, Stückzahlen und Aktionsspalten stehen überall rechts, wo `.rechts` gesetzt ist – +Kontenübersicht, Kontoblatt, Wertpapiere, Depotbuchungen, Kategorien und die Berichte +gleichermaßen. Die Klasse tut damit endlich das, was ihr Name sagt. Der neue E2E-Test misst alles drei: dass der Summenbericht deutlich schmaler bleibt als sein Rahmen und links wie rechts gleich viel Luft behält, dass die letzte Spalte diff --git a/public/static/app.css b/public/static/app.css index f0d0644..f091782 100644 --- a/public/static/app.css +++ b/public/static/app.css @@ -762,6 +762,13 @@ table.liste tbody tr:hover { background: var(--flaeche-gedaempft); } .rechts { text-align: right; } +/* In Listen reicht `.rechts` allein nicht: `table.liste th, table.liste td` + weiter oben setzt text-align auf links und ist spezifischer. Zahlen- und + Aktionsspalten stehen deshalb ausdrücklich rechts – die Klasse tut damit + überall das, was ihr Name sagt. */ +table.liste th.rechts, +table.liste td.rechts { text-align: right; } + .betrag { font-family: var(--schrift-zahl); font-variant-numeric: tabular-nums; @@ -1751,12 +1758,6 @@ table.liste.bericht { sonst quetschte max-width die zwölf Monatsspalten in Umbrüche. */ .breittabelle table.liste.bericht { max-width: none; } -/* `.rechts` allein reicht hier nicht: `table.liste th, td` setzt weiter oben - text-align auf links und ist spezifischer. Die Zahlenspalten – allen voran - die letzte – stehen deshalb ausdrücklich rechtsbündig. */ -table.liste.bericht th.rechts, -table.liste.bericht td.rechts { text-align: right; } - /* Abschnittsüberschrift „Einnahmen“ / „Ausgaben“. */ table.bericht tr.abschnittszeile th { text-align: left; diff --git a/tests/e2e/specs/module.spec.js b/tests/e2e/specs/module.spec.js index 1356400..bc4f1b3 100644 --- a/tests/e2e/specs/module.spec.js +++ b/tests/e2e/specs/module.spec.js @@ -54,6 +54,25 @@ test.describe('Module der Phasen 4 bis 7', () => { } }) + test('Zahlenspalten stehen in allen Listen rechtsbündig', async ({ page }) => { + // `.rechts` markiert Zahlen- und Aktionsspalten. Die Klasse muss die + // linksbündige Grundregel der Listen überall schlagen – nicht nur in den + // Berichten, wo es zuerst auffiel. + for (const pfad of ['/konten', '/wertpapiere', '/wertpapiere/buchungen']) { + await page.goto(pfad) + + for (const art of ['th', 'td']) { + const zellen = page.locator(`table.liste ${art}.rechts`) + const anzahl = await zellen.count() + expect(anzahl, `${pfad}: ${art}.rechts vorhanden`).toBeGreaterThan(0) + + for (const stelle of [0, anzahl - 1]) { + await expect(zellen.nth(stelle)).toHaveCSS('text-align', 'right') + } + } + } + }) + test('Das Cockpit zeichnet seine Kacheln', async ({ page }) => { await page.goto('/')