diff --git a/admin/reports.php b/admin/reports.php
new file mode 100644
index 0000000..e43b056
--- /dev/null
+++ b/admin/reports.php
@@ -0,0 +1,174 @@
+requireAdmin();
+I18n::init();
+
+$db = Database::getInstance();
+$appTitle = $db->getSetting('app_title', 'UniFi Voucher System');
+
+// Zeitraum (Tage)
+$days = max(1, min(365, (int)($_GET['days'] ?? 30)));
+
+// CSV-Export
+if (isset($_GET['export'])) {
+ $auth->requireAdmin();
+ header('Content-Type: text/csv; charset=utf-8');
+ $fn = 'report-' . $_GET['export'] . '-' . date('Y-m-d') . '.csv';
+ header('Content-Disposition: attachment; filename="' . $fn . '"');
+ $out = fopen('php://output', 'w');
+ fprintf($out, "\xEF\xBB\xBF"); // UTF-8 BOM für Excel
+
+ if ($_GET['export'] === 'per_site') {
+ fputcsv($out, ['Site', 'Gesamt', 'Gültig', 'Verwendet', 'Abgelaufen']);
+ $rows = $db->fetchAll(
+ "SELECT s.name,
+ COUNT(v.id) total,
+ SUM(v.status='valid') valid,
+ SUM(v.status='used') used,
+ SUM(v.status='expired') expired
+ FROM sites s LEFT JOIN vouchers v ON v.site_id=s.id
+ GROUP BY s.id ORDER BY total DESC"
+ );
+ foreach ($rows as $r) fputcsv($out, [$r['name'], (int)$r['total'], (int)$r['valid'], (int)$r['used'], (int)$r['expired']]);
+ } elseif ($_GET['export'] === 'per_user') {
+ fputcsv($out, ['Benutzer', 'E-Mail', 'Voucher erstellt']);
+ $rows = $db->fetchAll(
+ "SELECT u.name, u.email, COUNT(v.id) c FROM users u
+ LEFT JOIN vouchers v ON v.user_id=u.id GROUP BY u.id ORDER BY c DESC"
+ );
+ foreach ($rows as $r) fputcsv($out, [$r['name'], $r['email'], (int)$r['c']]);
+ } else { // daily
+ fputcsv($out, ['Datum', 'Erstellte Voucher']);
+ $rows = $db->fetchAll(
+ "SELECT DATE(created_at) d, COUNT(*) c FROM vouchers
+ WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
+ GROUP BY DATE(created_at) ORDER BY d", [$days]
+ );
+ foreach ($rows as $r) fputcsv($out, [$r['d'], (int)$r['c']]);
+ }
+ fclose($out);
+ exit;
+}
+
+// Kennzahlen
+$totals = $db->fetchOne(
+ "SELECT COUNT(*) total,
+ SUM(status='valid') valid, SUM(status='used') used, SUM(status='expired') expired
+ FROM vouchers"
+);
+$inPeriod = (int)($db->fetchOne(
+ "SELECT COUNT(*) c FROM vouchers WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)", [$days]
+)['c'] ?? 0);
+
+$perSite = $db->fetchAll(
+ "SELECT s.name,
+ COUNT(v.id) total, SUM(v.status='valid') valid, SUM(v.status='used') used, SUM(v.status='expired') expired
+ FROM sites s LEFT JOIN vouchers v ON v.site_id=s.id GROUP BY s.id ORDER BY total DESC"
+);
+$perUser = $db->fetchAll(
+ "SELECT u.name, COUNT(v.id) c FROM users u LEFT JOIN vouchers v ON v.user_id=u.id
+ GROUP BY u.id HAVING c > 0 ORDER BY c DESC LIMIT 10"
+);
+$daily = $db->fetchAll(
+ "SELECT DATE(created_at) d, COUNT(*) c FROM vouchers
+ WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
+ GROUP BY DATE(created_at) ORDER BY d", [$days]
+);
+$chartLabels = array_map(fn($r) => date('d.m', strtotime($r['d'])), $daily);
+$chartData = array_map(fn($r) => (int)$r['c'], $daily);
+
+$csrf = $auth->getCsrfToken();
+$currentPage = 'reports';
+$adminBase = '';
+?>
+
+
+
+
+
+Reporting – = htmlspecialchars($appTitle) ?>
+
+
+
+
+
+📊 Reporting
+
+
+
+
+
= (int)$totals['total'] ?>
Vouchers gesamt
+
= (int)$totals['valid'] ?>
Gültig
+
= (int)$totals['used'] ?>
Verwendet
+
= $inPeriod ?>
In = $days ?> Tagen erstellt
+
+
+
+
Erstellte Voucher (= $days ?> Tage)
+
+
+
+
+
Pro Site
+
| Site | Gesamt | Gültig | Verwendet | Abgelaufen |
+
+ | = htmlspecialchars($r['name']) ?> | = (int)$r['total'] ?> | = (int)$r['valid'] ?> | = (int)$r['used'] ?> | = (int)$r['expired'] ?> |
+
+
+
+
+
+
Top-Nutzer
+
| Benutzer | Voucher erstellt |
+
+ | = htmlspecialchars($r['name'] ?? '–') ?> | = (int)$r['c'] ?> |
+
+ | Keine Daten |
+
+
+
+
+
+
+
+
diff --git a/includes/admin_nav.php b/includes/admin_nav.php
index a3588c2..93489d2 100644
--- a/includes/admin_nav.php
+++ b/includes/admin_nav.php
@@ -110,6 +110,9 @@ $lang = I18n::getLanguage();
= __('nav_audit_log') ?>
+
+ = __('nav_reports') ?>
+
= __('nav_settings') ?>
diff --git a/lang/de.php b/lang/de.php
index 406cc9c..aab548d 100644
--- a/lang/de.php
+++ b/lang/de.php
@@ -7,6 +7,7 @@ return [
'nav_vouchers' => 'Live Vouchers',
'nav_templates' => 'Voucher-Profile',
'nav_audit_log' => 'Audit-Log',
+ 'nav_reports' => 'Reporting',
'nav_settings' => 'Einstellungen',
'nav_api_keys' => 'API-Schlüssel',
'nav_security' => 'Sicherheit (2FA)',
diff --git a/lang/en.php b/lang/en.php
index 55ba900..d92a83a 100644
--- a/lang/en.php
+++ b/lang/en.php
@@ -7,6 +7,7 @@ return [
'nav_vouchers' => 'Live Vouchers',
'nav_templates' => 'Voucher Profiles',
'nav_audit_log' => 'Audit Log',
+ 'nav_reports' => 'Reporting',
'nav_settings' => 'Settings',
'nav_api_keys' => 'API Keys',
'nav_security' => 'Security (2FA)',