$category_names[$cat] ?? 'No category', 'cat' => $cat, 'opened' => intval($row['opened']), 'resolved' => intval($row['resolved']), 'urgent' => intval($row['urgent']), 'seconds' => 0, 'resolve' => null]; } $resolve_times = []; $sql = mysqli_query($mysqli, "SELECT ticket_category AS cat, TIMESTAMPDIFF(SECOND, ticket_created_at, ticket_resolved_at) AS s FROM tickets WHERE $filter AND $opened_in AND ticket_resolved_at IS NOT NULL"); while ($row = mysqli_fetch_assoc($sql)) { $resolve_times[intval($row['cat'])][] = floatval($row['s']); } $sql = mysqli_query($mysqli, "SELECT ticket_category AS cat, SUM(TIME_TO_SEC(ticket_reply_time_worked)) AS s FROM ticket_replies JOIN tickets ON ticket_reply_ticket_id = ticket_id WHERE $filter AND $opened_in AND ticket_reply_archived_at IS NULL GROUP BY ticket_category"); while ($row = mysqli_fetch_assoc($sql)) { if (isset($categories[intval($row['cat'])])) { $categories[intval($row['cat'])]['seconds'] = intval($row['s']); } } foreach ($categories as $cat => &$row) { $row['resolve'] = reportMedian($resolve_times[$cat] ?? []); $row['share'] = reportShare($row['opened'], $opened, 1); } unset($row); uasort($categories, function ($a, $b) { return $b['opened'] <=> $a['opened']; }); $categories = array_values($categories); // Weekday by hour, of what was opened $heat = []; $sql = mysqli_query($mysqli, "SELECT WEEKDAY(ticket_created_at) + 1 AS d, HOUR(ticket_created_at) AS h, COUNT(ticket_id) AS n FROM tickets WHERE $filter AND $opened_in GROUP BY d, h"); while ($row = mysqli_fetch_assoc($sql)) { $heat[intval($row['d'])][intval($row['h'])] = intval($row['n']); } // Who raises the most $top_clients = []; if (!$client_id) { $sql = mysqli_query($mysqli, "SELECT ticket_client_id AS client_id, client_name, client_abbreviation, COUNT(ticket_id) AS n FROM tickets JOIN clients ON ticket_client_id = client_id WHERE $filter AND $opened_in GROUP BY ticket_client_id, client_name, client_abbreviation ORDER BY n DESC LIMIT 8"); while ($row = mysqli_fetch_assoc($sql)) { $top_clients[] = $row; } } $top_contacts = []; $sql = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_client_id, client_name, COUNT(ticket_id) AS n FROM tickets JOIN contacts ON ticket_contact_id = contact_id LEFT JOIN clients ON contact_client_id = client_id WHERE $filter AND $opened_in GROUP BY contact_id, contact_name, contact_client_id, client_name ORDER BY n DESC LIMIT 8"); while ($row = mysqli_fetch_assoc($sql)) { $top_contacts[] = $row; } $category_columns = [ 'category' => ['label' => 'Category'], 'opened' => ['label' => 'Opened', 'type' => 'count'], 'share' => ['label' => 'Share', 'type' => 'percent', 'class' => 'text-body-secondary'], 'urgent' => ['label' => 'Urgent or high', 'type' => 'count'], 'resolved' => ['label' => 'Resolved', 'type' => 'count'], 'resolve' => ['label' => 'Typical time to resolve', 'type' => 'duration', 'title' => 'The middle ticket - half took longer, half less'], 'seconds' => ['label' => 'Time logged', 'type' => 'hours'], ]; if (reportExporting('tickets')) { $tickets = []; $sql = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_category, ticket_priority, ticket_source, ticket_status_name, ticket_created_at, ticket_first_response_at, ticket_resolved_at, client_name, user_name, (SELECT SUM(TIME_TO_SEC(ticket_reply_time_worked)) FROM ticket_replies WHERE ticket_reply_ticket_id = ticket_id AND ticket_reply_archived_at IS NULL) AS seconds FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN users ON ticket_assigned_to = user_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id WHERE $filter AND $opened_in ORDER BY ticket_created_at ASC"); while ($row = mysqli_fetch_assoc($sql)) { $row['category'] = $category_names[intval($row['ticket_category'])] ?? ''; $row['response'] = $row['ticket_first_response_at'] ? strtotime($row['ticket_first_response_at']) - strtotime($row['ticket_created_at']) : null; $tickets[] = $row; } reportCsv('Tickets ' . $period['start'] . ' to ' . $period['end'], [ 'ticket' => ['label' => 'Ticket', 'csv' => function ($row) { return ticketReference($row); }], 'ticket_subject' => ['label' => 'Subject'], 'client_name' => ['label' => 'Client'], 'category' => ['label' => 'Category'], 'ticket_priority' => ['label' => 'Priority'], 'ticket_source' => ['label' => 'Source'], 'ticket_status_name' => ['label' => 'Status'], 'user_name' => ['label' => 'Assigned to'], 'ticket_created_at' => ['label' => 'Opened'], 'response' => ['label' => 'Hours to first reply', 'type' => 'hours'], 'ticket_resolved_at' => ['label' => 'Resolved'], 'seconds' => ['label' => 'Hours logged', 'type' => 'hours'], ], $tickets); } if (reportExporting()) { reportCsv('Tickets by Category ' . $period['start'] . ' to ' . $period['end'], $category_columns, $categories, ['category' => 'Total', 'opened' => $opened]); } require_once "includes/inc_report_layout.php"; $ticket_list = '/agent/tickets.php?' . http_build_query(array_filter(['client_id' => $client_id ?: null, 'assigned' => $agent_id ?: null])); $source_icons = ['Email' => 'envelope', 'Portal' => 'globe', 'Text' => 'sms', 'Agent' => 'headset', 'API' => 'plug', 'Recurring' => 'redo-alt', 'Project' => 'project-diagram']; $priority_tones = ['Urgent' => 'dark', 'High' => 'danger', 'Medium' => 'warning', 'Low' => 'info']; $source_rows = reportTopN(array_map(function ($k, $v) { return ['label' => $k, 'value' => $v]; }, array_keys($sources), array_values($sources)), 6); $source_colors = array_map(function ($i) { return reportPalette($i); }, array_keys($source_rows)); $priority_order = array_filter(['Urgent' => $priorities['Urgent'] ?? 0, 'High' => $priorities['High'] ?? 0, 'Medium' => $priorities['Medium'] ?? 0, 'Low' => $priorities['Low'] ?? 0]); $top_categories = array_slice($categories, 0, 10); ?>
$period, 'years' => reportPastYears("SELECT DISTINCT YEAR(ticket_created_at) AS y FROM tickets"), 'client' => $client_id, 'agent' => $agent_id, 'export' => ['categories' => 'By category', 'tickets' => 'Every ticket opened'], ]); ?> 'Opened', 'value' => reportCount($opened), 'sub' => reportChangeHtml($opened, $opened_prev, 'down', $period['compare']), 'url' => $ticket_list . '&show=all'], ['label' => 'Resolved', 'value' => reportCount($resolved), 'sub' => reportChangeHtml($resolved, $resolved_prev, 'up', $period['compare'])], ['label' => 'Open now', 'value' => reportCount($open_now), 'url' => $ticket_list, 'sub' => 'Whenever they were raised'], ['label' => 'First reply', 'value' => reportDuration($response), 'title' => 'The middle ticket - half got a first reply sooner, half later', 'sub' => $response_prev !== null ? 'Typically · was ' . reportDuration($response_prev) . '' : 'Typically'], ['label' => 'Time to resolve', 'value' => reportDuration($resolve), 'title' => 'The middle ticket - half were resolved sooner, half later', 'sub' => $resolve_prev !== null ? 'Typically · was ' . reportDuration($resolve_prev) . '' : 'Typically'], ['label' => 'Time logged', 'value' => reportHours($time_logged) . ' h', 'sub' => 'On replies in the period'], ]) ?>
'bar', 'labels' => $buckets['labels'], 'series' => [ ['label' => 'Opened', 'data' => $opened_series, 'tone' => 'accent'], ['label' => 'Resolved', 'data' => $resolved_series, 'tone' => 'success'], ], 'format' => 'count', ], '', 'Tickets opened and resolved over the period') ?>
'doughnut', 'labels' => array_column($source_rows, 'label'), 'series' => [['label' => 'Tickets', 'data' => array_column($source_rows, 'value')]], 'colors' => $source_colors, 'format' => 'count'], 'donut', 'Tickets by source') ?>
No tickets.
'dark', 'High' => 'danger', 'Medium' => 'warning', 'Low' => 'info']; $colors = array_map(function ($p) use ($priority_colors) { return $priority_colors[$p]; }, array_keys($priority_order)); ?> 'doughnut', 'labels' => array_keys($priority_order), 'series' => [['label' => 'Tickets', 'data' => array_values($priority_order)]], 'colors' => $colors, 'format' => 'count'], 'donut', 'Tickets by priority') ?>
No tickets.

Darker is busier - useful for planning who is on when.

'bar', 'horizontal' => true, 'labels' => array_column($top_categories, 'category'), 'series' => [['label' => 'Opened', 'data' => array_column($top_categories, 'opened'), 'tone' => 'accent']], 'format' => 'count', 'legend' => false, ], '', 'Tickets opened in the most common categories') ?>
No tickets.
No tickets with a contact.
'No tickets were opened in this period.', 'totals' => ['category' => 'Total', 'opened' => $opened, 'urgent' => array_sum(array_column($categories, 'urgent')), 'resolved' => array_sum(array_column($categories, 'resolved')), 'seconds' => array_sum(array_column($categories, 'seconds'))]]) ?>