// ========================================== // 1. INISIALISASI PETA & LAYER // ========================================== // 1. Definisikan Pilihan Basemap (Tile Layers) var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }); var googleSatellite = L.tileLayer('http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 20, subdomains: ['mt0', 'mt1', 'mt2', 'mt3'], attribution: 'Map data © Google' }); var darkMap = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors © CARTO' }); // 2. Inisialisasi Peta // Pastikan id 'map' sesuai dengan id div pada file index.php Anda var map = L.map('map', { center: [-0.0227, 109.3340], // Titik tengah peta (Pontianak) zoom: 13, layers: [osm] // Basemap default yang langsung aktif saat web dibuka }); // 3. Gabungkan Basemap ke dalam satu Grup Objek var baseMaps = { "Peta Standar (OSM)": osm, "Satelit (Google)": googleSatellite, "Mode Gelap (Dark)": darkMap }; // 4. Tambahkan Tombol Kontrol Layer ke Kanan Atas Peta L.control.layers(baseMaps).addTo(map); // Zoom control di kanan bawah (lebih bersih) L.control.zoom({ position: 'bottomright' }).addTo(map); // Custom pane initialization removed L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); // Layer Group untuk masing-masing data var drawnItems = new L.FeatureGroup().addTo(map); //var batasKotaLayer = L.layerGroup().addTo(map); //var sungaiLayer = L.layerGroup().addTo(map); var rumahIbadahLayer = L.layerGroup().addTo(map); var radiusLayer = L.layerGroup().addTo(map); var pendudukMiskinLayer = L.layerGroup().addTo(map); var pendudukMiskinCluster = L.markerClusterGroup({ showCoverageOnHover: false, zoomToBoundsOnClick: true, maxClusterRadius: 50 }).addTo(map); // Inisialisasi Heatmap kosong (akan diisi datanya saat load data) var povertyHeatLayer = L.heatLayer([], { radius: 22, blur: 18, maxZoom: 17 }).addTo(map); // Inisialisasi Layers Control (di kanan atas) var overlayMaps = { "🕌 Rumah Ibadah": rumahIbadahLayer, "🔥 Kepadatan Kemiskinan (Heatmap)": povertyHeatLayer, "👥 Semua Penduduk Miskin": pendudukMiskinCluster, "👥 Penduduk Terdekat (Analisis)": pendudukMiskinLayer }; L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map); // ========================================== // ICON DEFINITIONS // ========================================== // Unused marker icons removed // Icon Rumah Ibadah Kustom — bangunan rumah ibadah bergaya universal var rumahIbadahIcon = L.divIcon({ className: '', html: `
`, iconSize: [38, 38], iconAnchor: [19, 38], popupAnchor: [0, -38] }); var databasePendudukMiskin = []; function loadRumahIbadah() { rumahIbadahLayer.clearLayers(); fetch('load.php?jenis=rumah_ibadah') .then(res => res.json()) .then(dataRumahIbadah => { fetch('load.php?jenis=penduduk_miskin') .then(res => res.json()) .then(dataPendudukMiskin => { databasePendudukMiskin = dataPendudukMiskin; // Mengisi koordinat data kemiskinan ke dalam Heatmap var heatPoints = dataPendudukMiskin.map(p => [ parseFloat(p.lat || p.latitude), parseFloat(p.lng || p.longitude), 1.0 ]); povertyHeatLayer.setLatLngs(heatPoints); // Marker Clustering untuk semua penduduk miskin pendudukMiskinCluster.clearLayers(); dataPendudukMiskin.forEach(penduduk => { var latPenduduk = parseFloat(penduduk.lat || penduduk.latitude); var lngPenduduk = parseFloat(penduduk.lng || penduduk.longitude); var statusBantuan = penduduk.bantuan || 'Belum Menerima'; var poorFamilyIcon = L.divIcon({ className: '', html: `
`, iconSize: [14, 14], iconAnchor: [7, 7], popupAnchor: [0, -7] }); var actionButtons = ''; if (IS_ADMIN) { actionButtons = `
`; } var htmlIsi = ` `; var marker = L.marker([latPenduduk, lngPenduduk], { icon: poorFamilyIcon }) .bindPopup(htmlIsi); pendudukMiskinCluster.addLayer(marker); }); dataRumahIbadah.forEach(ibadah => { var latIbadah = ibadah.lat || ibadah.latitude; var lngIbadah = ibadah.lng || ibadah.longitude; var radiusMeter = 500; var ibadahId = ibadah.id; var actionButtons = ''; if (IS_ADMIN) { actionButtons = `
`; } // HTML untuk Popup Rumah Ibadah dengan range slider untuk mengatur radius analisis var htmlIbadah = ` `; var ibadahMarker = L.marker([latIbadah, lngIbadah], { icon: rumahIbadahIcon }) .bindPopup(htmlIbadah) .addTo(rumahIbadahLayer); databasePencarian.push({ namaLower: ibadah.nama.toLowerCase(), namaAsli: ibadah.nama, kategori: 'Rumah Ibadah', ikon: '🕌', tipe: 'titik', layer: ibadahMarker }); // Saat popup dibuka, jalankan fungsi updatePopupRadius ibadahMarker.on('popupopen', function (e) { map.flyTo(e.target.getLatLng(), 16, { animate: true }); setTimeout(function () { updatePopupRadius(ibadahId, latIbadah, lngIbadah); }, 50); }); }); }); }) .catch(e => console.log('Belum ada data Rumah Ibadah', e)); } // Fungsi untuk memperbarui lingkaran radius dan penduduk miskin terdekat secara interaktif function updatePopupRadius(ibadahId, lat, lng) { var slider = document.getElementById('radius-slider-' + ibadahId); var label = document.getElementById('radius-val-' + ibadahId); if (!slider || !label) return; var newRadius = parseInt(slider.value); label.textContent = newRadius + ' meter'; var centerPoint = L.latLng(lat, lng); radiusLayer.clearLayers(); pendudukMiskinLayer.clearLayers(); // Gambar lingkaran radius baru var areaRadius = L.circle(centerPoint, { color: '#2563eb', fillColor: '#3b82f6', fillOpacity: 0.1, radius: newRadius, interactive: true }).addTo(radiusLayer); areaRadius.on('click', function (evt) { L.DomEvent.stopPropagation(evt); }); var totalKK = 0; var sudahBantuan = 0; var belumBantuan = 0; var familiesInRadius = []; // Saring dan tampilkan penduduk miskin yang berada dalam radius baru databasePendudukMiskin.forEach(penduduk => { var latPenduduk = penduduk.lat || penduduk.latitude; var lngPenduduk = penduduk.lng || penduduk.longitude; var pendudukLatLng = L.latLng(latPenduduk, lngPenduduk); var jarak = centerPoint.distanceTo(pendudukLatLng); if (jarak <= newRadius) { totalKK++; var statusBantuan = penduduk.bantuan || 'Belum Menerima'; if (statusBantuan === 'Sudah Menerima') { sudahBantuan++; } else { belumBantuan++; } familiesInRadius.push(penduduk); var warnaDot = statusBantuan === 'Sudah Menerima' ? '#10b981' : '#ef4444'; var redDot = L.circleMarker(pendudukLatLng, { radius: 7, color: '#ffffff', fillColor: warnaDot, fillOpacity: 1, weight: 1.5 }).addTo(pendudukMiskinLayer); redDot.on('click', function (e) { L.DomEvent.stopPropagation(e); var pendudukId = penduduk.id; var namaKeluarga = penduduk.nama_kk ? penduduk.nama_kk : "Tanpa Nama"; var alamatLengkap = penduduk.alamat ? penduduk.alamat : "Alamat belum diinput manual"; var latAngka = parseFloat(latPenduduk); var lngAngka = parseFloat(lngPenduduk); var actionButtons = ''; if (IS_ADMIN) { actionButtons = `
`; } // Typo fix: penduid -> pendudukId actionButtons = actionButtons.replace('\'${penduid}\'', `'${pendudukId}'`); var htmlIsi = ` `; redDot.bindPopup(htmlIsi).openPopup(); }); } }); // Cari nama rumah ibadah dari databasePencarian var ibadahObj = databasePencarian.find(item => item.layer && item.layer._popup && item.layer._popup.getContent().includes('popup-ibadah-' + ibadahId)); var ibadahName = ibadahObj ? ibadahObj.namaAsli : "Rumah Ibadah"; showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius); } map.on('click', function () { if (sidebarPickMode) return; // Ditangani oleh listener sidebar radiusLayer.clearLayers(); pendudukMiskinLayer.clearLayers(); closeDashboard(); }); loadRumahIbadah(); // ========================================== // 7. HAPUS DATA // ========================================== function deleteData(tabel, id) { if (confirm('Apakah Anda yakin ingin menghapus data ini?')) { var formData = new FormData(); formData.append('id', id); formData.append('tabel', tabel); fetch('delete_data.php', { method: 'POST', body: formData }) .then(r => r.text()).then(res => { alert(res); map.closePopup(); loadRumahIbadah(); }); } } // ========================================== // 8. EDIT DATA // ========================================== function formEditRumahIbadah(id, nama, lat, lng) { setTimeout(function () { document.getElementById(`popup-ibadah-${id}`).innerHTML = ` `; }, 50); } function formEditPenduduk(id, nama, alamat, statusBantuan, lat, lng) { setTimeout(function () { document.getElementById(`popup-penduduk-${id}`).innerHTML = ` `; }, 50); } // ========================================== // 9. UPDATE DATA // ========================================== function updateData(e, tabel, id) { e.preventDefault(); var formData = new FormData(); formData.append('tabel', tabel); formData.append('id', id); if (tabel === 'rumah_ibadah') { formData.append('nama', document.getElementById('edit_nama_ibadah').value); formData.append('latitude', document.getElementById('edit_lat_ibadah').value); formData.append('longitude', document.getElementById('edit_lng_ibadah').value); } else if (tabel === 'penduduk_miskin') { formData.append('nama', document.getElementById('edit_nama_penduduk').value); formData.append('alamat', document.getElementById('edit_alamat_penduduk').value); formData.append('bantuan', document.getElementById('edit_status_bantuan').value); formData.append('latitude', document.getElementById('edit_lat_penduduk').value); formData.append('longitude', document.getElementById('edit_lng_penduduk').value); } fetch('update_data.php', { method: 'POST', body: formData }) .then(r => r.text()).then(res => { alert(res); map.closePopup(); if (tabel === 'rumah_ibadah' || tabel === 'penduduk_miskin') { loadRumahIbadah(); } }); } // ========================================== // 10. LEGENDA PETA // ========================================== var legend = L.control({ position: 'bottomleft' }); legend.onAdd = function (map) { var div = L.DomUtil.create('div', 'info legend'); div.innerHTML = `

Legenda Peta

Sosial & Keagamaan
Rumah Ibadah
Penduduk Miskin (Sudah Menerima)
Penduduk Miskin (Belum Menerima)
`; return div; }; legend.addTo(map); // ========================================== // 11. SISTEM PENCARIAN // ========================================== var databasePencarian = []; var ikonKategori = { 'Rumah Ibadah': '🕌', }; var warnaBadge = { 'Rumah Ibadah': 'badge-Rumah-Ibadah', }; function jalankanPencarian() { var kataKunci = document.getElementById('search-input').value.toLowerCase(); var kategori = document.getElementById('search-category').value; var wadahHasil = document.getElementById('search-results'); wadahHasil.innerHTML = ''; if (kataKunci.length < 2) { wadahHasil.style.display = 'none'; return; } var hasil = databasePencarian.filter(function (item) { var cocokNama = item.namaLower.includes(kataKunci); var cocokKategori = (kategori === 'Semua') || (item.kategori === kategori); return cocokNama && cocokKategori; }); if (hasil.length > 0) { wadahHasil.style.display = 'block'; wadahHasil.innerHTML = `
${hasil.length} hasil ditemukan
`; hasil.forEach(function (item) { var badgeClass = warnaBadge[item.kategori] || 'badge-default'; var ikon = ikonKategori[item.kategori] || '📍'; var bgColors = { 'Rumah Ibadah': 'rgba(59,130,246,0.12)' }; var div = document.createElement('div'); div.className = 'result-item'; div.innerHTML = `
${ikon}
${item.namaAsli}
Klik untuk zoom ke lokasi
${item.kategori}`; div.onclick = function () { if (item.tipe === 'titik') { map.flyTo(item.layer.getLatLng(), 17, { animate: true, duration: 1.5 }); } else { map.fitBounds(item.layer.getBounds()); } item.layer.openPopup(); wadahHasil.style.display = 'none'; document.getElementById('search-input').value = item.namaAsli; toggleClearBtn(); }; wadahHasil.appendChild(div); }); } else { wadahHasil.style.display = 'block'; wadahHasil.innerHTML = `
🔍
Data tidak ditemukan
Coba kata kunci lain
`; } } function toggleClearBtn() { var input = document.getElementById('search-input'); var clearBtn = document.getElementById('search-clear'); clearBtn.style.display = input.value.length > 0 ? 'flex' : 'none'; } function clearSearch() { document.getElementById('search-input').value = ''; document.getElementById('search-results').style.display = 'none'; document.getElementById('search-clear').style.display = 'none'; } // Tutup hasil saat klik di luar document.addEventListener('click', function (e) { var wrapper = document.querySelector('.search-wrapper'); if (!wrapper.contains(e.target)) { document.getElementById('search-results').style.display = 'none'; } }); // Shortcut Cmd/Ctrl + K document.addEventListener('keydown', function (e) { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); document.getElementById('search-input').focus(); } if (e.key === 'Escape') { clearSearch(); document.getElementById('search-input').blur(); } }); // ========================================== // 12. LOGIKA SIDEBAR INPUT TERPUSAT // ========================================== var sidebarPickMode = null; // 'ibadah' | 'penduduk' var sidebarTempLat = 0, sidebarTempLng = 0; // Buka/Tutup Sidebar document.getElementById('sidebar-toggle').addEventListener('click', function () { var sidebar = document.getElementById('input-sidebar'); sidebar.classList.toggle('open'); this.classList.toggle('open'); }); document.getElementById('sidebar-close').addEventListener('click', function () { document.getElementById('input-sidebar').classList.remove('open'); document.getElementById('sidebar-toggle').classList.remove('open'); }); // Ganti Tab function switchSidebarTab(tabName) { document.querySelectorAll('.stab').forEach(function (t) { t.classList.remove('active'); }); document.querySelectorAll('.stab-content').forEach(function (c) { c.classList.remove('active'); }); document.querySelector('.stab[data-tab="' + tabName + '"]').classList.add('active'); document.getElementById('tab-' + tabName).classList.add('active'); // batalkan mode pick/draw jika ganti tab deactivatePickMode(); } // Aktifkan mode pilih titik di peta function activatePickMode(jenis) { sidebarPickMode = jenis; document.getElementById('pick-overlay').classList.add('active'); var teks = { ibadah: 'Klik di peta untuk posisi Rumah Ibadah', penduduk: 'Klik di peta untuk posisi Penduduk Miskin' }; document.getElementById('pick-overlay-text').textContent = teks[jenis] || 'Klik lokasi di peta...'; var btnMap = { ibadah: 'btn-pick-ibadah', penduduk: 'btn-pick-penduduk' }; if (btnMap[jenis]) document.getElementById(btnMap[jenis]).classList.add('active-pick'); map.getContainer().style.cursor = 'crosshair'; } // Batalkan semua mode function deactivatePickMode() { sidebarPickMode = null; document.getElementById('pick-overlay').classList.remove('active'); document.querySelectorAll('.btn-pick-location').forEach(function (b) { b.classList.remove('active-pick'); }); map.getContainer().style.cursor = ''; } // Tangkap klik peta untuk mode pick titik map.on('click', function (e) { if (!sidebarPickMode) return; var lat = e.latlng.lat.toFixed(6); var lng = e.latlng.lng.toFixed(6); sidebarTempLat = e.latlng.lat; sidebarTempLng = e.latlng.lng; radiusLayer.clearLayers(); pendudukMiskinLayer.clearLayers(); if (sidebarPickMode === 'ibadah') { var el = document.getElementById('coords-ibadah-val'); el.textContent = lat + ', ' + lng; el.classList.add('has-coords'); } else if (sidebarPickMode === 'penduduk') { var el = document.getElementById('coords-penduduk-val'); el.textContent = lat + ', ' + lng; el.classList.add('has-coords'); var alamatInput = document.getElementById('sb_alamat_penduduk'); alamatInput.value = "Sedang mencari alamat..."; // Indikator loading // Memanggil API Nominatim OpenStreetMap fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`) .then(response => response.json()) .then(data => { if (data && data.display_name) { // Isi textarea alamat dengan hasil dari Nominatim alamatInput.value = data.display_name; } else { alamatInput.value = "Alamat tidak ditemukan, silakan isi manual."; } }) .catch(error => { console.error("Error Reverse Geocoding:", error); alamatInput.value = ""; }); } deactivatePickMode(); }); // ===== FUNGSI SIMPAN DARI SIDEBAR ===== function saveRumahIbadahSidebar() { var coords = document.getElementById('coords-ibadah-val').textContent; if (coords === 'Belum dipilih') { alert('Pilih lokasi di peta terlebih dahulu!'); return; } var nama = document.getElementById('sb_nama_ibadah').value.trim(); if (!nama) { alert('Nama rumah ibadah wajib diisi!'); return; } var formData = new FormData(); formData.append('nama', nama); formData.append('latitude', sidebarTempLat); formData.append('longitude', sidebarTempLng); fetch('save_rumah_ibadah.php', { method: 'POST', body: formData }) .then(r => r.text()).then(res => { alert(res); document.getElementById('sb_nama_ibadah').value = ''; document.getElementById('coords-ibadah-val').textContent = 'Belum dipilih'; document.getElementById('coords-ibadah-val').classList.remove('has-coords'); sidebarTempLat = 0; sidebarTempLng = 0; loadRumahIbadah(); }).catch(e => console.error(e)); } function savePendudukSidebar() { var coords = document.getElementById('coords-penduduk-val').textContent; if (coords === 'Belum dipilih') { alert('Pilih lokasi di peta terlebih dahulu!'); return; } var nama = document.getElementById('sb_nama_penduduk').value.trim(); var alamat = document.getElementById('sb_alamat_penduduk').value.trim(); var statusBantuan = document.getElementById('sb_status_bantuan').value; if (!nama) { alert('Nama kepala keluarga wajib diisi!'); return; } if (!alamat) { alert('Alamat wajib diisi!'); return; } var formData = new FormData(); formData.append('nama', nama); formData.append('alamat', alamat); formData.append('bantuan', statusBantuan); formData.append('latitude', sidebarTempLat); formData.append('longitude', sidebarTempLng); fetch('save_penduduk_miskin.php', { method: 'POST', body: formData }) .then(r => r.text()).then(res => { alert(res); document.getElementById('sb_nama_penduduk').value = ''; document.getElementById('sb_alamat_penduduk').value = ''; document.getElementById('sb_status_bantuan').value = 'Belum Menerima'; document.getElementById('coords-penduduk-val').textContent = 'Belum dipilih'; document.getElementById('coords-penduduk-val').classList.remove('has-coords'); sidebarTempLat = 0; sidebarTempLng = 0; loadRumahIbadah(); }).catch(e => console.error(e)); } // ========================================== // 14. LOGIKA CUSTOM DROPDOWN // ========================================== document.addEventListener('DOMContentLoaded', function () { var wrap = document.getElementById('custom-select-wrap'); var trigger = document.getElementById('custom-select-trigger'); var options = document.getElementById('custom-select-options'); var hiddenInput = document.getElementById('search-category'); var optionItems = document.querySelectorAll('.custom-option'); // Buka/Tutup dropdown saat kotak di-klik wrap.addEventListener('click', function (e) { e.stopPropagation(); options.classList.toggle('show'); }); // Saat salah satu pilihan (seperti SPBU atau Jalan) diklik optionItems.forEach(function (item) { item.addEventListener('click', function (e) { e.stopPropagation(); // Hapus status aktif dari yang lain, pasang ke yang di-klik optionItems.forEach(opt => opt.classList.remove('selected')); this.classList.add('selected'); // Ganti teks yang tampil dan simpan nilainya ke input tersembunyi trigger.textContent = this.textContent; hiddenInput.value = this.getAttribute('data-value'); // Tutup dropdown options.classList.remove('show'); // Otomatis jalankan fungsi pencarian (kode aslimu) jalankanPencarian(); }); }); // Tutup dropdown otomatis jika klik area manapun di luar peta/header document.addEventListener('click', function () { options.classList.remove('show'); }); }); // ========================================== // 15. DYNAMIC STATS DASHBOARD SIDEBAR (CHART.JS) // ========================================== var myPovertyChart = null; function showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius) { document.getElementById('dashboard-target-name').textContent = ibadahName; document.getElementById('stat-total-kk').textContent = totalKK; document.getElementById('stat-sudah-bantuan').textContent = sudahBantuan; document.getElementById('stat-belum-bantuan').textContent = belumBantuan; // Tampilkan sidebar kanan document.getElementById('dashboard-sidebar').classList.add('open'); // Buat daftar KK miskin dalam radius var listContainer = document.getElementById('dashboard-families-list'); listContainer.innerHTML = ''; if (familiesInRadius.length === 0) { listContainer.innerHTML = '
Tidak ada KK Miskin dalam radius ini
'; } else { familiesInRadius.forEach(f => { var item = document.createElement('div'); item.className = 'family-item'; var statusClass = f.bantuan === 'Sudah Menerima' ? 'badge-success' : 'badge-danger'; var statusText = f.bantuan === 'Sudah Menerima' ? 'Sudah' : 'Belum'; item.innerHTML = `
${f.nama_kk}
${f.alamat || 'Alamat tidak diisi'}
${statusText} `; // Klik untuk zoom/fly ke marker item.onclick = function () { var lat = parseFloat(f.lat || f.latitude); var lng = parseFloat(f.lng || f.longitude); map.flyTo([lat, lng], 18, { animate: true }); }; listContainer.appendChild(item); }); } // Gambar Chart.js var ctx = document.getElementById('povertyChart').getContext('2d'); if (myPovertyChart) { myPovertyChart.destroy(); } var chartLabels = ['Sudah Menerima', 'Belum Menerima']; var chartData = [sudahBantuan, belumBantuan]; var bgColors = ['#10b981', '#ef4444']; if (totalKK === 0) { chartLabels = ['Tidak Ada Data']; chartData = [1]; bgColors = ['#475569']; } myPovertyChart = new Chart(ctx, { type: 'doughnut', data: { labels: chartLabels, datasets: [{ data: chartData, backgroundColor: bgColors, borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#cbd5e1', font: { family: 'Plus Jakarta Sans', size: 10 }, boxWidth: 12 } } }, cutout: '65%' } }); } function closeDashboard() { document.getElementById('dashboard-sidebar').classList.remove('open'); }