Tourenplanung v1.0

CSV Import

📂
CSV hier ablegen oder klicken

Einstellungen

Min.
CSV hochladen und optimieren
Bereit
🗺️
Karte wird nach Optimierung angezeigt
Adressen werden geocodiert…
Das kann bei vielen Stopps 1–2 Minuten dauern
const data = await res.json(); setLoading(false); if (data.error) { setStatus('❌ Fehler: ' + data.error, 'err'); return; } renderResults(data); renderMap(data); setStatus(`✅ ${data.total} Stopps auf ${data.tours.length} Touren verteilt${data.failed > 0 ? ` (${data.failed} nicht geocodiert)` : ''}`, 'ok'); } catch(e) { setLoading(false); setStatus('❌ Netzwerkfehler: ' + e.message, 'err'); } } // ── Ergebnisse rendern ───────────────────────────────────────── function renderResults(data) { const container = document.getElementById('eb-results'); container.innerHTML = ''; document.getElementById('eb-map-hint').style.display = 'none'; data.tours.forEach((tour, i) => { const card = document.createElement('div'); card.className = 'eb-tour-card'; card.innerHTML = `
${tour.label}
${tour.count} Stopps
${tour.stops.map((s, j) => `
${j+1}. ${esc(s.first_name)} ${esc(s.last_name)} ${esc(s.order_number)}
${esc(s.street)}, ${esc(s.zip)} ${esc(s.city)}
`).join('')}
`; container.appendChild(card); }); // Tour-Daten für Download global speichern window._tourData = data.tours; } function toggleStops(i) { const el = document.getElementById('stops-' + i); el.classList.toggle('open'); } function downloadCsv(i, label) { const tour = window._tourData[i]; if (!tour) return; // CSV direkt im Browser generieren const lines = ['Name|Long|Lat']; tour.stops.forEach(s => { if (s.lat && s.lng) { lines.push(`${s.order_number}|${parseFloat(s.lng).toFixed(6)}|${parseFloat(s.lat).toFixed(6)}`); } }); const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = label.replace(/[^A-Za-z0-9_-]/g, '-') + '.csv'; a.click(); } // ── Karte rendern ────────────────────────────────────────────── function renderMap(data) { // Alte Layer entfernen layers.forEach(l => map.removeLayer(l)); layers = []; const bounds = []; data.tours.forEach(tour => { const pts = tour.stops.filter(s => s.lat && s.lng); if (pts.length < 2) return; // Linie const line = L.polyline(pts.map(s => [s.lat, s.lng]), { color: tour.color, weight: 3, opacity: .7, dashArray: '6,5' }).addTo(map); layers.push(line); // Marker pts.forEach((s, j) => { const icon = L.divIcon({ className: '', html: `
${j+1}
`, iconSize: [26,26], iconAnchor: [13,13] }); const m = L.marker([s.lat, s.lng], { icon }) .bindTooltip(`${s.first_name} ${s.last_name} (#${s.order_number})
${s.street}, ${s.zip} ${s.city}`) .addTo(map); layers.push(m); bounds.push([s.lat, s.lng]); }); }); if (bounds.length) map.fitBounds(bounds, { padding: [30, 30] }); } // ── Helfer ───────────────────────────────────────────────────── function setLoading(show, msg = '', sub = '') { const el = document.getElementById('eb-loading'); el.classList.toggle('active', show); if (msg) el.querySelector('.eb-loading-msg').textContent = msg; if (sub) document.getElementById('eb-loading-sub').textContent = sub; } function setStatus(msg, type = '') { const el = document.getElementById('eb-status'); el.textContent = msg; el.className = 'eb-status' + (type ? ' eb-status-' + type : ''); } function esc(s) { return s ? String(s).replace(/&/g,'&').replace(/