feat: implement web GIS layout with sidebar, custom UI controls, and modular map feature integration

This commit is contained in:
Syariffullah
2026-06-06 07:35:03 +07:00
parent 08d6d30e02
commit 977dec4e36
11 changed files with 193 additions and 153 deletions
+1
View File
@@ -13,6 +13,7 @@ if($result->num_rows > 0) {
"id" => $row['id'], "id" => $row['id'],
"nama" => $row['nama'], "nama" => $row['nama'],
"kategori_bantuan" => $row['kategori_bantuan'], "kategori_bantuan" => $row['kategori_bantuan'],
"jumlah_jiwa" => isset($row['jumlah_jiwa']) ? (int)$row['jumlah_jiwa'] : 1,
"lat" => (float)$row['lat'], "lat" => (float)$row['lat'],
"lng" => (float)$row['lng'] "lng" => (float)$row['lng']
); );
+1
View File
@@ -12,6 +12,7 @@ if($result->num_rows > 0) {
$item = array( $item = array(
"id" => $row['id'], "id" => $row['id'],
"nama" => $row['nama'], "nama" => $row['nama'],
"jenis" => $row['jenis'],
"alamat" => $row['alamat'], "alamat" => $row['alamat'],
"radius" => (float)$row['radius'], "radius" => (float)$row['radius'],
"lat" => (float)$row['lat'], "lat" => (float)$row['lat'],
+23 -10
View File
@@ -84,10 +84,6 @@ body, html {
/* Custom Layer Control Button */ /* Custom Layer Control Button */
.custom-layer-btn { .custom-layer-btn {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
background-color: white; background-color: white;
border-radius: 8px; border-radius: 8px;
width: 48px; width: 48px;
@@ -105,6 +101,18 @@ body, html {
background-color: #f8f9fa; background-color: #f8f9fa;
} }
#layerBtn {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
body.right-panel-open #layerBtn {
right: 340px;
}
.custom-layer-panel { .custom-layer-panel {
position: absolute; position: absolute;
top: 80px; top: 80px;
@@ -116,6 +124,11 @@ body, html {
padding: 15px; padding: 15px;
width: 250px; width: 250px;
display: none; display: none;
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
body.right-panel-open .custom-layer-panel {
right: 340px;
} }
@@ -138,13 +151,13 @@ body, html {
.sidebar-toggle-btn { .sidebar-toggle-btn {
position: absolute; position: absolute;
left: 20px; left: 20px;
top: 90px; top: 190px;
z-index: 1001; z-index: 1001;
width: 36px; border-radius: 8px;
height: 36px; width: 48px;
height: 48px;
background: white; background: white;
border: none; border: none;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0,0,0,0.15); box-shadow: 0 3px 10px rgba(0,0,0,0.15);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -170,7 +183,7 @@ body, html {
.left-sidebar { .left-sidebar {
position: absolute; position: absolute;
left: 20px; left: 20px;
top: 136px; /* di bawah toggle button */ top: 250px; /* di bawah toggle button */
z-index: 1000; z-index: 1000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -795,7 +808,7 @@ body, html {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
pointer-events: none; pointer-events: auto; /* changed from none to allow click/drag */
} }
.emoji-marker .bubble { .emoji-marker .bubble {
+8 -11
View File
@@ -37,36 +37,33 @@ map.on(L.Draw.Event.DRAWSTOP, function (e) {
isDrawingMode = false; isDrawingMode = false;
window.currentDrawMode = null; window.currentDrawMode = null;
window.activeDrawHandler = null; window.activeDrawHandler = null;
deactivateAddMode(); window.deactivateAddMode();
}, 200); }, 200);
}); });
window.activateDraw = function(type) { window.activateDraw = function(type) {
// Toggle: cek dari class button, lebih reliable daripada state variable console.log("-> activateDraw dipanggil untuk tipe:", type);
const btnJalan = document.getElementById('btnMenuJalan');
const btnParsil = document.getElementById('btnMenuParsil'); // Toggle: jika mode yang sama ditekan lagi, batalkan
const isJalanActive = btnJalan.classList.contains('active'); if (window.currentDrawMode === type) {
const isParsilActive = btnParsil.classList.contains('active'); console.log("Mode", type, "sudah aktif, membatalkan...");
if ((type === 'polyline' && isJalanActive) || (type === 'polygon' && isParsilActive)) {
window.deactivateAddMode(); window.deactivateAddMode();
return; return;
} }
deactivateAddMode(); window.deactivateAddMode();
window.currentDrawMode = type; window.currentDrawMode = type;
console.log("Mengaktifkan mode menggambar:", type);
if (type === 'polyline') { if (type === 'polyline') {
const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline); const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline);
handler.enable(); handler.enable();
window.activeDrawHandler = handler; window.activeDrawHandler = handler;
btnJalan.classList.add('active');
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan'; if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan';
} else if (type === 'polygon') { } else if (type === 'polygon') {
const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon); const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon);
handler.enable(); handler.enable();
window.activeDrawHandler = handler; window.activeDrawHandler = handler;
btnParsil.classList.add('active');
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil'; if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil';
} }
}; };
+1
View File
@@ -105,6 +105,7 @@ window.saveEditJalan = function(id, namaId, statusId) {
.then(data => { .then(data => {
if(data.status === 'success') { if(data.status === 'success') {
map.closePopup(); map.closePopup();
closeModal();
loadJalan(); loadJalan();
} else { } else {
alert(data.message); alert(data.message);
+21 -7
View File
@@ -63,7 +63,7 @@ map.on('mouseup', function(e) {
isResizing = false; isResizing = false;
map.dragging.enable(); map.dragging.enable();
if (resizingIbadah) { if (resizingIbadah) {
updateIbadah(resizingIbadah.id, resizingIbadah.nama, resizingIbadah.alamat, resizingIbadah.radius, resizingIbadah.lat, resizingIbadah.lng); updateIbadah(resizingIbadah.id, resizingIbadah.nama, resizingIbadah.jenis, resizingIbadah.alamat, resizingIbadah.radius, resizingIbadah.lat, resizingIbadah.lng);
} }
resizingCircle = null; resizingCircle = null;
resizingIbadah = null; resizingIbadah = null;
@@ -89,11 +89,17 @@ function loadRumahIbadah() {
} }
function addIbadahMarker(item) { function addIbadahMarker(item) {
const marker = L.marker([item.lat, item.lng], { icon: makeIbadahIcon(item.jenis), draggable: true }); const lat = parseFloat(item.lat);
const lng = parseFloat(item.lng);
const radius = parseFloat(item.radius) || 100; // fallback if invalid
if (isNaN(lat) || isNaN(lng)) return;
const marker = L.marker([lat, lng], { icon: makeIbadahIcon(item.jenis), draggable: true });
// Lingkaran radius // Lingkaran radius
const circle = L.circle([item.lat, item.lng], { const circle = L.circle([lat, lng], {
radius: item.radius, radius: radius,
color: '#ff7800', color: '#ff7800',
weight: 2, weight: 2,
fillColor: '#ff7800', fillColor: '#ff7800',
@@ -153,7 +159,7 @@ function addIbadahMarker(item) {
item.lat = newPos.lat; item.lat = newPos.lat;
item.lng = newPos.lng; item.lng = newPos.lng;
// Update ke DB // Update ke DB
updateIbadah(item.id, item.nama, item.alamat, item.radius, newPos.lat, newPos.lng); updateIbadah(item.id, item.nama, item.jenis, item.alamat, item.radius, newPos.lat, newPos.lng);
}); });
rumahIbadahLayer.addLayer(circle); rumahIbadahLayer.addLayer(circle);
@@ -340,7 +346,11 @@ function updateIbadah(id, nama, jenis, alamat, radius, lat, lng) {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, nama, jenis, alamat, radius, lat, lng }) body: JSON.stringify({ id, nama, jenis, alamat, radius, lat, lng })
}).then(res => res.json()).then(data => { }).then(res => res.json()).then(data => {
if(data.status === 'success') { map.closePopup(); loadRumahIbadah(); } if(data.status === 'success') {
if (typeof closeModal === 'function') closeModal();
map.closePopup();
loadRumahIbadah();
}
}); });
} }
@@ -375,7 +385,11 @@ function loadPendudukMiskin() {
} }
function addMiskinMarker(item) { function addMiskinMarker(item) {
const marker = L.marker([item.lat, item.lng], { icon: makeMiskinIcon(false), draggable: true }); const lat = parseFloat(item.lat);
const lng = parseFloat(item.lng);
if (isNaN(lat) || isNaN(lng)) return;
const marker = L.marker([lat, lng], { icon: makeMiskinIcon(false), draggable: true });
marker.miskinData = item; marker.miskinData = item;
const d = item; const d = item;
+1
View File
@@ -97,6 +97,7 @@ window.saveEditParsil = function(id, namaId, statusId) {
.then(data => { .then(data => {
if(data.status === 'success') { if(data.status === 'success') {
map.closePopup(); map.closePopup();
closeModal();
loadParsil(); loadParsil();
} else { } else {
alert(data.message); alert(data.message);
+1
View File
@@ -175,6 +175,7 @@ function updateSpbu(id, nama, no_wa, is_24_jam, lat, lng) {
.then(data => { .then(data => {
if(data.status === 'success') { if(data.status === 'success') {
map.closePopup(); map.closePopup();
closeModal();
loadSpbu(); loadSpbu();
} else { } else {
alert(data.message); alert(data.message);
+5
View File
@@ -22,6 +22,10 @@ const geoJsonLayer = L.featureGroup().addTo(map);
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const searchClear = document.getElementById('searchClear'); const searchClear = document.getElementById('searchClear');
searchInput.addEventListener('focus', function() {
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
});
searchInput.addEventListener('input', function() { searchInput.addEventListener('input', function() {
const val = this.value.toLowerCase(); const val = this.value.toLowerCase();
if (val.length > 0) { if (val.length > 0) {
@@ -111,6 +115,7 @@ const layerBtn = document.getElementById('layerBtn');
const layerPanel = document.getElementById('layerPanel'); const layerPanel = document.getElementById('layerPanel');
layerBtn.addEventListener('click', function() { layerBtn.addEventListener('click', function() {
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block'; layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block';
}); });
+4
View File
@@ -55,8 +55,10 @@
// ---- Open / Close Panel ---- // ---- Open / Close Panel ----
function openPanel(panel) { function openPanel(panel) {
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
activePanel = panel; activePanel = panel;
rightPanel.classList.add('open'); rightPanel.classList.add('open');
document.body.classList.add('right-panel-open');
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
const activeBtn = document.querySelector(`.sidebar-btn[data-panel="${panel}"]`); const activeBtn = document.querySelector(`.sidebar-btn[data-panel="${panel}"]`);
if (activeBtn) activeBtn.classList.add('active'); if (activeBtn) activeBtn.classList.add('active');
@@ -64,8 +66,10 @@
} }
function closePanel() { function closePanel() {
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
activePanel = null; activePanel = null;
rightPanel.classList.remove('open'); rightPanel.classList.remove('open');
document.body.classList.remove('right-panel-open');
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
} }
+127 -125
View File
@@ -76,130 +76,132 @@
</nav> </nav>
<!-- Right Data Panel (includes layer control) -->
<div class="right-panel" id="rightPanel">
<div class="right-panel-header">
<h3 id="rightPanelTitle">Data</h3>
<button class="right-panel-close" id="rightPanelClose"><i class="fas fa-times"></i></button>
</div>
<div class="right-panel-actions" id="rightPanelActions"></div>
<div class="right-panel-body" id="rightPanelBody">
<div class="panel-empty">Pilih menu di kiri untuk menampilkan data</div>
</div>
</div><!-- /right-panel -->
<!-- Custom Layer Control Button --> <!-- Custom Layer Control Button -->
<button class="custom-layer-btn" id="layerBtn" title="Atur Layer"> <button class="custom-layer-btn" id="layerBtn" title="Atur Layer">
<i class="fas fa-layer-group fa-lg"></i> <i class="fas fa-layer-group fa-lg"></i>
</button> </button>
<!-- Custom Layer Control Panel --> <div>
<div class="custom-layer-panel" id="layerPanel"> <!-- Custom Layer Control Panel -->
<h3>Daftar Layer</h3> <div class="custom-layer-panel" id="layerPanel">
<h3>Daftar Layer</h3>
<!-- SPBU -->
<div class="layer-group-item"> <!-- SPBU -->
<div class="layer-group-header"> <div class="layer-group-item">
<label class="layer-option" style="margin:0; flex:1;"> <div class="layer-group-header">
<input type="checkbox" id="layerSpbu" checked> SPBU <label class="layer-option" style="margin:0; flex:1;">
</label> <input type="checkbox" id="layerSpbu" checked> SPBU
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subSpbu', this)"><i class="fas fa-chevron-down"></i></span> </label>
</div> <span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subSpbu', this)"><i class="fas fa-chevron-down"></i></span>
<div id="subSpbu" class="sub-layer-list" style="display:none;"> </div>
<label class="layer-option sub-option"> <div id="subSpbu" class="sub-layer-list" style="display:none;">
<input type="checkbox" class="sub-spbu" value="1" checked onchange="applySubFilter('spbu')"> 24 Jam <label class="layer-option sub-option">
</label> <input type="checkbox" class="sub-spbu" value="1" checked onchange="applySubFilter('spbu')"> 24 Jam
<label class="layer-option sub-option"> </label>
<input type="checkbox" class="sub-spbu" value="0" checked onchange="applySubFilter('spbu')"> Tidak 24 Jam <label class="layer-option sub-option">
</label> <input type="checkbox" class="sub-spbu" value="0" checked onchange="applySubFilter('spbu')"> Tidak 24 Jam
</div> </label>
</div>
<!-- Jalan -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerJalan" checked> Jalan
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subJalan', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subJalan" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Nasional" checked onchange="applySubFilter('jalan')"> Nasional
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Provinsi" checked onchange="applySubFilter('jalan')"> Provinsi
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Kabupaten" checked onchange="applySubFilter('jalan')"> Kabupaten
</label>
</div>
</div>
<!-- Parsil -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerParsil" checked> Parsil Tanah
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subParsil', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subParsil" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="SHM" checked onchange="applySubFilter('parsil')"> SHM
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HGB" checked onchange="applySubFilter('parsil')"> HGB
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HGU" checked onchange="applySubFilter('parsil')"> HGU
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HP" checked onchange="applySubFilter('parsil')"> HP
</label>
</div>
</div>
<!-- Rumah Ibadah -->
<label class="layer-option">
<input type="checkbox" id="layerRumahIbadah" checked> Rumah Ibadah
</label>
<!-- Penduduk Miskin -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerMiskin" checked> Penduduk Miskin
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subMiskin', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subMiskin" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-miskin" value="Makan" checked onchange="applySubFilter('miskin')"> Bantuan Makan
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-miskin" value="Pemberdayaan" checked onchange="applySubFilter('miskin')"> Pemberdayaan
</label>
</div>
</div>
<!-- GeoJSON Eksternal -->
<div class="layer-group" style="margin-top: 10px; border-top: 1px solid #eee; padding-top: 10px;">
<div class="layer-group-header" onclick="window.toggleGeoJsonMenu()" style="cursor:pointer; display:flex; justify-content:space-between; align-items:center; font-size:14px; margin-bottom:8px; font-weight: 500;">
<span><i class="fas fa-folder"></i> GeoJSON Eksternal</span>
<i id="geoJsonToggleIcon" class="fas fa-plus"></i>
</div>
<div id="geoJsonFileList" style="display:none; padding-left:15px; margin-bottom:10px;">
<div id="geoJsonLayersContainer"></div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px dashed #ddd;">
<label style="font-size: 12px; display:block; margin-bottom: 5px;">Import GeoJSON Baru:</label>
<input type="file" id="fileGeoJson" accept=".geojson,.json" style="font-size: 12px; max-width: 100%;">
</div> </div>
</div> </div>
</div>
</div><!-- /custom-layer-panel --> <!-- Jalan -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerJalan" checked> Jalan
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subJalan', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subJalan" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Nasional" checked onchange="applySubFilter('jalan')"> Nasional
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Provinsi" checked onchange="applySubFilter('jalan')"> Provinsi
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-jalan" value="Kabupaten" checked onchange="applySubFilter('jalan')"> Kabupaten
</label>
</div>
</div>
<!-- Parsil -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerParsil" checked> Parsil Tanah
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subParsil', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subParsil" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="SHM" checked onchange="applySubFilter('parsil')"> SHM
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HGB" checked onchange="applySubFilter('parsil')"> HGB
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HGU" checked onchange="applySubFilter('parsil')"> HGU
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-parsil" value="HP" checked onchange="applySubFilter('parsil')"> HP
</label>
</div>
</div>
<!-- Rumah Ibadah -->
<label class="layer-option">
<input type="checkbox" id="layerRumahIbadah" checked> Rumah Ibadah
</label>
<!-- Penduduk Miskin -->
<div class="layer-group-item">
<div class="layer-group-header">
<label class="layer-option" style="margin:0; flex:1;">
<input type="checkbox" id="layerMiskin" checked> Penduduk Miskin
</label>
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subMiskin', this)"><i class="fas fa-chevron-down"></i></span>
</div>
<div id="subMiskin" class="sub-layer-list" style="display:none;">
<label class="layer-option sub-option">
<input type="checkbox" class="sub-miskin" value="Makan" checked onchange="applySubFilter('miskin')"> Bantuan Makan
</label>
<label class="layer-option sub-option">
<input type="checkbox" class="sub-miskin" value="Pemberdayaan" checked onchange="applySubFilter('miskin')"> Pemberdayaan
</label>
</div>
</div>
<!-- GeoJSON Eksternal -->
<div class="layer-group" style="margin-top: 10px; border-top: 1px solid #eee; padding-top: 10px;">
<div class="layer-group-header" onclick="window.toggleGeoJsonMenu()" style="cursor:pointer; display:flex; justify-content:space-between; align-items:center; font-size:14px; margin-bottom:8px; font-weight: 500;">
<span><i class="fas fa-folder"></i> GeoJSON Eksternal</span>
<i id="geoJsonToggleIcon" class="fas fa-plus"></i>
</div>
<div id="geoJsonFileList" style="display:none; padding-left:15px; margin-bottom:10px;">
<div id="geoJsonLayersContainer"></div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px dashed #ddd;">
<label style="font-size: 12px; display:block; margin-bottom: 5px;">Import GeoJSON Baru:</label>
<input type="file" id="fileGeoJson" accept=".geojson,.json" style="font-size: 12px; max-width: 100%;">
</div>
</div>
</div>
</div><!-- /custom-layer-panel -->
<!-- Right Data Panel -->
<div class="right-panel" id="rightPanel">
<div class="right-panel-header">
<h3 id="rightPanelTitle">Data</h3>
<button class="right-panel-close" id="rightPanelClose"><i class="fas fa-times"></i></button>
</div>
<div class="right-panel-actions" id="rightPanelActions"></div>
<div class="right-panel-body" id="rightPanelBody">
<div class="panel-empty">Pilih menu di kiri untuk menampilkan data</div>
</div>
</div><!-- /right-panel -->
</div>
<!-- Map Container --> <!-- Map Container -->
@@ -298,16 +300,16 @@
<script src="https://cdn.jsdelivr.net/npm/leaflet-textpath@1.2.3/leaflet.textpath.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/leaflet-textpath@1.2.3/leaflet.textpath.min.js"></script>
<!-- Main Map Initialization --> <!-- Main Map Initialization -->
<script src="assets/js/map.js"></script> <script src="assets/js/map.js?v=<?= time() ?>"></script>
<!-- Fitur & Komponen --> <!-- Fitur & Komponen -->
<script src="assets/js/features/spbu.js"></script> <script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
<script src="assets/js/features/jalan.js"></script> <script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
<script src="assets/js/features/parsil.js"></script> <script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
<script src="assets/js/features/draw_control.js"></script> <script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
<script src="assets/js/features/geolocation.js"></script> <script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
<script src="assets/js/features/kemiskinan.js"></script> <script src="assets/js/features/kemiskinan.js?v=<?= time() ?>"></script>
<script src="assets/js/features/geojson.js"></script> <script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
<script src="assets/js/panel.js"></script> <script src="assets/js/panel.js?v=<?= time() ?>"></script>
</body> </body>
</html> </html>