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:
@@ -160,19 +160,86 @@ if (container) {
|
||||
return section;
|
||||
}
|
||||
|
||||
function formatResolution(hours) {
|
||||
if (hours === null || hours === undefined) return '—';
|
||||
if (hours < 1) return '< 1h';
|
||||
if (hours < 24) return hours + 'h';
|
||||
const d = Math.floor(hours / 24);
|
||||
const remainder = hours % 24;
|
||||
return remainder > 0 ? d + 'd ' + remainder + 'h' : d + 'd';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a compact row for the performance tab.
|
||||
* Build a small ranked list (top customers / top categories).
|
||||
*/
|
||||
function buildPerformanceRow(m) {
|
||||
const isQueue = m.support_user === '';
|
||||
const section = h('div', 'helpdesk-team-perf-row');
|
||||
function buildRankedList(items, label) {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
if (!isQueue) {
|
||||
section.appendChild(h('span', 'helpdesk-team-avatar-inline', initials(m.display_name)));
|
||||
const wrap = h('div', 'helpdesk-team-perf-stat');
|
||||
wrap.appendChild(h('span', 'helpdesk-team-perf-stat-label', label));
|
||||
|
||||
const list = h('ol', 'helpdesk-team-perf-ranked');
|
||||
for (const item of items) {
|
||||
const li = h('li');
|
||||
li.appendChild(h('span', 'helpdesk-team-perf-ranked-name', item.name));
|
||||
li.appendChild(h('span', 'helpdesk-team-perf-ranked-count', String(item.count)));
|
||||
list.appendChild(li);
|
||||
}
|
||||
section.appendChild(h('span', 'helpdesk-team-perf-name', isQueue ? labelQueue : m.display_name));
|
||||
section.appendChild(h('span', 'helpdesk-team-perf-count', String(m.resolved_in_period)));
|
||||
wrap.appendChild(list);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a performance widget for one agent — card with insights.
|
||||
*/
|
||||
function buildPerformanceWidget(m) {
|
||||
const section = h('section', 'helpdesk-team-widget');
|
||||
const perf = m.performance || {};
|
||||
|
||||
// Header: avatar + name + resolved count
|
||||
const title = h('h3', 'helpdesk-support-section-title');
|
||||
if (m.support_user !== '') {
|
||||
title.appendChild(h('span', 'helpdesk-team-avatar-inline', initials(m.display_name)));
|
||||
}
|
||||
title.appendChild(document.createTextNode(m.support_user === '' ? labelQueue : m.display_name));
|
||||
title.appendChild(h('span', 'helpdesk-team-title-count', String(m.resolved_in_period)));
|
||||
section.appendChild(title);
|
||||
|
||||
// Stats grid
|
||||
const grid = h('div', 'helpdesk-team-perf-grid');
|
||||
|
||||
// Resolution time KPIs
|
||||
const timeStats = h('div', 'helpdesk-team-perf-stat');
|
||||
timeStats.appendChild(h('span', 'helpdesk-team-perf-stat-label', container.dataset.labelResolutionTime || 'Resolution time'));
|
||||
|
||||
const times = h('div', 'helpdesk-team-perf-times');
|
||||
const avgEl = h('div', 'helpdesk-team-perf-time');
|
||||
avgEl.appendChild(h('span', 'helpdesk-team-perf-time-value', formatResolution(perf.avg_resolution_hours)));
|
||||
avgEl.appendChild(h('span', 'helpdesk-team-perf-time-label', 'Ø'));
|
||||
times.appendChild(avgEl);
|
||||
|
||||
const minEl = h('div', 'helpdesk-team-perf-time');
|
||||
minEl.appendChild(h('span', 'helpdesk-team-perf-time-value', formatResolution(perf.min_resolution_hours)));
|
||||
minEl.appendChild(h('span', 'helpdesk-team-perf-time-label', 'Min'));
|
||||
times.appendChild(minEl);
|
||||
|
||||
const maxEl = h('div', 'helpdesk-team-perf-time');
|
||||
maxEl.appendChild(h('span', 'helpdesk-team-perf-time-value', formatResolution(perf.max_resolution_hours)));
|
||||
maxEl.appendChild(h('span', 'helpdesk-team-perf-time-label', 'Max'));
|
||||
times.appendChild(maxEl);
|
||||
|
||||
timeStats.appendChild(times);
|
||||
grid.appendChild(timeStats);
|
||||
|
||||
// Top customers
|
||||
const customersEl = buildRankedList(perf.top_customers, container.dataset.labelTopCustomers || 'Top customers');
|
||||
if (customersEl) grid.appendChild(customersEl);
|
||||
|
||||
// Top categories
|
||||
const categoriesEl = buildRankedList(perf.top_categories, container.dataset.labelTopCategories || 'Top categories');
|
||||
if (categoriesEl) grid.appendChild(categoriesEl);
|
||||
|
||||
section.appendChild(grid);
|
||||
return section;
|
||||
}
|
||||
|
||||
@@ -210,7 +277,7 @@ if (container) {
|
||||
return b.resolved_in_period - a.resolved_in_period;
|
||||
});
|
||||
|
||||
for (const m of sorted) elPerformanceCards.appendChild(buildPerformanceRow(m));
|
||||
for (const m of sorted) elPerformanceCards.appendChild(buildPerformanceWidget(m));
|
||||
}
|
||||
|
||||
async function fetchData(refresh = false) {
|
||||
|
||||
Reference in New Issue
Block a user