// ── State ─────────────────────────────────────────
let dashPage = 1;
let dashPerPage = 50;
let dashSortCol = 'order_date';
let dashSortDir = 'desc';
let dashSearchTimeout = null;
let currentQmSku = '';
let currentQmOrderNumber = '';
let qmAcTimeout = null;
// Sync polling state
let _pollInterval = null;
let _lastSyncStatus = null;
let _lastRunId = null;
let _currentRunId = null;
// ── Init ──────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
loadSchedulerStatus();
loadDashOrders();
startSyncPolling();
wireFilterBar();
});
// ── Smart Sync Polling ────────────────────────────
function startSyncPolling() {
if (_pollInterval) clearInterval(_pollInterval);
_pollInterval = setInterval(pollSyncStatus, 30000);
pollSyncStatus(); // immediate first call
}
async function pollSyncStatus() {
try {
const data = await fetchJSON('/api/sync/status');
updateSyncPanel(data);
const isRunning = data.status === 'running';
const wasRunning = _lastSyncStatus === 'running';
if (isRunning && !wasRunning) {
// Switched to running — speed up polling
clearInterval(_pollInterval);
_pollInterval = setInterval(pollSyncStatus, 3000);
} else if (!isRunning && wasRunning) {
// Sync just completed — slow down and refresh orders
clearInterval(_pollInterval);
_pollInterval = setInterval(pollSyncStatus, 30000);
loadDashOrders();
}
_lastSyncStatus = data.status;
} catch (e) {
console.warn('Sync status poll failed:', e);
}
}
function updateSyncPanel(data) {
const dot = document.getElementById('syncStatusDot');
const txt = document.getElementById('syncStatusText');
const progressArea = document.getElementById('syncProgressArea');
const progressText = document.getElementById('syncProgressText');
const startBtn = document.getElementById('syncStartBtn');
if (dot) {
dot.className = 'sync-status-dot ' + (data.status || 'idle');
}
const statusLabels = { running: 'A ruleaza...', idle: 'Inactiv', completed: 'Finalizat', failed: 'Eroare' };
if (txt) txt.textContent = statusLabels[data.status] || data.status || 'Inactiv';
if (startBtn) startBtn.disabled = data.status === 'running';
// Track current running sync run_id
if (data.status === 'running' && data.run_id) {
_currentRunId = data.run_id;
} else {
_currentRunId = null;
}
// Live progress area
if (progressArea) {
progressArea.style.display = data.status === 'running' ? 'flex' : 'none';
}
if (progressText && data.phase_text) {
progressText.textContent = data.phase_text;
}
// Last run info
const lr = data.last_run;
if (lr) {
_lastRunId = lr.run_id;
const d = document.getElementById('lastSyncDate');
const dur = document.getElementById('lastSyncDuration');
const cnt = document.getElementById('lastSyncCounts');
const st = document.getElementById('lastSyncStatus');
if (d) d.textContent = lr.started_at ? lr.started_at.replace('T', ' ').slice(0, 16) : '\u2014';
if (dur) dur.textContent = lr.duration_seconds ? Math.round(lr.duration_seconds) + 's' : '\u2014';
if (cnt) {
const newImp = lr.new_imported || 0;
const already = lr.already_imported || 0;
if (already > 0) {
cnt.innerHTML = `${newImp} noi, ${already} deja ${lr.skipped || 0} omise ${lr.errors || 0} erori`;
} else {
cnt.innerHTML = `${lr.imported || 0} imp. ${lr.skipped || 0} omise ${lr.errors || 0} erori`;
}
}
if (st) {
st.textContent = lr.status === 'completed' ? '\u2713' : '\u2715';
st.style.color = lr.status === 'completed' ? '#10b981' : '#ef4444';
}
}
}
// Wire last-sync-row click → journal (use current running sync if active)
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('lastSyncRow')?.addEventListener('click', () => {
const targetId = _currentRunId || _lastRunId;
if (targetId) window.location = (window.ROOT_PATH || '') + '/logs?run=' + targetId;
});
document.getElementById('lastSyncRow')?.addEventListener('keydown', (e) => {
const targetId = _currentRunId || _lastRunId;
if ((e.key === 'Enter' || e.key === ' ') && targetId) {
window.location = '/logs?run=' + targetId;
}
});
});
// ── Sync Controls ─────────────────────────────────
async function startSync() {
try {
const res = await fetch('/api/sync/start', { method: 'POST' });
const data = await res.json();
if (data.error) {
alert(data.error);
return;
}
// Polling will detect the running state — just speed it up immediately
pollSyncStatus();
} catch (err) {
alert('Eroare: ' + err.message);
}
}
async function stopSync() {
try {
await fetch('/api/sync/stop', { method: 'POST' });
pollSyncStatus();
} catch (err) {
alert('Eroare: ' + err.message);
}
}
async function toggleScheduler() {
const enabled = document.getElementById('schedulerToggle').checked;
const interval = parseInt(document.getElementById('schedulerInterval').value) || 10;
try {
await fetch('/api/sync/schedule', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled, interval_minutes: interval })
});
} catch (err) {
alert('Eroare scheduler: ' + err.message);
}
}
async function updateSchedulerInterval() {
const enabled = document.getElementById('schedulerToggle').checked;
if (enabled) {
await toggleScheduler();
}
}
async function loadSchedulerStatus() {
try {
const res = await fetch('/api/sync/schedule');
const data = await res.json();
document.getElementById('schedulerToggle').checked = data.enabled || false;
if (data.interval_minutes) {
document.getElementById('schedulerInterval').value = data.interval_minutes;
}
} catch (err) {
console.error('loadSchedulerStatus error:', err);
}
}
// ── Filter Bar wiring ─────────────────────────────
function wireFilterBar() {
// Period dropdown
document.getElementById('periodSelect')?.addEventListener('change', function () {
const cr = document.getElementById('customRangeInputs');
if (this.value === 'custom') {
cr?.classList.add('visible');
} else {
cr?.classList.remove('visible');
dashPage = 1;
loadDashOrders();
}
});
// Custom range inputs
['periodStart', 'periodEnd'].forEach(id => {
document.getElementById(id)?.addEventListener('change', () => {
const s = document.getElementById('periodStart')?.value;
const e = document.getElementById('periodEnd')?.value;
if (s && e) { dashPage = 1; loadDashOrders(); }
});
});
// Status pills
document.querySelectorAll('.filter-pill[data-status]').forEach(btn => {
btn.addEventListener('click', function () {
document.querySelectorAll('.filter-pill[data-status]').forEach(b => b.classList.remove('active'));
this.classList.add('active');
dashPage = 1;
loadDashOrders();
});
});
// Search — 300ms debounce
document.getElementById('orderSearch')?.addEventListener('input', () => {
clearTimeout(dashSearchTimeout);
dashSearchTimeout = setTimeout(() => {
dashPage = 1;
loadDashOrders();
}, 300);
});
}
// ── Dashboard Orders Table ────────────────────────
function dashSortBy(col) {
if (dashSortCol === col) {
dashSortDir = dashSortDir === 'asc' ? 'desc' : 'asc';
} else {
dashSortCol = col;
dashSortDir = 'asc';
}
document.querySelectorAll('.sort-icon').forEach(span => {
const c = span.dataset.col;
span.textContent = c === dashSortCol ? (dashSortDir === 'asc' ? '\u2191' : '\u2193') : '';
});
dashPage = 1;
loadDashOrders();
}
async function loadDashOrders() {
const periodVal = document.getElementById('periodSelect')?.value || '7';
const params = new URLSearchParams();
if (periodVal === 'custom') {
const s = document.getElementById('periodStart')?.value;
const e = document.getElementById('periodEnd')?.value;
if (s && e) {
params.set('period_start', s);
params.set('period_end', e);
params.set('period_days', '0');
}
} else {
params.set('period_days', periodVal);
}
const activeStatus = document.querySelector('.filter-pill.active')?.dataset.status;
if (activeStatus && activeStatus !== 'all') params.set('status', activeStatus);
const search = document.getElementById('orderSearch')?.value?.trim();
if (search) params.set('search', search);
params.set('page', dashPage);
params.set('per_page', dashPerPage);
params.set('sort_by', dashSortCol);
params.set('sort_dir', dashSortDir);
try {
const res = await fetch(`/api/dashboard/orders?${params}`);
const data = await res.json();
// Update filter-pill badge counts
const c = data.counts || {};
const el = (id) => document.getElementById(id);
if (el('cntAll')) el('cntAll').textContent = c.total || 0;
if (el('cntImp')) el('cntImp').textContent = c.imported_all || c.imported || 0;
if (el('cntSkip')) el('cntSkip').textContent = c.skipped || 0;
if (el('cntErr')) el('cntErr').textContent = c.error || c.errors || 0;
if (el('cntFact')) el('cntFact').textContent = c.facturate || 0;
if (el('cntNef')) el('cntNef').textContent = c.nefacturate || c.uninvoiced || 0;
const tbody = document.getElementById('dashOrdersBody');
const orders = data.orders || [];
if (orders.length === 0) {
tbody.innerHTML = '
| Nicio comanda |
';
} else {
tbody.innerHTML = orders.map(o => {
const dateStr = fmtDate(o.order_date);
const orderTotal = o.order_total != null ? Number(o.order_total).toFixed(2) : '-';
return `
| ${statusDot(o.status)} |
${dateStr} |
${renderClientCell(o)}
${esc(o.order_number)} |
${o.items_count || 0} |
${fmtCost(o.delivery_cost)} |
${fmtCost(o.discount_total)} |
${orderTotal} |
${invoiceDot(o)} |
`;
}).join('');
}
// Mobile flat rows
const mobileList = document.getElementById('dashMobileList');
if (mobileList) {
if (orders.length === 0) {
mobileList.innerHTML = 'Nicio comanda
';
} else {
mobileList.innerHTML = orders.map(o => {
const d = o.order_date || '';
let dateFmt = '-';
if (d.length >= 10) {
dateFmt = d.slice(8, 10) + '.' + d.slice(5, 7) + '.' + d.slice(2, 4);
if (d.length >= 16) dateFmt += ' ' + d.slice(11, 16);
}
const name = o.customer_name || o.shipping_name || o.billing_name || '\u2014';
const totalStr = o.order_total ? Number(o.order_total).toFixed(2) : '';
return `
${statusDot(o.status)}
${dateFmt}
${esc(name)}
x${o.items_count || 0}${totalStr ? ' · ' + totalStr + '' : ''}
`;
}).join('');
}
}
// Mobile segmented control
renderMobileSegmented('dashMobileSeg', [
{ label: 'Toate', count: c.total || 0, value: 'all', active: (activeStatus || 'all') === 'all', colorClass: 'fc-neutral' },
{ label: 'Imp.', count: c.imported_all || c.imported || 0, value: 'IMPORTED', active: activeStatus === 'IMPORTED', colorClass: 'fc-green' },
{ label: 'Omise', count: c.skipped || 0, value: 'SKIPPED', active: activeStatus === 'SKIPPED', colorClass: 'fc-yellow' },
{ label: 'Erori', count: c.error || c.errors || 0, value: 'ERROR', active: activeStatus === 'ERROR', colorClass: 'fc-red' },
{ label: 'Fact.', count: c.facturate || 0, value: 'INVOICED', active: activeStatus === 'INVOICED', colorClass: 'fc-green' },
{ label: 'Nefact.', count: c.nefacturate || c.uninvoiced || 0, value: 'UNINVOICED', active: activeStatus === 'UNINVOICED', colorClass: 'fc-red' }
], (val) => {
document.querySelectorAll('.filter-pill[data-status]').forEach(b => b.classList.remove('active'));
const pill = document.querySelector(`.filter-pill[data-status="${val}"]`);
if (pill) pill.classList.add('active');
dashPage = 1;
loadDashOrders();
});
// Pagination
const pag = data.pagination || {};
const totalPages = pag.total_pages || data.pages || 1;
const totalOrders = (data.counts || {}).total || data.total || 0;
const pagOpts = { perPage: dashPerPage, perPageFn: 'dashChangePerPage', perPageOptions: [25, 50, 100, 250] };
const pagHtml = `${totalOrders} comenzi | Pagina ${dashPage} din ${totalPages}` + renderUnifiedPagination(dashPage, totalPages, 'dashGoPage', pagOpts);
const pagDiv = document.getElementById('dashPagination');
if (pagDiv) pagDiv.innerHTML = pagHtml;
const pagDivTop = document.getElementById('dashPaginationTop');
if (pagDivTop) pagDivTop.innerHTML = pagHtml;
// Update sort icons
document.querySelectorAll('.sort-icon').forEach(span => {
const c = span.dataset.col;
span.textContent = c === dashSortCol ? (dashSortDir === 'asc' ? '\u2191' : '\u2193') : '';
});
} catch (err) {
document.getElementById('dashOrdersBody').innerHTML =
`| ${esc(err.message)} |
`;
}
}
function dashGoPage(p) {
dashPage = p;
loadDashOrders();
}
function dashChangePerPage(val) {
dashPerPage = parseInt(val) || 50;
dashPage = 1;
loadDashOrders();
}
// ── Client cell with Cont tooltip (Task F4) ───────
function renderClientCell(order) {
const display = (order.customer_name || order.shipping_name || '').trim();
const billing = (order.billing_name || '').trim();
const shipping = (order.shipping_name || '').trim();
const isDiff = display !== shipping && shipping;
if (isDiff) {
return `${escHtml(display)} ▲ | `;
}
return `${escHtml(display || billing || '\u2014')} | `;
}
// ── Helper functions ──────────────────────────────
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
function escHtml(s) {
if (s == null) return '';
return String(s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Alias kept for backward compat with inline handlers in modal
function esc(s) { return escHtml(s); }
function fmtCost(v) {
return v > 0 ? Number(v).toFixed(2) : '–';
}
function statusLabelText(status) {
switch ((status || '').toUpperCase()) {
case 'IMPORTED': return 'Importat';
case 'ALREADY_IMPORTED': return 'Deja imp.';
case 'SKIPPED': return 'Omis';
case 'ERROR': return 'Eroare';
default: return esc(status);
}
}
function orderStatusBadge(status) {
switch ((status || '').toUpperCase()) {
case 'IMPORTED': return 'Importat';
case 'ALREADY_IMPORTED': return 'Deja importat';
case 'SKIPPED': return 'Omis';
case 'ERROR': return 'Eroare';
case 'DELETED_IN_ROA': return 'Sters din ROA';
default: return `${esc(status)}`;
}
}
function invoiceDot(order) {
if (order.status !== 'IMPORTED' && order.status !== 'ALREADY_IMPORTED') return '–';
if (order.invoice && order.invoice.facturat) return '';
return '';
}
function renderCodmatCell(item) {
if (!item.codmat_details || item.codmat_details.length === 0) {
return `${esc(item.codmat || '-')}`;
}
if (item.codmat_details.length === 1) {
const d = item.codmat_details[0];
return `${esc(d.codmat)}`;
}
return item.codmat_details.map(d =>
`${esc(d.codmat)} \xd7${d.cantitate_roa} (${d.procent_pret}%)
`
).join('');
}
// ── Refresh Invoices ──────────────────────────────
async function refreshInvoices() {
const btn = document.getElementById('btnRefreshInvoices');
const btnM = document.getElementById('btnRefreshInvoicesMobile');
if (btn) { btn.disabled = true; btn.textContent = '⟳ Se verifica...'; }
if (btnM) { btnM.disabled = true; }
try {
const res = await fetch('/api/dashboard/refresh-invoices', { method: 'POST' });
const data = await res.json();
if (data.error) {
alert('Eroare: ' + data.error);
} else {
loadDashOrders();
}
} catch (err) {
alert('Eroare: ' + err.message);
} finally {
if (btn) { btn.disabled = false; btn.textContent = '↻ Facturi'; }
if (btnM) { btnM.disabled = false; }
}
}
// ── Order Detail Modal ────────────────────────────
async function openDashOrderDetail(orderNumber) {
document.getElementById('detailOrderNumber').textContent = '#' + orderNumber;
document.getElementById('detailCustomer').textContent = '...';
document.getElementById('detailDate').textContent = '';
document.getElementById('detailStatus').innerHTML = '';
document.getElementById('detailIdComanda').textContent = '-';
document.getElementById('detailIdPartener').textContent = '-';
document.getElementById('detailIdAdresaFact').textContent = '-';
document.getElementById('detailIdAdresaLivr').textContent = '-';
document.getElementById('detailItemsBody').innerHTML = '| Se incarca... |
';
document.getElementById('detailError').style.display = 'none';
const invInfo = document.getElementById('detailInvoiceInfo');
if (invInfo) invInfo.style.display = 'none';
const detailItemsTotal = document.getElementById('detailItemsTotal');
if (detailItemsTotal) detailItemsTotal.textContent = '-';
const detailOrderTotal = document.getElementById('detailOrderTotal');
if (detailOrderTotal) detailOrderTotal.textContent = '-';
const mobileContainer = document.getElementById('detailItemsMobile');
if (mobileContainer) mobileContainer.innerHTML = '';
const modalEl = document.getElementById('orderDetailModal');
const existing = bootstrap.Modal.getInstance(modalEl);
if (existing) { existing.show(); } else { new bootstrap.Modal(modalEl).show(); }
try {
const res = await fetch(`/api/sync/order/${encodeURIComponent(orderNumber)}`);
const data = await res.json();
if (data.error) {
document.getElementById('detailError').textContent = data.error;
document.getElementById('detailError').style.display = '';
return;
}
const order = data.order || {};
document.getElementById('detailCustomer').textContent = order.customer_name || '-';
document.getElementById('detailDate').textContent = fmtDate(order.order_date);
document.getElementById('detailStatus').innerHTML = orderStatusBadge(order.status);
document.getElementById('detailIdComanda').textContent = order.id_comanda || '-';
document.getElementById('detailIdPartener').textContent = order.id_partener || '-';
document.getElementById('detailIdAdresaFact').textContent = order.id_adresa_facturare || '-';
document.getElementById('detailIdAdresaLivr').textContent = order.id_adresa_livrare || '-';
// Invoice info
const invInfo = document.getElementById('detailInvoiceInfo');
const inv = order.invoice;
if (inv && inv.facturat) {
const serie = inv.serie_act || '';
const numar = inv.numar_act || '';
document.getElementById('detailInvoiceNumber').textContent = serie ? `${serie} ${numar}` : numar;
document.getElementById('detailInvoiceDate').textContent = inv.data_act ? fmtDate(inv.data_act) : '-';
if (invInfo) invInfo.style.display = '';
} else {
if (invInfo) invInfo.style.display = 'none';
}
if (order.error_message) {
document.getElementById('detailError').textContent = order.error_message;
document.getElementById('detailError').style.display = '';
}
const dlvEl = document.getElementById('detailDeliveryCost');
if (dlvEl) dlvEl.textContent = order.delivery_cost > 0 ? Number(order.delivery_cost).toFixed(2) + ' lei' : '–';
const dscEl = document.getElementById('detailDiscount');
if (dscEl) dscEl.textContent = order.discount_total > 0 ? '–' + Number(order.discount_total).toFixed(2) + ' lei' : '–';
const items = data.items || [];
if (items.length === 0) {
document.getElementById('detailItemsBody').innerHTML = '| Niciun articol |
';
return;
}
// Update totals row
const itemsTotal = items.reduce((sum, item) => sum + (Number(item.price || 0) * Number(item.quantity || 0)), 0);
document.getElementById('detailItemsTotal').textContent = itemsTotal.toFixed(2) + ' lei';
document.getElementById('detailOrderTotal').textContent = order.order_total != null ? Number(order.order_total).toFixed(2) + ' lei' : '-';
// Store items for quick map pre-population
window._detailItems = items;
// Mobile article flat list
const mobileContainer = document.getElementById('detailItemsMobile');
if (mobileContainer) {
mobileContainer.innerHTML = '' + items.map((item, idx) => {
const codmatText = item.codmat_details?.length
? item.codmat_details.map(d => `
${esc(d.codmat)}`).join(' ')
: `
${esc(item.codmat || '–')}`;
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
return `
${esc(item.sku)}
${codmatText}
${esc(item.product_name || '–')}
x${item.quantity || 0}
${valoare} lei
`;
}).join('') + '
';
}
document.getElementById('detailItemsBody').innerHTML = items.map((item, idx) => {
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
return `
${esc(item.sku)} |
${esc(item.product_name || '-')} |
${renderCodmatCell(item)} |
${item.quantity || 0} |
${item.price != null ? Number(item.price).toFixed(2) : '-'} |
${valoare} |
`;
}).join('');
} catch (err) {
document.getElementById('detailError').textContent = err.message;
document.getElementById('detailError').style.display = '';
}
}
// ── Quick Map Modal ───────────────────────────────
function openQuickMap(sku, productName, orderNumber, itemIdx) {
currentQmSku = sku;
currentQmOrderNumber = orderNumber;
document.getElementById('qmSku').textContent = sku;
document.getElementById('qmProductName').textContent = productName || '-';
document.getElementById('qmPctWarning').style.display = 'none';
const container = document.getElementById('qmCodmatLines');
container.innerHTML = '';
// Pre-populate with existing codmat_details if available
const item = (window._detailItems || [])[itemIdx];
const details = item?.codmat_details;
if (details && details.length > 0) {
details.forEach(d => {
addQmCodmatLine({ codmat: d.codmat, cantitate: d.cantitate_roa, procent: d.procent_pret, denumire: d.denumire });
});
} else {
addQmCodmatLine();
}
new bootstrap.Modal(document.getElementById('quickMapModal')).show();
}
function addQmCodmatLine(prefill) {
const container = document.getElementById('qmCodmatLines');
const idx = container.children.length;
const codmatVal = prefill?.codmat || '';
const cantVal = prefill?.cantitate || 1;
const pctVal = prefill?.procent || 100;
const denumireVal = prefill?.denumire || '';
const div = document.createElement('div');
div.className = 'qm-line';
div.innerHTML = `
${escHtml(denumireVal)}
`;
container.appendChild(div);
const input = div.querySelector('.qm-codmat');
const dropdown = div.querySelector('.qm-ac-dropdown');
const selected = div.querySelector('.qm-selected');
input.addEventListener('input', () => {
clearTimeout(qmAcTimeout);
qmAcTimeout = setTimeout(() => qmAutocomplete(input, dropdown, selected), 250);
});
input.addEventListener('blur', () => {
setTimeout(() => dropdown.classList.add('d-none'), 200);
});
}
async function qmAutocomplete(input, dropdown, selectedEl) {
const q = input.value;
if (q.length < 2) { dropdown.classList.add('d-none'); return; }
try {
const res = await fetch(`/api/articles/search?q=${encodeURIComponent(q)}`);
const data = await res.json();
if (!data.results || data.results.length === 0) { dropdown.classList.add('d-none'); return; }
dropdown.innerHTML = data.results.map(r =>
`
${esc(r.codmat)} — ${esc(r.denumire)}${r.um ? ` (${esc(r.um)})` : ''}
`
).join('');
dropdown.classList.remove('d-none');
} catch { dropdown.classList.add('d-none'); }
}
function qmSelectArticle(el, codmat, label) {
const line = el.closest('.qm-line');
line.querySelector('.qm-codmat').value = codmat;
line.querySelector('.qm-selected').textContent = label;
line.querySelector('.qm-ac-dropdown').classList.add('d-none');
}
async function saveQuickMapping() {
const lines = document.querySelectorAll('.qm-line');
const mappings = [];
for (const line of lines) {
const codmat = line.querySelector('.qm-codmat').value.trim();
const cantitate = parseFloat(line.querySelector('.qm-cantitate').value) || 1;
const procent = parseFloat(line.querySelector('.qm-procent').value) || 100;
if (!codmat) continue;
mappings.push({ codmat, cantitate_roa: cantitate, procent_pret: procent });
}
if (mappings.length === 0) { alert('Selecteaza cel putin un CODMAT'); return; }
if (mappings.length > 1) {
const totalPct = mappings.reduce((s, m) => s + m.procent_pret, 0);
if (Math.abs(totalPct - 100) > 0.01) {
document.getElementById('qmPctWarning').textContent = `Suma procentelor trebuie sa fie 100% (actual: ${totalPct.toFixed(2)}%)`;
document.getElementById('qmPctWarning').style.display = '';
return;
}
}
document.getElementById('qmPctWarning').style.display = 'none';
try {
let res;
if (mappings.length === 1) {
res = await fetch('/api/mappings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sku: currentQmSku, codmat: mappings[0].codmat, cantitate_roa: mappings[0].cantitate_roa, procent_pret: mappings[0].procent_pret })
});
} else {
res = await fetch('/api/mappings/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sku: currentQmSku, mappings })
});
}
const data = await res.json();
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('quickMapModal')).hide();
if (currentQmOrderNumber) openDashOrderDetail(currentQmOrderNumber);
loadDashOrders();
} else {
alert('Eroare: ' + (data.error || 'Unknown'));
}
} catch (err) {
alert('Eroare: ' + err.message);
}
}