// logs.js - Jurnale Import page logic function esc(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function fmtDatetime(iso) { if (!iso) return '-'; try { return new Date(iso).toLocaleString('ro-RO', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { return iso; } } function fmtDuration(startedAt, finishedAt) { if (!startedAt || !finishedAt) return '-'; const diffMs = new Date(finishedAt) - new Date(startedAt); if (isNaN(diffMs) || diffMs < 0) return '-'; const secs = Math.round(diffMs / 1000); if (secs < 60) return secs + 's'; return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's'; } function statusBadge(status) { switch ((status || '').toUpperCase()) { case 'IMPORTED': return 'IMPORTED'; case 'SKIPPED': return 'SKIPPED'; case 'ERROR': return 'ERROR'; default: return `${esc(status || '-')}`; } } function runStatusBadge(status) { switch ((status || '').toUpperCase()) { case 'SUCCESS': return 'SUCCESS'; case 'ERROR': return 'ERROR'; case 'RUNNING': return 'RUNNING'; case 'PARTIAL': return 'PARTIAL'; default: return `${esc(status || '')}`; } } async function loadRuns() { const sel = document.getElementById('runSelector'); sel.innerHTML = ''; try { const res = await fetch('/api/sync/history?per_page=20'); if (!res.ok) throw new Error('HTTP ' + res.status); const data = await res.json(); const runs = data.runs || []; if (runs.length === 0) { sel.innerHTML = ''; return; } sel.innerHTML = '' + runs.map(r => { const date = fmtDatetime(r.started_at); const stats = `${r.total_orders || 0} total / ${r.imported || 0} ok / ${r.errors || 0} err`; const statusText = (r.status || '').toUpperCase(); return ``; }).join(''); } catch (err) { sel.innerHTML = ''; } } async function loadRunLog(runId) { const tbody = document.getElementById('logsBody'); const filterRow = document.getElementById('filterRow'); const runSummary = document.getElementById('runSummary'); tbody.innerHTML = '
Se incarca...'; filterRow.style.display = 'none'; runSummary.style.display = 'none'; try { const res = await fetch(`/api/sync/run/${encodeURIComponent(runId)}/log`); if (!res.ok) throw new Error('HTTP ' + res.status); const data = await res.json(); const run = data.run || {}; const orders = data.orders || []; // Populate summary bar document.getElementById('sum-total').textContent = run.total_orders ?? '-'; document.getElementById('sum-imported').textContent = run.imported ?? '-'; document.getElementById('sum-skipped').textContent = run.skipped ?? '-'; document.getElementById('sum-errors').textContent = run.errors ?? '-'; document.getElementById('sum-duration').textContent = fmtDuration(run.started_at, run.finished_at); runSummary.style.display = ''; if (orders.length === 0) { tbody.innerHTML = 'Nicio comanda in acest sync run'; filterRow.style.display = 'none'; updateFilterCount(); return; } tbody.innerHTML = orders.map(order => { const status = (order.status || '').toUpperCase(); // Parse missing_skus — API returns JSON string or null let missingSkuTags = ''; if (order.missing_skus) { try { const skus = typeof order.missing_skus === 'string' ? JSON.parse(order.missing_skus) : order.missing_skus; if (Array.isArray(skus) && skus.length > 0) { missingSkuTags = '
' + skus.map(s => `${esc(s)}`).join('') + '
'; } } catch (e) { // malformed JSON — skip } } const details = order.error_message ? `${esc(order.error_message)}${missingSkuTags}` : missingSkuTags || '-'; return ` ${esc(order.order_number || '-')} ${esc(order.customer_name || '-')} ${order.items_count ?? '-'} ${statusBadge(status)} ${details} `; }).join(''); filterRow.style.display = ''; // Reset filter to "Toate" document.querySelectorAll('[data-filter]').forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === 'all'); }); applyFilter('all'); } catch (err) { tbody.innerHTML = ` ${esc(err.message)} `; filterRow.style.display = 'none'; runSummary.style.display = 'none'; } } function applyFilter(filter) { const rows = document.querySelectorAll('#logsBody tr[data-status]'); let visible = 0; rows.forEach(row => { const show = filter === 'all' || row.dataset.status === filter; row.style.display = show ? '' : 'none'; if (show) visible++; }); updateFilterCount(visible, rows.length, filter); } function updateFilterCount(visible, total, filter) { const el = document.getElementById('filterCount'); if (!el) return; if (visible == null) { el.textContent = ''; return; } if (filter === 'all') { el.textContent = `${total} comenzi`; } else { el.textContent = `${visible} din ${total} comenzi`; } } document.addEventListener('DOMContentLoaded', () => { loadRuns(); // Dropdown change document.getElementById('runSelector').addEventListener('change', function () { const runId = this.value; if (!runId) { document.getElementById('logsBody').innerHTML = ` Selecteaza un sync run din lista de sus `; document.getElementById('filterRow').style.display = 'none'; document.getElementById('runSummary').style.display = 'none'; return; } loadRunLog(runId); }); // Filter buttons document.querySelectorAll('[data-filter]').forEach(btn => { btn.addEventListener('click', function () { document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active')); this.classList.add('active'); applyFilter(this.dataset.filter); }); }); });