feat(js): add app-http contracts and migrate helpdesk runtime layer

This commit is contained in:
2026-04-20 22:31:13 +02:00
parent ec2f03e0cf
commit f189ef9df6
34 changed files with 2023 additions and 1134 deletions

View File

@@ -3,9 +3,34 @@
* Current tab: per-agent widget with section title + ticket table.
* Performance tab: compact agent rows with resolved counts.
*/
import { getJson } from '/js/core/app-http.js';
import { resolveHost } from '/js/core/app-dom.js';
const EMPTY_API = Object.freeze({ destroy: () => {} });
const resolveContainer = (root) => {
const host = resolveHost(root);
if (host instanceof HTMLElement && host.matches('.app-details-container[data-app-component="helpdesk-team"]')) {
return host;
}
return host.querySelector('.app-details-container[data-app-component="helpdesk-team"]');
};
export function initHelpdeskTeam(root = document) {
const container = resolveContainer(root);
if (!container) {
return EMPTY_API;
}
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 });
};
const container = document.querySelector('.app-details-container[data-team-workload-url]');
if (container) {
const dataUrl = container.dataset.teamWorkloadUrl;
const labelQueue = container.dataset.labelQueue || 'Queue';
const labelTickets = container.dataset.labelTickets || 'Tickets';
@@ -202,7 +227,7 @@ if (container) {
for (const item of items) {
const li = h('li', 'helpdesk-team-perf-ranked-clickable');
li.addEventListener('click', () => {
on(li, 'click', () => {
if (activeItem === li) {
activeItem.classList.remove('active');
activeItem = null;
@@ -363,8 +388,9 @@ if (container) {
if (refresh) params.set('refresh', '1');
try {
const res = await fetch(dataUrl + '?' + params.toString(), { credentials: 'same-origin' });
const json = await res.json();
const json = await getJson(`${dataUrl}?${params.toString()}`, {
signal: requestController.signal,
});
if (!json.ok) { showState('error'); return; }
if (!json.members || json.members.length === 0) { showState('empty'); return; }
@@ -372,25 +398,31 @@ if (container) {
renderCurrentTab(json.members);
renderPerformanceTab(json.members);
showState('success');
} catch {
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
return;
}
showState('error');
}
}
if (periodSelector) {
periodSelector.addEventListener('change', (e) => {
const radio = e.target.closest('input[name="team-period"]');
if (!radio) return;
const period = parseInt(radio.value, 10);
if (!period || period === currentPeriod) return;
currentPeriod = period;
fetchData();
});
}
on(periodSelector, 'change', (e) => {
const radio = e.target.closest('input[name="team-period"]');
if (!radio) return;
const period = parseInt(radio.value, 10);
if (!period || period === currentPeriod) return;
currentPeriod = period;
void fetchData();
});
if (elRefresh) {
elRefresh.addEventListener('click', () => fetchData(true));
}
on(elRefresh, 'click', () => void fetchData(true));
fetchData();
void fetchData();
return {
destroy: () => {
listenerController.abort();
requestController.abort();
},
};
}