/** * 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 `

${esc(t('No activity found.'))}

`; } let html = '
'; 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 += `
${esc(date)}
`; lastDate = date; } html += `
`; html += `
`; html += '
'; html += `${esc(user)} ${esc(label)}`; if (time) { html += `${esc(time)}`; } html += '
'; html += '
'; } html += '
'; return html; } function renderError(message) { return ``; } // --- 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(); }