Files
breadcrumb-the-shire/modules/helpdesk/web/js/helpdesk-ticket.js

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();
}