feat(js): add app-http contracts and migrate helpdesk runtime layer
This commit is contained in:
@@ -1,66 +1,82 @@
|
||||
/**
|
||||
* 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 { getJson } from '/js/core/app-http.js';
|
||||
import { resolveHost } from '/js/core/app-dom.js';
|
||||
import {
|
||||
renderCommunicationError,
|
||||
renderCommunicationFeed,
|
||||
renderCommunicationHint,
|
||||
} from './helpdesk-communication.js';
|
||||
|
||||
const container = document.querySelector('.app-details-container[data-ticket-no]');
|
||||
if (container) {
|
||||
init(container);
|
||||
}
|
||||
const EMPTY_API = Object.freeze({ destroy: () => {} });
|
||||
|
||||
const resolveContainer = (root) => {
|
||||
const host = resolveHost(root);
|
||||
if (host instanceof HTMLElement && host.matches('.app-details-container[data-ticket-no]')) {
|
||||
return host;
|
||||
}
|
||||
return host.querySelector('.app-details-container[data-ticket-no]');
|
||||
};
|
||||
|
||||
export function initHelpdeskTicket(root = document) {
|
||||
const container = resolveContainer(root);
|
||||
if (!container) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
function init(container) {
|
||||
const ticketNo = container.dataset.ticketNo || '';
|
||||
const communicationUrl = container.dataset.ticketCommunicationUrl || '';
|
||||
const fileDownloadUrl = container.dataset.fileDownloadUrl || '';
|
||||
if (!ticketNo || !communicationUrl) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
if (!ticketNo || !communicationUrl) return;
|
||||
const listenerController = new AbortController();
|
||||
const requestController = new AbortController();
|
||||
const on = (target, type, handler, options = {}) => {
|
||||
if (!target || typeof target.addEventListener !== 'function') {
|
||||
return;
|
||||
}
|
||||
target.addEventListener(type, handler, { ...options, signal: listenerController.signal });
|
||||
};
|
||||
|
||||
// i18n
|
||||
const i18nEl = document.getElementById('helpdesk-ticket-i18n');
|
||||
const i18n = i18nEl ? JSON.parse(i18nEl.textContent) : {};
|
||||
const i18n = i18nEl ? JSON.parse(i18nEl.textContent || '{}') : {};
|
||||
const t = (key) => i18n[key] || key;
|
||||
|
||||
function showLoading(id) {
|
||||
const toggleHidden = (id, hidden) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.hidden = false;
|
||||
}
|
||||
if (el) {
|
||||
el.hidden = hidden;
|
||||
}
|
||||
};
|
||||
|
||||
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 loadCommunication = async (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 (!feedEl || !loadingEl || !contentEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentEl) contentEl.hidden = true;
|
||||
showLoading('ticket-communication-loading');
|
||||
contentEl.hidden = true;
|
||||
loadingEl.hidden = false;
|
||||
|
||||
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();
|
||||
if (refresh) {
|
||||
params.set('refresh', '1');
|
||||
}
|
||||
|
||||
hideLoading('ticket-communication-loading');
|
||||
showContent('ticket-communication-content');
|
||||
const data = await getJson(`${communicationUrl}?${params.toString()}`, {
|
||||
signal: requestController.signal,
|
||||
});
|
||||
|
||||
if (!data.ok) {
|
||||
toggleHidden('ticket-communication-loading', true);
|
||||
toggleHidden('ticket-communication-content', false);
|
||||
|
||||
if (!data || data.ok !== true) {
|
||||
feedEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
||||
return;
|
||||
}
|
||||
@@ -82,17 +98,30 @@ function init(container) {
|
||||
});
|
||||
|
||||
feedEl.innerHTML = html;
|
||||
} catch {
|
||||
hideLoading('ticket-communication-loading');
|
||||
showContent('ticket-communication-content');
|
||||
feedEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
toggleHidden('ticket-communication-loading', true);
|
||||
toggleHidden('ticket-communication-content', false);
|
||||
const feedEl = document.getElementById('ticket-communication-feed');
|
||||
if (feedEl) {
|
||||
feedEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refreshButton = container.querySelector('button[name="ticket-refresh"]');
|
||||
if (refreshButton) {
|
||||
refreshButton.addEventListener('click', () => loadCommunication(true));
|
||||
}
|
||||
on(refreshButton, 'click', () => {
|
||||
void loadCommunication(true);
|
||||
});
|
||||
|
||||
loadCommunication();
|
||||
void loadCommunication();
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
listenerController.abort();
|
||||
requestController.abort();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user