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