forked from fa/breadcrumb-the-shire
New SOAP method getTicketFile() on BcSoapGateway calls BC's GetTicketFile to retrieve file attachments as Base64, decodes in memory and returns binary data without writing to disk. New proxy endpoint helpdesk/ticket-file-data streams files directly to the browser with correct MIME type. Images served inline, other files as download attachment. TicketCommunicationService now attaches file_entry_no and file_name to file-type events. Frontend renders download links with bi-download icon and inline image previews for jpg/png/gif attachments. Works in both ticket detail and debitor detail communication feeds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.0 KiB
JavaScript
99 lines
3.0 KiB
JavaScript
/**
|
|
* Helpdesk ticket detail page — async communication aside.
|
|
*
|
|
* Loads the ticket communication feed via fetch and renders
|
|
* it in the aside panel, matching the debitor detail pattern.
|
|
*/
|
|
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 communicationUrl = container.dataset.ticketCommunicationUrl || '';
|
|
const fileDownloadUrl = container.dataset.fileDownloadUrl || '';
|
|
|
|
if (!ticketNo || !communicationUrl) return;
|
|
|
|
// i18n
|
|
const i18nEl = document.getElementById('helpdesk-ticket-i18n');
|
|
const i18n = i18nEl ? JSON.parse(i18nEl.textContent) : {};
|
|
const t = (key) => i18n[key] || key;
|
|
|
|
function showLoading(id) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.hidden = false;
|
|
}
|
|
|
|
function hideLoading(id) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.hidden = true;
|
|
}
|
|
|
|
function showContent(id) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.hidden = false;
|
|
}
|
|
|
|
async function loadCommunication(refresh = false) {
|
|
const feedEl = document.getElementById('ticket-communication-feed');
|
|
const loadingEl = document.getElementById('ticket-communication-loading');
|
|
const contentEl = document.getElementById('ticket-communication-content');
|
|
if (!feedEl || !loadingEl || !contentEl) return;
|
|
|
|
if (contentEl) contentEl.hidden = true;
|
|
showLoading('ticket-communication-loading');
|
|
|
|
try {
|
|
const params = new URLSearchParams({ ticketNo });
|
|
if (refresh) params.set('refresh', '1');
|
|
const response = await fetch(communicationUrl + '?' + params.toString(), { credentials: 'same-origin' });
|
|
const data = await response.json();
|
|
|
|
hideLoading('ticket-communication-loading');
|
|
showContent('ticket-communication-content');
|
|
|
|
if (!data.ok) {
|
|
feedEl.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,
|
|
fileDownloadUrl,
|
|
});
|
|
|
|
feedEl.innerHTML = html;
|
|
} catch {
|
|
hideLoading('ticket-communication-loading');
|
|
showContent('ticket-communication-content');
|
|
feedEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
|
}
|
|
}
|
|
|
|
const refreshButton = container.querySelector('button[name="ticket-refresh"]');
|
|
if (refreshButton) {
|
|
refreshButton.addEventListener('click', () => loadCommunication(true));
|
|
}
|
|
|
|
loadCommunication();
|
|
}
|