let refreshInterval = null; document.addEventListener('DOMContentLoaded', () => { loadDashboard(); // Auto-refresh every 10 seconds refreshInterval = setInterval(loadDashboard, 10000); }); async function loadDashboard() { await Promise.all([ loadSyncStatus(), loadSyncHistory(), loadMissingSkus(), loadSchedulerStatus() ]); } async function loadSyncStatus() { try { const res = await fetch('/api/sync/status'); const data = await res.json(); // Update stats const stats = data.stats || {}; document.getElementById('stat-imported').textContent = stats.imported || 0; document.getElementById('stat-skipped').textContent = stats.skipped || 0; document.getElementById('stat-missing').textContent = stats.missing_skus || 0; // Update sync status badge const badge = document.getElementById('syncStatusBadge'); const status = data.status || 'idle'; badge.textContent = status; badge.className = 'badge ' + (status === 'running' ? 'bg-primary' : status === 'failed' ? 'bg-danger' : 'bg-secondary'); // Show/hide start/stop buttons if (status === 'running') { document.getElementById('btnStartSync').classList.add('d-none'); document.getElementById('btnStopSync').classList.remove('d-none'); document.getElementById('syncProgressText').textContent = data.progress || 'Running...'; } else { document.getElementById('btnStartSync').classList.remove('d-none'); document.getElementById('btnStopSync').classList.add('d-none'); // Show last run info if (stats.last_run) { const lr = stats.last_run; const started = lr.started_at ? new Date(lr.started_at).toLocaleString('ro-RO') : ''; document.getElementById('syncProgressText').textContent = `Ultimul: ${started} | ${lr.imported || 0} ok, ${lr.skipped || 0} skip, ${lr.errors || 0} err`; } else { document.getElementById('syncProgressText').textContent = ''; } } } catch (err) { console.error('loadSyncStatus error:', err); } } async function loadSyncHistory() { try { const res = await fetch('/api/sync/history?per_page=10'); const data = await res.json(); const tbody = document.getElementById('syncRunsBody'); if (!data.runs || data.runs.length === 0) { tbody.innerHTML = 'Niciun sync run'; return; } tbody.innerHTML = data.runs.map(r => { const started = r.started_at ? new Date(r.started_at).toLocaleString('ro-RO', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) : '-'; let duration = '-'; if (r.started_at && r.finished_at) { const sec = Math.round((new Date(r.finished_at) - new Date(r.started_at)) / 1000); duration = sec < 60 ? `${sec}s` : `${Math.floor(sec/60)}m ${sec%60}s`; } const statusClass = r.status === 'completed' ? 'bg-success' : r.status === 'running' ? 'bg-primary' : 'bg-danger'; return ` ${started} ${esc(r.status)} ${r.total_orders || 0} ${r.imported || 0} ${r.skipped || 0} ${r.errors || 0} ${duration} `; }).join(''); } catch (err) { console.error('loadSyncHistory error:', err); } } async function loadMissingSkus() { try { const res = await fetch('/api/validate/missing-skus'); const data = await res.json(); const tbody = document.getElementById('missingSkusBody'); // Update stat card document.getElementById('stat-missing').textContent = data.unresolved || 0; const unresolved = (data.missing_skus || []).filter(s => !s.resolved); if (unresolved.length === 0) { tbody.innerHTML = 'Toate SKU-urile sunt mapate'; return; } tbody.innerHTML = unresolved.slice(0, 10).map(s => ` ${esc(s.sku)} ${esc(s.product_name || '-')} ${s.first_seen ? new Date(s.first_seen).toLocaleDateString('ro-RO') : '-'} `).join(''); } catch (err) { console.error('loadMissingSkus error:', err); } } 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); } } async function startSync() { try { const res = await fetch('/api/sync/start', { method: 'POST' }); const data = await res.json(); if (data.error) { alert(data.error); } loadDashboard(); } catch (err) { alert('Eroare: ' + err.message); } } async function stopSync() { try { await fetch('/api/sync/stop', { method: 'POST' }); loadDashboard(); } catch (err) { alert('Eroare: ' + err.message); } } async function scanOrders() { const btn = document.getElementById('btnScan'); btn.disabled = true; btn.innerHTML = ' Scanning...'; try { const res = await fetch('/api/validate/scan', { method: 'POST' }); const data = await res.json(); // Update pending/ready stats document.getElementById('stat-pending').textContent = data.total_orders || 0; document.getElementById('stat-ready').textContent = data.importable || 0; let msg = `Scan complet: ${data.total_orders || 0} comenzi, ${data.importable || 0} ready, ${data.skipped || 0} skipped`; if (data.skus && data.skus.missing > 0) { msg += `, ${data.skus.missing} SKU-uri lipsa`; } alert(msg); loadDashboard(); } catch (err) { alert('Eroare scan: ' + err.message); } finally { btn.disabled = false; btn.innerHTML = ' Scan'; } } async function toggleScheduler() { const enabled = document.getElementById('schedulerToggle').checked; const interval = parseInt(document.getElementById('schedulerInterval').value) || 5; 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(); } } function esc(s) { if (s == null) return ''; return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); }