// --- INISIALISASI PETA (Koordinat Pontianak) --- const map = L.map('map').setView([-0.055348, 109.349512], 13); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); const popup = L.popup(); const daftarMarker = {}, daftarPolyline = {}, daftarPolygon = {}; // 1. BUAT KERTAS TRANSPARAN (LAYER GROUPS) const layerTercakup = L.layerGroup().addTo(map); const layerTidakTercakup = L.layerGroup().addTo(map); const layerIbadah = L.layerGroup().addTo(map); const layerRadius = L.layerGroup().addTo(map); function toggleLayer(namaLayer, checkbox) { const layers = { 'tercakup': layerTercakup, 'tidakTercakup': layerTidakTercakup, 'ibadah': layerIbadah, 'radius': layerRadius, }; if (checkbox.checked) { layers[namaLayer].addTo(map); } else { map.removeLayer(layers[namaLayer]); } } let legend = L.control({ position: 'topright' }); legend.onAdd = function() { let div = L.DomUtil.create('div', 'map-legend'); div.innerHTML = `

Legenda

Prasejahtera Tercakup
Prasejahtera Tidak Tercakup

Masjid
Gereja
Vihara
Pura
Klenteng

Radius Jangkauan
Heatmap Kemiskinan
`; return div; }; legend.addTo(map); let currentMode = 'none'; let tempLatLngs = [], tempPolyline = null; let tempPolygonLatLngs = [], tempPolygon = null; let targetEditJalanId = null; // Menyimpan ID jalan yang sedang diedit let targetEditKavlingId = null; // Menyimpan ID kavling yang sedang diedit // Konfigurasi SweetAlert2 (Toast Pojok Kanan Atas) const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true }); // Definisi Ikon Kustom const ikonHijau = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); const ikonMerah = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); // ikon rumah prasejahtera const ikonPrasejahtera = L.divIcon({ html: `
`, iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -34], className: '' }); const ikonPrasejahteraTercakup = L.divIcon({ html: `
`, iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -34], className: '' }); function buatIkonIbadah(jenis) { const config = { 'Masjid': { icon: 'fa-mosque', color: '#1abc9c' }, 'Gereja': { icon: 'fa-church', color: '#3498db' }, 'Vihara': { icon: 'fa-torii-gate', color: '#e67e22' }, 'Pura': { icon: 'fa-om', color: '#9b59b6' }, 'Klenteng': { icon: 'fa-yin-yang', color: '#e74c3c' }, }; const c = config[jenis] || { icon: 'fa-place-of-worship', color: '#7f8c8d' }; return L.divIcon({ html: `
`, iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -34], className: '' }); } // Simpan data ibadah dan prasejahtera dalam memori untuk kalkulasi const dataPrasejahtera = {}; // { id: {lat, lng, marker} } const dataIbadah = {}; // { id: {lat, lng, radius_m, lingkaran} } function perbaruiWarnaPrasejahtera() { for (let pid in dataPrasejahtera) { let p = dataPrasejahtera[pid]; let tercakup = false; for (let iid in dataIbadah) { let ib = dataIbadah[iid]; let jarak = map.distance([p.lat, p.lng], [ib.lat, ib.lng]); if (jarak <= ib.radius_m) { tercakup = true; break; } } // Ganti ikon sesuai status let ikonBaru = tercakup ? ikonPrasejahteraTercakup : ikonPrasejahtera; p.marker.setIcon(ikonBaru); layerTercakup.removeLayer(p.marker); layerTidakTercakup.removeLayer(p.marker); if (tercakup) { p.marker.addTo(layerTercakup); } else { p.marker.addTo(layerTidakTercakup); } } updateRingkasan(); } // --- MANAJEMEN MODE UI --- function ubahMode(mode) { if (ROLE === 'viewer') return; console.log('Mode diklik:', mode, '| Mode saat ini:', currentMode); // ← tambah ini if (currentMode === mode) { // Jika mode yang diklik sama dengan yang aktif, matikan semua currentMode = 'none'; // Mode netral map.pm.disableGlobalEditMode(); if (targetEditJalanId) batalkanEditJalan(); if (targetEditKavlingId) batalkanEditKavling(); // Matikan semua highlight tombol aktif document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); // Sembunyikan semua panel sidebar document.getElementById('polyline-actions').style.display = 'none'; document.getElementById('polygon-actions').style.display = 'none'; document.getElementById('edit-jalan-actions').style.display = 'none'; document.getElementById('edit-kavling-actions').style.display = 'none'; map.closePopup(); batalPolyline(); batalPolygon(); return; // Hentikan eksekusi, tidak perlu lanjut ke bawah } currentMode = mode; // Matikan mode edit Geoman global jika sedang aktif map.pm.disableGlobalEditMode(); // Reset state edit if (targetEditJalanId) batalkanEditJalan(); if (targetEditKavlingId) batalkanEditKavling(); // Atur tampilan tombol aktif document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); document.querySelector(`.mode-btn[data-mode="${mode}"]`).classList.add('active'); // Munculkan menu yang sesuai di sidebar document.getElementById('polyline-actions').style.display = (mode === 'polyline') ? 'block' : 'none'; document.getElementById('polygon-actions').style.display = (mode === 'polygon') ? 'block' : 'none'; document.getElementById('edit-jalan-actions').style.display = (mode === 'edit_jalan') ? 'block' : 'none'; document.getElementById('edit-kavling-actions').style.display = (mode === 'edit_kavling') ? 'block' : 'none'; map.closePopup(); if (mode !== 'polyline') batalPolyline(); if (mode !== 'polygon') batalPolygon(); } // --- LOAD DATA DARI DATABASE --- function loadIbadah() { fetch('api/rumahibadah/load_ibadah.php').then(res => res.json()).then(data => { data.forEach(ib => { let marker = L.marker([ib.lat, ib.lng], { icon: buatIkonIbadah(ib.jenis), draggable: ROLE === 'admin' || ROLE === 'operator' }) .addTo(layerIbadah); // Lingkaran radius let lingkaran = L.circle([ib.lat, ib.lng], { radius: ib.radius_m, color: '#8e44ad', fillColor: '#8e44ad', fillOpacity: 0.1, weight: 1, dashArray: '5,5' }).addTo(layerRadius); const totalPrasejahtera = Object.keys(dataPrasejahtera).length; const persen = totalPrasejahtera > 0 ? Math.round((ib.jumlah_tercakup / totalPrasejahtera) * 100) : 0; marker.bindPopup(`
${ib.nama_ibadah}
Jenis: ${ib.jenis}
Jangkauan Bantuan: ${ib.radius_m} m

${ib.jumlah_tercakup}
rumah tercakup dari ${totalPrasejahtera} total
${persen}% dari total prasejahtera
${ROLE === 'admin' || ROLE === 'operator' ? ` ` : ''} ${ROLE === 'admin' ? ` ` : ''}
`); marker.on('dragend', function(e) { let pos = e.target.getLatLng(); lingkaran.setLatLng(pos); dataIbadah[ib.id] = { lat: pos.lat, lng: pos.lng, radius_m: ib.radius_m, lingkaran }; updateLokasiIbadah(ib.id, pos.lat, pos.lng); perbaruiWarnaPrasejahtera(); }); dataIbadah[ib.id] = { lat: ib.lat, lng: ib.lng, radius_m: ib.radius_m, lingkaran }; daftarMarker['ib_' + ib.id] = marker; }); // Setelah semua ibadah dimuat, perbarui warna prasejahtera perbaruiWarnaPrasejahtera(); }); } function formatRupiah(angka) { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(angka); } function loadDB() { fetch('api/prasejahtera/load_prasejahtera.php').then(res => res.json()).then(data => { data.forEach(rumah => { // PERBAIKAN: Gunakan 'rumah.', bukan 'data.' let penghasilanTeks = formatRupiah(rumah.penghasilan); let statusOrtuTeks = `Status Ortu: ${rumah.status_ortu}
`; if (rumah.status_ortu === 'Bekerja') { statusOrtuTeks += `Penghasilan: ${formatRupiah(rumah.penghasilan)}
`; } else if (rumah.status_ortu === 'Pensiun') { statusOrtuTeks += `Uang Pensiun: ${rumah.ada_pensiun === 'Ya' ? formatRupiah(rumah.nominal_pensiun) : 'Tidak Ada'}
`; } else if (rumah.status_ortu === 'Meninggal') { statusOrtuTeks += `Uang Terusan: ${rumah.ada_terusan === 'Ya' ? formatRupiah(rumah.nominal_terusan) : 'Tidak Ada'}
`; } let isiPopup = `
${rumah.nama_kepala_keluarga}
Anak: ${rumah.jumlah_anak} orang (Umur: ${rumah.umur_anak || '-'})
${statusOrtuTeks} Keterangan: ${rumah.keterangan || '-'}

${ROLE === 'admin' || ROLE === 'operator' ? ` ` : ''} ${ROLE === 'admin' ? ` ` : ''}
`; let marker = L.marker([rumah.lat, rumah.lng], { icon: ikonPrasejahtera, draggable: ROLE === 'admin' || ROLE === 'operator' }); marker.bindPopup(isiPopup); marker.on('dragend', function(e) { let pos = e.target.getLatLng(); dataPrasejahtera[rumah.id].lat = pos.lat; dataPrasejahtera[rumah.id].lng = pos.lng; updateLokasiPrasejahtera(rumah.id, pos.lat, pos.lng); perbaruiWarnaPrasejahtera(); }); dataPrasejahtera[rumah.id] = { lat: rumah.lat, lng: rumah.lng, marker }; daftarMarker['p_' + rumah.id] = marker; marker.bindPopup(isiPopup); marker.on('dragend', function(e) { let pos = e.target.getLatLng(); dataPrasejahtera[rumah.id].lat = pos.lat; dataPrasejahtera[rumah.id].lng = pos.lng; updateLokasiPrasejahtera(rumah.id, pos.lat, pos.lng); perbaruiWarnaPrasejahtera(); }); dataPrasejahtera[rumah.id] = { lat: rumah.lat, lng: rumah.lng, marker }; daftarMarker['p_' + rumah.id] = marker; }); // Perbarui heatmap setelah data prasejahtera termuat if (document.getElementById('checkbox-heatmap')?.checked) { buatHeatmap(); heatmapAktif = true; } // Muat ibadah SETELAH prasejahtera selesai loadIbadah(); }); } loadDB(); // --- FUNGSI UNTUK ME-REFRESH PETA SECARA OTOMATIS (SOFT REFRESH) --- function refreshPeta() { layerTercakup.clearLayers(); layerTidakTercakup.clearLayers(); layerIbadah.clearLayers(); layerRadius.clearLayers(); // 2. Bersihkan memori agar ID tidak menumpuk/ganda for (let id in dataIbadah) delete dataIbadah[id]; for (let id in dataPrasejahtera) delete dataPrasejahtera[id]; // 3. Panggil ulang data terbaru dari database loadDB(); if (heatmapAktif) buatHeatmap(); } // --- EVENT KLIK PETA --- function onMapClick(e) { if (ROLE === 'viewer') return; if (currentMode === 'point') { let formHtml = `

SPBU Baru


 

`; popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); } else if (currentMode === 'polyline') { tempLatLngs.push(e.latlng); if (!tempPolyline) tempPolyline = L.polyline(tempLatLngs, {color: '#3498db', weight: 4}).addTo(map); else tempPolyline.setLatLngs(tempLatLngs); } else if (currentMode === 'polygon') { tempPolygonLatLngs.push(e.latlng); if (!tempPolygon) tempPolygon = L.polygon(tempPolygonLatLngs, {color: '#2ecc71', fillColor: '#2ecc71', fillOpacity: 0.4}).addTo(map); else tempPolygon.setLatLngs(tempPolygonLatLngs); } else if (currentMode === 'prasejahtera') { let formHtml = `

Data Prasejahtera

`; popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); } else if (currentMode === 'ibadah') { let formHtml = `

Rumah Ibadah

`; popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); } } map.on('click', onMapClick); function simpanPrasejahtera(lat, lng) { // 1. Validasi Manual let nama_kk = document.getElementById('nama_kk').value.trim(); if (!nama_kk || nama_kk === "") { Toast.fire({ icon: 'warning', title: 'Nama Kepala Keluarga wajib diisi!' }); return; // Hentikan proses eksekusi jika kosong } // 2. Susun Data let dataKirim = { lat: lat, lng: lng, nama_kk: nama_kk, penghasilan: parseInt(document.getElementById('penghasilan').value) || 0, jumlah_anak: parseInt(document.getElementById('jumlah_anak').value) || 0, umur_anak: document.getElementById('umur_anak').value, status_ortu: document.getElementById('status_ortu').value, ada_pensiun: document.getElementById('ada_pensiun').value, nominal_pensiun: parseInt(document.getElementById('nominal_pensiun').value) || 0, ada_terusan: document.getElementById('ada_terusan').value, nominal_terusan: parseInt(document.getElementById('nominal_terusan').value) || 0, keterangan: document.getElementById('keterangan').value }; // 3. Kirim ke Database fetch('api/prasejahtera/simpan_prasejahtera.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(dataKirim) }) .then(res => res.json()) .then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Data Berhasil Disimpan!' }); map.closePopup(); refreshPeta(); // Sinkronisasi seketika } else { Toast.fire({ icon: 'error', title: 'Gagal Menyimpan: ' + (data.message || 'Error Database') }); } }) .catch(error => { console.error("Terjadi masalah:", error); Toast.fire({ icon: 'error', title: 'Koneksi ke Server Gagal!' }); }); } function hapusPrasejahtera(id) { Swal.fire({ title: 'Hapus data ini?', icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' }).then(result => { if (result.isConfirmed) { fetch('api/prasejahtera/hapus_prasejahtera.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id: id }) }) .then(res => res.json()).then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Data Dihapus!' }); // Cukup panggil fungsi ini, Leaflet akan menyapu bersih peta // dan memanggil ulang data terbaru dari database secara otomatis! refreshPeta(); } else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); }); } }); } function updateLokasiPrasejahtera(id, lat, lng) { fetch('api/prasejahtera/update_prasejahtera.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id, lat, lng }) }) .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); } function simpanIbadah(event, lat, lng) { event.preventDefault(); let dataKirim = { lat, lng, nama_ibadah: document.getElementById('nama_ibadah').value, jenis: document.getElementById('jenis_ibadah').value, radius_m: parseInt(document.getElementById('radius_ibadah').value) }; fetch('api/rumahibadah/simpan_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(dataKirim) }) .then(res => res.json()).then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Rumah Ibadah Disimpan!' }); map.closePopup(); refreshPeta(); } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); }); } function hapusIbadah(id) { Swal.fire({ title: 'Hapus Rumah Ibadah?', icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' }).then(result => { if (result.isConfirmed) { fetch('api/rumahibadah/hapus_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id }) }) .then(res => res.json()).then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Data Dihapus!' }); // Gunakan refreshPeta agar sinkron dengan database 100% refreshPeta(); } else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); }); } }); } function updateLokasiIbadah(id, lat, lng) { fetch('api/rumahibadah/update_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id, lat, lng }) }) .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); } // --- FUNGSI TOGGLE FORM PRASEJAHTERA --- function toggleStatusOrtu() { let status = document.getElementById('status_ortu').value; // Atur visibilitas grup document.getElementById('grup_bekerja').style.display = (status === 'Bekerja') ? 'block' : 'none'; document.getElementById('grup_pensiun').style.display = (status === 'Pensiun') ? 'block' : 'none'; document.getElementById('grup_meninggal').style.display = (status === 'Meninggal') ? 'block' : 'none'; // Reset nilai yang tersembunyi agar tidak ikut tersimpan salah if(status !== 'Bekerja') document.getElementById('penghasilan').value = 0; if(status !== 'Pensiun') { document.getElementById('ada_pensiun').value = 'Tidak'; togglePensiun(); } if(status !== 'Meninggal') { document.getElementById('ada_terusan').value = 'Tidak'; toggleTerusan(); } } function togglePensiun() { let ada = document.getElementById('ada_pensiun').value; document.getElementById('grup_nominal_pensiun').style.display = (ada === 'Ya') ? 'block' : 'none'; if(ada === 'Tidak') document.getElementById('nominal_pensiun').value = 0; } function toggleTerusan() { let ada = document.getElementById('ada_terusan').value; document.getElementById('grup_nominal_terusan').style.display = (ada === 'Ya') ? 'block' : 'none'; if(ada === 'Tidak') document.getElementById('nominal_terusan').value = 0; } // --- EDIT PRASEJAHTERA --- function editPrasejahtera(id) { fetch(`api/prasejahtera/get_prasejahtera.php?id=${id}`) .then(res => res.json()) .then(d => { let formHtml = `

Edit Data Prasejahtera

`; // Ganti konten popup yang sudah terbuka dengan form edit daftarMarker['p_' + id].getPopup().setContent(formHtml); }) .catch(() => Toast.fire({ icon: 'error', title: 'Gagal memuat data!' })); } function toggleEditStatusOrtu() { let status = document.getElementById('edit_status_ortu').value; document.getElementById('edit_grup_bekerja').style.display = status === 'Bekerja' ? 'block' : 'none'; document.getElementById('edit_grup_pensiun').style.display = status === 'Pensiun' ? 'block' : 'none'; document.getElementById('edit_grup_meninggal').style.display = status === 'Meninggal' ? 'block' : 'none'; if (status !== 'Bekerja') document.getElementById('edit_penghasilan').value = 0; if (status !== 'Pensiun') { document.getElementById('edit_ada_pensiun').value = 'Tidak'; toggleEditPensiun(); } if (status !== 'Meninggal') { document.getElementById('edit_ada_terusan').value = 'Tidak'; toggleEditTerusan(); } } function toggleEditPensiun() { let ada = document.getElementById('edit_ada_pensiun').value; document.getElementById('edit_grup_nominal_pensiun').style.display = ada === 'Ya' ? 'block' : 'none'; if (ada === 'Tidak') document.getElementById('edit_nominal_pensiun').value = 0; } function toggleEditTerusan() { let ada = document.getElementById('edit_ada_terusan').value; document.getElementById('edit_grup_nominal_terusan').style.display = ada === 'Ya' ? 'block' : 'none'; if (ada === 'Tidak') document.getElementById('edit_nominal_terusan').value = 0; } function simpanEditPrasejahtera(id) { let nama_kk = document.getElementById('edit_nama_kk').value.trim(); if (!nama_kk) { Toast.fire({ icon: 'warning', title: 'Nama Kepala Keluarga wajib diisi!' }); return; } let dataKirim = { id: id, nama_kk: nama_kk, jumlah_anak: parseInt(document.getElementById('edit_jumlah_anak').value) || 0, umur_anak: document.getElementById('edit_umur_anak').value, status_ortu: document.getElementById('edit_status_ortu').value, penghasilan: parseInt(document.getElementById('edit_penghasilan').value) || 0, ada_pensiun: document.getElementById('edit_ada_pensiun').value, nominal_pensiun: parseInt(document.getElementById('edit_nominal_pensiun').value) || 0, ada_terusan: document.getElementById('edit_ada_terusan').value, nominal_terusan: parseInt(document.getElementById('edit_nominal_terusan').value) || 0, keterangan: document.getElementById('edit_keterangan').value }; fetch('api/prasejahtera/update_prasejahtera.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) .then(res => res.json()) .then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Data Berhasil Diperbarui!' }); map.closePopup(); refreshPeta(); } else { Toast.fire({ icon: 'error', title: 'Gagal: ' + (data.message || 'Error') }); } }); } // --- EDIT IBADAH --- function editIbadah(id) { fetch(`api/rumahibadah/get_ibadah.php?id=${id}`) .then(res => res.json()) .then(d => { let jenisOptions = ['Masjid', 'Gereja', 'Vihara', 'Pura', 'Klenteng'].map(j => `` ).join(''); let formHtml = `

Edit Rumah Ibadah

`; daftarMarker['ib_' + id].getPopup().setContent(formHtml); }) .catch(() => Toast.fire({ icon: 'error', title: 'Gagal memuat data!' })); } function simpanEditIbadah(id) { let nama = document.getElementById('edit_nama_ibadah').value.trim(); if (!nama) { Toast.fire({ icon: 'warning', title: 'Nama Rumah Ibadah wajib diisi!' }); return; } let dataKirim = { id: id, nama_ibadah: nama, jenis: document.getElementById('edit_jenis_ibadah').value, radius_m: parseInt(document.getElementById('edit_radius_ibadah').value) }; fetch('api/rumahibadah/update_ibadah.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) .then(res => res.json()) .then(data => { if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Rumah Ibadah Diperbarui!' }); map.closePopup(); refreshPeta(); } else { Toast.fire({ icon: 'error', title: 'Gagal: ' + (data.message || 'Error') }); } }); } // --- HEATMAP --- let heatmapLayer = null; let heatmapAktif = false; function toggleHeatmap() { let checkbox = document.getElementById('checkbox-heatmap'); if (checkbox.checked) { buatHeatmap(); heatmapAktif = true; } else { if (heatmapLayer) map.removeLayer(heatmapLayer); heatmapAktif = false; } } function buatHeatmap() { // Ambil koordinat dari dataPrasejahtera yang sudah ada di memori let points = []; for (let id in dataPrasejahtera) { let p = dataPrasejahtera[id]; points.push([p.lat, p.lng, 1]); // [lat, lng, intensity] } // Hapus layer lama kalau ada if (heatmapLayer) map.removeLayer(heatmapLayer); heatmapLayer = L.heatLayer(points, { radius: 35, // ukuran area panas per titik blur: 25, // tingkat kehalusan gradasi maxZoom: 17, // zoom maksimal sebelum intensitas penuh max: 1.0, gradient: { 0.2: '#3498db', // biru = kepadatan rendah 0.5: '#f1c40f', // kuning = kepadatan sedang 0.8: '#e67e22', // oranye = kepadatan tinggi 1.0: '#e74c3c' // merah = kepadatan sangat tinggi } }).addTo(map); } function updateRingkasan() { let totalPrasejahtera = Object.keys(dataPrasejahtera).length; let totalIbadah = Object.keys(dataIbadah).length; let totalTercakup = layerTercakup.getLayers().length; let totalTidak = layerTidakTercakup.getLayers().length; let persen = totalPrasejahtera > 0 ? Math.round((totalTercakup / totalPrasejahtera) * 100) : 0; document.getElementById('stat-total').textContent = totalPrasejahtera; document.getElementById('stat-tercakup').textContent = totalTercakup; document.getElementById('stat-tidak').textContent = totalTidak; document.getElementById('stat-persen').textContent = persen + '%'; document.getElementById('stat-ibadah').textContent = totalIbadah; // Update progress bar document.getElementById('stat-progress').style.width = persen + '%'; // Warna progress bar berdasarkan persentase let warna = persen >= 70 ? '#27ae60' : persen >= 40 ? '#f39c12' : '#e74c3c'; document.getElementById('stat-progress').style.background = warna; }