Kontoformular zweispaltig
Konto bearbeiten und Neues Konto zeigen ihre vierzehn Felder in zwei Spalten, damit alles ohne vertikales Scrollen auf einen Bildschirm passt. - Neue Formularvariante .formular.zweispaltig: Grid mit zwei Spalten, je Feld ein .feld aus Beschriftung, Eingabe und Hinweis. Überschriften, Knopfleiste und .feld.ganz laufen über beide Spalten, unter 720 px fällt das Formular auf eine Spalte zurück. - Feldreihenfolge zeilenweise; Namen, Werte und Feld-IDs unverändert. - E2E-Test: Kontoart steht rechts neben dem Namen, das Formular endet oberhalb der Fensterhöhe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2c86e8522b
commit
a22c33bb12
4 changed files with 159 additions and 61 deletions
17
Changes.md
17
Changes.md
|
|
@ -1,5 +1,22 @@
|
||||||
# Änderungen
|
# Änderungen
|
||||||
|
|
||||||
|
## Kontoformular zweispaltig (17.08.2026)
|
||||||
|
|
||||||
|
*Konto bearbeiten* und *Neues Konto* zeigen ihre Felder in zwei Spalten. Alle Eingaben
|
||||||
|
stehen damit auf einem Bildschirm, ohne zu scrollen; bisher lagen die vierzehn Felder
|
||||||
|
untereinander.
|
||||||
|
|
||||||
|
- Neue Formularvariante `.formular.zweispaltig` (`public/static/app.css`): ein Grid mit
|
||||||
|
zwei Spalten, je Feld ein `.feld` aus Beschriftung, Eingabe und Hinweis. Überschriften,
|
||||||
|
Knopfleiste und `.feld.ganz` laufen über beide Spalten. Unter 720 px Fensterbreite fällt
|
||||||
|
das Formular auf eine Spalte zurück.
|
||||||
|
- Feldreihenfolge zeilenweise: Name/Kontoart, Kontengruppe/Beschreibung, Bank/Kontonummer,
|
||||||
|
IBAN/BIC, Anfangssaldo/Kreditlimit, Sortierung/Kennzeichen. Namen, Werte und Feld-IDs
|
||||||
|
bleiben unverändert.
|
||||||
|
- Der Bereich *Konto löschen* darunter bekommt dieselbe Breite, bleibt aber einspaltig.
|
||||||
|
- E2E-Test in `tests/e2e/specs/stammdaten.spec.js`: Kontoart steht rechts neben dem Namen,
|
||||||
|
und das Formular endet oberhalb der Fensterhöhe.
|
||||||
|
|
||||||
## Import und Export in zwei Formaten (17.08.2026)
|
## Import und Export in zwei Formaten (17.08.2026)
|
||||||
|
|
||||||
Aus der Kachel *QIF-Import* wird **Import/Export** mit je einem Knopf für beide Richtungen.
|
Aus der Kachel *QIF-Import* wird **Import/Export** mit je einem Knopf für beide Richtungen.
|
||||||
|
|
|
||||||
|
|
@ -1231,6 +1231,45 @@ table.kostentabelle .gruppenzeile th { padding-top: .7rem; font-weight: 600; }
|
||||||
margin-top: .6rem;
|
margin-top: .6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Zweispaltiges Formular: je Feld ein .feld mit Beschriftung, Eingabe und
|
||||||
|
Hinweis, zwei davon nebeneinander. Lange Formulare – das Kontoformular etwa –
|
||||||
|
bleiben so ohne vertikales Scrollen einsehbar. */
|
||||||
|
.formular.zweispaltig {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
align-items: start;
|
||||||
|
gap: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formular.breit.zweispaltig { max-width: 900px; }
|
||||||
|
|
||||||
|
.formular.zweispaltig .feld {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formular.zweispaltig .feld label { margin-top: 0; }
|
||||||
|
|
||||||
|
.formular.zweispaltig .feld .feldhinweis { margin-top: 0; }
|
||||||
|
|
||||||
|
/* Über beide Spalten: verstecktes CSRF-Feld, Überschriften, Knopfleiste und
|
||||||
|
Felder, die für eine halbe Zeile zu breit sind. */
|
||||||
|
.formular.zweispaltig > input[type="hidden"],
|
||||||
|
.formular.zweispaltig > h3,
|
||||||
|
.formular.zweispaltig > p,
|
||||||
|
.formular.zweispaltig > .knopfleiste,
|
||||||
|
.formular.zweispaltig .feld.ganz { grid-column: 1 / -1; }
|
||||||
|
|
||||||
|
/* Die Abstände kommen im Grid aus dem gap – eigene Ränder addierten sich dazu. */
|
||||||
|
.formular.zweispaltig > h3 { margin: 0; }
|
||||||
|
.formular.zweispaltig > p { margin: 0; }
|
||||||
|
.formular.zweispaltig > .knopfleiste { margin-top: .5rem; }
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.formular.zweispaltig { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
.formular label.ankreuzfeld {
|
.formular label.ankreuzfeld {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,15 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
<a href="/konten" class="knopf">Zurück zur Kontenübersicht</a>
|
<a href="/konten" class="knopf">Zurück zur Kontenübersicht</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="<?= View::h($ziel) ?>" class="formular breit">
|
<form method="post" action="<?= View::h($ziel) ?>" class="formular breit zweispaltig">
|
||||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="name">Name des Kontos</label>
|
<label for="name">Name des Kontos</label>
|
||||||
<input type="text" id="name" name="name" value="<?= $wert('name') ?>" required autofocus>
|
<input type="text" id="name" name="name" value="<?= $wert('name') ?>" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="kontoart">Kontoart</label>
|
<label for="kontoart">Kontoart</label>
|
||||||
<select id="kontoart" name="kontoart">
|
<select id="kontoart" name="kontoart">
|
||||||
<?php foreach (Konto::ARTEN as $schluessel => $beschriftung): ?>
|
<?php foreach (Konto::ARTEN as $schluessel => $beschriftung): ?>
|
||||||
|
|
@ -34,7 +37,9 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="kontengruppe_id">Kontengruppe</label>
|
<label for="kontengruppe_id">Kontengruppe</label>
|
||||||
<select id="kontengruppe_id" name="kontengruppe_id">
|
<select id="kontengruppe_id" name="kontengruppe_id">
|
||||||
<option value="">Nicht gruppiert</option>
|
<option value="">Nicht gruppiert</option>
|
||||||
|
|
@ -45,19 +50,34 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
|
<label for="beschreibung">Beschreibung <span class="optional">(optional)</span></label>
|
||||||
|
<input type="text" id="beschreibung" name="beschreibung" value="<?= $wert('beschreibung') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="bankname">Bank <span class="optional">(optional)</span></label>
|
<label for="bankname">Bank <span class="optional">(optional)</span></label>
|
||||||
<input type="text" id="bankname" name="bankname" value="<?= $wert('bankname') ?>">
|
<input type="text" id="bankname" name="bankname" value="<?= $wert('bankname') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="kontonummer">Kontonummer <span class="optional">(optional)</span></label>
|
<label for="kontonummer">Kontonummer <span class="optional">(optional)</span></label>
|
||||||
<input type="text" id="kontonummer" name="kontonummer" value="<?= $wert('kontonummer') ?>">
|
<input type="text" id="kontonummer" name="kontonummer" value="<?= $wert('kontonummer') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="iban">IBAN <span class="optional">(optional)</span></label>
|
<label for="iban">IBAN <span class="optional">(optional)</span></label>
|
||||||
<input type="text" id="iban" name="iban" value="<?= $wert('iban') ?>">
|
<input type="text" id="iban" name="iban" value="<?= $wert('iban') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="bic">BIC <span class="optional">(optional)</span></label>
|
<label for="bic">BIC <span class="optional">(optional)</span></label>
|
||||||
<input type="text" id="bic" name="bic" value="<?= $wert('bic') ?>">
|
<input type="text" id="bic" name="bic" value="<?= $wert('bic') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="anfangssaldo">Anfangssaldo</label>
|
<label for="anfangssaldo">Anfangssaldo</label>
|
||||||
<input type="text" id="anfangssaldo" name="anfangssaldo" inputmode="decimal"
|
<input type="text" id="anfangssaldo" name="anfangssaldo" inputmode="decimal"
|
||||||
value="<?= View::h(View::betrag($konto['anfangssaldo'] ?? 0, false)) ?>">
|
value="<?= View::h(View::betrag($konto['anfangssaldo'] ?? 0, false)) ?>">
|
||||||
|
|
@ -65,19 +85,22 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
Kontostand vor der ersten erfassten Buchung. Der QIF-Export enthält ihn nicht –
|
Kontostand vor der ersten erfassten Buchung. Der QIF-Export enthält ihn nicht –
|
||||||
nach einem Import wird er hier je Konto nachgetragen.
|
nach einem Import wird er hier je Konto nachgetragen.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label for="kreditlimit">Kreditlimit <span class="optional">(optional)</span></label>
|
<label for="kreditlimit">Kreditlimit <span class="optional">(optional)</span></label>
|
||||||
<input type="text" id="kreditlimit" name="kreditlimit" inputmode="decimal"
|
<input type="text" id="kreditlimit" name="kreditlimit" inputmode="decimal"
|
||||||
value="<?= $konto === null || $konto['kreditlimit'] === null
|
value="<?= $konto === null || $konto['kreditlimit'] === null
|
||||||
? '' : View::h(View::betrag($konto['kreditlimit'], false)) ?>">
|
? '' : View::h(View::betrag($konto['kreditlimit'], false)) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="beschreibung">Beschreibung <span class="optional">(optional)</span></label>
|
<div class="feld">
|
||||||
<input type="text" id="beschreibung" name="beschreibung" value="<?= $wert('beschreibung') ?>">
|
|
||||||
|
|
||||||
<label for="sortierung">Sortierung</label>
|
<label for="sortierung">Sortierung</label>
|
||||||
<input type="number" id="sortierung" name="sortierung" value="<?= $wert('sortierung', '0') ?>" step="1">
|
<input type="number" id="sortierung" name="sortierung" value="<?= $wert('sortierung', '0') ?>" step="1">
|
||||||
<p class="feldhinweis">Kleinere Werte stehen in der Kontenübersicht weiter oben.</p>
|
<p class="feldhinweis">Kleinere Werte stehen in der Kontenübersicht weiter oben.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feld">
|
||||||
<label class="ankreuzfeld">
|
<label class="ankreuzfeld">
|
||||||
<input type="checkbox" name="aktiv" value="1" <?= $neu || (int)$konto['aktiv'] === 1 ? 'checked' : '' ?>>
|
<input type="checkbox" name="aktiv" value="1" <?= $neu || (int)$konto['aktiv'] === 1 ? 'checked' : '' ?>>
|
||||||
Konto ist aktiv
|
Konto ist aktiv
|
||||||
|
|
@ -93,6 +116,7 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
darstellen – durchlaufende Auslagen etwa –, bleiben so aus der Gesamtsumme heraus,
|
darstellen – durchlaufende Auslagen etwa –, bleiben so aus der Gesamtsumme heraus,
|
||||||
erscheinen aber weiter in der Übersicht.
|
erscheinen aber weiter in der Übersicht.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<button type="submit" class="knopf primaer"><?= $neu ? 'Konto anlegen' : 'Speichern' ?></button>
|
<button type="submit" class="knopf primaer"><?= $neu ? 'Konto anlegen' : 'Speichern' ?></button>
|
||||||
|
|
@ -101,7 +125,7 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php if (!$neu): ?>
|
<?php if (!$neu): ?>
|
||||||
<form method="post" action="/konten/<?= (int)$konto['id'] ?>/loeschen" class="formular breit"
|
<form method="post" action="/konten/<?= (int)$konto['id'] ?>/loeschen" class="formular breit zweispaltig"
|
||||||
data-bestaetigen="Konto „<?= View::h($konto['name']) ?>“ wirklich löschen?">
|
data-bestaetigen="Konto „<?= View::h($konto['name']) ?>“ wirklich löschen?">
|
||||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||||
<h3>Konto löschen</h3>
|
<h3>Konto löschen</h3>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,24 @@ test.describe('Stammdaten', () => {
|
||||||
await expect(page.locator('tr', { hasText: name })).not.toContainText('ohne Σ')
|
await expect(page.locator('tr', { hasText: name })).not.toContainText('ohne Σ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Das Kontoformular steht zweispaltig, damit alle Felder ohne vertikales
|
||||||
|
// Scrollen sichtbar sind. Geprüft wird an den Kästen: Kontoart steht rechts
|
||||||
|
// neben dem Namen, und das Formular endet oberhalb der Fensterhöhe.
|
||||||
|
test('Kontoformular steht zweispaltig und ohne Scrollen', async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1280, height: 900 })
|
||||||
|
await page.goto('/konten/neu')
|
||||||
|
|
||||||
|
const name = await page.locator('#name').boundingBox()
|
||||||
|
const kontoart = await page.locator('#kontoart').boundingBox()
|
||||||
|
|
||||||
|
// Gleiche Zeile, zweite Spalte.
|
||||||
|
expect(Math.abs(name.y - kontoart.y)).toBeLessThan(5)
|
||||||
|
expect(kontoart.x).toBeGreaterThan(name.x + name.width)
|
||||||
|
|
||||||
|
const knopf = await page.locator('.formular button[type="submit"]').first().boundingBox()
|
||||||
|
expect(knopf.y + knopf.height).toBeLessThan(900)
|
||||||
|
})
|
||||||
|
|
||||||
test('Kategorie mit Unterkategorie anlegen', async ({ page }) => {
|
test('Kategorie mit Unterkategorie anlegen', async ({ page }) => {
|
||||||
const oben = `E2E Wohnen ${MARKE}`
|
const oben = `E2E Wohnen ${MARKE}`
|
||||||
const unten = `Strom ${MARKE}`
|
const unten = `Strom ${MARKE}`
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue