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, ''');
}