feat(js): add app-http contracts and migrate helpdesk runtime layer
This commit is contained in:
@@ -1,19 +1,34 @@
|
||||
/**
|
||||
* Dynamic domain select for handover wizard step 1.
|
||||
*
|
||||
* Reads config from data-* attributes on #wizard-domain-group:
|
||||
* data-domains-url, data-text-initial, data-text-loading,
|
||||
* data-text-select, data-text-empty, data-text-error,
|
||||
* data-initial-debitor, data-initial-domain
|
||||
*/
|
||||
import { getJson } from '/js/core/app-http.js';
|
||||
import { resolveHost } from '/js/core/app-dom.js';
|
||||
|
||||
const group = document.getElementById('wizard-domain-group');
|
||||
const lookupContainer = document.querySelector('[data-app-lookup]');
|
||||
const domainSelect = document.getElementById('wizard-domain');
|
||||
const domainUrlInput = document.getElementById('wizard-domain-url');
|
||||
const feedback = document.getElementById('wizard-domain-feedback');
|
||||
const EMPTY_API = Object.freeze({ destroy: () => {} });
|
||||
|
||||
const resolveGroup = (root) => {
|
||||
const host = resolveHost(root);
|
||||
if (host instanceof HTMLElement && host.matches('#wizard-domain-group[data-app-component="helpdesk-handover-domain-select"]')) {
|
||||
return host;
|
||||
}
|
||||
return host.querySelector('#wizard-domain-group[data-app-component="helpdesk-handover-domain-select"]');
|
||||
};
|
||||
|
||||
export function initHandoverDomainSelect(root = document) {
|
||||
const group = resolveGroup(root);
|
||||
if (!group) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
const scope = group.closest('form') || document;
|
||||
const lookupContainer = scope.querySelector('[data-app-lookup]');
|
||||
const domainSelect = group.querySelector('#wizard-domain');
|
||||
const domainUrlInput = group.querySelector('#wizard-domain-url');
|
||||
const feedback = group.querySelector('#wizard-domain-feedback');
|
||||
if (!lookupContainer || !domainSelect) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
if (group && lookupContainer && domainSelect) {
|
||||
const domainsUrl = group.dataset.domainsUrl || '';
|
||||
const textInitial = group.dataset.textInitial || '';
|
||||
const textLoading = group.dataset.textLoading || '';
|
||||
@@ -21,35 +36,48 @@ if (group && lookupContainer && domainSelect) {
|
||||
const textEmpty = group.dataset.textEmpty || '';
|
||||
const textError = group.dataset.textError || '';
|
||||
|
||||
function clearOptions() {
|
||||
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 clearOptions = () => {
|
||||
while (domainSelect.options.length > 0) {
|
||||
domainSelect.remove(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addPlaceholder(text) {
|
||||
const addPlaceholder = (text) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = text;
|
||||
domainSelect.appendChild(opt);
|
||||
}
|
||||
};
|
||||
|
||||
function setFeedback(text, variant) {
|
||||
if (!feedback) return;
|
||||
const setFeedback = (text, variant) => {
|
||||
if (!feedback) {
|
||||
return;
|
||||
}
|
||||
feedback.textContent = text;
|
||||
feedback.hidden = !text;
|
||||
feedback.dataset.variant = variant || '';
|
||||
}
|
||||
};
|
||||
|
||||
function resetDomain() {
|
||||
const resetDomain = () => {
|
||||
clearOptions();
|
||||
addPlaceholder(textInitial);
|
||||
domainSelect.disabled = true;
|
||||
if (domainUrlInput) domainUrlInput.value = '';
|
||||
if (domainUrlInput) {
|
||||
domainUrlInput.value = '';
|
||||
}
|
||||
setFeedback('', '');
|
||||
}
|
||||
};
|
||||
|
||||
async function loadDomains(debitorNo) {
|
||||
const loadDomains = async (debitorNo) => {
|
||||
clearOptions();
|
||||
addPlaceholder(textLoading);
|
||||
domainSelect.disabled = true;
|
||||
@@ -57,10 +85,9 @@ if (group && lookupContainer && domainSelect) {
|
||||
setFeedback('', '');
|
||||
|
||||
try {
|
||||
const res = await fetch(domainsUrl + '?debitor_no=' + encodeURIComponent(debitorNo), {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
const data = await getJson(`${domainsUrl}?debitor_no=${encodeURIComponent(debitorNo)}`, {
|
||||
signal: requestController.signal,
|
||||
});
|
||||
const data = await res.json();
|
||||
const items = Array.isArray(data) ? data : [];
|
||||
|
||||
clearOptions();
|
||||
@@ -72,7 +99,7 @@ if (group && lookupContainer && domainSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(function (item) {
|
||||
items.forEach((item) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = item.value || '';
|
||||
opt.textContent = item.label || item.value || '';
|
||||
@@ -80,45 +107,53 @@ if (group && lookupContainer && domainSelect) {
|
||||
domainSelect.appendChild(opt);
|
||||
});
|
||||
domainSelect.disabled = false;
|
||||
} catch {
|
||||
clearOptions();
|
||||
addPlaceholder(textSelect);
|
||||
domainSelect.disabled = true;
|
||||
setFeedback(textError, 'error');
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error && error.name === 'AbortError')) {
|
||||
clearOptions();
|
||||
addPlaceholder(textSelect);
|
||||
domainSelect.disabled = true;
|
||||
setFeedback(textError, 'error');
|
||||
}
|
||||
} finally {
|
||||
group.removeAttribute('aria-busy');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
domainSelect.addEventListener('change', function () {
|
||||
on(domainSelect, 'change', () => {
|
||||
const selected = domainSelect.options[domainSelect.selectedIndex];
|
||||
if (domainUrlInput) {
|
||||
domainUrlInput.value = selected?.dataset.url || '';
|
||||
}
|
||||
});
|
||||
|
||||
lookupContainer.addEventListener('lookup:select', function (e) {
|
||||
const debitorNo = e.detail?.value || '';
|
||||
on(lookupContainer, 'lookup:select', (event) => {
|
||||
const debitorNo = event.detail?.value || '';
|
||||
if (debitorNo) {
|
||||
loadDomains(debitorNo);
|
||||
void loadDomains(debitorNo);
|
||||
} else {
|
||||
resetDomain();
|
||||
}
|
||||
});
|
||||
|
||||
lookupContainer.addEventListener('lookup:clear', function () {
|
||||
on(lookupContainer, 'lookup:clear', () => {
|
||||
resetDomain();
|
||||
});
|
||||
|
||||
// Restore selection if returning to step 1 with session data
|
||||
const initialDebitor = group.dataset.initialDebitor || '';
|
||||
const initialDomain = group.dataset.initialDomain || '';
|
||||
if (initialDebitor) {
|
||||
loadDomains(initialDebitor).then(function () {
|
||||
void loadDomains(initialDebitor).then(() => {
|
||||
if (initialDomain && !domainSelect.disabled) {
|
||||
domainSelect.value = initialDomain;
|
||||
domainSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
listenerController.abort();
|
||||
requestController.abort();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user