Berichte: Tabellen so breit wie nötig, Zahlen rechtsbündig

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 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-08-18 21:00:32 +02:00
parent 280664b8a3
commit 43cd73afb7
4 changed files with 95 additions and 5 deletions

View file

@ -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

View file

@ -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;

View file

@ -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')

View file

@ -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/)