208 lines
6.2 KiB
JavaScript
208 lines
6.2 KiB
JavaScript
/**
|
|
* Helpdesk ticket detail page — async activity timeline.
|
|
*
|
|
* Loads the ticket activity log (PBI_FP_TicketLog) via fetch and renders
|
|
* a chronological timeline of status changes and messages.
|
|
*/
|
|
import {
|
|
renderCommunicationError,
|
|
renderCommunicationFeed,
|
|
renderCommunicationHint,
|
|
} from './helpdesk-communication.js';
|
|
|
|
const container = document.querySelector('.app-details-container[data-ticket-no]');
|
|
if (container) {
|
|
init(container);
|
|
}
|
|
|
|
function init(container) {
|
|
const ticketNo = container.dataset.ticketNo || '';
|
|
const logUrl = container.dataset.ticketLogUrl || '';
|
|
const communicationUrl = container.dataset.ticketCommunicationUrl || '';
|
|
|
|
if (!ticketNo || !logUrl) return;
|
|
|
|
// i18n
|
|
const i18nEl = document.getElementById('helpdesk-ticket-i18n');
|
|
const i18n = i18nEl ? JSON.parse(i18nEl.textContent) : {};
|
|
const t = (key) => i18n[key] || key;
|
|
|
|
// Date/time formatter
|
|
const dateFmt = new Intl.DateTimeFormat('de-DE', {
|
|
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
});
|
|
const timeFmt = new Intl.DateTimeFormat('de-DE', {
|
|
hour: '2-digit', minute: '2-digit',
|
|
});
|
|
|
|
function esc(str) {
|
|
const d = document.createElement('div');
|
|
d.textContent = str;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function formatDate(raw) {
|
|
if (!raw || raw === '0001-01-01' || raw === '0001-01-01T00:00:00Z') return '';
|
|
try {
|
|
return dateFmt.format(new Date(raw));
|
|
} catch {
|
|
return raw;
|
|
}
|
|
}
|
|
|
|
function formatTime(raw) {
|
|
if (!raw) return '';
|
|
// BC time format: "15:21:09.76" — parse as today's time
|
|
const parts = raw.split(':');
|
|
if (parts.length < 2) return raw;
|
|
return parts[0] + ':' + parts[1];
|
|
}
|
|
|
|
function typeIcon(type) {
|
|
switch (type) {
|
|
case 'Nachricht': return 'bi-chat-dots-fill';
|
|
case 'Status': return 'bi-arrow-repeat';
|
|
case 'Weiterleitung': return 'bi-forward-fill';
|
|
case 'Datei': return 'bi-paperclip';
|
|
default: return 'bi-circle-fill';
|
|
}
|
|
}
|
|
|
|
function typeVariant(type) {
|
|
switch (type) {
|
|
case 'Nachricht': return 'message';
|
|
case 'Status': return 'status';
|
|
case 'Weiterleitung': return 'forward';
|
|
case 'Datei': return 'file';
|
|
default: return 'other';
|
|
}
|
|
}
|
|
|
|
function typeLabel(type, stateSubtype) {
|
|
switch (type) {
|
|
case 'Nachricht': return t('sent a message');
|
|
case 'Status': {
|
|
if (stateSubtype) {
|
|
return t('changed status') + ' → ' + stateSubtype;
|
|
}
|
|
return t('changed status');
|
|
}
|
|
case 'Weiterleitung': return t('Forwarded');
|
|
case 'Datei': return t('attached a file');
|
|
default: return type || t('Activity');
|
|
}
|
|
}
|
|
|
|
function renderTimeline(entries) {
|
|
if (entries.length === 0) {
|
|
return `<div class="app-empty-state app-empty-state-compact">
|
|
<p class="app-empty-state-message">${esc(t('No activity found.'))}</p>
|
|
</div>`;
|
|
}
|
|
|
|
let html = '<div class="helpdesk-timeline">';
|
|
let lastDate = '';
|
|
|
|
for (const entry of entries) {
|
|
const date = formatDate(entry.Creation_Date);
|
|
const time = formatTime(entry.Creation_Time);
|
|
const type = entry.Type || '';
|
|
const user = entry.Created_By || '';
|
|
const variant = typeVariant(type);
|
|
const icon = typeIcon(type);
|
|
const label = typeLabel(type, entry.State_Subtype || '');
|
|
|
|
// Date separator
|
|
if (date !== lastDate) {
|
|
html += `<div class="helpdesk-timeline-date">${esc(date)}</div>`;
|
|
lastDate = date;
|
|
}
|
|
|
|
html += `<div class="helpdesk-timeline-entry" data-variant="${variant}">`;
|
|
html += `<div class="helpdesk-timeline-icon"><i class="bi ${icon}" aria-hidden="true"></i></div>`;
|
|
html += '<div class="helpdesk-timeline-body">';
|
|
html += `<strong>${esc(user)}</strong> ${esc(label)}`;
|
|
if (time) {
|
|
html += `<span class="helpdesk-timeline-time">${esc(time)}</span>`;
|
|
}
|
|
html += '</div>';
|
|
html += '</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
function renderError(message) {
|
|
return `<div class="notice" data-variant="warning" role="alert"><p>${esc(message)}</p></div>`;
|
|
}
|
|
|
|
// --- Load and render ---
|
|
|
|
async function loadTimeline() {
|
|
const timelineEl = document.getElementById('ticket-timeline');
|
|
const loadingEl = document.getElementById('timeline-loading');
|
|
if (!timelineEl) return;
|
|
|
|
try {
|
|
const params = new URLSearchParams({ ticketNo });
|
|
const response = await fetch(logUrl + '?' + params.toString(), { credentials: 'same-origin' });
|
|
const data = await response.json();
|
|
|
|
if (loadingEl) loadingEl.hidden = true;
|
|
|
|
if (!data.ok) {
|
|
timelineEl.innerHTML = renderError(t('Could not load activity log.'));
|
|
return;
|
|
}
|
|
|
|
timelineEl.innerHTML = renderTimeline(data.log || []);
|
|
} catch {
|
|
if (loadingEl) loadingEl.hidden = true;
|
|
timelineEl.innerHTML = renderError(t('Could not load activity log.'));
|
|
}
|
|
}
|
|
|
|
async function loadCommunication() {
|
|
const communicationEl = document.getElementById('ticket-communication');
|
|
const loadingEl = document.getElementById('ticket-communication-loading');
|
|
if (!communicationEl || !communicationUrl) return;
|
|
|
|
try {
|
|
const params = new URLSearchParams({ ticketNo });
|
|
const response = await fetch(communicationUrl + '?' + params.toString(), { credentials: 'same-origin' });
|
|
const data = await response.json();
|
|
|
|
if (loadingEl) loadingEl.hidden = true;
|
|
|
|
if (!data.ok) {
|
|
communicationEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
if (
|
|
data.meta
|
|
&& data.meta.soap_used === false
|
|
&& data.meta.fallback_used === true
|
|
&& data.meta.soap_error
|
|
) {
|
|
html += renderCommunicationHint(t('Live communication unavailable, showing activity fallback.'));
|
|
}
|
|
html += renderCommunicationFeed(data.entries || [], {
|
|
t,
|
|
emptyMessage: 'No communication found.',
|
|
showTicketNo: false,
|
|
});
|
|
|
|
communicationEl.innerHTML = html;
|
|
} catch {
|
|
if (loadingEl) loadingEl.hidden = true;
|
|
communicationEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
|
}
|
|
}
|
|
|
|
loadTimeline();
|
|
loadCommunication();
|
|
}
|