Keine Daten vorhanden.

'; $palette = ['#0f6f6a', '#2563eb', '#d97706', '#dc2626', '#a855f7', '#16a34a', '#6b7280']; $max = max(max($data), 1); $count = count($data); $barWidth = 38; $gap = 16; $width = $count * ($barWidth + $gap) + $gap; $chartHeight = $height - 46; $svg = ""; $x = $gap; $i = 0; foreach ($data as $label => $value) { $barH = (int) round(($value / $max) * $chartHeight); $y = $chartHeight - $barH + 18; $color = $palette[$i % count($palette)]; $svg .= ""; $svg .= "" . (int) $value . ""; $svg .= "" . htmlspecialchars((string) $label) . ""; $x += $barWidth + $gap; $i++; } $svg .= ''; return $svg; } /** Mehrlinien-Diagramm als SVG. $buckets: [label => [serieKey => wert]]. $series: [serieKey => ['label'=>..,'color'=>..]] */ function svg_multiline_chart(array $buckets, array $series): string { if (!$buckets) return '

Keine Daten im gewählten Zeitraum.

'; $labels = array_keys($buckets); $n = count($labels); $width = max(640, $n * 48); $height = 260; $padLeft = 30; $padBottom = 36; $padTop = 16; $padRight = 16; $chartW = $width - $padLeft - $padRight; $chartH = $height - $padTop - $padBottom; $maxVal = 1; foreach ($buckets as $b) { foreach (array_keys($series) as $key) { $maxVal = max($maxVal, (int) ($b[$key] ?? 0)); } } $svg = ""; $svg .= ""; $stepX = $n > 1 ? $chartW / ($n - 1) : 0; foreach ($series as $key => $info) { $points = []; foreach ($labels as $i => $label) { $val = (int) ($buckets[$label][$key] ?? 0); $x = round($padLeft + $i * $stepX, 1); $y = round($padTop + $chartH - ($val / $maxVal) * $chartH, 1); $points[] = "{$x},{$y}"; } $svg .= ''; foreach ($labels as $i => $label) { $val = (int) ($buckets[$label][$key] ?? 0); $x = round($padLeft + $i * $stepX, 1); $y = round($padTop + $chartH - ($val / $maxVal) * $chartH, 1); $svg .= ""; } } $labelEvery = max(1, (int) ceil($n / 12)); foreach ($labels as $i => $label) { if ($i % $labelEvery !== 0 && $i !== $n - 1) continue; $x = round($padLeft + $i * $stepX, 1); $svg .= "" . htmlspecialchars((string) $label) . ""; } $svg .= ''; return $svg; } // --------------------------------------------------------------- // Zeitraum bestimmen // --------------------------------------------------------------- $preset = $_GET['range'] ?? '30d'; $today = new DateTime('today'); switch ($preset) { case '7d': $from = (clone $today)->modify('-6 days'); $to = clone $today; break; case 'thismonth': $from = new DateTime(date('Y-m-01')); $to = clone $today; break; case 'thisyear': $from = new DateTime(date('Y-01-01')); $to = clone $today; break; case 'custom': // Ungültige Eingaben (kein Datum) fallen auf den Standardzeitraum zurück, statt eine Fehlerseite zu erzeugen $parseDay = function (string $key, DateTime $fallback): DateTime { $raw = (string) ($_GET[$key] ?? ''); $d = $raw !== '' ? DateTime::createFromFormat('!Y-m-d', $raw) : false; return $d instanceof DateTime ? $d : $fallback; }; $from = $parseDay('from', (clone $today)->modify('-29 days')); $to = $parseDay('to', clone $today); break; case '30d': default: $preset = '30d'; $from = (clone $today)->modify('-29 days'); $to = clone $today; } if ($from > $to) { [$from, $to] = [$to, $from]; } $rangeFrom = $from->format('Y-m-d 00:00:00'); $rangeTo = $to->format('Y-m-d 23:59:59'); $rangeDays = (int) $from->diff($to)->days + 1; $useWeekly = $rangeDays > 45; // --------------------------------------------------------------- // Kennzahlen (gesamt, nicht zeitraumgefiltert – schneller Überblick) // --------------------------------------------------------------- [$deptFilterT, $deptParamsT] = dept_filter_sql($myDeptIds, 'department_id'); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE 1=1{$deptFilterT}"); $stmt->execute($deptParamsT); $totalTickets = (int) $stmt->fetchColumn(); $monthStart = date('Y-m-01 00:00:00'); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE resolved_at >= ?{$deptFilterT}"); $stmt->execute([$monthStart, ...$deptParamsT]); $resolvedThisMonth = (int) $stmt->fetchColumn(); $respTimes = []; $stmt = $pdo->prepare("SELECT created_at, first_responded_at FROM tickets WHERE first_responded_at IS NOT NULL{$deptFilterT}"); $stmt->execute($deptParamsT); foreach ($stmt->fetchAll() as $r) { $respTimes[] = (strtotime($r['first_responded_at']) - strtotime($r['created_at'])) / 3600; } $avgResponse = $respTimes ? array_sum($respTimes) / count($respTimes) : null; $resolTimes = []; $stmt = $pdo->prepare("SELECT created_at, resolved_at FROM tickets WHERE resolved_at IS NOT NULL{$deptFilterT}"); $stmt->execute($deptParamsT); foreach ($stmt->fetchAll() as $r) { $resolTimes[] = (strtotime($r['resolved_at']) - strtotime($r['created_at'])) / 3600; } $avgResolution = $resolTimes ? array_sum($resolTimes) / count($resolTimes) : null; // --------------------------------------------------------------- // Mehrlinien-Aktivitätsdiagramm für den gewählten Zeitraum // --------------------------------------------------------------- function bucket_key_for(string $datetime, bool $weekly): string { $d = new DateTime($datetime); return $weekly ? 'KW' . $d->format('W') : $d->format('d.m'); } $buckets = []; $cursor = clone $from; while ($cursor <= $to) { $key = $useWeekly ? 'KW' . $cursor->format('W') : $cursor->format('d.m'); if (!isset($buckets[$key])) { $buckets[$key] = ['erstellt' => 0, 'zugewiesen' => 0, 'geschlossen' => 0, 'geloescht' => 0, 'ueberfaellig' => 0]; } $cursor->modify('+1 day'); } $stmt = $pdo->prepare("SELECT created_at FROM tickets WHERE created_at BETWEEN ? AND ?{$deptFilterT}"); $stmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); foreach ($stmt->fetchAll() as $r) { $k = bucket_key_for($r['created_at'], $useWeekly); if (isset($buckets[$k])) $buckets[$k]['erstellt']++; } $stmt = $pdo->prepare( "SELECT al.created_at FROM activity_log al JOIN tickets t ON t.id = al.ticket_id WHERE al.action LIKE 'Mitarbeiter zugewiesen:%' AND al.created_at BETWEEN ? AND ?{$deptFilterT}" ); $stmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); foreach ($stmt->fetchAll() as $r) { $k = bucket_key_for($r['created_at'], $useWeekly); if (isset($buckets[$k])) $buckets[$k]['zugewiesen']++; } $stmt = $pdo->prepare("SELECT closed_at FROM tickets WHERE closed_at BETWEEN ? AND ?{$deptFilterT}"); $stmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); foreach ($stmt->fetchAll() as $r) { $k = bucket_key_for($r['closed_at'], $useWeekly); if (isset($buckets[$k])) $buckets[$k]['geschlossen']++; } [$deptFilterDel, $deptParamsDel] = dept_filter_sql($myDeptIds, 'department_id'); $stmt = $pdo->prepare("SELECT deleted_at FROM deleted_tickets_log WHERE deleted_at BETWEEN ? AND ?{$deptFilterDel}"); $stmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsDel)); foreach ($stmt->fetchAll() as $r) { $k = bucket_key_for($r['deleted_at'], $useWeekly); if (isset($buckets[$k])) $buckets[$k]['geloescht']++; } $stmt = $pdo->prepare( "SELECT al.created_at FROM activity_log al JOIN tickets t ON t.id = al.ticket_id WHERE al.action LIKE 'SLA %' AND al.created_at BETWEEN ? AND ?{$deptFilterT}" ); $stmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); foreach ($stmt->fetchAll() as $r) { $k = bucket_key_for($r['created_at'], $useWeekly); if (isset($buckets[$k])) $buckets[$k]['ueberfaellig']++; } $chartSeries = [ 'erstellt' => ['label' => 'Erstellt', 'color' => '#16a34a'], 'zugewiesen' => ['label' => 'Zugewiesen', 'color' => '#2563eb'], 'geschlossen' => ['label' => 'Geschlossen', 'color' => '#0f6f6a'], 'geloescht' => ['label' => 'Gelöscht', 'color' => '#a855f7'], 'ueberfaellig' => ['label' => 'Überfällig', 'color' => '#dc2626'], ]; // --------------------------------------------------------------- // Nach Status / Priorität (Gesamtüberblick, unabhängig vom Zeitraum) // --------------------------------------------------------------- $byStatus = []; foreach (['open', 'pending', 'answered', 'on_hold', 'resolved', 'closed'] as $s) { [$label] = status_label($s); $byStatus[$label] = 0; } $stmt = $pdo->prepare("SELECT status, COUNT(*) c FROM tickets WHERE 1=1{$deptFilterT} GROUP BY status"); $stmt->execute($deptParamsT); foreach ($stmt->fetchAll() as $r) { [$label] = status_label($r['status']); $byStatus[$label] = (int) $r['c']; } $byPrio = []; $stmt = $pdo->prepare( "SELECT p.name, p.sort_order, COUNT(t.id) c FROM priorities p LEFT JOIN tickets t ON t.priority_id = p.id{$deptFilterT} GROUP BY p.id, p.name, p.sort_order ORDER BY p.sort_order" ); $stmt->execute($deptParamsT); foreach ($stmt->fetchAll() as $r) { $byPrio[$r['name']] = (int) $r['c']; } // --------------------------------------------------------------- // SLA-Einhaltung im Zeitraum // --------------------------------------------------------------- $slaStmt = $pdo->prepare( "SELECT COUNT(*) AS total, SUM(CASE WHEN resolved_at <= resolution_due_at THEN 1 ELSE 0 END) AS on_time FROM tickets WHERE status IN ('resolved','closed') AND resolved_at IS NOT NULL AND resolution_due_at IS NOT NULL AND resolved_at BETWEEN ? AND ?{$deptFilterT}" ); $slaStmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); $slaRow = $slaStmt->fetch(); $slaTotal = (int)($slaRow['total'] ?? 0); $slaOk = (int)($slaRow['on_time'] ?? 0); $slaRate = $slaTotal > 0 ? round($slaOk / $slaTotal * 100) : null; // Mitarbeiter-Performance im Zeitraum $agentPerf = []; if ($staff['role'] === 'admin') { $avgResponseHours = report_avg_response_hours_expression( $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) ); $apStmt = $pdo->prepare( "SELECT s.name, COUNT(*) AS total, SUM(CASE WHEN t.status IN ('resolved','closed') THEN 1 ELSE 0 END) AS resolved, AVG(CASE WHEN t.first_responded_at IS NOT NULL THEN {$avgResponseHours} END) AS avg_resp FROM tickets t JOIN staff s ON s.id = t.assigned_staff_id WHERE t.created_at BETWEEN ? AND ?{$deptFilterT} GROUP BY t.assigned_staff_id, s.name ORDER BY resolved DESC LIMIT 10" ); $apStmt->execute(array_merge([$rangeFrom, $rangeTo], $deptParamsT)); $agentPerf = $apStmt->fetchAll(); } // Chart.js-Daten für Zeitreihe $chartLabels = array_keys($buckets); $chartCreated = array_column(array_values($buckets), 'erstellt'); $chartClosed = array_column(array_values($buckets), 'geschlossen'); // --------------------------------------------------------------- // Statistik-Tabellen je Abteilung / Thema / Agent // --------------------------------------------------------------- $deptRows = build_report_table($pdo, 'department_id', $rangeFrom, $rangeTo, $myDeptIds); $topicRows = build_report_table($pdo, 'help_topic_id', $rangeFrom, $rangeTo, $myDeptIds); $departments = $pdo->query('SELECT id, name FROM departments')->fetchAll(PDO::FETCH_KEY_PAIR); $agentNames = $pdo->query('SELECT id, name FROM staff')->fetchAll(PDO::FETCH_KEY_PAIR); $helpTopics = $pdo->query('SELECT id, name FROM help_topics')->fetchAll(PDO::FETCH_KEY_PAIR); function render_report_table(array $rows, array $labelMap, string $noneLabel): string { if (!$rows) return '

Keine Daten im gewählten Zeitraum.

'; ob_start(); ?> $r): ?>
BezeichnungGeöffnetZugewiesenÜberfälligGeschlossen Wieder geöffnetGelöschtServicezeit (Std)Reaktionszeit (Std)
prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed'){$deptFilterT}"); $stmt->execute($deptParamsT); $dashOpenTickets = (int) $stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id = ? AND status NOT IN ('resolved','closed'){$deptFilterT}"); $stmt->execute(array_merge([$staff['id']], $deptParamsT)); $dashMine = (int) $stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id IS NULL AND status NOT IN ('resolved','closed'){$deptFilterT}"); $stmt->execute($deptParamsT); $dashUnassigned = (int) $stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND (is_overdue = 1 OR (sla_paused_at IS NULL AND resolution_due_at IS NOT NULL AND resolution_due_at < ?)){$deptFilterT}"); $stmt->execute(array_merge([$now], $deptParamsT)); $dashOverdue = (int) $stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status = 'open'{$deptFilterT}"); $stmt->execute($deptParamsT); $dashUnanswered = (int) $stmt->fetchColumn(); $mentionStmt = $pdo->prepare('SELECT COUNT(*) FROM staff_mentions WHERE mentioned_staff_id=? AND is_read=0'); $mentionStmt->execute([$staff['id']]); $unreadMentions = (int) $mentionStmt->fetchColumn(); $tsColors = ['available'=>'#22c55e','busy'=>'#f59e0b','break'=>'#6366f1','offline'=>'#94a3b8']; $tsLabels = ['available'=>'Verfügbar','busy'=>'Beschäftigt','break'=>'Pause','offline'=>'Offline']; $teamStatus = []; $myAvailStatus = 'available'; try { staff_run_auto_offline($pdo); $tsStmt = $pdo->query( "SELECT s.id, s.name, s.is_on_vacation, sa.status AS avail_status, sa.note AS avail_note FROM staff s LEFT JOIN staff_availability sa ON sa.staff_id = s.id WHERE s.is_active = 1 ORDER BY s.name" ); foreach ($tsStmt->fetchAll() as $row) { if ($row['id'] === $staff['id']) { $myAvailStatus = $row['avail_status'] ?? 'available'; } else { $teamStatus[] = $row; } } } catch (Throwable $e) {} $pageTitle = 'Übersicht'; $activePage = 'reports'; require __DIR__ . '/_header.php'; ?> 0); $isAdmin = $staff['role'] === 'admin'; $pageTabs = ['overview' => 'Überblick', 'activity' => 'Verlauf', 'analysis' => 'Auswertung']; if ($showCsat) $pageTabs['csat'] = 'Zufriedenheit'; $tab = (string) ($_GET['tab'] ?? 'overview'); if (!isset($pageTabs[$tab])) $tab = 'overview'; $__latestVersion = null; $__hasUpdate = false; if ($isAdmin && $tab === 'overview') { // der Hinweis steht nur im Überblick // Update-Check: neuestes Release von Gitea holen. Das Ergebnis wird eine Stunde gemerkt (bei Fehlern 15 Minuten), damit nicht jeder Aufruf der Seite wartet. $__cache = json_decode((string) get_setting('update_check_cache', ''), true); if (is_array($__cache) && isset($__cache['at']) && time() - (int) $__cache['at'] < (empty($__cache['tag']) ? 900 : 3600)) { $__latestVersion = $__cache['tag'] ?: null; } else { if (function_exists('curl_init')) { $__ch = curl_init('https://git.viper.ipv64.net/api/v1/repos/M_Viper/deskhub/releases?limit=1'); curl_setopt_array($__ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 4, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_HTTPHEADER => update_http_headers()]); $__resp = curl_exec($__ch); curl_close($__ch); $__rel = @json_decode($__resp, true); if (!empty($__rel[0]['tag_name'])) $__latestVersion = $__rel[0]['tag_name']; } set_setting('update_check_cache', json_encode(['at' => time(), 'tag' => $__latestVersion])); } $__hasUpdate = $__latestVersion && version_compare($__latestVersion, DESKHUB_VERSION, '>'); } $rangeLabel = $from->format('d.m.Y') . ' – ' . $to->format('d.m.Y'); $exportParams = ['range' => $preset, 'from' => $from->format('Y-m-d'), 'to' => $to->format('Y-m-d')]; ?>

Willkommen,

🔄 Updates
prepare("$ovBase WHERE t.assigned_staff_id = ? AND $ovOpen AND $ovVisible{$ovDept} ORDER BY t.resolution_due_at IS NULL, t.resolution_due_at ASC, t.updated_at DESC LIMIT 6"); $q->execute([$staff['id'], ...$ovDeptParams]); $ovMine = $q->fetchAll(); $q = $pdo->prepare("$ovBase WHERE t.assigned_staff_id IS NULL AND $ovOpen AND $ovVisible{$ovDept} ORDER BY t.created_at DESC LIMIT 6"); $q->execute($ovDeptParams); $ovUnassigned = $q->fetchAll(); $todayStart = date('Y-m-d 00:00:00'); $q = $pdo->prepare("SELECT COUNT(*) FROM tickets t WHERE t.created_at >= ?{$ovDept}"); $q->execute([$todayStart, ...$ovDeptParams]); $ovTodayNew = (int) $q->fetchColumn(); $q = $pdo->prepare("SELECT COUNT(*) FROM tickets t WHERE t.resolved_at >= ?{$ovDept}"); $q->execute([$todayStart, ...$ovDeptParams]); $ovTodayDone = (int) $q->fetchColumn(); $soon = date('Y-m-d H:i:s', time() + 4 * 3600); $q = $pdo->prepare("SELECT COUNT(*) FROM tickets t WHERE $ovOpen AND t.sla_paused_at IS NULL AND t.resolution_due_at IS NOT NULL AND t.resolution_due_at >= ? AND t.resolution_due_at < ?{$ovDept}"); $q->execute([db_now(), $soon, ...$ovDeptParams]); $ovAtRisk = (int) $q->fetchColumn(); // Letzte 7 Tage: neue und gelöste Tickets je Tag $weekStart = date('Y-m-d 00:00:00', strtotime('-6 days')); for ($i = 6; $i >= 0; $i--) { $day = date('Y-m-d', strtotime("-$i days")); $ovWeek[$day] = ['new' => 0, 'done' => 0]; } $q = $pdo->prepare("SELECT t.created_at FROM tickets t WHERE t.created_at >= ?{$ovDept}"); $q->execute([$weekStart, ...$ovDeptParams]); foreach ($q->fetchAll(PDO::FETCH_COLUMN) as $c) { $k = substr((string) $c, 0, 10); if (isset($ovWeek[$k])) $ovWeek[$k]['new']++; } $q = $pdo->prepare("SELECT t.resolved_at FROM tickets t WHERE t.resolved_at >= ?{$ovDept}"); $q->execute([$weekStart, ...$ovDeptParams]); foreach ($q->fetchAll(PDO::FETCH_COLUMN) as $c) { $k = substr((string) $c, 0, 10); if (isset($ovWeek[$k])) $ovWeek[$k]['done']++; } $q = $pdo->prepare("SELECT ta.id, ta.title, ta.due_at, ta.status, t.ticket_number FROM tasks ta LEFT JOIN tickets t ON t.id = ta.ticket_id WHERE ta.assigned_to = ? AND ta.status != 'done' ORDER BY ta.due_at IS NULL, ta.due_at ASC, ta.created_at DESC LIMIT 5"); $q->execute([$staff['id']]); $ovTasks = $q->fetchAll(); $q = $pdo->prepare("SELECT d.name, COUNT(*) AS n FROM tickets t JOIN departments d ON d.id = t.department_id WHERE $ovOpen{$ovDept} GROUP BY d.id, d.name ORDER BY n DESC LIMIT 8"); $q->execute($ovDeptParams); $ovDepts = $q->fetchAll(); $q = $pdo->prepare("SELECT a.action, a.created_at, s.name AS staff_name, t.ticket_number, t.subject FROM activity_log a JOIN tickets t ON t.id = a.ticket_id LEFT JOIN staff s ON s.id = a.staff_id WHERE 1=1{$ovDept} ORDER BY a.created_at DESC, a.id DESC LIMIT 8"); $q->execute($ovDeptParams); $ovFeed = $q->fetchAll(); } catch (Throwable $e) { error_log('Übersicht: ' . $e->getMessage()); } /** Eine Ticketzeile für die Listen im Überblick */ function ov_ticket_row(array $t): string { $over = !in_array($t['status'], ['resolved', 'closed'], true) && (!empty($t['is_overdue']) || (empty($t['sla_paused_at']) && !empty($t['resolution_due_at']) && strtotime((string) $t['resolution_due_at']) < time())); $due = !empty($t['sla_paused_at']) ? 'Pausiert' : (!empty($t['resolution_due_at']) ? time_until($t['resolution_due_at']) : ''); $color = preg_match('/^#[0-9a-fA-F]{3,8}$/', (string) $t['priority_color']) ? $t['priority_color'] : '#94a3b8'; return '' . '' . '' . e($t['subject']) . '' . '#' . e($t['ticket_number']) . ' · ' . e($t['requester_name']) . ' · ' . e($t['department_name']) . '' . ($due !== '' ? '' . e($due) . '' : '') . ''; } $ovWeekMax = 1; foreach ($ovWeek as $__d) { $ovWeekMax = max($ovWeekMax, $__d['new'], $__d['done']); } $ovWeekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; $ovDeptMax = 1; foreach ($ovDepts as $__d) { $ovDeptMax = max($ovDeptMax, (int) $__d['n']); } ?>
>
🆕
Update verfügbar: Version
Aktuell installiert: v
Zum Update-Bereich →

Jetzt zu tun

Offene Tickets
Mir zugewiesen
Nicht zugewiesen
Überfällig
Unbeantwortet
Frist in < 4 Std
0): ?>
🔔 Neue Erwähnungen

Meine fälligsten Tickets

Alle anzeigen →
🎉 Nichts offen, was dir zugewiesen ist.

Nicht zugewiesen 0): ?>

Alle anzeigen →
✅ Alles ist verteilt.

Heute & letzte 7 Tage

neu heute
gelöst heute
$__v): ?>
NeuGelöst

Meine Aufgaben

Alle anzeigen →
Keine offenen Aufgaben.
· #

Offen nach Abteilung

Keine offenen Tickets.

Letzte Aktivität

Noch keine Aktivität.
· · #

Team

Mein Status: $sc): $active = $myAvailStatus === $sk; ?>
Team:

Gesamtzahlen

Tickets gesamt
Gelöst diesen Monat
Ø Erstreaktion
Ø Lösungszeit
>

Ticketaktivität ()

0): ?>
SLA-Einhaltung im Zeitraum
%
von Tickets innerhalb der Frist gelöst
Im ZeitlimitÜberfällig
✓ ✗

Nach Status

Nach Priorität

>

Statistiken ()

0 ? (int) round((($csat['dist'][4] ?? 0) + ($csat['dist'][5] ?? 0)) * 100 / $csatAnswered) : null; $csatAvgColor = $csat['avg'] === null ? 'var(--muted)' : ($csat['avg'] >= 4.5 ? '#16a34a' : ($csat['avg'] >= 3.5 ? '#f59e0b' : '#ef4444')); $csatWithComment = 0; foreach ($csat['recent'] ?? [] as $__r) { if ($__r['comment'] !== '') $csatWithComment++; } ?>
>
⭐

Noch keine Abfragen im Zeitraum

Sobald Kunden nach einem geschlossenen oder gelösten Ticket um eine Bewertung gebeten wurden, erscheint die Auswertung hier.

Ø Bewertung
/ 5
Zufrieden (4–5 ★)
aller Antworten
Antworten
von Abfragen
Rücklaufquote

Verteilung

= 1; $i--): $cnt = (int) $csat['dist'][$i]; $pct = $csatAnswered > 0 ? (int) round($cnt * 100 / $csatAnswered) : 0; ?>
( %)

Nach Mitarbeiter

Noch keine Bewertungen zugeordnet.

= 4.5 ? '#16a34a' : ($row['avg'] >= 3.5 ? '#f59e0b' : '#ef4444'); ?>
★ ·

Letzte Bewertungen

0): ?>
#
„“
query('SELECT name, color FROM priorities ORDER BY sort_order')->fetchAll() as $pc) { $prioColorMap[$pc['name']] = $pc['color']; } $jsChartLabels = json_encode($chartLabels); $jsChartCreated = json_encode($chartCreated); $jsChartClosed = json_encode($chartClosed); $jsStatusLabels = json_encode(array_keys($byStatus)); $jsStatusData = json_encode(array_values($byStatus)); $jsPrioLabels = json_encode(array_keys($byPrio)); $jsPrioData = json_encode(array_values($byPrio)); $jsPrioColors = json_encode($prioColorMap); ?>