feat(helpdesk): enrich team ticket table with links, customer name, description, last activity

- Switch gateway from PBI_LV_Tickets to PBI_FP_Tickets to get
  Description and Company_Contact_Name fields
- Ticket number links to ticket detail page
- Customer column shows Company_Contact_Name with link to debitor detail
- Description column with text-overflow ellipsis
- Last activity column shows formatted date (was: age in hours)
- Backend provides last_activity ISO string and customer_name per ticket
- Table headers use i18n labels via data attributes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 19:29:02 +02:00
parent 8fd742141c
commit 98f703b231
5 changed files with 82 additions and 14 deletions

View File

@@ -597,9 +597,9 @@ class BcODataGateway
*/
public function getTicketsForTeam(): array
{
$url = $this->settingsGateway->buildEntityUrl(self::ENTITY_TICKETS_LV)
$url = $this->settingsGateway->buildEntityUrl(self::ENTITY_TICKETS)
. '?$top=500'
. '&$select=' . rawurlencode('No,Customer_No,Category_1_Code,Escalation_Code,Ticket_State,Last_Activity_Date,Created_On,Process_Stage,Process_Code,Support_User_Name')
. '&$select=' . rawurlencode('No,Description,Company_Contact_Name,Ticket_State,Support_User_Name,Created_On,Last_Activity_Date')
. '&$orderby=' . rawurlencode('Created_On desc');
$response = $this->request('GET', $url);

View File

@@ -1651,12 +1651,18 @@ class DebitorDetailService
$globalOpenCount++;
}
$lastActivityRaw = trim((string) ($ticket['Last_Activity_Date'] ?? ''));
$lastActivityDisplay = ($lastActivityRaw !== '' && !str_starts_with($lastActivityRaw, '0001'))
? $lastActivityRaw
: trim((string) ($ticket['Created_On'] ?? ''));
$agents[$normalizedUser]['open_ticket_details'][] = [
'no' => trim((string) ($ticket['No'] ?? '')),
'description' => trim((string) ($ticket['Description'] ?? '')),
'age_hours' => $ageHours,
'critical' => $isCritical,
'customer' => trim((string) ($ticket['Customer_No'] ?? '')),
'customer_name' => trim((string) ($ticket['Company_Contact_Name'] ?? '')),
'last_activity' => $lastActivityDisplay,
];
}
}

View File

@@ -22,6 +22,12 @@ $periodLabels = [
data-label-error="<?php e(t('Could not load team dashboard.')); ?>"
data-label-empty="<?php e(t('No team data available.')); ?>"
data-label-critical="<?php e(t('Critical (>48h open)')); ?>"
data-ticket-base-url="<?php e(lurl('helpdesk/ticket/')); ?>"
data-debitor-base-url="<?php e(lurl('helpdesk/debitor/')); ?>"
data-label-ticket="<?php e(t('Ticket')); ?>"
data-label-customer="<?php e(t('Debtor')); ?>"
data-label-description="<?php e(t('Description')); ?>"
data-label-last-activity="<?php e(t('Last activity')); ?>"
>
<section>
<?php

View File

@@ -202,10 +202,36 @@
.helpdesk-team-ticket-no {
font-variant-numeric: tabular-nums;
font-weight: 500;
white-space: nowrap;
}
.helpdesk-team-ticket-no a,
.helpdesk-team-ticket-table td a {
color: inherit;
text-decoration: none;
}
.helpdesk-team-ticket-no a:hover,
.helpdesk-team-ticket-table td a:hover {
color: var(--app-primary, #0d6efd);
text-decoration: underline;
}
.helpdesk-team-ticket-desc {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 20ch;
}
.helpdesk-team-ticket-date {
white-space: nowrap;
color: var(--app-muted-color);
}
/* Critical: only highlight the ticket number, not the entire row */
.helpdesk-team-ticket-critical .helpdesk-team-ticket-no {
.helpdesk-team-ticket-critical .helpdesk-team-ticket-no,
.helpdesk-team-ticket-critical .helpdesk-team-ticket-no a {
color: var(--app-warning, #f59e0b);
font-weight: 600;
}

View File

@@ -10,6 +10,8 @@ if (container) {
const labelQueue = container.dataset.labelQueue || 'Queue';
const labelTickets = container.dataset.labelTickets || 'Tickets';
const labelCritical = container.dataset.labelCritical || 'Critical (>48h open)';
const ticketBaseUrl = container.dataset.ticketBaseUrl || '';
const debitorBaseUrl = container.dataset.debitorBaseUrl || '';
const elLoading = document.getElementById('team-loading');
const elError = document.getElementById('team-error');
@@ -55,18 +57,46 @@ if (container) {
return d.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' });
}
function formatDateTime(isoString) {
if (!isoString) return '—';
const d = new Date(isoString);
if (isNaN(d.getTime())) return '—';
return d.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' });
}
/**
* Build a ticket table row: No | Customer | Last activity | Age
* Build a ticket table row: No (link) | Customer (link) | Description | Last activity
*/
function buildTicketRow(t) {
const tr = h('tr', t.critical ? 'helpdesk-team-ticket-critical' : '');
const noCell = h('td', 'helpdesk-team-ticket-no', t.no);
// Ticket number as link
const noCell = h('td', 'helpdesk-team-ticket-no');
if (ticketBaseUrl && t.no) {
const a = h('a', '', t.no);
a.href = ticketBaseUrl + encodeURIComponent(t.no);
noCell.appendChild(a);
} else {
noCell.textContent = t.no || '—';
}
tr.appendChild(noCell);
tr.appendChild(h('td', '', t.customer || '—'));
tr.appendChild(h('td', '', t.description || '—'));
const ageCell = h('td', '', formatAge(t.age_hours));
tr.appendChild(ageCell);
// Customer name as link to debitor
const customerCell = h('td');
if (debitorBaseUrl && t.customer_name) {
const a = h('a', '', t.customer_name);
a.href = debitorBaseUrl + encodeURIComponent(t.customer_name);
customerCell.appendChild(a);
} else {
customerCell.textContent = t.customer_name || '—';
}
tr.appendChild(customerCell);
// Description
tr.appendChild(h('td', 'helpdesk-team-ticket-desc', t.description || '—'));
// Last activity date
tr.appendChild(h('td', 'helpdesk-team-ticket-date', formatDateTime(t.last_activity)));
return tr;
}
@@ -97,10 +127,10 @@ if (container) {
const table = h('table', 'helpdesk-team-ticket-table');
const thead = h('thead');
const headRow = h('tr');
headRow.appendChild(h('th', '', 'Ticket'));
headRow.appendChild(h('th', '', 'Kunde'));
headRow.appendChild(h('th', '', 'Beschreibung'));
headRow.appendChild(h('th', '', 'Alter'));
headRow.appendChild(h('th', '', container.dataset.labelTicket || 'Ticket'));
headRow.appendChild(h('th', '', container.dataset.labelCustomer || 'Customer'));
headRow.appendChild(h('th', '', container.dataset.labelDescription || 'Description'));
headRow.appendChild(h('th', '', container.dataset.labelLastActivity || 'Last activity'));
thead.appendChild(headRow);
table.appendChild(thead);