0, 'us' => 0, 'late' => 0]; $totals = $empty; $by_agent = []; $by_client = []; foreach ($tickets as $ticket) { $a = intval($ticket['ticket_assigned_to']); $c = intval($ticket['ticket_client_id']); $by_agent[$a] = $by_agent[$a] ?? ['user_id' => $a, 'agent' => $a ? ($ticket['user_name'] ?? '') : '', 'avatar' => $ticket['user_avatar'] ?? ''] + $empty; $by_client[$c] = $by_client[$c] ?? ['client_id' => $c, 'client' => $ticket['client_name'] ?? '', 'abbr' => $ticket['client_abbreviation'] ?? ''] + $empty; foreach ([&$by_agent[$a], &$by_client[$c], &$totals] as &$group) { $group[$ticket['bucket']]++; $group['total']++; $group['us'] += $ticket['move'] === 'us' ? 1 : 0; $group['late'] += $ticket['late'] ? 1 : 0; } unset($group); } $sort = function ($a, $b) { return $b['old'] + $b['90'] <=> $a['old'] + $a['90'] ?: $b['total'] <=> $a['total']; }; uasort($by_agent, $sort); uasort($by_client, $sort); $count_cell = function ($key) { return function ($row) use ($key) { if (!$row[$key]) { return '–'; } $tone = in_array($key, ['90', 'old'], true) ? 'text-danger-emphasis fw-semibold' : ($key === '30' ? 'text-warning-emphasis fw-semibold' : ''); return '' . $row[$key] . ''; }; }; $bucket_columns = []; foreach (REPORT_AGE_BUCKETS as $key => [$label]) { $bucket_columns[$key] = ['label' => $label, 'type' => 'count', 'html' => $count_cell($key)]; } $tail_columns = [ 'total' => ['label' => 'Open', 'type' => 'count', 'class' => 'fw-semibold'], 'us' => ['label' => 'Waiting on us', 'type' => 'count', 'title' => 'The client spoke last, or nobody has replied yet'], 'late' => ['label' => 'Late', 'type' => 'count', 'title' => 'Past their SLA or their due date', 'html' => function ($row) { return $row['late'] ? '' . $row['late'] . '' : '–'; }], ]; $agent_columns = ['agent' => ['label' => 'Agent', 'csv' => function ($row) { return $row['user_id'] ? $row['agent'] : 'Unassigned'; }, 'html' => function ($row) use ($client_id) { $url = '/agent/tickets.php?' . http_build_query(array_filter(['client_id' => $client_id ?: null, 'assigned' => $row['user_id'] ?: 'unassigned'])); return '' . formatAssignee($row['user_id'], $row['agent'], $row['avatar'], true) . ''; }]] + $bucket_columns + $tail_columns; $client_columns = ['client' => ['label' => 'Client', 'csv' => function ($row) { return $row['client_id'] ? $row['client'] : 'No client'; }, 'html' => function ($row) { return reportClientCell($row['client_id'], $row['client'], $row['abbr'], 'tickets.php'); }]] + $bucket_columns + $tail_columns; $move_labels = ['us' => ['Waiting on us', 'danger', 'fa-reply'], 'client' => ['Waiting on client', 'secondary', 'fa-hourglass-half'], 'parked' => ['Parked', 'secondary', 'fa-pause']]; $ticket_columns = [ 'ticket' => ['label' => 'Ticket', 'class' => 'itflow-report-text', 'csv' => function ($row) { return ticketReference($row) . ' ' . $row['ticket_subject']; }, 'html' => function ($row) { return reportPriorityDot($row['ticket_priority']) . reportTicketLink($row); }], 'client_name' => ['label' => 'Client', 'html' => function ($row) { return reportClientCell($row['ticket_client_id'], $row['client_name'] ?? '', $row['client_abbreviation'] ?? ''); }], 'user_name' => ['label' => 'Agent', 'csv' => function ($row) { return $row['user_name'] ?: 'Unassigned'; }, 'html' => function ($row) { return formatAssignee($row['ticket_assigned_to'], $row['user_name'] ?? '', $row['user_avatar'] ?? '', true); }], 'ticket_status_name' => ['label' => 'Status', 'html' => function ($row) { return ticketStatusHtml($row['ticket_status_name'], $row['ticket_status_color'], 'detail'); }], 'move' => ['label' => 'Whose move', 'csv' => function ($row) use ($move_labels) { return $move_labels[$row['move']][0] ?? ''; }, 'html' => function ($row) use ($move_labels) { if (!isset($move_labels[$row['move']])) { return ''; } [$label, $tone, $icon] = $move_labels[$row['move']]; return '' . $label . ''; }], 'sla' => ['label' => 'SLA / due', 'sort' => false, 'csv' => function ($row) { return trim(($row['sla']['key'] !== 'none' ? $row['sla']['key'] : '') . ' ' . ($row['due'] ? 'due ' . $row['ticket_due_at'] : '')); }, 'html' => function ($row) { return '' . ticketSlaPill($row['sla']) . ticketDuePill($row['due']) . ''; }], 'age_days' => ['label' => 'Open for', 'type' => 'decimal', 'decimals' => 1, 'csv_label' => 'Days open', 'html' => function ($row) { $days = $row['age_days']; $tone = $days > 30 ? 'text-danger-emphasis fw-semibold' : ($days > 7 ? 'text-warning-emphasis fw-semibold' : ''); return '' . reportDuration($row['age_seconds']) . ''; }], 'last_activity' => ['label' => 'Last activity', 'type' => 'date', 'csv' => function ($row) { return $row['last_activity']; }, 'html' => function ($row) { return '' . escapeHtml(timeAgo($row['last_activity'])) . ''; }], ]; if (reportExporting('agents')) { reportCsv('Open Tickets by Agent', $agent_columns, array_values($by_agent), ['agent' => 'Total'] + $totals); } if (reportExporting('clients')) { reportCsv('Open Tickets by Client', $client_columns, array_values($by_client), ['client' => 'Total'] + $totals); } if (reportExporting()) { reportCsv('Open Tickets', $ticket_columns, $tickets); } require_once "includes/inc_report_layout.php"; $unassigned = $by_agent[0]['total'] ?? 0; $oldest = $tickets[0] ?? null; $bucket_tones = ['day' => 'success', '3' => 'info', '7' => 'accent', '30' => 'warning', '90' => 'orange', 'old' => 'danger']; $agent_choices = '
'; ?>
$client_id, 'controls' => $agent_choices, 'subtitle' => 'As of ' . date('M j, Y g:i A'), 'export' => ['tickets' => 'Every open ticket', 'agents' => 'By agent', 'clients' => 'By client'], ]); ?> 'Open tickets', 'value' => reportCount(count($tickets)), 'url' => '/agent/tickets.php' . ($client_id ? "?client_id=$client_id" : ''), 'sub' => reportCount($totals['30'] + $totals['90'] + $totals['old']) . ' open over a week'], ['label' => 'Waiting on us', 'value' => reportCount($totals['us']), 'tone' => $totals['us'] ? 'warning' : '', 'url' => '/agent/tickets.php?show=reply' . ($client_id ? "&client_id=$client_id" : ''), 'sub' => 'The client spoke last, or no reply yet'], ['label' => 'Late', 'value' => reportCount($totals['late']), 'tone' => $totals['late'] ? 'danger' : 'success', 'url' => '/agent/tickets.php?show=overdue' . ($client_id ? "&client_id=$client_id" : ''), 'sub' => 'Past their SLA or due date'], ['label' => 'Unassigned', 'value' => reportCount($unassigned), 'tone' => $unassigned ? 'warning' : '', 'url' => '/agent/tickets.php?assigned=unassigned' . ($client_id ? "&client_id=$client_id" : ''), 'sub' => $oldest ? 'Oldest open ' . reportDuration($oldest['age_seconds']) . '' : ''], ]) ?>
'bar', 'horizontal' => true, 'labels' => array_map(function ($b) { return $b[0]; }, array_values(REPORT_AGE_BUCKETS)), 'series' => [['label' => 'Open tickets', 'data' => array_map(function ($k) use ($totals) { return $totals[$k]; }, $bucket_keys), 'tones' => array_map(function ($k) use ($bucket_tones) { return $bucket_tones[$k]; }, $bucket_keys)]], 'format' => 'count', 'legend' => false, ], '', 'Open tickets by how long they have been open') ?>
reportEmpty('No open tickets.'), 'totals' => ['agent' => 'Total'] + $totals]) ?> reportEmpty('No open tickets.'), 'limit' => 50, 'export_table' => 'clients', 'totals' => ['client' => 'Total'] + $totals]) ?> 300, 'export_table' => 'tickets']) ?>