221 lines
9.9 KiB
JavaScript
221 lines
9.9 KiB
JavaScript
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 = `
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Filter Layer</div>
|
|
${['Nasional', 'Provinsi', 'Kabupaten'].map(s => `
|
|
<div class="layer-item ${layerVisible['jalan' + s] ? 'checked' : ''}" onclick="toggleLayer('jalan${s}',this)">
|
|
<div class="layer-check">${layerVisible['jalan' + s] ? '✓' : ''}</div>
|
|
<div class="layer-dot" style="background:${jalanCfg[s].color};border-radius:2px;height:4px;width:18px;border-radius:2px"></div>
|
|
<div class="layer-label">Jalan ${s}</div>
|
|
<div class="layer-count">${cnt(s)}</div>
|
|
</div>`).join('')}
|
|
</div>
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Cara Menambahkan Data Jalan</div>
|
|
<div style="font-size:11px;color:var(--text2);line-height:1.9;padding:10px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius)">
|
|
1. Klik ikon <b style="color:var(--accent)">polyline</b> di toolbar kiri peta<br>
|
|
2. Klik titik-titik rute jalan di peta<br>
|
|
3. <b>Double-klik</b> untuk selesai menggambar<br>
|
|
4. Form simpan + panjang otomatis muncul
|
|
</div>
|
|
</div>
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Data Jalan (${data.length})</div>
|
|
<div class="data-list">
|
|
${data.length ? data.map(d => `
|
|
<div class="data-item" onclick="focusGeom(${d.id},'jalan')">
|
|
<div class="item-icon">🛣</div>
|
|
<div class="item-info">
|
|
<div class="item-name">${esc(d.nama_jalan)}</div>
|
|
<div class="item-sub">${(d.panjang_meter / 1000).toFixed(3)} km</div>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:5px">
|
|
<div class="item-badge" style="background:${jalanCfg[d.status_jalan].color}18;color:${jalanCfg[d.status_jalan].color};border:1px solid ${jalanCfg[d.status_jalan].color}44">${d.status_jalan.toUpperCase()}</div>
|
|
<button class="btn btn-ghost btn-sm" onclick="event.stopPropagation();editJalan(${d.id})">✏</button>
|
|
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation();deleteJalan(${d.id})">✕</button>
|
|
</div>
|
|
</div>`).join('') : '<div class="loading">Belum ada data jalan</div>'}
|
|
</div>
|
|
</div>`;
|
|
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(`<h4>🛣 ${esc(d.nama_jalan)}</h4>
|
|
<p><b>Status:</b> Jalan ${d.status_jalan}</p>
|
|
<p><b>Panjang:</b> ${(d.panjang_meter / 1000).toFixed(3)} km</p>
|
|
${d.keterangan ? `<p>${esc(d.keterangan)}</p>` : ''}
|
|
<div class="popup-actions"><button class="btn btn-ghost btn-sm" onclick="editJalan(${d.id})">✏ Edit</button><button class="btn btn-danger btn-sm" onclick="deleteJalan(${d.id})">✕ Hapus</button></div>`);
|
|
line._dataId = d.id;
|
|
line._groupKey = 'jalan' + d.status_jalan;
|
|
layers['jalan' + d.status_jalan].addLayer(line);
|
|
});
|
|
}
|
|
|
|
function openJalanForm() {
|
|
openModal('Simpan Data Jalan', `
|
|
<div class="form-calc">📏 Panjang: <b>${pendingLength.toFixed(1)} m</b> = ${(pendingLength / 1000).toFixed(3)} km </div>
|
|
<div class="form-group"><label class="form-label">Nama Jalan *</label>
|
|
<input class="form-input" id="jl-nama" placeholder="Jl. Ahmad Yani…"/></div>
|
|
<div class="form-group"><label class="form-label">Status Jalan *</label>
|
|
<select class="form-select" id="jl-status">
|
|
<option value="Nasional">Jalan Nasional</option>
|
|
<option value="Provinsi">Jalan Provinsi</option>
|
|
<option value="Kabupaten">Jalan Kabupaten</option>
|
|
</select></div>
|
|
<div class="form-group"><label class="form-label">Keterangan</label>
|
|
<input class="form-input" id="jl-ket" placeholder="Opsional…"/></div>`,
|
|
`<button class="btn btn-ghost" onclick="closeModal();drawnItems.clearLayers()">Batal</button>
|
|
<button class="btn btn-primary" onclick="saveJalan()">💾 Simpan</button>`);
|
|
}
|
|
|
|
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 = `
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Edit Jalan</div>
|
|
<div style="font-size:11px;line-height:1.7;color:var(--text2);padding:10px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:12px">
|
|
Geser titik-titik garis di peta lalu simpan perubahan.
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nama Jalan *</label>
|
|
<input class="form-input" id="jl-edit-nama" value="${esc(data.nama_jalan)}" />
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Status Jalan *</label>
|
|
<select class="form-select" id="jl-edit-status">
|
|
${['Nasional', 'Provinsi', 'Kabupaten'].map(s => `<option value="${s}"${data.status_jalan === s ? ' selected' : ''}>Jalan ${s}</option>`).join('')}
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Panjang Otomatis</label>
|
|
<input class="form-input" id="jl-edit-panjang" value="${(data.panjang_meter / 1000).toFixed(3)} km" disabled />
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Keterangan</label>
|
|
<input class="form-input" id="jl-edit-ket" value="${esc(data.keterangan || '')}" />
|
|
</div>
|
|
</div>`;
|
|
document.getElementById('panel-body').insertAdjacentHTML('beforeend', `
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn btn-ghost" style="flex:1" onclick="cancelGeometryEdit()">Batal</button>
|
|
<button class="btn btn-primary" style="flex:1" onclick="saveJalanEdit(${id})">Simpan Perubahan</button>
|
|
</div>`);
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|