Das Ändern von Kategorie oder Klasse im Kontoblatt lud bisher die ganze Seite neu, um die Zeile zu aktualisieren – dabei sprang der Bildschirm nach ganz oben. kontoblatt.js schickt die Auswahl jetzt per fetch im Hintergrund; Kategorie, Klasse und das Symbol der Bargeld-Abgleichspalte aktualisieren sich an Ort und Stelle, die Bildlaufposition bleibt stehen. Ohne JavaScript bleibt es bei der bisherigen Weiterleitung.
170 lines
5.8 KiB
PHP
170 lines
5.8 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Finanzmanager;
|
||
|
||
class View
|
||
{
|
||
private string $viewDir;
|
||
private array $flash = [];
|
||
|
||
public function __construct()
|
||
{
|
||
$this->viewDir = __DIR__ . '/Views';
|
||
|
||
Auth::startSession();
|
||
if (isset($_SESSION['flash'])) {
|
||
$this->flash = $_SESSION['flash'];
|
||
unset($_SESSION['flash']);
|
||
}
|
||
}
|
||
|
||
public function render(string $template, array $daten = []): void
|
||
{
|
||
$daten['flash'] = $this->flash;
|
||
$daten['aktuellerPfad'] = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||
|
||
extract($daten, EXTR_SKIP);
|
||
include $this->viewDir . '/' . $template;
|
||
}
|
||
|
||
public static function redirect(string $pfad, string $meldung = '', string $typ = 'erfolg'): void
|
||
{
|
||
Auth::startSession();
|
||
if ($meldung !== '') {
|
||
$_SESSION['flash'][] = ['typ' => $typ, 'meldung' => $meldung];
|
||
}
|
||
header('Location: ' . $pfad);
|
||
exit;
|
||
}
|
||
|
||
public static function h(mixed $wert): string
|
||
{
|
||
return htmlspecialchars((string)$wert, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
|
||
/**
|
||
* Beträge im deutschen Format. Das Leerzeichen vor dem Euro-Zeichen ist
|
||
* geschützt, sonst rutscht das Zeichen in schmalen Tabellenspalten allein
|
||
* in die nächste Zeile.
|
||
*/
|
||
public static function betrag(mixed $wert, bool $mitWaehrung = true): string
|
||
{
|
||
$text = number_format((float)$wert, 2, ',', '.');
|
||
return $mitWaehrung ? $text . "\u{00A0}€" : $text;
|
||
}
|
||
|
||
/**
|
||
* Stückzahlen. Fonds werden in Bruchteilen geführt, Aktien in ganzen
|
||
* Stücken – dieselbe Spalte trägt beides, deshalb sechs Nachkommastellen
|
||
* ohne die Nullen, die niemand liest: „12“ statt „12,000000“.
|
||
*/
|
||
public static function stueck(mixed $wert): string
|
||
{
|
||
return rtrim(rtrim(number_format((float)$wert, 6, ',', '.'), '0'), ',');
|
||
}
|
||
|
||
/**
|
||
* Monatsnamen auf Deutsch. Nicht über date('F') oder IntlDateFormatter –
|
||
* date() ist immer englisch und intl ist keine Standarderweiterung. Der
|
||
* Budgetplaner und die Auswertungen greifen später ebenfalls hierauf zu.
|
||
*/
|
||
public const MONATE = [
|
||
1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April',
|
||
5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August',
|
||
9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember',
|
||
];
|
||
|
||
/** Kurzform für schmale Spalten (zwölf Monatsspalten im Budgetplaner). */
|
||
public const MONATE_KURZ = [
|
||
1 => 'Jan', 2 => 'Feb', 3 => 'Mär', 4 => 'Apr',
|
||
5 => 'Mai', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug',
|
||
9 => 'Sep', 10 => 'Okt', 11 => 'Nov', 12 => 'Dez',
|
||
];
|
||
|
||
public static function monat(int $monat, bool $kurz = false): string
|
||
{
|
||
$namen = $kurz ? self::MONATE_KURZ : self::MONATE;
|
||
return $namen[$monat] ?? (string)$monat;
|
||
}
|
||
|
||
/** Wochentagskürzel, Montag zuerst – wie MONATE nicht über date() oder intl. */
|
||
public const WOCHENTAGE = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||
|
||
public static function wochentag(?string $datum): string
|
||
{
|
||
if (!$datum) {
|
||
return '';
|
||
}
|
||
|
||
$zeit = strtotime(substr($datum, 0, 10));
|
||
if ($zeit === false) {
|
||
return '';
|
||
}
|
||
|
||
// date('N') zählt Montag als 1.
|
||
return self::WOCHENTAGE[(int)date('N', $zeit) - 1] ?? '';
|
||
}
|
||
|
||
public static function datum(?string $datum): string
|
||
{
|
||
if (!$datum) {
|
||
return '–';
|
||
}
|
||
$d = \DateTime::createFromFormat('Y-m-d', substr($datum, 0, 10));
|
||
return $d ? $d->format('d.m.Y') : $datum;
|
||
}
|
||
|
||
/** CSS-Klasse für Beträge: negative Werte werden rot dargestellt. */
|
||
public static function betragKlasse(mixed $wert): string
|
||
{
|
||
return (float)$wert < 0 ? 'betrag negativ' : 'betrag';
|
||
}
|
||
|
||
/**
|
||
* Das Symbol der Abgleichspalte im Kontoblatt. Vier Zustände, in dieser
|
||
* Reihenfolge zu lesen: gefunden, nicht gefunden, Abruf gescheitert – und
|
||
* der graue Kreis für alles, was mit dem Bargeld-Tool nichts zu tun hat.
|
||
*
|
||
* Die Zeichen stehen als Text und nicht als Grafik da: Sie erben damit
|
||
* Schriftgröße und – beim Ausrufezeichen ausdrücklich gewünscht – die
|
||
* Farbe des übrigen Textes, in jedem Farbschema.
|
||
*
|
||
* Eigenständige Methode statt einer Schleife im Kontoblatt, weil auch die
|
||
* Schnellzuordnung (kontoblatt.js) dasselbe Symbol braucht, ohne die Seite
|
||
* neu zu laden.
|
||
*/
|
||
public static function bargeldMarke(?string $status, ?string $hinweis): string
|
||
{
|
||
$hinweis = trim((string)$hinweis);
|
||
|
||
[$zeichen, $klasse, $standardtitel] = match ($status) {
|
||
'gefunden' => ['✓', 'gefunden', 'Im Bargeld-Tool gefunden'],
|
||
'fehlt' => ['✗', 'fehlt', 'Im Bargeld-Tool nicht gefunden'],
|
||
'fehler' => ['!', 'fehler', 'Der Abruf beim Bargeld-Tool ist gescheitert'],
|
||
default => ['●', 'neutral', 'Wird nicht mit dem Bargeld-Tool abgeglichen'],
|
||
};
|
||
|
||
return '<span class="bargeldmarke bargeld-' . $klasse . '" title="'
|
||
. self::h($hinweis !== '' ? $hinweis : $standardtitel) . '" role="img" aria-label="'
|
||
. self::h($standardtitel) . '">' . $zeichen . '</span>';
|
||
}
|
||
|
||
/**
|
||
* Farbschema für das data-thema-Attribut: 'hell', 'dunkel' oder '' für die
|
||
* Systemvorgabe. Fällt auf die Systemvorgabe zurück, wenn die Einstellung
|
||
* nicht lesbar ist – die Fehlerseite muss auch ohne Datenbank darstellbar
|
||
* bleiben.
|
||
*/
|
||
public static function thema(): string
|
||
{
|
||
try {
|
||
$thema = \Finanzmanager\Models\Einstellung::get('darstellung_thema', 'system');
|
||
} catch (\Throwable) {
|
||
return '';
|
||
}
|
||
|
||
return in_array($thema, ['hell', 'dunkel'], true) ? $thema : '';
|
||
}
|
||
}
|