- Add server-side ticket summary endpoint for exact KPI counts instead of client-side calculation from first 200 tickets - Extract summarizeTickets() as shared static method to avoid duplication - Replace custom .helpdesk-spinner CSS with core [aria-busy] pattern - Migrate settings connection-test feedback to showAsyncFlash() - Replace helpdesk-clickable-rows.js with delegated event handling - Remove unused helpdesk-search.js (dead code, never loaded) - Harmonize German wording: Debitoren → Kunden (8 i18n keys) - Add 6 PHPUnit tests for loadTicketSummary() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
159 lines
5.8 KiB
PHTML
159 lines
5.8 KiB
PHTML
<?php
|
|
|
|
/**
|
|
* @var string $ticketNo
|
|
* @var string $fromCustomerNo
|
|
* @var array|null $ticket
|
|
* @var bool $connectionError
|
|
* @var string $ticketCustomerNo
|
|
* @var string $ticketDescription
|
|
* @var string $ticketLogUrl
|
|
*/
|
|
|
|
$ticketNo = $ticketNo ?? '';
|
|
$fromCustomerNo = $fromCustomerNo ?? '';
|
|
$ticket = is_array($ticket ?? null) ? $ticket : null;
|
|
$connectionError = $connectionError ?? false;
|
|
$ticketCustomerNo = $ticketCustomerNo ?? '';
|
|
$ticketDescription = $ticketDescription ?? '';
|
|
$ticketLogUrl = $ticketLogUrl ?? '';
|
|
|
|
$backUrl = $ticketCustomerNo !== '' ? lurl('helpdesk/debitor/' . rawurlencode($ticketCustomerNo)) : lurl('helpdesk/debitor');
|
|
|
|
?>
|
|
<div class="app-details-container"
|
|
data-ticket-no="<?php e($ticketNo); ?>"
|
|
data-ticket-log-url="<?php e($ticketLogUrl); ?>"
|
|
>
|
|
<section>
|
|
<?php
|
|
$breadcrumbs = [
|
|
['label' => t('Home'), 'path' => 'admin'],
|
|
['label' => t('Helpdesk'), 'path' => 'helpdesk/debitor'],
|
|
];
|
|
if ($ticketCustomerNo !== '') {
|
|
$breadcrumbs[] = ['label' => (string) ($ticket['Company_Contact_Name'] ?? $ticketCustomerNo), 'path' => 'helpdesk/debitor/' . rawurlencode($ticketCustomerNo)];
|
|
}
|
|
$breadcrumbs[] = ['label' => t('Ticket') . ' ' . $ticketNo];
|
|
require templatePath('partials/app-breadcrumb.phtml');
|
|
?>
|
|
|
|
<?php
|
|
$titlebar = [
|
|
'title' => t('Ticket') . ' ' . $ticketNo,
|
|
'backHref' => $backUrl,
|
|
'backTitle' => t('Back'),
|
|
'actions' => [],
|
|
];
|
|
require templatePath('partials/app-details-titlebar.phtml');
|
|
?>
|
|
|
|
<?php require templatePath('partials/app-flash.phtml'); ?>
|
|
|
|
<?php if ($connectionError): ?>
|
|
<div class="notice" data-variant="error" role="alert">
|
|
<?php e(t('Could not connect to Business Central.')); ?>
|
|
</div>
|
|
<?php elseif ($ticket !== null): ?>
|
|
<?php
|
|
$createdRaw = (string) ($ticket['Created_On'] ?? '');
|
|
$createdFormatted = $createdRaw !== '' && $createdRaw !== '0001-01-01T00:00:00Z'
|
|
? (new DateTimeImmutable($createdRaw))->format('d.m.Y H:i')
|
|
: '—';
|
|
$lastActivityRaw = (string) ($ticket['Last_Activity_Date'] ?? '');
|
|
$lastActivityFormatted = $lastActivityRaw !== '' && $lastActivityRaw !== '0001-01-01T00:00:00Z'
|
|
? (new DateTimeImmutable($lastActivityRaw))->format('d.m.Y H:i')
|
|
: '—';
|
|
$ticketState = (string) ($ticket['Ticket_State'] ?? '');
|
|
$stateVariant = match ($ticketState) {
|
|
'Offen', 'Open' => 'primary',
|
|
'In Bearbeitung', 'In Progress' => 'warning',
|
|
'Erledigt', 'Resolved', 'Closed', 'Geschlossen' => 'success',
|
|
default => 'neutral',
|
|
};
|
|
?>
|
|
|
|
<div class="grid grid-2">
|
|
<div class="app-stats-table">
|
|
<div class="app-stats-table-header"><?php e(t('Ticket details')); ?></div>
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong><?php e(t('Ticket No.')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['No'] ?? '')); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Description')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['Description'] ?? '')); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Status')); ?></strong></td>
|
|
<td><span class="badge" data-variant="<?php e($stateVariant); ?>"><?php e($ticketState); ?></span></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Category')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['Category_1_Description'] ?? '')); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Support')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['Support_User_Name'] ?? '')); ?></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="app-stats-table">
|
|
<div class="app-stats-table-header"><?php e(t('Contact & dates')); ?></div>
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong><?php e(t('Company')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['Company_Contact_Name'] ?? '')); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Contact')); ?></strong></td>
|
|
<td><?php e((string) ($ticket['Current_Contact_Name'] ?? '')); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Created')); ?></strong></td>
|
|
<td><?php e($createdFormatted); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php e(t('Last activity')); ?></strong></td>
|
|
<td><?php e($lastActivityFormatted); ?></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Activity timeline — loaded async -->
|
|
<div class="grid">
|
|
<div class="app-stats-table">
|
|
<div class="app-stats-table-header"><?php e(t('Activity timeline')); ?></div>
|
|
<div id="ticket-timeline">
|
|
<div id="timeline-loading" aria-busy="true"><?php e(t('Loading...')); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- i18n strings for JS rendering -->
|
|
<script type="application/json" id="helpdesk-ticket-i18n"><?php gridJsonForJs([
|
|
'Message' => t('Message'),
|
|
'Status change' => t('Status change'),
|
|
'Activity' => t('Activity'),
|
|
'No activity found.' => t('No activity found.'),
|
|
'Could not load activity log.' => t('Could not load activity log.'),
|
|
'sent a message' => t('sent a message'),
|
|
'changed status' => t('changed status'),
|
|
'Forwarded' => t('Forwarded'),
|
|
'attached a file' => t('attached a file'),
|
|
'Process' => t('Process'),
|
|
'Loading...' => t('Loading...'),
|
|
]); ?></script>
|
|
|
|
<script type="module" src="<?php e(assetVersion('modules/helpdesk/js/helpdesk-ticket.js')); ?>"></script>
|