const map = L.map('map').setView([43.17, 5.70], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OSM contributors', maxZoom: 19 }).addTo(map); const status = document.getElementById('status'); let trackLayer = null; async function loadJobs() { const res = await fetch('/api/jobs'); const jobs = await res.json(); const sel = document.getElementById('job-select'); jobs.forEach(j => { const opt = document.createElement('option'); opt.value = j.id; opt.textContent = 'job_' + j.id; sel.appendChild(opt); }); const urlJob = new URLSearchParams(window.location.search).get('job'); const target = urlJob ? parseInt(urlJob) : (jobs.length > 0 ? jobs[0].id : null); if (target) { sel.value = target; loadJob(target); } else { status.textContent = 'Aucun job disponible'; } } async function loadJob(jobId) { status.textContent = 'Chargement job_' + jobId + '...'; const res = await fetch('/api/job/' + jobId + '/nav'); const d = await res.json(); if (trackLayer) map.removeLayer(trackLayer); if (!d.track || !d.track.x.length) { status.textContent = 'Pas de données track pour job_' + jobId; return; } const cx = d.track.x.reduce((a,b)=>a+b,0)/d.track.x.length; const cy = d.track.y.reduce((a,b)=>a+b,0)/d.track.y.length; const pts = d.track.x.map((x,i) => [43.17 + (d.track.y[i]-cy)*0.000009, 5.70 + (x-cx)*0.000009]); trackLayer = L.polyline(pts, { color: '#4ade80', weight: 2 }).addTo(map); map.fitBounds(trackLayer.getBounds()); status.textContent = 'job_' + jobId + ' — ' + d.n_poses + ' poses | PLY: ' + (d.ply_ready ? 'prêt' : 'non dispo'); } document.getElementById('job-select').addEventListener('change', e => { if (e.target.value) loadJob(parseInt(e.target.value)); }); loadJobs();