179 lines
12 KiB
PHP
179 lines
12 KiB
PHP
<?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'; ?>
|