Termine und Verträge unter der Verwaltung, Kacheln vereinheitlicht
Die Seitenleiste war lang geworden. Termine und Verträge sind kein täglicher Einstieg und stehen deshalb nicht mehr im Hauptmenü, sondern als letzte Kacheln in der Verwaltung. - Menüeinträge können weitere Pfade mitführen (`auch`), unter denen sie als aktiv gelten – die Verwaltung leuchtet damit auch unter /termine und /vertraege, wo die Seitenleiste sonst ohne Markierung bliebe. - Neue Kacheln Termine (aktive Reihen, offene Erinnerungen) und Verträge (laufende Verträge, Fristen der nächsten 90 Tage). - Alle Kacheln der Verwaltung tragen jetzt dieselben kleinen Knöpfe; vorher standen zwei Größen nebeneinander im selben Raster. - Das Raster richtet seine Knopfleisten am Kachelfuß aus, statt sie der jeweiligen Textlänge folgen zu lassen. - .knopfleiste bricht um, statt über den rechten Kachelrand zu ragen. Test: tests/24_NavigationTest.php Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
dd7025805c
commit
1fbc3edc5e
6 changed files with 327 additions and 14 deletions
42
Changes.md
42
Changes.md
|
|
@ -1,5 +1,47 @@
|
||||||
# Änderungen
|
# Änderungen
|
||||||
|
|
||||||
|
## Termine und Verträge wandern unter die Verwaltung (13.08.2026)
|
||||||
|
|
||||||
|
Die Seitenleiste war lang geworden. *Termine* und *Verträge* sind kein täglicher Einstieg –
|
||||||
|
die Reihen laufen von selbst, Verträge sind Stammdaten – und stehen deshalb nicht mehr im
|
||||||
|
Hauptmenü, sondern als letzte Kacheln in der Verwaltung.
|
||||||
|
|
||||||
|
**Navigation** (`Views/layout/kopf.php`)
|
||||||
|
- Die Menüpunkte *Termine* und *Verträge* entfallen.
|
||||||
|
- Ein Menüeintrag kann jetzt weitere Pfade mitführen (`auch`), unter denen er als aktiv
|
||||||
|
gilt. *Verwaltung* leuchtet damit auch auf `/termine`, `/termine/kalender`,
|
||||||
|
`/termine/erinnerungen` und unterhalb von `/vertraege` – sonst stünde die Seitenleiste
|
||||||
|
dort ohne Markierung.
|
||||||
|
|
||||||
|
**Verwaltung** (`Views/verwaltung/index.php`, `VerwaltungController`)
|
||||||
|
- Neue Kachel *Termine* mit der Zahl der aktiven Reihen, den offenen Erinnerungen und
|
||||||
|
Wegen zu regelmäßigen Buchungen, Finanzkalender und Erinnerungen.
|
||||||
|
- Neue letzte Kachel *Verträge* mit der Zahl der laufenden Verträge, den in den nächsten
|
||||||
|
90 Tagen ablaufenden Kündigungsfristen und Wegen zu Verträgen, Adressen, Sparzielen und
|
||||||
|
Krediten.
|
||||||
|
- Beide Kacheln führen mehrere Wege und nutzen deshalb kleine Knöpfe und einen knappen
|
||||||
|
Beitext.
|
||||||
|
- Alle übrigen Kacheln ziehen nach: *Kategorien*, *Klassen*, *Konten*, *QIF-Import* und
|
||||||
|
*Online-Banking* tragen jetzt dieselben kleinen Knöpfe. Vorher standen zwei Größen
|
||||||
|
nebeneinander im selben Raster.
|
||||||
|
|
||||||
|
**Knopfleisten stehen am Kachelfuß** (`static/app.css`)
|
||||||
|
- Das Raster der Verwaltung heißt `kachelraster fussbuendig`: die Kacheln werden zu
|
||||||
|
Flexspalten, der freie Platz sammelt sich über der Knopfleiste. Die Knöpfe stehen damit
|
||||||
|
in jeder Kachel einer Zeile auf gleicher Höhe unten, statt der jeweiligen Textlänge zu
|
||||||
|
folgen. Der Abstand zum Text steckt im Padding – `margin-top: auto` schluckt den
|
||||||
|
Außenabstand und ließe die Knöpfe in der höchsten Kachel sonst am Text kleben.
|
||||||
|
|
||||||
|
**Knopfleisten brechen um** (`static/app.css`)
|
||||||
|
- `.knopfleiste` bekommt `flex-wrap: wrap`. Bisher ragten Leisten mit mehreren Knöpfen bei
|
||||||
|
schmalen Kacheln über den rechten Rand hinaus, statt in die nächste Zeile zu rutschen.
|
||||||
|
Das gilt für alle Knopfleisten, nicht nur die der Verwaltung.
|
||||||
|
|
||||||
|
Alle Adressen unter `/termine` und `/vertraege` bleiben unverändert erreichbar, ebenso die
|
||||||
|
Reiter innerhalb der Seiten und die Verlinkungen aus Cockpit und Buchungsvorlagen.
|
||||||
|
|
||||||
|
**Test:** `tests/24_NavigationTest.php`
|
||||||
|
|
||||||
## QIF-Import: nur ausgewählte Konten übernehmen (12.08.2026)
|
## QIF-Import: nur ausgewählte Konten übernehmen (12.08.2026)
|
||||||
|
|
||||||
Der QIF-Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich nur ganz
|
Der QIF-Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich nur ganz
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,23 @@ form.inline { display: inline; margin: 0; }
|
||||||
|
|
||||||
.kachel.einzeln { max-width: 640px; }
|
.kachel.einzeln { max-width: 640px; }
|
||||||
|
|
||||||
|
/* Kacheln mit unterschiedlich langem Text – etwa in der Verwaltung – lassen ihre
|
||||||
|
Knopfleisten sonst auf verschiedenen Höhen enden. Die Kachel wird zur
|
||||||
|
Flexspalte, der freie Platz sammelt sich über der Knopfleiste, und die Knöpfe
|
||||||
|
stehen in jeder Kachel derselben Zeile auf gleicher Höhe am unteren Rand. */
|
||||||
|
.kachelraster.fussbuendig > .kachel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Der Abstand nach oben wandert ins Padding: `margin-top: auto` schluckt den
|
||||||
|
Außenabstand und ließe die Knöpfe in der höchsten Kachel – die keinen freien
|
||||||
|
Platz mehr hat – direkt am Text kleben. */
|
||||||
|
.kachelraster.fussbuendig > .kachel > .knopfleiste {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.kachel header { margin-bottom: .6rem; }
|
.kachel header { margin-bottom: .6rem; }
|
||||||
|
|
||||||
.kachel header.mitschalter {
|
.kachel header.mitschalter {
|
||||||
|
|
@ -1159,8 +1176,11 @@ table.splittabelle input.rechts { text-align: right; }
|
||||||
|
|
||||||
.optional { font-weight: 400; color: var(--text-gedaempft); }
|
.optional { font-weight: 400; color: var(--text-gedaempft); }
|
||||||
|
|
||||||
|
/* Umbruch statt Überlauf: Knopfleisten mit mehreren Wegen – etwa in den Kacheln
|
||||||
|
der Verwaltung – ragten sonst über den rechten Kachelrand hinaus. */
|
||||||
.knopfleiste {
|
.knopfleiste {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: .6rem;
|
gap: .6rem;
|
||||||
margin-top: 1.25rem;
|
margin-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Finanzmanager\Controllers;
|
namespace Finanzmanager\Controllers;
|
||||||
|
|
||||||
|
use Finanzmanager\Models\Erinnerung;
|
||||||
use Finanzmanager\Models\Kategorie;
|
use Finanzmanager\Models\Kategorie;
|
||||||
use Finanzmanager\Models\Klasse;
|
use Finanzmanager\Models\Klasse;
|
||||||
use Finanzmanager\Models\Konto;
|
use Finanzmanager\Models\Konto;
|
||||||
use Finanzmanager\Models\Kontengruppe;
|
use Finanzmanager\Models\Kontengruppe;
|
||||||
|
use Finanzmanager\Models\RegelmaessigeBuchung;
|
||||||
|
use Finanzmanager\Models\Vertrag;
|
||||||
use Finanzmanager\View;
|
use Finanzmanager\View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,6 +33,10 @@ class VerwaltungController extends BaseController
|
||||||
'klassen' => Klasse::anzahl(),
|
'klassen' => Klasse::anzahl(),
|
||||||
'konten' => Konto::anzahl(),
|
'konten' => Konto::anzahl(),
|
||||||
'kontengruppen' => count(Kontengruppe::alle()),
|
'kontengruppen' => count(Kontengruppe::alle()),
|
||||||
|
'reihen' => count(RegelmaessigeBuchung::alle(true)),
|
||||||
|
'erinnerungen' => Erinnerung::anzahlOffen(),
|
||||||
|
'vertraege' => count(Vertrag::alle(true)),
|
||||||
|
'fristen' => count(Vertrag::fristenLaufenAb(90)),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,26 @@ $navigation = [
|
||||||
['pfad' => '/zahlungsverkehr', 'name' => 'Zahlungsverkehr', 'icon' => '⇄'],
|
['pfad' => '/zahlungsverkehr', 'name' => 'Zahlungsverkehr', 'icon' => '⇄'],
|
||||||
['pfad' => '/auswertungen', 'name' => 'Auswertungen', 'icon' => '◱'],
|
['pfad' => '/auswertungen', 'name' => 'Auswertungen', 'icon' => '◱'],
|
||||||
['pfad' => '/planung', 'name' => 'Planung', 'icon' => '◷'],
|
['pfad' => '/planung', 'name' => 'Planung', 'icon' => '◷'],
|
||||||
['pfad' => '/termine', 'name' => 'Termine', 'icon' => '⏱'],
|
|
||||||
['pfad' => '/vertraege', 'name' => 'Verträge', 'icon' => '§'],
|
|
||||||
['pfad' => '/wertpapiere', 'name' => 'Wertpapiere', 'icon' => '↗'],
|
['pfad' => '/wertpapiere', 'name' => 'Wertpapiere', 'icon' => '↗'],
|
||||||
['pfad' => '/verwaltung', 'name' => 'Verwaltung', 'icon' => '⚙'],
|
// Termine und Verträge haben keinen eigenen Menüpunkt mehr, sind aber weiter
|
||||||
|
// erreichbar – deshalb leuchtet für sie die Verwaltung, unter der sie hängen.
|
||||||
|
['pfad' => '/verwaltung', 'name' => 'Verwaltung', 'icon' => '⚙', 'auch' => ['/termine', '/vertraege']],
|
||||||
];
|
];
|
||||||
|
|
||||||
$istAktiv = static function (string $pfad) use ($aktuellerPfad): bool {
|
/** @param list<string> $auch */
|
||||||
|
$istAktiv = static function (string $pfad, array $auch = []) use ($aktuellerPfad): bool {
|
||||||
|
if (!is_string($aktuellerPfad)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if ($pfad === '/') {
|
if ($pfad === '/') {
|
||||||
return $aktuellerPfad === '/' || $aktuellerPfad === '';
|
return $aktuellerPfad === '/' || $aktuellerPfad === '';
|
||||||
}
|
}
|
||||||
return is_string($aktuellerPfad) && str_starts_with($aktuellerPfad, $pfad);
|
foreach ([$pfad, ...$auch] as $kandidat) {
|
||||||
|
if (str_starts_with($aktuellerPfad, $kandidat)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
@ -48,7 +57,7 @@ $istAktiv = static function (string $pfad) use ($aktuellerPfad): bool {
|
||||||
<?php foreach ($navigation as $eintrag): ?>
|
<?php foreach ($navigation as $eintrag): ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="<?= View::h($eintrag['pfad']) ?>"
|
<a href="<?= View::h($eintrag['pfad']) ?>"
|
||||||
class="<?= $istAktiv($eintrag['pfad']) ? 'aktiv' : '' ?>">
|
class="<?= $istAktiv($eintrag['pfad'], $eintrag['auch'] ?? []) ? 'aktiv' : '' ?>">
|
||||||
<span class="icon" aria-hidden="true"><?= $eintrag['icon'] ?></span>
|
<span class="icon" aria-hidden="true"><?= $eintrag['icon'] ?></span>
|
||||||
<?= View::h($eintrag['name']) ?>
|
<?= View::h($eintrag['name']) ?>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use Finanzmanager\View;
|
||||||
include __DIR__ . '/../layout/kopf.php';
|
include __DIR__ . '/../layout/kopf.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="kachelraster">
|
<div class="kachelraster fussbuendig">
|
||||||
<div class="kachel">
|
<div class="kachel">
|
||||||
<header><h2>Kategorien</h2></header>
|
<header><h2>Kategorien</h2></header>
|
||||||
<p class="kennzahl"><?= (int)$zahlen['kategorien'] ?></p>
|
<p class="kennzahl"><?= (int)$zahlen['kategorien'] ?></p>
|
||||||
|
|
@ -16,7 +16,7 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
Hierarchisch, mit Klassen als zweiter Auswertungsdimension.
|
Hierarchisch, mit Klassen als zweiter Auswertungsdimension.
|
||||||
</p>
|
</p>
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<a href="/verwaltung/kategorien" class="knopf primaer">Kategorien verwalten</a>
|
<a href="/verwaltung/kategorien" class="knopf klein primaer">Kategorien verwalten</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
Zweite Dimension neben der Kategorie – etwa Ehemann, Ehefrau, Haus 1.
|
Zweite Dimension neben der Kategorie – etwa Ehemann, Ehefrau, Haus 1.
|
||||||
</p>
|
</p>
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<a href="/verwaltung/klassen" class="knopf primaer">Klassen verwalten</a>
|
<a href="/verwaltung/klassen" class="knopf klein primaer">Klassen verwalten</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -38,8 +38,8 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
In <?= (int)$zahlen['kontengruppen'] ?> Kontengruppen gegliedert.
|
In <?= (int)$zahlen['kontengruppen'] ?> Kontengruppen gegliedert.
|
||||||
</p>
|
</p>
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<a href="/konten" class="knopf primaer">Kontenübersicht</a>
|
<a href="/konten" class="knopf klein primaer">Kontenübersicht</a>
|
||||||
<a href="/verwaltung/kontengruppen" class="knopf">Kontengruppen</a>
|
<a href="/verwaltung/kontengruppen" class="knopf klein">Kontengruppen</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
übernehmen – erst im Trockenlauf prüfen, dann übernehmen.
|
übernehmen – erst im Trockenlauf prüfen, dann übernehmen.
|
||||||
</p>
|
</p>
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<a href="/verwaltung/import" class="knopf primaer">Import öffnen</a>
|
<a href="/verwaltung/import" class="knopf klein primaer">Import öffnen</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -61,8 +61,35 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
Buchungen und Regeln für die automatische Kategorisierung.
|
Buchungen und Regeln für die automatische Kategorisierung.
|
||||||
</p>
|
</p>
|
||||||
<div class="knopfleiste">
|
<div class="knopfleiste">
|
||||||
<a href="/banking" class="knopf primaer">Bankverbindungen</a>
|
<a href="/banking" class="knopf klein primaer">Bankverbindungen</a>
|
||||||
<a href="/banking/regeln" class="knopf">Regeln</a>
|
<a href="/banking/regeln" class="knopf klein">Regeln</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kachel">
|
||||||
|
<header><h2>Termine</h2></header>
|
||||||
|
<p class="kennzahl"><?= (int)$zahlen['reihen'] ?></p>
|
||||||
|
<p class="platzhalter">
|
||||||
|
Aktive Reihen – offene Erinnerungen: <?= (int)$zahlen['erinnerungen'] ?>.
|
||||||
|
</p>
|
||||||
|
<div class="knopfleiste">
|
||||||
|
<a href="/termine" class="knopf klein primaer">Regelmäßige Buchungen</a>
|
||||||
|
<a href="/termine/kalender" class="knopf klein">Finanzkalender</a>
|
||||||
|
<a href="/termine/erinnerungen" class="knopf klein">Erinnerungen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kachel">
|
||||||
|
<header><h2>Verträge</h2></header>
|
||||||
|
<p class="kennzahl"><?= (int)$zahlen['vertraege'] ?></p>
|
||||||
|
<p class="platzhalter">
|
||||||
|
Laufende Verträge – Fristen in den nächsten 90 Tagen: <?= (int)$zahlen['fristen'] ?>.
|
||||||
|
</p>
|
||||||
|
<div class="knopfleiste">
|
||||||
|
<a href="/vertraege" class="knopf klein primaer">Verträge</a>
|
||||||
|
<a href="/vertraege/adressen" class="knopf klein">Adressen</a>
|
||||||
|
<a href="/vertraege/sparziele" class="knopf klein">Sparziele</a>
|
||||||
|
<a href="/vertraege/kredite" class="knopf klein">Kredite</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
208
tests/24_NavigationTest.php
Normal file
208
tests/24_NavigationTest.php
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Hauptnavigation führt Termine und Verträge nicht mehr als eigene
|
||||||
|
* Menüpunkte – sie hängen jetzt als letzte Kacheln unter der Verwaltung.
|
||||||
|
*/
|
||||||
|
class NavigationTest extends TestCase
|
||||||
|
{
|
||||||
|
protected string $testName = 'Test 24: Navigation (Termine und Verträge unter Verwaltung)';
|
||||||
|
|
||||||
|
/** Die Zahlen, die die Verwaltungsseite für ihre Kacheln erwartet. */
|
||||||
|
private const ZAHLEN = [
|
||||||
|
'kategorien' => 0, 'einnahmen' => 0, 'ausgaben' => 0, 'klassen' => 0,
|
||||||
|
'konten' => 0, 'kontengruppen' => 0, 'reihen' => 3, 'erinnerungen' => 2,
|
||||||
|
'vertraege' => 7, 'fristen' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$this->menueOhneTermineUndVertraege();
|
||||||
|
$this->verwaltungLeuchtetFuerBeide();
|
||||||
|
$this->verwaltungsseiteFuehrtZuDenTerminen();
|
||||||
|
$this->verwaltungsseiteFuehrtZuDenVertraegen();
|
||||||
|
$this->alleKnoepfeGleichUndFussbuendig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Kacheln der Verwaltung tragen dieselben kleinen Knöpfe wie die von
|
||||||
|
* Termine, und die Leisten stehen unabhängig von der Textlänge auf gleicher
|
||||||
|
* Höhe am unteren Kachelrand.
|
||||||
|
*/
|
||||||
|
private function alleKnoepfeGleichUndFussbuendig(): void
|
||||||
|
{
|
||||||
|
$kacheln = $this->kacheln();
|
||||||
|
|
||||||
|
// Das abschließende \b hält die umgebende .knopfleiste heraus.
|
||||||
|
preg_match_all('#class="knopf\b([^"]*)"#', $kacheln, $treffer);
|
||||||
|
$this->assertEquals(
|
||||||
|
substr_count($kacheln, '<div class="kachel">'),
|
||||||
|
substr_count($kacheln, '<div class="knopfleiste">'),
|
||||||
|
'Jede Kachel führt eine Knopfleiste'
|
||||||
|
);
|
||||||
|
$this->assertGreaterThan(0, count($treffer[1]), 'Die Kacheln führen Knöpfe');
|
||||||
|
foreach ($treffer[1] as $zusatz) {
|
||||||
|
$this->assert(
|
||||||
|
str_contains($zusatz, ' klein'),
|
||||||
|
'Knopf "knopf' . $zusatz . '" ist klein wie die unter Termine'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#<div class="kachelraster fussbuendig">#', $kacheln),
|
||||||
|
'Das Kachelraster richtet seine Knopfleisten am Fuß aus'
|
||||||
|
);
|
||||||
|
|
||||||
|
$stil = $this->stilblatt();
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#\.kachelraster\.fussbuendig > \.kachel\s*\{[^}]*flex-direction:\s*column#', $stil),
|
||||||
|
'Die Kacheln des Rasters sind Flexspalten'
|
||||||
|
);
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match(
|
||||||
|
'#\.kachelraster\.fussbuendig > \.kachel > \.knopfleiste\s*\{[^}]*margin-top:\s*auto[^}]*padding-top:#',
|
||||||
|
$stil
|
||||||
|
),
|
||||||
|
'Die Knopfleiste sinkt nach unten und behält ihren Abstand zum Text'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function menueOhneTermineUndVertraege(): void
|
||||||
|
{
|
||||||
|
$menue = $this->seitenleiste($this->rendere('layout/kopf.php', ['aktuellerPfad' => '/']));
|
||||||
|
|
||||||
|
$this->assert(!str_contains($menue, 'href="/termine"'), 'Kein Menüpunkt Termine mehr');
|
||||||
|
$this->assert(!str_contains($menue, '>Termine'), 'Auch die Beschriftung Termine ist verschwunden');
|
||||||
|
$this->assert(!str_contains($menue, 'href="/vertraege"'), 'Kein Menüpunkt Verträge mehr');
|
||||||
|
$this->assert(!str_contains($menue, '>Verträge'), 'Auch die Beschriftung Verträge ist verschwunden');
|
||||||
|
$this->assert(str_contains($menue, 'href="/verwaltung"'), 'Verwaltung steht weiter im Menü');
|
||||||
|
$this->assert(str_contains($menue, 'href="/planung"'), 'Die übrigen Menüpunkte bleiben unberührt');
|
||||||
|
$this->assert(str_contains($menue, 'href="/wertpapiere"'), 'Wertpapiere stehen weiter im Menü');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verwaltungLeuchtetFuerBeide(): void
|
||||||
|
{
|
||||||
|
// Ohne eigenen Menüpunkt bliebe die Seitenleiste auf diesen Seiten
|
||||||
|
// sonst ganz ohne Markierung.
|
||||||
|
$pfade = [
|
||||||
|
'/termine', '/termine/kalender', '/termine/erinnerungen',
|
||||||
|
'/vertraege', '/vertraege/adressen', '/vertraege/sparziele', '/vertraege/kredite',
|
||||||
|
];
|
||||||
|
foreach ($pfade as $pfad) {
|
||||||
|
$menue = $this->seitenleiste($this->rendere('layout/kopf.php', ['aktuellerPfad' => $pfad]));
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#href="/verwaltung"\s+class="aktiv"#', $menue),
|
||||||
|
'Verwaltung ist markiert auf ' . $pfad
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cockpit = $this->seitenleiste($this->rendere('layout/kopf.php', ['aktuellerPfad' => '/']));
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#href="/"\s+class="aktiv"#', $cockpit),
|
||||||
|
'Cockpit ist auf der Startseite markiert'
|
||||||
|
);
|
||||||
|
$this->assert(
|
||||||
|
!preg_match('#href="/verwaltung"\s+class="aktiv"#', $cockpit),
|
||||||
|
'Verwaltung leuchtet nicht überall mit'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verwaltungsseiteFuehrtZuDenTerminen(): void
|
||||||
|
{
|
||||||
|
$kacheln = $this->kacheln();
|
||||||
|
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/termine"'), 'Regelmäßige Buchungen sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/termine/kalender"'), 'Finanzkalender ist verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/termine/erinnerungen"'), 'Erinnerungen sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, '<h2>Termine</h2>'), 'Die Kachel heißt Termine');
|
||||||
|
$this->assert(str_contains($kacheln, '>3<'), 'Die Kennzahl zeigt die Zahl der Reihen');
|
||||||
|
$this->assert(str_contains($kacheln, 'offene Erinnerungen: 2'), 'Offene Erinnerungen werden genannt');
|
||||||
|
|
||||||
|
foreach (['/termine', '/termine/kalender', '/termine/erinnerungen'] as $ziel) {
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#href="' . preg_quote($ziel, '#') . '" class="knopf klein#', $kacheln),
|
||||||
|
'Der Knopf zu ' . $ziel . ' ist klein'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Umbruch statt Überlauf am rechten Kachelrand.
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#\.knopfleiste\s*\{[^}]*flex-wrap:\s*wrap#', $this->stilblatt()),
|
||||||
|
'Die Knopfleiste bricht um'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assert(
|
||||||
|
strrpos($kacheln, '<h2>Termine</h2>') > (int)strrpos($kacheln, '<h2>Online-Banking</h2>'),
|
||||||
|
'Termine stehen hinter dem Online-Banking'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verwaltungsseiteFuehrtZuDenVertraegen(): void
|
||||||
|
{
|
||||||
|
$kacheln = $this->kacheln();
|
||||||
|
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/vertraege"'), 'Verträge sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/vertraege/adressen"'), 'Adressen sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/vertraege/sparziele"'), 'Sparziele sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, 'href="/vertraege/kredite"'), 'Kredite sind verlinkt');
|
||||||
|
$this->assert(str_contains($kacheln, '<h2>Verträge</h2>'), 'Die Kachel heißt Verträge');
|
||||||
|
$this->assert(str_contains($kacheln, '>7<'), 'Die Kennzahl zeigt die Zahl der aktiven Verträge');
|
||||||
|
$this->assert(str_contains($kacheln, 'nächsten 90 Tagen: 1'), 'Ablaufende Fristen werden genannt');
|
||||||
|
|
||||||
|
// Vier Wege in einer Kachel: nur als kleine Knöpfe, und die Leiste bricht um.
|
||||||
|
foreach (['/vertraege', '/vertraege/adressen', '/vertraege/sparziele', '/vertraege/kredite'] as $ziel) {
|
||||||
|
$this->assert(
|
||||||
|
(bool)preg_match('#href="' . preg_quote($ziel, '#') . '" class="knopf klein#', $kacheln),
|
||||||
|
'Der Knopf zu ' . $ziel . ' ist klein'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letzter Punkt: keine weitere Kachel hinter der von Verträge.
|
||||||
|
$letzte = (int)strrpos($kacheln, '<h2>Verträge</h2>');
|
||||||
|
foreach (['Kategorien', 'Klassen', 'Konten', 'QIF-Import', 'Online-Banking', 'Termine'] as $vorher) {
|
||||||
|
$this->assert(
|
||||||
|
$letzte > (int)strrpos($kacheln, '<h2>' . $vorher . '</h2>'),
|
||||||
|
'Verträge stehen hinter der Kachel ' . $vorher
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stilblatt(): string
|
||||||
|
{
|
||||||
|
return (string)file_get_contents(dirname(__DIR__) . '/public/static/app.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nur der Inhalt der Verwaltungsseite – die Seitenleiste soll nicht mitzählen. */
|
||||||
|
private function kacheln(): string
|
||||||
|
{
|
||||||
|
$seite = $this->rendere('verwaltung/index.php', [
|
||||||
|
'aktuellerPfad' => '/verwaltung',
|
||||||
|
'zahlen' => self::ZAHLEN,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return substr($seite, (int)strpos($seite, '<main'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string,mixed> $daten */
|
||||||
|
private function rendere(string $view, array $daten): string
|
||||||
|
{
|
||||||
|
$daten += ['titel' => 'Test', 'flash' => [], 'benutzer' => null, 'csrfToken' => 'x'];
|
||||||
|
|
||||||
|
extract($daten, EXTR_SKIP);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
include dirname(__DIR__) . '/src/Views/' . $view;
|
||||||
|
return (string)ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nur die Seitenleiste – Verlinkungen im Inhalt sollen nicht mitzählen. */
|
||||||
|
private function seitenleiste(string $html): string
|
||||||
|
{
|
||||||
|
$anfang = (int)strpos($html, '<nav');
|
||||||
|
$ende = (int)strpos($html, '</nav>');
|
||||||
|
|
||||||
|
return substr($html, $anfang, $ende - $anfang);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue