Files
deskhub/DeskHub-Webseite/staff/report_builder.php
T
2026-10-16 19:12:21 +00:00

179 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/_ui.php';
$staff = require_staff_login();
if (!has_permission($staff, 'view_reports')) {
flash('error', 'Für Berichte fehlt die erforderliche Berechtigung.');
redirect('/staff/dashboard.php');
}
$pdo = get_db();
$scope = staff_department_ids($staff); // null = alle Abteilungen
$dimensions = report_dimensions($pdo);
$metrics = report_metrics($pdo);
// ── Gespeicherte Berichte ───────────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'save') {
$name = trim($_POST['report_name'] ?? '');
if ($name === '') {
flash('error', 'Bitte einen Namen für den Bericht angeben.');
} else {
parse_str((string) ($_POST['return_query'] ?? ''), $cfgIn);
$cfg = report_clean_config($pdo, $cfgIn);
$now = db_now();
$pdo->prepare('INSERT INTO saved_reports (staff_id, name, config, is_shared, created_at, updated_at) VALUES (?,?,?,?,?,?)')
->execute([$staff['id'], mb_substr($name, 0, 120), json_encode($cfg, JSON_UNESCAPED_UNICODE), !empty($_POST['shared']) ? 1 : 0, $now, $now]);
flash('success', 'Bericht gespeichert.');
}
redirect('/staff/report_builder.php?' . ltrim((string) ($_POST['return_query'] ?? ''), '?'));
}
if ($action === 'delete') {
$row = $pdo->prepare('SELECT * FROM saved_reports WHERE id = ?');
$row->execute([(int) $_POST['id']]);
if (($r = $row->fetch()) && ((int) $r['staff_id'] === (int) $staff['id'] || $staff['role'] === 'admin')) {
$pdo->prepare('DELETE FROM saved_reports WHERE id = ?')->execute([$r['id']]);
flash('success', 'Bericht gelöscht.');
}
redirect('/staff/report_builder.php');
}
}
// ── Konfiguration: aus gespeichertem Bericht oder aus der URL ───────────────
$saved = $pdo->prepare('SELECT r.*, s.name AS owner FROM saved_reports r LEFT JOIN staff s ON s.id = r.staff_id WHERE r.staff_id = ? OR r.is_shared = 1 ORDER BY r.name');
$saved->execute([$staff['id']]);
$savedReports = $saved->fetchAll();
$cfgIn = $_GET;
if (isset($_GET['saved'])) {
foreach ($savedReports as $sr) {
if ((int) $sr['id'] === (int) $_GET['saved']) { $cfgIn = json_decode($sr['config'], true) ?: []; break; }
}
}
$cfg = report_clean_config($pdo, $cfgIn);
$report = report_run($pdo, $cfg, $scope);
$queryOf = fn(array $c) => http_build_query([
'dimension' => $c['dimension'], 'from' => $c['from'], 'to' => $c['to'], 'sort' => $c['sort'], 'desc' => $c['desc'] ? 1 : 0,
'metrics' => $c['metrics'], 'filters' => $c['filters'],
]);
if (($_GET['export'] ?? '') === 'csv') {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="bericht-' . $cfg['dimension'] . '-' . $cfg['from'] . '_' . $cfg['to'] . '.csv"');
echo report_to_csv($report);
exit;
}
$departments = $pdo->query('SELECT id, name FROM departments ORDER BY name')->fetchAll();
$priorities = $pdo->query('SELECT id, name FROM priorities ORDER BY sort_order')->fetchAll();
$agents = $pdo->query('SELECT id, name FROM staff WHERE is_active = 1 ORDER BY name')->fetchAll();
$topics = help_topic_options($pdo, false, false);
$fmtValue = function ($v, string $unit) {
if ($v === null) return '–';
return ($unit === '%' || $unit === 'Std' ? number_format((float) $v, 1, ',', '.') : (string) $v) . ($unit === '%' ? ' %' : ($unit === 'Std' ? ' Std' : ''));
};
$maxByMetric = [];
foreach (array_slice($report['columns'], 1) as $c) $maxByMetric[$c['key']] = max(1, ...array_map(fn($r) => (float) ($r[$c['key']] ?? 0), $report['rows'] ?: [[$c['key'] => 0]]));
$pageTitle = 'Bericht-Baukasten';
$activePage = 'report_builder';
require __DIR__ . '/_header.php';
?>
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
<h1 style="margin:0">Bericht-Baukasten</h1>
<div style="display:flex;gap:8px">
<a href="/staff/reports.php" class="btn secondary">Standard-Berichte</a>
<a href="/staff/report_builder.php?<?= e($queryOf($cfg)) ?>&export=csv" class="btn secondary">CSV exportieren</a>
</div>
</div>
<p class="muted">Wähle Gruppierung, Kennzahlen, Zeitraum und Filter – nach dem Erstellungsdatum der Tickets. Berichte lassen sich speichern und mit allen teilen.</p>
<?php if ($savedReports): ?>
<div class="card" style="padding:10px 14px;margin-bottom:12px">
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<strong style="font-size:.8rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">Gespeichert</strong>
<?php foreach ($savedReports as $sr): ?>
<span style="display:inline-flex;align-items:center;gap:2px">
<a class="btn secondary" style="padding:3px 12px;font-size:.82rem" href="/staff/report_builder.php?saved=<?= (int) $sr['id'] ?>" title="<?= (int) $sr['staff_id'] === (int) $staff['id'] ? 'Mein Bericht' : 'Geteilt von ' . e($sr['owner'] ?? '') ?>"><?= e($sr['name']) ?><?= $sr['is_shared'] ? ' ⇄' : '' ?></a>
<?php if ((int) $sr['staff_id'] === (int) $staff['id'] || $staff['role'] === 'admin'): ?>
<form method="post" style="margin:0" onsubmit="return confirm('Bericht löschen?')"><?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int) $sr['id'] ?>">
<button type="submit" title="Löschen" style="background:none;border:none;color:var(--muted);cursor:pointer;padding:0 4px">✕</button></form>
<?php endif; ?>
</span>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<form method="get" class="card">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;align-items:end">
<div><label for="dimension" style="margin-top:0">Gruppieren nach</label>
<select id="dimension" name="dimension"><?php foreach ($dimensions as $k => $d): ?><option value="<?= e($k) ?>" <?= $cfg['dimension'] === $k ? 'selected' : '' ?>><?= e($d[0]) ?></option><?php endforeach; ?></select></div>
<div><label for="from" style="margin-top:0">Von</label><input type="date" id="from" name="from" value="<?= e($cfg['from']) ?>"></div>
<div><label for="to" style="margin-top:0">Bis</label><input type="date" id="to" name="to" value="<?= e($cfg['to']) ?>"></div>
<div><label for="f_dept" style="margin-top:0">Abteilung</label>
<select id="f_dept" name="filters[departmentId]"><option value="">Alle</option><?php foreach ($departments as $d): ?><option value="<?= $d['id'] ?>" <?= ($cfg['filters']['departmentId'] ?? '') == $d['id'] ? 'selected' : '' ?>><?= e($d['name']) ?></option><?php endforeach; ?></select></div>
<div><label for="f_prio" style="margin-top:0">Priorität</label>
<select id="f_prio" name="filters[priorityId]"><option value="">Alle</option><?php foreach ($priorities as $p): ?><option value="<?= $p['id'] ?>" <?= ($cfg['filters']['priorityId'] ?? '') == $p['id'] ? 'selected' : '' ?>><?= e($p['name']) ?></option><?php endforeach; ?></select></div>
<div><label for="f_agent" style="margin-top:0">Mitarbeiter</label>
<select id="f_agent" name="filters[agentId]"><option value="">Alle</option><?php foreach ($agents as $a): ?><option value="<?= $a['id'] ?>" <?= ($cfg['filters']['agentId'] ?? '') == $a['id'] ? 'selected' : '' ?>><?= e($a['name']) ?></option><?php endforeach; ?></select></div>
<div><label for="f_topic" style="margin-top:0">Hilfethema</label>
<select id="f_topic" name="filters[helpTopicId]"><option value="">Alle</option><?php foreach ($topics as $t): ?><option value="<?= $t['id'] ?>" <?= ($cfg['filters']['helpTopicId'] ?? '') == $t['id'] ? 'selected' : '' ?>><?= e($t['path']) ?></option><?php endforeach; ?></select></div>
<div><label for="f_source" style="margin-top:0">Quelle</label>
<select id="f_source" name="filters[source]"><option value="">Alle</option><?php foreach (['web' => 'Web', 'email' => 'E-Mail', 'phone' => 'Telefon', 'staff' => 'Mitarbeiter', 'chat' => 'Live-Chat', 'api' => 'API'] as $k => $l): ?><option value="<?= $k ?>" <?= ($cfg['filters']['source'] ?? '') === $k ? 'selected' : '' ?>><?= $l ?></option><?php endforeach; ?></select></div>
</div>
<label style="margin-top:14px">Kennzahlen</label>
<div style="display:flex;flex-wrap:wrap;gap:6px 18px">
<?php foreach ($metrics as $k => $m): ?>
<label style="display:flex;align-items:center;gap:6px;font-weight:400;margin:0"><input type="checkbox" name="metrics[]" value="<?= e($k) ?>" style="width:auto" <?= in_array($k, $cfg['metrics'], true) ? 'checked' : '' ?>> <?= e($m[0]) ?></label>
<?php endforeach; ?>
</div>
<div style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;margin-top:12px">
<div><label for="sort" style="margin-top:0">Sortieren nach</label>
<select id="sort" name="sort"><option value="label" <?= $cfg['sort'] === 'label' ? 'selected' : '' ?>>Gruppe</option><?php foreach ($metrics as $k => $m): ?><option value="<?= e($k) ?>" <?= $cfg['sort'] === $k ? 'selected' : '' ?>><?= e($m[0]) ?></option><?php endforeach; ?></select></div>
<label style="display:flex;align-items:center;gap:6px;font-weight:400;margin:0 0 8px"><input type="checkbox" name="desc" value="1" style="width:auto" <?= $cfg['desc'] ? 'checked' : '' ?>> absteigend</label>
<button type="submit" class="btn">Bericht erstellen</button>
</div>
</form>
<div class="card" style="padding:0;overflow:auto">
<table>
<thead><tr><?php foreach ($report['columns'] as $c): ?><th><?= e($c['label']) ?></th><?php endforeach; ?></tr></thead>
<tbody>
<?php foreach ($report['rows'] as $row): ?>
<tr>
<td><strong><?= e((string) $row['label']) ?></strong></td>
<?php foreach (array_slice($report['columns'], 1) as $c): $v = $row[$c['key']]; ?>
<td>
<?= e($fmtValue($v, $c['unit'])) ?>
<?php if ($v !== null && $c['unit'] === ''): ?><div style="height:4px;background:var(--panel2,#eef1f5);border-radius:3px;margin-top:3px"><div style="height:100%;width:<?= (int) round(($v / $maxByMetric[$c['key']]) * 100) ?>%;background:var(--primary,#1f8fe0);border-radius:3px"></div></div><?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php if (!$report['rows']): ?><tr><td colspan="<?= count($report['columns']) ?>" class="muted">Keine Tickets im gewählten Zeitraum.</td></tr><?php else: ?>
<tr style="font-weight:700;background:var(--panel2,#f4f6f9)"><td>Gesamt</td>
<?php foreach (array_slice($report['columns'], 1) as $c): ?><td><?= e($fmtValue($report['totals'][$c['key']] ?? null, $c['unit'])) ?><?= $c['unit'] !== '' ? ' <span class="muted" style="font-weight:400;font-size:.72rem">Ø</span>' : '' ?></td><?php endforeach; ?></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<details class="card" style="margin-top:14px">
<summary style="cursor:pointer;font-weight:600">Diesen Bericht speichern</summary>
<form method="post" style="margin-top:10px;max-width:420px">
<?= csrf_field() ?>
<input type="hidden" name="action" value="save">
<input type="hidden" name="return_query" value="<?= e($queryOf($cfg)) ?>">
<label for="report_name" style="margin-top:0">Name</label>
<input type="text" id="report_name" name="report_name" maxlength="120" placeholder="z. B. Monatsbericht Support" required>
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-top:10px"><input type="checkbox" name="shared" value="1" style="width:auto"> Für alle Mitarbeiter sichtbar</label>
<div class="btn-row"><button type="submit" class="btn">Speichern</button></div>
</form>
</details>
<?php require __DIR__ . '/_footer.php'; ?>