feat(helpdesk): redesign Historical tab with per-agent performance widgets

Each agent gets a card with three insight columns:
- Resolution time: Ø / Min / Max (formatted as hours or days)
- Top 3 customers by resolved ticket count (ranked list)
- Top 3 categories by resolved ticket count (ranked list)

Backend collects resolved ticket details per agent: customer names,
categories, and resolution hours. Aggregates top-3 and min/max/avg.
Replaces the old compact single-row layout with rich widget cards
matching the Current tab pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 20:11:44 +02:00
parent 7a574768db
commit 4ce7a32bfc
6 changed files with 211 additions and 38 deletions

View File

@@ -1620,13 +1620,29 @@ class DebitorDetailService
'age_hours_sum' => 0,
'resolved_in_period' => 0,
'open_ticket_details' => [],
'resolved_customers' => [],
'resolved_categories' => [],
'resolution_hours' => [],
];
}
if ($isClosed) {
$createdTs = self::parseIsoToTimestamp((string) ($ticket['Created_On'] ?? ''));
if ($activityTs !== null && $activityTs >= $periodStart) {
$agents[$normalizedUser]['resolved_in_period']++;
$globalResolved++;
$customerName = trim((string) ($ticket['Company_Contact_Name'] ?? (string) ($ticket['Cust_Name'] ?? '')));
$category = trim((string) ($ticket['Category_1_Description'] ?? (string) ($ticket['Category_1_Code'] ?? '')));
if ($customerName !== '') {
$agents[$normalizedUser]['resolved_customers'][$customerName] = ($agents[$normalizedUser]['resolved_customers'][$customerName] ?? 0) + 1;
}
if ($category !== '') {
$agents[$normalizedUser]['resolved_categories'][$category] = ($agents[$normalizedUser]['resolved_categories'][$category] ?? 0) + 1;
}
if ($createdTs !== null && $activityTs > $createdTs) {
$agents[$normalizedUser]['resolution_hours'][] = intdiv($activityTs - $createdTs, 3600);
}
}
} else {
$agents[$normalizedUser]['open_tickets']++;
@@ -1685,6 +1701,20 @@ class DebitorDetailService
return $b['age_hours'] <=> $a['age_hours'];
});
// Performance insights from resolved tickets
$topCustomers = $agent['resolved_customers'];
arsort($topCustomers);
$topCustomers = array_slice($topCustomers, 0, 3, true);
$topCategories = $agent['resolved_categories'];
arsort($topCategories);
$topCategories = array_slice($topCategories, 0, 3, true);
$resHours = $agent['resolution_hours'];
$avgResolution = $resHours !== [] ? (int) round(array_sum($resHours) / count($resHours)) : null;
$minResolution = $resHours !== [] ? min($resHours) : null;
$maxResolution = $resHours !== [] ? max($resHours) : null;
$members[] = [
'support_user' => $agent['support_user'],
'display_name' => $agent['display_name'],
@@ -1693,6 +1723,21 @@ class DebitorDetailService
'avg_age_hours' => $avgAge,
'resolved_in_period' => $agent['resolved_in_period'],
'open_ticket_details' => $details,
'performance' => [
'top_customers' => array_map(
static fn (string $name, int $count): array => ['name' => $name, 'count' => $count],
array_keys($topCustomers),
array_values($topCustomers)
),
'top_categories' => array_map(
static fn (string $name, int $count): array => ['name' => $name, 'count' => $count],
array_keys($topCategories),
array_values($topCategories)
),
'avg_resolution_hours' => $avgResolution,
'min_resolution_hours' => $minResolution,
'max_resolution_hours' => $maxResolution,
],
];
}