Merge pull request 'Termine und Verträge unter der Verwaltung, Kacheln vereinheitlicht' (#12) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/12
This commit is contained in:
commit
423ce4122d
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