Select had name="domain_no" but JS was looking for input[name="domain_no"]. Moved name to a hidden input #wizard-domain-no; select syncs into it on change; manual text input syncs into it on input event. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
201 lines
6.1 KiB
JavaScript
201 lines
6.1 KiB
JavaScript
/**
|
|
* Dynamic domain select for handover wizard step 1.
|
|
*/
|
|
import { getJson } from '/js/core/app-http.js';
|
|
import { resolveHost } from '/js/core/app-dom.js';
|
|
|
|
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 domainNoInput = group.querySelector('#wizard-domain-no');
|
|
const domainUrlInput = group.querySelector('#wizard-domain-url');
|
|
const feedback = group.querySelector('#wizard-domain-feedback');
|
|
const manualBlock = group.querySelector('#wizard-domain-manual');
|
|
const manualNoInput = group.querySelector('#wizard-domain-manual-no');
|
|
const manualUrlInput = group.querySelector('#wizard-domain-manual-url');
|
|
if (!lookupContainer || !domainSelect) {
|
|
return EMPTY_API;
|
|
}
|
|
|
|
const domainsUrl = group.dataset.domainsUrl || '';
|
|
const textInitial = group.dataset.textInitial || '';
|
|
const textLoading = group.dataset.textLoading || '';
|
|
const textSelect = group.dataset.textSelect || '';
|
|
const textEmpty = group.dataset.textEmpty || '';
|
|
const textError = group.dataset.textError || '';
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
const addPlaceholder = (text) => {
|
|
const opt = document.createElement('option');
|
|
opt.value = '';
|
|
opt.textContent = text;
|
|
domainSelect.appendChild(opt);
|
|
};
|
|
|
|
const setFeedback = (text, variant) => {
|
|
if (!feedback) {
|
|
return;
|
|
}
|
|
feedback.textContent = text;
|
|
feedback.hidden = !text;
|
|
feedback.dataset.variant = variant || '';
|
|
};
|
|
|
|
const showManual = (show) => {
|
|
if (!manualBlock) {
|
|
return;
|
|
}
|
|
manualBlock.hidden = !show;
|
|
domainSelect.closest('.app-wizard-domain-select-wrap')?.toggleAttribute('hidden', show);
|
|
if (!show && manualNoInput) {
|
|
manualNoInput.value = '';
|
|
}
|
|
if (!show && manualUrlInput) {
|
|
manualUrlInput.value = '';
|
|
}
|
|
// When showing manual, clear the hidden domain_no/url so only manual values submit
|
|
if (show) {
|
|
if (domainNoInput) domainNoInput.value = '';
|
|
if (domainUrlInput) domainUrlInput.value = '';
|
|
}
|
|
};
|
|
|
|
const resetDomain = () => {
|
|
clearOptions();
|
|
addPlaceholder(textInitial);
|
|
domainSelect.disabled = true;
|
|
if (domainNoInput) domainNoInput.value = '';
|
|
if (domainUrlInput) domainUrlInput.value = '';
|
|
setFeedback('', '');
|
|
showManual(false);
|
|
};
|
|
|
|
const loadDomains = async (debitorNo) => {
|
|
clearOptions();
|
|
addPlaceholder(textLoading);
|
|
domainSelect.disabled = true;
|
|
group.setAttribute('aria-busy', 'true');
|
|
setFeedback('', '');
|
|
|
|
try {
|
|
const data = await getJson(`${domainsUrl}?debitor_no=${encodeURIComponent(debitorNo)}`, {
|
|
signal: requestController.signal,
|
|
});
|
|
const items = Array.isArray(data) ? data : [];
|
|
|
|
clearOptions();
|
|
addPlaceholder(textSelect);
|
|
|
|
if (items.length === 0) {
|
|
domainSelect.disabled = true;
|
|
setFeedback('', '');
|
|
showManual(true);
|
|
return;
|
|
}
|
|
showManual(false);
|
|
|
|
items.forEach((item) => {
|
|
const opt = document.createElement('option');
|
|
opt.value = item.value || '';
|
|
opt.textContent = item.label || item.value || '';
|
|
opt.dataset.url = item.url || '';
|
|
domainSelect.appendChild(opt);
|
|
});
|
|
domainSelect.disabled = false;
|
|
} catch (error) {
|
|
if (!(error instanceof Error && error.name === 'AbortError')) {
|
|
clearOptions();
|
|
addPlaceholder(textSelect);
|
|
domainSelect.disabled = true;
|
|
setFeedback(textError, 'error');
|
|
}
|
|
} finally {
|
|
group.removeAttribute('aria-busy');
|
|
}
|
|
};
|
|
|
|
on(domainSelect, 'change', () => {
|
|
const selected = domainSelect.options[domainSelect.selectedIndex];
|
|
if (domainNoInput) domainNoInput.value = selected?.value || '';
|
|
if (domainUrlInput) domainUrlInput.value = selected?.dataset.url || '';
|
|
});
|
|
|
|
// Sync manual inputs → hidden domain_no / domain_url fields
|
|
if (manualNoInput) {
|
|
on(manualNoInput, 'input', () => {
|
|
if (domainNoInput) domainNoInput.value = manualNoInput.value.trim();
|
|
});
|
|
}
|
|
if (manualUrlInput) {
|
|
on(manualUrlInput, 'input', () => {
|
|
if (domainUrlInput) {
|
|
domainUrlInput.value = manualUrlInput.value.trim();
|
|
}
|
|
});
|
|
}
|
|
|
|
on(lookupContainer, 'lookup:select', (event) => {
|
|
const debitorNo = event.detail?.value || '';
|
|
if (debitorNo) {
|
|
void loadDomains(debitorNo);
|
|
} else {
|
|
resetDomain();
|
|
}
|
|
});
|
|
|
|
on(lookupContainer, 'lookup:clear', () => {
|
|
resetDomain();
|
|
});
|
|
|
|
const initialDebitor = group.dataset.initialDebitor || '';
|
|
const initialDomain = group.dataset.initialDomain || '';
|
|
if (initialDebitor) {
|
|
void loadDomains(initialDebitor).then(() => {
|
|
if (initialDomain && !domainSelect.disabled) {
|
|
domainSelect.value = initialDomain;
|
|
domainSelect.dispatchEvent(new Event('change'));
|
|
}
|
|
});
|
|
} else if (domainNoInput && domainNoInput.value) {
|
|
// Restore hidden value on page reload with validation errors (manual mode)
|
|
if (manualNoInput) manualNoInput.value = domainNoInput.value;
|
|
}
|
|
|
|
return {
|
|
destroy: () => {
|
|
listenerController.abort();
|
|
requestController.abort();
|
|
},
|
|
};
|
|
}
|