Merge pull request 'Berichte: Tabellen so breit wie nötig, Zahlen rechtsbündig' (#37) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/37
This commit is contained in:
commit
e3d5e58900
5 changed files with 123 additions and 5 deletions
28
Changes.md
28
Changes.md
|
|
@ -1,5 +1,33 @@
|
|||
# Ä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 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
|
||||
`.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. 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
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1737,6 +1744,20 @@ 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 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 –
|
||||
sonst quetschte max-width die zwölf Monatsspalten in Umbrüche. */
|
||||
.breittabelle table.liste.bericht { max-width: none; }
|
||||
|
||||
/* Abschnittsüberschrift „Einnahmen“ / „Ausgaben“. */
|
||||
table.bericht tr.abschnittszeile th {
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -145,6 +145,38 @@ 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 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
|
||||
// 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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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('/')
|
||||
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue