Files
customerTicket/static/js/chaharsu/customer-picker.js
T
2026-10-06 10:03:22 +03:30

95 lines
5.6 KiB
JavaScript

/* Local, server-filtered search. Explicit choice only; no customer data in storage. */
(() => {
'use strict';
const root = document.querySelector('[data-payment-customer-picker]');
if (!root) return;
const search = root.querySelector('input[type=search]');
const value = root.querySelector('#id_customer');
const results = root.querySelector('[data-customer-results]');
const selected = root.querySelector('[data-selected-customer]');
const status = root.querySelector('[role=status]');
let timer, controller, version = 0;
const node = (tag, text, className) => {
const element = document.createElement(tag);
if (text !== undefined) element.textContent = text;
if (className) element.className = className;
return element;
};
const history = customer => {
const details = node('details', undefined, 'pay-customer-history');
details.append(node('summary', `سوابق قابل مشاهده · ${customer.ticket_count.toLocaleString('fa-IR')} تیکت`));
if (!customer.history.length) details.append(node('p', 'سابقه تیکت قابل مشاهده‌ای وجود ندارد.'));
customer.history.forEach(ticket => {
const link = node('a', `${ticket.number} · ${ticket.subject}`);
link.href = ticket.url; link.target = '_blank'; link.rel = 'noopener';
const row = node('div'); row.append(link, node('small', ticket.status)); details.append(row);
});
return details;
};
const identity = customer => {
const container = node('div', undefined, 'pay-customer-identity');
container.append(node('strong', customer.name), node('bdi', customer.mobile));
if (customer.organization) container.append(node('span', customer.organization));
container.append(node('small', customer.location || 'موقعیت ثبت نشده'));
return container;
};
const choose = (customer, focus = true) => {
clearTimeout(timer); version++; controller?.abort();
value.value = customer.value; value.dispatchEvent(new Event('change', {bubbles:true}));
search.value = ''; results.hidden = true; results.replaceChildren();
selected.replaceChildren(node('span', 'مشتری انتخاب‌شده', 'pay-selected-label'), identity(customer), history(customer));
const change = node('button', 'تغییر مشتری', 'pay-button secondary'); change.type = 'button';
change.addEventListener('click', () => {
value.value = ''; value.dispatchEvent(new Event('change', {bubbles:true})); selected.hidden = true; search.focus();
status.textContent = 'مشتری جدید را جست‌وجو و انتخاب کنید.';
});
selected.append(change); selected.hidden = false;
status.textContent = 'قبل از صدور، نام و شماره تماس مشتری انتخاب‌شده را بررسی کنید.';
if (focus) change.focus({preventScroll:true});
};
const lookup = async () => {
const query = search.value.trim(), current = ++version;
controller?.abort(); controller = new AbortController();
if (query.length < 2) { results.hidden = true; status.textContent = 'حداقل دو حرف یا رقم وارد کنید.'; return; }
status.textContent = 'در حال جست‌وجو…';
try {
const response = await fetch(`${root.dataset.searchUrl}?q=${encodeURIComponent(query)}`, {
credentials: 'same-origin', cache: 'no-store', signal: controller.signal
});
if (!response.ok) throw new Error('lookup');
const data = await response.json();
if (current !== version) return;
results.replaceChildren();
data.results.forEach(customer => {
const card = node('article', undefined, 'pay-customer-result');
const button = node('button', 'انتخاب این مشتری', 'pay-button secondary'); button.type = 'button';
button.addEventListener('click', () => choose(customer));
card.append(identity(customer), history(customer), button); results.append(card);
});
results.hidden = !data.results.length;
status.textContent = data.results.length ? `${data.results.length.toLocaleString('fa-IR')} نتیجه${data.has_more ? '؛ برای نتایج دقیق‌تر نام یا شماره کامل‌تری وارد کنید.' : '؛ مشتری موردنظر را انتخاب کنید.'}` : 'مشتری فعالی پیدا نشد؛ نام یا شماره تماس را بررسی کنید.';
} catch (error) {
if (error.name === 'AbortError' || current !== version) return;
results.hidden = true;
status.textContent = 'جست‌وجو انجام نشد؛ اتصال را بررسی کنید و دوباره جست‌وجو کنید.';
}
};
search.addEventListener('input', () => {
clearTimeout(timer); version++; controller?.abort();
value.value = ''; value.dispatchEvent(new Event('change', {bubbles:true})); selected.hidden = true; results.hidden = true;
if (search.value.trim().length < 2) { status.textContent = 'حداقل دو حرف یا رقم وارد کنید.'; return; }
timer = setTimeout(lookup, 350);
});
search.addEventListener('keydown', event => {
if (event.key === 'Enter') { event.preventDefault(); clearTimeout(timer); lookup(); }
});
root.closest('form').addEventListener('submit', event => {
if (!value.value) {
event.preventDefault(); status.textContent = 'مشتری را از نتایج جست‌وجو انتخاب کنید.';
search.focus(); search.scrollIntoView({block:'center'});
}
});
const initial = JSON.parse(document.querySelector('#payment-selected-customer').textContent);
if (initial && String(initial.value) === value.value) choose(initial, false);
})();