const jalanCfg = { Nasional: { color: '#dc2626', weight: 5 }, Provinsi: { color: '#d97706', weight: 3.5 }, Kabupaten: { color: '#2563eb', weight: 2.5 } }; function renderJalanPanel(pb) { return callAPI('data_jalan.php').then(res => { const data = res.data || []; const cnt = k => data.filter(d => d.status_jalan === k).length; pb.innerHTML = `
Filter Layer
${['Nasional', 'Provinsi', 'Kabupaten'].map(s => `
${layerVisible['jalan' + s] ? '✓' : ''}
Jalan ${s}
${cnt(s)}
`).join('')}
Tambah Data
Data Jalan (${data.length})
${data.length ? data.map(d => `
🛣
${esc(d.nama_jalan)}
${(d.panjang_meter / 1000).toFixed(3)} km
${d.status_jalan.toUpperCase()}
`).join('') : '
Belum ada data jalan
'}
`; loadJalanLayer(data); registerSearchEntries('jalan', data.map(d => ({ key: `jalan:${d.id}`, label: d.nama_jalan, subtitle: `Jalan ${d.status_jalan} · ${(d.panjang_meter / 1000).toFixed(3)} km${d.keterangan ? ` · ${d.keterangan}` : ''}`, typeLabel: 'Jalan', icon: '🛣', searchText: normalizeSearchText(`${d.nama_jalan} ${d.status_jalan} jalan`), focus: () => focusGeom(d.id, 'jalan') }))); updateStatusCount(); }); } function loadJalanLayer(data) { ['Nasional', 'Provinsi', 'Kabupaten'].forEach(k => layers['jalan' + k].clearLayers()); data.forEach(d => { const cfg = jalanCfg[d.status_jalan], coords = d.geojson.coordinates.map(c => [c[1], c[0]]); const line = L.polyline(coords, { color: cfg.color, weight: cfg.weight, opacity: 0.9 }) .bindPopup(`

🛣 ${esc(d.nama_jalan)}

Status: Jalan ${d.status_jalan}

Panjang: ${(d.panjang_meter / 1000).toFixed(3)} km

${d.keterangan ? `

${esc(d.keterangan)}

` : ''} `); line._dataId = d.id; line._groupKey = 'jalan' + d.status_jalan; layers['jalan' + d.status_jalan].addLayer(line); }); } function openJalanForm() { openModal('Simpan Data Jalan', `
📏 Panjang: ${pendingLength.toFixed(1)} m = ${(pendingLength / 1000).toFixed(3)} km
`, ` `); } async function saveJalan() { const body = { nama_jalan: document.getElementById('jl-nama').value.trim(), status_jalan: document.getElementById('jl-status').value, panjang_meter: pendingLength, keterangan: document.getElementById('jl-ket').value, geojson: pendingGeoJSON }; if (!body.nama_jalan) return toast('Nama wajib diisi', 'error'); const res = await callAPI('data_jalan.php', 'POST', body); if (res.status === 'success') { toast(res.message, 'success'); closeModal(); drawnItems.clearLayers(); pendingGeoJSON = null; pendingLength = null; switchTab('jalan'); } else { toast(res.message, 'error'); } } async function editJalan(id) { map.closePopup(); const res = await callAPI(`data_jalan.php?id=${id}`); const data = res.data; const sourceLayer = findGeomLayer('jalan', id); if (!data) return toast('Data jalan tidak ditemukan', 'error'); const groupKey = getGeometryGroupKey('jalan', data); const editLayer = sourceLayer ? cloneEditableLayer('jalan', sourceLayer) : buildGeometryLayer('jalan', data); editLayer._dataId = id; editLayer._groupKey = groupKey; if (sourceLayer) layers[groupKey].removeLayer(sourceLayer); drawnItems.clearLayers(); drawnItems.addLayer(editLayer); geometryEditState = { type: 'jalan', id, groupKey, sourceLayer, editLayer }; if (editLayer.editing && editLayer.editing.enable) { editLayer.editing.enable(); } else { const handler = getGeometryEditHandler(); if (!handler) { drawnItems.clearLayers(); if (sourceLayer) layers[groupKey].addLayer(sourceLayer); geometryEditState = null; return toast('Mode edit geometri belum siap', 'error'); } handler.enable(); } map.fitBounds(editLayer.getBounds(), { padding: [50, 50], duration: 1 }); document.getElementById('panel-title').textContent = 'Edit Data Jalan'; document.getElementById('panel-body').innerHTML = `
Edit Jalan
Geser titik-titik garis di peta lalu simpan perubahan.
`; document.getElementById('panel-body').insertAdjacentHTML('beforeend', `
`); } async function saveJalanEdit(id) { const state = geometryEditState; if (!state || state.type !== 'jalan') return; const layer = state.editLayer; const coords = layer.getLatLngs().map(ll => [ll.lng, ll.lat]); const panjang_meter = turf.length(turf.lineString(coords), { units: 'meters' }); const body = { nama_jalan: document.getElementById('jl-edit-nama').value.trim(), status_jalan: document.getElementById('jl-edit-status').value, panjang_meter, keterangan: document.getElementById('jl-edit-ket').value, geojson: { type: 'LineString', coordinates: coords } }; if (!body.nama_jalan) return toast('Nama wajib diisi', 'error'); const res = await callAPI(`data_jalan.php?id=${id}`, 'PUT', body); if (res.status === 'success') { toast(res.message, 'success'); if (layer.editing && layer.editing.disable) layer.editing.disable(); const handler = getGeometryEditHandler(); if (handler) handler.disable(); drawnItems.clearLayers(); geometryEditState = null; switchTab('jalan'); } else { toast(res.message, 'error'); } } async function deleteJalan(id) { if (!confirm('Hapus?')) return; const r = await callAPI(`data_jalan.php?id=${id}`, 'DELETE'); if (r.status === 'success') { toast('Dihapus', 'success'); map.closePopup(); switchTab('jalan'); } }