From 81dc642d348c30c19886713989ea77a5385957e8 Mon Sep 17 00:00:00 2001 From: fs Date: Sun, 5 Apr 2026 20:56:31 +0200 Subject: [PATCH] feat(helpdesk): separate queue from agents in team dashboard with wait time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Queue detection: any member with 'warteschlange' in support_user name (or empty) is treated as queue, not as a regular agent. Current tab: queue cards shown at top with a wait time summary (Ø/Min/Max from open ticket ages). Agents listed below. Historical tab: queue cards shown before agents, same performance widget pattern. No backend changes — purely client-side separation using existing data. Co-Authored-By: Claude Opus 4.6 (1M context) --- modules/helpdesk/i18n/default_de.json | 3 +- modules/helpdesk/i18n/default_en.json | 3 +- .../pages/helpdesk/team/index(default).phtml | 1 + modules/helpdesk/web/css/helpdesk.css | 9 +++ modules/helpdesk/web/js/helpdesk-team.js | 62 +++++++++++++++---- 5 files changed, 65 insertions(+), 13 deletions(-) diff --git a/modules/helpdesk/i18n/default_de.json b/modules/helpdesk/i18n/default_de.json index 83e51e4..d2772e3 100644 --- a/modules/helpdesk/i18n/default_de.json +++ b/modules/helpdesk/i18n/default_de.json @@ -301,5 +301,6 @@ "Current": "Aktuell", "Performance": "Leistung", "Historical": "Historisch", - "Queue": "Warteschlange" + "Queue": "Warteschlange", + "Wait time": "Wartezeit" } diff --git a/modules/helpdesk/i18n/default_en.json b/modules/helpdesk/i18n/default_en.json index 0046435..5778982 100644 --- a/modules/helpdesk/i18n/default_en.json +++ b/modules/helpdesk/i18n/default_en.json @@ -301,5 +301,6 @@ "Current": "Current", "Performance": "Performance", "Historical": "Historical", - "Queue": "Queue" + "Queue": "Queue", + "Wait time": "Wait time" } diff --git a/modules/helpdesk/pages/helpdesk/team/index(default).phtml b/modules/helpdesk/pages/helpdesk/team/index(default).phtml index 8f96574..0fc860a 100644 --- a/modules/helpdesk/pages/helpdesk/team/index(default).phtml +++ b/modules/helpdesk/pages/helpdesk/team/index(default).phtml @@ -32,6 +32,7 @@ $periodLabels = [ data-label-resolution-time="" data-label-top-customers="" data-label-top-categories="" + data-label-wait-time="" >
48h open)'; + const labelWaitTime = container.dataset.labelWaitTime || 'Wait time'; const ticketBaseUrl = container.dataset.ticketBaseUrl || ''; const debitorBaseUrl = container.dataset.debitorBaseUrl || ''; @@ -38,6 +39,13 @@ if (container) { return e; } + /** + * Detect queue entries — Support_User_Name contains 'warteschlange'. + */ + function isQueueMember(m) { + return m.support_user === '' || m.support_user.includes('warteschlange'); + } + function initials(name) { const parts = name.trim().split(/\s+/); if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); @@ -298,14 +306,48 @@ if (container) { return section; } + /** + * Build a queue summary with wait time Ø/Min/Max from open ticket ages. + */ + function buildQueueWaitSummary(queueMembers) { + const allAges = queueMembers.flatMap(m => (m.open_ticket_details || []).map(t => t.age_hours)); + if (allAges.length === 0) return null; + + const stats = computeResolution(allAges); + const wrap = h('div', 'helpdesk-team-queue-wait'); + wrap.appendChild(h('span', 'helpdesk-team-perf-stat-label', labelWaitTime)); + + const list = h('ol', 'helpdesk-team-perf-ranked helpdesk-team-perf-ranked-plain'); + const items = [ + ['Ø', formatResolution(stats.avg)], + ['Min', formatResolution(stats.min)], + ['Max', formatResolution(stats.max)], + ]; + for (const [label, value] of items) { + const li = h('li'); + li.appendChild(h('span', 'helpdesk-team-perf-ranked-name', label)); + li.appendChild(h('span', 'helpdesk-team-perf-ranked-count', value)); + list.appendChild(li); + } + wrap.appendChild(list); + return wrap; + } + function renderCurrentTab(members) { if (!elCurrentCards) return; elCurrentCards.replaceChildren(); - const queue = members.find(m => m.support_user === '' && m.open_tickets > 0); - const agents = members.filter(m => m.support_user !== '' && (m.open_tickets > 0 || m.critical_tickets > 0)); + const queueMembers = members.filter(m => isQueueMember(m) && m.open_tickets > 0); + const agents = members.filter(m => !isQueueMember(m) && (m.open_tickets > 0 || m.critical_tickets > 0)); - if (queue) elCurrentCards.appendChild(buildAgentWidget(queue, true)); + // Queue section(s) at top + for (const q of queueMembers) elCurrentCards.appendChild(buildAgentWidget(q, true)); + + // Wait time summary across all queues + const waitSummary = buildQueueWaitSummary(queueMembers); + if (waitSummary) elCurrentCards.appendChild(waitSummary); + + // Agent cards for (const m of agents) elCurrentCards.appendChild(buildAgentWidget(m, false)); // Legend @@ -324,15 +366,13 @@ if (container) { if (!elPerformanceCards) return; elPerformanceCards.replaceChildren(); - const sorted = [...members] - .filter(m => m.resolved_in_period > 0) - .sort((a, b) => { - if (a.support_user === '') return 1; - if (b.support_user === '') return -1; - return b.resolved_in_period - a.resolved_in_period; - }); + const queueResolved = members.filter(m => isQueueMember(m) && m.resolved_in_period > 0); + const agentsResolved = members + .filter(m => !isQueueMember(m) && m.resolved_in_period > 0) + .sort((a, b) => b.resolved_in_period - a.resolved_in_period); - for (const m of sorted) elPerformanceCards.appendChild(buildPerformanceWidget(m)); + for (const q of queueResolved) elPerformanceCards.appendChild(buildPerformanceWidget(q)); + for (const m of agentsResolved) elPerformanceCards.appendChild(buildPerformanceWidget(m)); } async function fetchData(refresh = false) {