Quelltext des aktuellen Standes
<?php
$this->layout('layouts/main', [
'page_title' => 'Budget-Auswertung',
'current_route' => 'budget',
'full_width' => true,
'breadcrumbs' => [],
]);
// Maximalwert für die Balkenskalierung
$maxVal = 0.0;
foreach ($breakdown as $row) {
$maxVal = max($maxVal, (float)$row['planned'], (float)$row['actual']);
}
$sumPlanned = array_sum(array_map(static fn($r) => (float)$r['planned'], $breakdown));
$sumActual = array_sum(array_map(static fn($r) => (float)$r['actual'], $breakdown));
?>
<div class="br-wrap">
<div class="content-header">
<div>
<h1><?= $this->te('Budget-Auswertung') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/budget"><?= $this->te('Budget') ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Auswertung') ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="/budget" class="btn btn-default">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
<?= $this->te('Zurück') ?>
</a>
</div>
</div>
<div class="page-body">
<!-- Filter -->
<div class="card card-outline card-primary mb-3">
<div class="filter-card-body">
<form action="/budget/reports" method="GET" class="br-filter-form">
<label for="f-1599-year" style="font-weight:600;color:var(--gray-700);font-size:0.85rem;"><?= $this->te('Geschäftsjahr:') ?></label>
<select id="f-1599-year" name="year" class="form-control" style="width:auto;" onchange="this.form.submit()">
<option value=""><?= $this->te('Alle Jahre') ?></option>
<?php foreach ($fiscal_years as $fy): ?>
<option value="<?= (int)$fy['fiscal_year'] ?>"
<?= $year_filter === (int)$fy['fiscal_year'] ? 'selected' : '' ?>>
<?= (int)$fy['fiscal_year'] ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
</div>
<!-- KPI -->
<div class="kpi-row">
<div class="kpi-col">
<div class="small-box bg-info">
<div class="inner"><h3><?= (int)$stats['active_budgets'] ?></h3><p><?= $this->te('Aktive Budgets') ?></p></div>
<div class="icon"><i class="fas fa-chart-bar"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-primary">
<div class="inner"><h3><?= $this->money($sumPlanned, 0) ?></h3><p><?= $this->te('Geplant (gefiltert)') ?></p></div>
<div class="icon"><i class="fas fa-euro-sign"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box <?= $sumActual > $sumPlanned ? 'bg-danger' : 'bg-success' ?>">
<div class="inner"><h3><?= $this->money($sumActual, 0) ?></h3><p><?= $this->te('Tatsächlich (gefiltert)') ?></p></div>
<div class="icon"><i class="fas fa-receipt"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-secondary">
<div class="inner"><h3><?= $sumPlanned > 0 ? (int)round($sumActual / $sumPlanned * 100) : 0 ?>%</h3><p><?= $this->te('Gesamtauslastung') ?></p></div>
<div class="icon"><i class="fas fa-percent"></i></div>
</div>
</div>
</div>
<!-- Kategorie-Aufschlüsselung -->
<div class="br-card">
<div class="br-card-head">
<h2 class="br-card-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><path d="M3 3v18h18"/><path d="M18 17V9M13 17V5M8 17v-3"/></svg>
<?= $this->te('Ausgaben nach Kategorie (Plan vs. Ist)') ?>
</h2>
</div>
<?php if (empty($breakdown)): ?>
<p class="br-empty"><?= $this->te('Keine Daten für den gewählten Zeitraum.') ?></p>
<?php else: ?>
<div class="br-chart">
<?php foreach ($breakdown as $row):
$planned = (float)$row['planned'];
$actual = (float)$row['actual'];
$pPct = $maxVal > 0 ? round($planned / $maxVal * 100, 1) : 0;
$aPct = $maxVal > 0 ? round($actual / $maxVal * 100, 1) : 0;
$over = $actual > $planned && $planned > 0;
?>
<div class="br-row">
<div class="br-row-label">
<span class="br-dot" style="background:<?= $this->e($row['category_color']) ?>"></span>
<?= $this->e($row['category_name']) ?>
<small>(<?= (int)$row['position_count'] ?>)</small>
</div>
<div class="br-bars">
<div class="br-bar-line">
<div class="br-bar br-bar-plan" style="width:<?= $pPct ?>%"></div>
<span class="br-bar-val"><?= $this->money($planned, 2) ?></span>
</div>
<div class="br-bar-line">
<div class="br-bar <?= $over ? 'br-bar-over' : 'br-bar-actual' ?>" style="width:<?= $aPct ?>%"></div>
<span class="br-bar-val <?= $over ? 'br-over' : '' ?>"><?= $this->money($actual, 2) ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="br-legend">
<span><span class="br-key br-bar-plan"></span> <?= $this->te('Geplant') ?></span>
<span><span class="br-key br-bar-actual"></span> <?= $this->te('Tatsächlich') ?></span>
<span><span class="br-key br-bar-over"></span> <?= $this->te('Überschritten') ?></span>
</div>
<?php endif; ?>
</div>
</div><!-- /page-body -->
</div><!-- /br-wrap -->
<style>
.br-wrap { display:flex; flex-direction:column; }
.br-filter-form { display:flex; gap:0.5rem; align-items:center; }
.br-card { background:var(--color-bg); border-radius:0.25rem; border:1px solid rgba(0,0,0,.125); border-top:3px solid var(--bs-primary); box-shadow:0 1px 3px rgba(0,0,0,.1); }
.br-card-head { padding:0.875rem 1.25rem; border-bottom:1px solid var(--gray-100); }
.br-card-title { display:flex; align-items:center; gap:0.45rem; margin:0; font-size:0.82rem; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:var(--gray-700); }
.br-empty { padding:2rem; color:var(--gray-400); text-align:center; margin:0; }
.br-chart { padding:1.25rem; display:flex; flex-direction:column; gap:1rem; }
.br-row { display:grid; grid-template-columns:200px 1fr; gap:1rem; align-items:center; }
.br-row-label { font-size:0.85rem; font-weight:600; color:var(--gray-700); display:flex; align-items:center; gap:0.4rem; }
.br-row-label small { color:var(--gray-400); font-weight:400; }
.br-dot { width:11px; height:11px; border-radius:50%; flex-shrink:0; }
.br-bars { display:flex; flex-direction:column; gap:0.35rem; }
.br-bar-line { display:flex; align-items:center; gap:0.5rem; }
.br-bar { height:14px; border-radius:3px; min-width:2px; transition:width .3s; }
.br-bar-plan { background:var(--blue-300); }
.br-bar-actual { background:var(--green-600); }
.br-bar-over { background:var(--red-600); }
.br-bar-val { font-size:0.78rem; color:var(--gray-500); font-variant-numeric:tabular-nums; white-space:nowrap; }
.br-over { color:var(--red-600); font-weight:600; }
.br-legend { display:flex; gap:1.5rem; padding:0 1.25rem 1.25rem; font-size:0.78rem; color:var(--gray-500); }
.br-legend span { display:inline-flex; align-items:center; gap:0.35rem; }
.br-key { display:inline-block; width:14px; height:10px; border-radius:2px; }
@media (max-width:768px) {
.br-row { grid-template-columns:1fr; gap:0.4rem; }
}
</style>