/* ============================================================ loadData.js – WebGIS Pemetaan Kemiskinan & Masjid v2.1 ============================================================ */ /* ── Toast ── */ window.showToast = function (message, type) { type = type || "info"; var container = document.getElementById("toast-wrap"); if (!container) { return; } var toast = document.createElement("div"); toast.className = "toast " + type; var icon = type === "success" ? "" : type === "error" ? "" : ""; toast.innerHTML = icon + " " + message; container.appendChild(toast); setTimeout(function () { toast.classList.add("removing"); setTimeout(function () { toast.remove(); }, 300); }, 3500); }; /* ── Label helpers ── */ window.labelStatusKeluarga = { aktif: ' Aktif', meninggal: ' Meninggal', pindah: ' Pindah', sudah_mampu: ' Sudah Mampu' }; /* ── Detail Panel state ── */ window._currentDetailItem = null; window._currentDetailKemiskinanId = null; /* ── Open Detail Panel ── */ window.openDetailPanel = function (item) { window._currentDetailItem = item; window._currentDetailKemiskinanId = item.id; document.getElementById('detail-panel-nama').textContent = item.nama_kepala_keluarga; document.getElementById('detail-panel-alamat').textContent = item.alamat; var banner = document.getElementById('detail-status-banner'); var status = item.status_keluarga || 'aktif'; var statusLabels = { aktif: ' Aktif – Membutuhkan Bantuan', meninggal: ' Meninggal', pindah: ' Pindah', sudah_mampu: ' Sudah Mampu' }; banner.className = 'dp-status ' + status; banner.innerHTML = statusLabels[status] || status; /* Actions bar */ var actBar = document.getElementById('detail-actions-bar'); actBar.innerHTML = ''; var btnLaporan = document.createElement('button'); btnLaporan.className = 'btn btn-lapor'; btnLaporan.innerHTML = ' Laporkan Kondisi'; btnLaporan.onclick = function () { window.openLaporanPublikModal(item.id, item.nama_kepala_keluarga); }; actBar.appendChild(btnLaporan); if (window.appAuth.role === 'pengurus_masjid') { var btnEdit = document.createElement('button'); btnEdit.className = 'btn btn-edit'; btnEdit.innerHTML = ' Edit Data KK'; btnEdit.onclick = function () { window.openEditKemiskinanModal(item); }; actBar.appendChild(btnEdit); } document.getElementById('detail-panel').classList.add('open'); window.switchDetailTab('anggota'); }; window.closeDetailPanel = function () { document.getElementById('detail-panel').classList.remove('open'); window._currentDetailItem = null; window._currentDetailKemiskinanId = null; }; window.switchDetailTab = function (tabName) { ['anggota', 'bantuan', 'laporan-detail'].forEach(function (t) { var btnId = 'tab-btn-' + t; var contentId = 'detail-tab-' + t; var btn = document.getElementById(btnId); var content = document.getElementById(contentId); if (!btn || !content) return; var isActive = (t === tabName); btn.classList.toggle('active', isActive); content.classList.toggle('hidden', !isActive); }); if (!window._currentDetailItem) return; if (tabName === 'anggota') window.renderTabAnggota(window._currentDetailItem); if (tabName === 'bantuan') window.renderTabBantuan(window._currentDetailItem); if (tabName === 'laporan-detail') window.renderTabLaporanDetail(window._currentDetailItem); }; /* Laporan per KK */ window.renderTabLaporanDetail = async function (item) { var container = document.getElementById('detail-tab-laporan-detail'); if (!container) return; container.innerHTML = '
Memuat laporan...
'; var data = await window.fetchJson('php/read_laporan.php?kemiskinan_id=' + item.id, []); container.innerHTML = ''; var btnLapor = document.createElement('button'); btnLapor.className = 'btn btn-add'; btnLapor.innerHTML = '+ Laporkan Kondisi Baru'; btnLapor.onclick = function () { window.openLaporanPublikModal(item.id, item.nama_kepala_keluarga); }; container.appendChild(btnLapor); if (!data.length) { var emptyMsg = document.createElement('div'); emptyMsg.className = 'empty'; emptyMsg.textContent = 'Belum ada laporan untuk keluarga ini.'; container.appendChild(emptyMsg); return; } data.forEach(function (l) { var card = document.createElement('div'); card.className = 'l-card ' + l.status_laporan; card.innerHTML = '
' + '' + (window.labelKategoriLaporan[l.kategori] || l.kategori) + '' + '' + (window.labelStatusLaporan[l.status_laporan] || l.status_laporan) + '' + '
' + '
' + window.escapeHtml(l.deskripsi) + '
' + '
👤 ' + window.escapeHtml(l.nama_pelapor) + ' · 🕐 ' + l.created_fmt + '
'; container.appendChild(card); }); }; /* ── Modal: Kemiskinan ── */ window.openKemiskinanModal = function (lat, lng) { var latVal = Number(lat).toFixed(6); var lngVal = Number(lng).toFixed(6); document.getElementById("kem-lat").value = latVal; document.getElementById("kem-lng").value = lngVal; document.getElementById("kem-nama").value = ""; document.getElementById("kem-jumlah").value = ""; document.getElementById("kem-status").value = "aktif"; var alamatInput = document.getElementById("kem-alamat"); alamatInput.value = ""; alamatInput.placeholder = "Mencari alamat..."; alamatInput.dataset.geoLat = latVal; alamatInput.dataset.geoLng = lngVal; fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${latVal}&lon=${lngVal}&accept-language=id`) .then(res => res.json()) .then(data => { if (alamatInput.dataset.geoLat === latVal && alamatInput.dataset.geoLng === lngVal) { if (data && data.display_name) { alamatInput.value = data.display_name; } else { alamatInput.placeholder = "Gagal memuat alamat. Silakan isi manual."; } } }) .catch(err => { console.error("Reverse geocoding failed", err); if (alamatInput.dataset.geoLat === latVal && alamatInput.dataset.geoLng === lngVal) { alamatInput.placeholder = "Gagal memuat alamat. Silakan isi manual."; } }); var overlay = document.getElementById("modal-kemiskinan"); overlay.style.display = "flex"; document.getElementById("kem-nama").focus(); overlay.onclick = function (e) { if (e.target === overlay) window.closeModal("kemiskinan"); }; }; window._submitKemiskinan = async function (event) { if (event && event.preventDefault) event.preventDefault(); var btn = document.getElementById("btn-submit-kemiskinan"); var nama = document.getElementById("kem-nama").value.trim(); var jumlah = parseInt(document.getElementById("kem-jumlah").value, 10); var alamat = document.getElementById("kem-alamat").value.trim(); var status = document.getElementById("kem-status").value; var lat = document.getElementById("kem-lat").value.trim(); var lng = document.getElementById("kem-lng").value.trim(); if (!nama) { window.showToast("Nama kepala keluarga wajib diisi.", "error"); return; } if (!Number.isInteger(jumlah) || jumlah < 1) { window.showToast("Jumlah KK harus minimal 1.", "error"); return; } if (!lat || isNaN(Number(lat)) || Number(lat) < -90 || Number(lat) > 90) { window.showToast("Latitude harus berupa angka antara -90 dan 90.", "error"); return; } if (!lng || isNaN(Number(lng)) || Number(lng) < -180 || Number(lng) > 180) { window.showToast("Longitude harus berupa angka antara -180 dan 180.", "error"); return; } if (!alamat) { window.showToast("Alamat wajib diisi.", "error"); return; } btn.disabled = true; btn.textContent = "Menyimpan..."; try { await window.postForm("php/create_kemiskinan.php", { nama_kepala_keluarga: nama, jumlah_kk: jumlah, alamat: alamat, status_keluarga: status, lat: Number(lat).toFixed(6), lng: Number(lng).toFixed(6) }); window.closeModal("kemiskinan"); await window.loadAllData(); window.showToast("Data berhasil disimpan.", "success"); } catch (err) { window.showToast("Gagal: " + err.message, "error"); } finally { btn.disabled = false; btn.textContent = "Simpan"; } }; /* ── Modal: Masjid ── */ window.openMasjidModal = function (lat, lng) { var latVal = Number(lat).toFixed(6); var lngVal = Number(lng).toFixed(6); document.getElementById("msj-lat").value = latVal; document.getElementById("msj-lng").value = lngVal; document.getElementById("msj-jenis").value = "masjid"; document.getElementById("msj-nama").value = ""; document.getElementById("msj-pic").value = ""; document.getElementById("msj-radius").value = "500"; var alamatInput = document.getElementById("msj-alamat"); alamatInput.value = ""; alamatInput.placeholder = "Mencari alamat..."; alamatInput.dataset.geoLat = latVal; alamatInput.dataset.geoLng = lngVal; fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${latVal}&lon=${lngVal}&accept-language=id`) .then(res => res.json()) .then(data => { if (alamatInput.dataset.geoLat === latVal && alamatInput.dataset.geoLng === lngVal) { if (data && data.display_name) { alamatInput.value = data.display_name; } else { alamatInput.placeholder = "Gagal memuat alamat. Silakan isi manual."; } } }) .catch(err => { console.error("Reverse geocoding failed", err); if (alamatInput.dataset.geoLat === latVal && alamatInput.dataset.geoLng === lngVal) { alamatInput.placeholder = "Gagal memuat alamat. Silakan isi manual."; } }); var overlay = document.getElementById("modal-masjid"); overlay.style.display = "flex"; document.getElementById("msj-nama").focus(); overlay.onclick = function (e) { if (e.target === overlay) window.closeModal("masjid"); }; }; window._submitMasjid = async function (event) { if (event && event.preventDefault) event.preventDefault(); var btn = document.getElementById("btn-submit-masjid"); var jenis = document.getElementById("msj-jenis").value; var nama = document.getElementById("msj-nama").value.trim(); var pic = document.getElementById("msj-pic").value.trim(); var alamat = document.getElementById("msj-alamat").value.trim(); var radius = parseInt(document.getElementById("msj-radius").value, 10); var lat = document.getElementById("msj-lat").value.trim(); var lng = document.getElementById("msj-lng").value.trim(); if (!nama) { window.showToast("Nama rumah ibadah wajib diisi.", "error"); return; } if (!pic) { window.showToast("PIC wajib diisi.", "error"); return; } if (!lat || isNaN(Number(lat)) || Number(lat) < -90 || Number(lat) > 90) { window.showToast("Latitude harus berupa angka antara -90 dan 90.", "error"); return; } if (!lng || isNaN(Number(lng)) || Number(lng) < -180 || Number(lng) > 180) { window.showToast("Longitude harus berupa angka antara -180 dan 180.", "error"); return; } if (!alamat) { window.showToast("Alamat wajib diisi.", "error"); return; } if (!Number.isInteger(radius) || radius < 100 || radius > 5000) { window.showToast("Radius harus antara 100–5000 m.", "error"); return; } btn.disabled = true; btn.textContent = "Menyimpan..."; try { await window.postForm("php/create_masjid.php", { jenis_ibadah: jenis, nama_masjid: nama, pic_masjid: pic, alamat: alamat, radius: radius, lat: Number(lat).toFixed(6), lng: Number(lng).toFixed(6) }); window.closeModal("masjid"); await window.loadAllData(); window.showToast("Rumah ibadah berhasil disimpan.", "success"); } catch (err) { window.showToast("Gagal: " + err.message, "error"); } finally { btn.disabled = false; btn.textContent = "Simpan"; } }; window.closeModal = function (type) { var overlay = document.getElementById("modal-" + type); if (overlay) overlay.style.display = "none"; }; /* ── Modal: Edit Kemiskinan ── */ window.openEditKemiskinanModal = function (item) { document.getElementById("edit-kem-id").value = item.id; document.getElementById("edit-kem-nama").value = item.nama_kepala_keluarga; document.getElementById("edit-kem-jumlah").value = item.jumlah_kk; document.getElementById("edit-kem-alamat").value = item.alamat; document.getElementById("edit-kem-status").value = item.status_keluarga || 'aktif'; var overlay = document.getElementById("modal-edit-kemiskinan"); overlay.style.display = "flex"; document.getElementById("edit-kem-nama").focus(); overlay.onclick = function (e) { if (e.target === overlay) window.closeModal("edit-kemiskinan"); }; }; window._submitEditKemiskinan = async function (event) { if (event && event.preventDefault) event.preventDefault(); var btn = document.getElementById("btn-submit-edit-kemiskinan"); var id = document.getElementById("edit-kem-id").value; var nama = document.getElementById("edit-kem-nama").value.trim(); var jumlah = parseInt(document.getElementById("edit-kem-jumlah").value, 10); var alamat = document.getElementById("edit-kem-alamat").value.trim(); var status = document.getElementById("edit-kem-status").value; if (!nama) { window.showToast("Nama wajib diisi.", "error"); return; } if (!Number.isInteger(jumlah) || jumlah < 1) { window.showToast("Jumlah KK minimal 1.", "error"); return; } if (!alamat) { window.showToast("Alamat wajib diisi.", "error"); return; } btn.disabled = true; btn.textContent = "Menyimpan..."; try { await window.postForm("php/update_kemiskinan.php", { id, nama_kepala_keluarga: nama, jumlah_kk: jumlah, alamat, status_keluarga: status }); window.closeModal("edit-kemiskinan"); await window.loadAllData(); window.showToast("Data berhasil diperbarui.", "success"); if (window._currentDetailKemiskinanId === parseInt(id)) { var updatedItem = Object.assign({}, window._currentDetailItem, { nama_kepala_keluarga: nama, jumlah_kk: jumlah, alamat, status_keluarga: status }); window.openDetailPanel(updatedItem); } } catch (err) { window.showToast("Gagal: " + err.message, "error"); } finally { btn.disabled = false; btn.textContent = "Simpan Perubahan"; } }; /* ── Modal: Edit Rumah Ibadah ── */ window.openEditMasjidModal = function (item) { document.getElementById("edit-msj-id").value = item.id; document.getElementById("edit-msj-jenis").value = item.jenis_ibadah || 'masjid'; document.getElementById("edit-msj-nama").value = item.nama_masjid; document.getElementById("edit-msj-pic").value = item.pic_masjid; document.getElementById("edit-msj-alamat").value = item.alamat; var overlay = document.getElementById("modal-edit-masjid"); overlay.style.display = "flex"; document.getElementById("edit-msj-nama").focus(); overlay.onclick = function (e) { if (e.target === overlay) window.closeModal("edit-masjid"); }; }; window._submitEditMasjid = async function (event) { if (event && event.preventDefault) event.preventDefault(); var btn = document.getElementById("btn-submit-edit-masjid"); var id = document.getElementById("edit-msj-id").value; var jenis = document.getElementById("edit-msj-jenis").value; var nama = document.getElementById("edit-msj-nama").value.trim(); var pic = document.getElementById("edit-msj-pic").value.trim(); var alamat = document.getElementById("edit-msj-alamat").value.trim(); if (!nama) { window.showToast("Nama rumah ibadah wajib diisi.", "error"); return; } if (!pic) { window.showToast("PIC wajib diisi.", "error"); return; } if (!alamat) { window.showToast("Alamat wajib diisi.", "error"); return; } btn.disabled = true; btn.textContent = "Menyimpan..."; try { await window.postForm("php/update_masjid.php", { id, jenis_ibadah: jenis, nama_masjid: nama, pic_masjid: pic, alamat }); window.closeModal("edit-masjid"); await window.loadAllData(); window.showToast("Data rumah ibadah diperbarui.", "success"); } catch (err) { window.showToast("Gagal: " + err.message, "error"); } finally { btn.disabled = false; btn.textContent = "Simpan Perubahan"; } }; /* ── Fetch helper ── */ window.fetchJson = async function (url, fallback) { var response = await fetch(url, { cache: "no-store" }); var text = await response.text(); if (!response.ok) { console.error("HTTP " + response.status, url); return fallback !== undefined ? fallback : []; } if (!text.trim()) { return fallback !== undefined ? fallback : []; } try { return JSON.parse(text); } catch (e) { console.error("JSON parse:", e); return fallback !== undefined ? fallback : []; } }; /* ── Delete ── */ window.deleteRecord = async function (url, id, label) { if (window.appState.mode !== "edit") { window.showToast("Aktifkan Mode Edit untuk menghapus.", "error"); return; } if (!confirm("Hapus data " + label + "? Tidak dapat dibatalkan.")) return; try { await window.postForm(url, { id }); await window.loadAllData(); window.showToast("Data dihapus.", "success"); } catch (err) { window.showToast("Gagal: " + err.message, "error"); } }; /* ── Update radius ── */ window.updateMasjidRadiusFromCard = async function (masjidId, inputEl, circle, btnEl) { if (window.appState.mode !== "edit") { window.showToast("Aktifkan Mode Edit.", "error"); return; } var radius = parseInt(inputEl.value, 10); if (!Number.isInteger(radius) || radius < 100 || radius > 5000) { window.showToast("Radius harus 100–5000 m.", "error"); return; } btnEl.disabled = true; btnEl.textContent = "..."; try { await window.postForm("php/update_masjid_radius.php", { id: masjidId, radius }); if (circle) circle.setRadius(radius); window.showToast("Radius diperbarui: " + radius + " m.", "success"); await window.loadAllData(); } catch (err) { window.showToast("Gagal: " + err.message, "error"); btnEl.disabled = false; btnEl.textContent = "Simpan"; } }; /* ── Layer filter section ── */ window.createLayerFilterSection = function () { var section = document.createElement("div"); section.className = "sec"; var hd = document.createElement("div"); hd.className = "sec-hd"; hd.innerHTML = 'Filter Layer'; section.appendChild(hd); var filters = [ { key: "kemiskinan", label: "Keluarga Prasejahtera", color: "#ef4444" }, { key: "masjid", label: "Masjid / Rumah Ibadah", color: "#3b82f6" } ]; filters.forEach(function (f) { var row = document.createElement("label"); row.className = "filter-row"; var cb = document.createElement("input"); cb.type = "checkbox"; cb.dataset.layerKey = f.key; cb.checked = !!window.appState.layerVisibility[f.key]; cb.onchange = function () { window.setLayerVisibility(f.key, cb.checked); }; var dot = document.createElement("span"); dot.className = "filter-dot"; dot.style.background = f.color; var txt = document.createElement("span"); txt.textContent = f.label; row.appendChild(cb); row.appendChild(dot); row.appendChild(txt); section.appendChild(row); }); return section; }; window.refreshLayerFilterUI = function () { document.querySelectorAll("input[data-layer-key]").forEach(function (cb) { cb.checked = !!window.appState.layerVisibility[cb.dataset.layerKey]; }); }; /* ── loadAllData ── */ window.loadAllData = async function () { var sidebarBody = document.getElementById("sidebar-body"); var sidebarSub = document.getElementById("sidebar-sub"); if (!sidebarBody || !window.map) return; sidebarBody.innerHTML = '
Memuat data...
'; if (sidebarSub) sidebarSub.textContent = 'Memuat...'; if (window.appState.layers.kemiskinan) window.appState.layers.kemiskinan.clearLayers(); if (window.appState.layers.masjid) window.appState.layers.masjid.clearLayers(); try { var results = await Promise.all([ window.fetchJson("php/read_kemiskinan.php", []), window.fetchJson("php/read_masjid.php", []) ]); var dataKem = results[0]; var dataMsj = results[1]; var totalKeluarga = 0; var perluBantuan = 0; var countMasjid = 0, countGereja = 0, countVihara = 0; var totalJiwa = 0; var keluargaMentas = 0; var heatData = []; if (dataMsj && Array.isArray(dataMsj)) { dataMsj.forEach(function(m) { var j = (m.jenis_ibadah || 'masjid').toLowerCase(); if(j === 'masjid') countMasjid++; else if(j === 'gereja') countGereja++; else if(j === 'vihara') countVihara++; }); } if (dataKem && Array.isArray(dataKem)) { dataKem.forEach(function(item) { if (!window.isValidCoordinate(item.latitude, item.longitude)) return; totalKeluarga++; // Add jiwa count totalJiwa += parseInt(item.jumlah_kk) || 0; var kemLL = L.latLng(item.latitude, item.longitude); var status = item.status_keluarga || 'aktif'; if (status === 'sudah_mampu') { keluargaMentas++; } var isIn = false; if (dataMsj && dataMsj.length) { for (var i = 0; i < dataMsj.length; i++) { var m = dataMsj[i], r = Number(m.radius); if (window.isValidCoordinate(m.latitude, m.longitude) && window.isValidRadius(r)) { if (window.map.distance(kemLL, L.latLng(m.latitude, m.longitude)) <= r) { isIn = true; break; } } } } if (!isIn && status === 'aktif') { perluBantuan++; var lat = parseFloat(item.latitude); var lng = parseFloat(item.longitude); if (!isNaN(lat) && !isNaN(lng)) { heatData.push([lat, lng, 100]); // Use higher intensity } } }); } // Render Heatmap if (window.appState.layers.heatmap) { window.appState.layers.heatmap.clearLayers(); if (heatData.length > 0 && typeof L.heatLayer === 'function') { var heat = L.heatLayer(heatData, { radius: 35, blur: 25, maxZoom: 15, max: 100, gradient: {0.4: 'blue', 0.6: 'lime', 0.8: 'yellow', 1.0: 'red'} }); heat.addTo(window.appState.layers.heatmap); } } var statTotalEl = document.getElementById('stat-total-keluarga'); var statPerluEl = document.getElementById('stat-perlu-bantuan'); var statJiwaEl = document.getElementById('stat-total-jiwa'); var statMentasEl = document.getElementById('stat-keluarga-mentas'); var statProgText = document.getElementById('stat-progress-text'); var statProgFill = document.getElementById('stat-progress-fill'); if (statTotalEl) statTotalEl.textContent = totalKeluarga; if (statPerluEl) statPerluEl.textContent = perluBantuan; if (statJiwaEl) statJiwaEl.textContent = totalJiwa; if (statMentasEl) statMentasEl.textContent = keluargaMentas; if (statProgText && statProgFill) { var pct = 0; if (totalKeluarga > 0) { var terjangkau = totalKeluarga - perluBantuan; pct = Math.round((terjangkau / totalKeluarga) * 100); } statProgText.textContent = pct + '%'; statProgFill.style.width = pct + '%'; } var sm = document.getElementById('stat-masjid'); var sg = document.getElementById('stat-gereja'); var sv = document.getElementById('stat-vihara'); if(sm) sm.textContent = countMasjid; if(sg) sg.textContent = countGereja; if(sv) sv.textContent = countVihara; if (sidebarSub) { var hint = window.appState.mode === "edit" ? "Mode Edit — klik peta untuk tambah data" : "Mode Lihat — klik Detail untuk info lengkap"; sidebarSub.textContent = hint; } sidebarBody.innerHTML = ''; var divider = document.createElement('div'); divider.className = 'sec-divider'; sidebarBody.appendChild(divider); var secKem = window.renderKemiskinan(dataKem, dataMsj); secKem.id = 'sec-kemiskinan'; var secMsj = window.renderMasjid(dataMsj); secMsj.id = 'sec-masjid'; sidebarBody.appendChild(secKem); sidebarBody.appendChild(secMsj); if (typeof window.filterSidebar === 'function') { window.filterSidebar('semua'); } } catch (err) { sidebarBody.innerHTML = '
Terjadi kendala saat membaca data.
' + window.escapeHtml(err.message) + '
'; console.error(err); } }; /* ── Render: Kemiskinan ── */ window.renderKemiskinan = function (dataKem, dataMsj) { var sec = document.createElement('div'); sec.className = 'sec'; var hd = document.createElement('div'); hd.className = 'sec-hd'; hd.innerHTML = 'Keluarga Prasejahtera' + '' + dataKem.length + ''; sec.appendChild(hd); if (!dataKem.length) { var empty = document.createElement('div'); empty.className = 'empty'; empty.textContent = 'Belum ada data. Aktifkan Mode Edit dan klik peta.'; sec.appendChild(empty); return sec; } dataKem.forEach(function (item) { if (!window.isValidCoordinate(item.latitude, item.longitude)) return; var kemLL = L.latLng(item.latitude, item.longitude); var status = item.status_keluarga || 'aktif'; var isIn = false; if (dataMsj && dataMsj.length) { for (var i = 0; i < dataMsj.length; i++) { var m = dataMsj[i], r = Number(m.radius); if (window.isValidCoordinate(m.latitude, m.longitude) && window.isValidRadius(r)) { if (window.map.distance(kemLL, L.latLng(m.latitude, m.longitude)) <= r) { isIn = true; break; } } } } var icon = isIn ? window.greenIcon : window.redIcon; if (status !== 'aktif') icon = window.orangeIcon; var marker = L.marker([item.latitude, item.longitude], { icon, searchName: (item.nama_kepala_keluarga || '').toLowerCase() }) .addTo(window.appState.layers.kemiskinan) .bindPopup( '' + window.escapeHtml(item.nama_kepala_keluarga) + '
' + 'Status: ' + (window.labelStatusKeluarga[status] || status) + '
' + 'Anggota: ' + item.jumlah_kk + ' jiwa
' + window.escapeHtml(item.alamat) ); marker.on('click', function(e) { if (window.appState && window.appState.isRouting) { window.handleRoutingClick(item.latitude, item.longitude, item.nama_kepala_keluarga); marker.closePopup(); } }); var card = document.createElement('div'); card.dataset.searchName = (item.nama_kepala_keluarga || '').toLowerCase(); card.className = 'card' + (isIn ? ' in-radius' : ' out-radius') + (status === 'meninggal' ? ' st-meninggal' : '') + (status === 'pindah' ? ' st-pindah' : '') + (status === 'sudah_mampu' ? ' st-mampu' : ''); var chipClass = isIn ? 'chip-green' : 'chip-red'; if (status === 'meninggal') chipClass = 'chip-gray'; if (status === 'pindah') chipClass = 'chip-amber'; if (status === 'sudah_mampu') chipClass = 'chip-green'; var chipText = isIn ? 'Terjangkau' : 'Luar Radius'; if (status !== 'aktif') chipText = window.labelStatusKeluarga[status] || status; var updTs = item.updated_at_fmt ? '
' + item.updated_at_fmt + '
' : ''; card.innerHTML = '
' + window.escapeHtml(item.nama_kepala_keluarga) + '
' + '
' + item.jumlah_kk + ' anggota keluarga
' + '
' + window.escapeHtml(item.alamat) + '
' + updTs; var foot = document.createElement('div'); foot.className = 'card-foot'; var chip = document.createElement('span'); chip.className = 'chip ' + chipClass; chip.innerHTML = chipText; foot.appendChild(chip); var btns = document.createElement('div'); btns.className = 'card-btns'; var btnF = document.createElement('button'); btnF.className = 'btn btn-focus'; btnF.innerHTML = ''; btnF.title = 'Fokus ke peta'; btnF.onclick = (function(it, mk){ return function(e){ e.stopPropagation(); window.map.setView([it.latitude, it.longitude], 17); mk.openPopup(); }; })(item, marker); btns.appendChild(btnF); var btnD = document.createElement('button'); btnD.className = 'btn btn-detail'; btnD.innerHTML = ' Detail'; btnD.onclick = (function(it){ return function(e){ e.stopPropagation(); window.openDetailPanel(it); }; })(item); btns.appendChild(btnD); var btnL = document.createElement('button'); btnL.className = 'btn btn-lapor'; btnL.innerHTML = ''; btnL.title = 'Laporkan kondisi'; btnL.onclick = (function(it){ return function(e){ e.stopPropagation(); window.openLaporanPublikModal(it.id, it.nama_kepala_keluarga); }; })(item); btns.appendChild(btnL); if (window.appAuth && window.appAuth.role === 'pengurus_masjid') { var btnE = document.createElement('button'); btnE.className = 'btn btn-edit'; btnE.innerHTML = ''; btnE.title = 'Edit data'; btnE.disabled = window.appState.mode !== 'edit'; btnE.onclick = (function(it){ return function(e){ e.stopPropagation(); window.openEditKemiskinanModal(it); }; })(item); btns.appendChild(btnE); var btnH = document.createElement('button'); btnH.className = 'btn btn-delete'; btnH.innerHTML = ''; btnH.title = 'Hapus data'; btnH.disabled = window.appState.mode !== 'edit'; btnH.onclick = (function(it){ return function(e){ e.stopPropagation(); window.deleteRecord('php/delete_kemiskinan.php', it.id, 'Kemiskinan'); }; })(item); btns.appendChild(btnH); } foot.appendChild(btns); card.appendChild(foot); sec.appendChild(card); }); return sec; }; /* ── Jenis ibadah helpers ── */ window._jenisIbadahEmoji = { masjid: '', gereja: '', vihara: '', pura: '', klenteng: '', kapel: '', lainnya: '' }; window._jenisIbadahLabel = { masjid: 'Masjid', gereja: 'Gereja', vihara: 'Vihara', pura: 'Pura', klenteng: 'Klenteng', kapel: 'Kapel', lainnya: 'Rumah Ibadah' }; window._jenisIbadahColor = { masjid: '#1d4ed8', gereja: '#7c3aed', vihara: '#b45309', pura: '#b91c1c', klenteng: '#b45309', kapel: '#4338ca', lainnya: '#6b7280' }; window._jenisIbadahFill = { masjid: '#93c5fd', gereja: '#c4b5fd', vihara: '#fcd34d', pura: '#fca5a5', klenteng: '#fcd34d', kapel: '#a5b4fc', lainnya: '#d1d5db' }; /* ── Render: Rumah Ibadah ── */ window.renderMasjid = function (dataMsj) { var sec = document.createElement('div'); sec.className = 'sec'; var hd = document.createElement('div'); hd.className = 'sec-hd'; hd.innerHTML = 'Rumah Ibadah' + '' + dataMsj.length + ''; sec.appendChild(hd); if (!dataMsj.length) { var empty = document.createElement('div'); empty.className = 'empty'; empty.textContent = 'Belum ada data. Aktifkan Mode Edit dan klik peta.'; sec.appendChild(empty); return sec; } dataMsj.forEach(function (item) { var radius = Number(item.radius); if (!window.isValidCoordinate(item.latitude, item.longitude) || !window.isValidRadius(radius)) return; var jenis = item.jenis_ibadah || 'masjid'; var emoji = window._jenisIbadahEmoji[jenis] || ''; var label = window._jenisIbadahLabel[jenis] || 'Rumah Ibadah'; var color = window._jenisIbadahColor[jenis] || '#6b7280'; var fill = window._jenisIbadahFill[jenis] || '#d1d5db'; /* Ikon pin warna sesuai jenis */ var pinIcon = window.createPinIcon(color, '&#' + ({ masjid: 128332, gereja: 9962, vihara: 127983, pura: 128469, klenteng: 127982, kapel: 9769, lainnya: 127963 }[jenis] || 127963) + ';' ); var popup = '' + emoji + ' ' + window.escapeHtml(item.nama_masjid) + '
' + 'Jenis: ' + label + '
' + 'PIC: ' + window.escapeHtml(item.pic_masjid) + '
' + 'Radius: ' + window.formatMeters(radius); var circle = L.circle([item.latitude, item.longitude], { radius, color, fillColor: fill, fillOpacity: 0.12, weight: 1.5, interactive: false /* ← jangan tangkap klik peta, biarkan map.on('click') jalan */ }).addTo(window.appState.layers.masjid); var marker = L.marker([item.latitude, item.longitude], { icon: pinIcon, searchName: (item.nama_masjid || '').toLowerCase(), type: 'masjid' }) .addTo(window.appState.layers.masjid).bindPopup(popup); marker.on('click', function(e) { if (window.appState && window.appState.isRouting) { window.handleRoutingClick(item.latitude, item.longitude, item.nama_masjid); marker.closePopup(); } }); var card = document.createElement('div'); card.dataset.searchName = (item.nama_masjid || '').toLowerCase(); card.dataset.type = 'masjid'; card.className = 'card masjid-card'; card.innerHTML = '
' + emoji + ' ' + window.escapeHtml(item.nama_masjid) + '
' + '
PIC: ' + window.escapeHtml(item.pic_masjid) + '
' + '
' + window.escapeHtml(item.alamat) + '
'; /* Radius row */ var canEdit = window.appAuth && window.appAuth.role === 'pengurus_masjid'; var rr = document.createElement('div'); rr.className = 'radius-row'; rr.innerHTML = ''; var ri = document.createElement('input'); ri.type = 'number'; ri.min = '100'; ri.max = '5000'; ri.value = Math.round(radius); ri.disabled = !canEdit || window.appState.mode !== 'edit'; var ru = document.createElement('span'); ru.className = 'radius-unit'; ru.textContent = 'm'; var rb = document.createElement('button'); rb.className = 'btn btn-update'; rb.textContent = 'Simpan'; rb.disabled = !canEdit || window.appState.mode !== 'edit'; rb.onclick = (function(mid, rinput, ci, rbt){ return function(e){ e.stopPropagation(); window.updateMasjidRadiusFromCard(mid, rinput, ci, rbt); }; })(item.id, ri, circle, rb); rr.appendChild(ri); rr.appendChild(ru); rr.appendChild(rb); card.appendChild(rr); var foot = document.createElement('div'); foot.className = 'card-foot'; var chip = document.createElement('span'); chip.className = 'chip chip-blue'; chip.innerHTML = emoji + ' ' + label; chip.style.background = fill; chip.style.color = color; chip.style.border = '1px solid ' + color + '40'; foot.appendChild(chip); var btns = document.createElement('div'); btns.className = 'card-btns'; var btnF = document.createElement('button'); btnF.className = 'btn btn-focus'; btnF.innerHTML = ''; btnF.title = 'Fokus ke peta'; btnF.onclick = (function(ci, mk){ return function(e){ e.stopPropagation(); window.map.fitBounds(ci.getBounds(), { padding:[40,40] }); mk.openPopup(); }; })(circle, marker); btns.appendChild(btnF); if (canEdit) { var btnE = document.createElement('button'); btnE.className = 'btn btn-edit'; btnE.innerHTML = ''; btnE.title = 'Edit'; btnE.disabled = window.appState.mode !== 'edit'; btnE.onclick = (function(it){ return function(e){ e.stopPropagation(); window.openEditMasjidModal(it); }; })(item); btns.appendChild(btnE); var btnH = document.createElement('button'); btnH.className = 'btn btn-delete'; btnH.innerHTML = ''; btnH.title = 'Hapus'; btnH.disabled = window.appState.mode !== 'edit'; btnH.onclick = (function(it){ return function(e){ e.stopPropagation(); window.deleteRecord('php/delete_masjid.php', it.id, label); }; })(item); btns.appendChild(btnH); } foot.appendChild(btns); card.appendChild(foot); sec.appendChild(card); }); return sec; }; /* ── Search Map Data ── */ window.searchMapData = function(query) { var q = (query || '').toLowerCase().trim(); // Filter Kemiskinan if (window.appState && window.appState.layers && window.appState.layers.kemiskinan) { window.appState.layers.kemiskinan.eachLayer(function(layer) { if(layer instanceof L.Marker && layer.options.searchName !== undefined) { if(q === '' || layer.options.searchName.includes(q)) { layer.setOpacity(1); } else { layer.setOpacity(0.15); } } }); } // Filter Masjid if (window.appState && window.appState.layers && window.appState.layers.masjid) { window.appState.layers.masjid.eachLayer(function(layer) { if(layer instanceof L.Marker && layer.options.searchName !== undefined) { if(q === '' || layer.options.searchName.includes(q)) { layer.setOpacity(1); } else { layer.setOpacity(0.15); } } }); } // Filter Cards in Sidebar var sidebarBody = document.getElementById('sidebar-body'); if (sidebarBody) { var cards = sidebarBody.getElementsByClassName('card'); for (var i = 0; i < cards.length; i++) { var cName = cards[i].dataset.searchName || ''; if (q === '' || cName.includes(q)) { cards[i].style.display = 'block'; } else { cards[i].style.display = 'none'; } } } }; /* ── Extra Filters ── */ window.toggleFilterPerluBantuan = function(isActive) { if (window.appState && window.appState.layers && window.appState.layers.kemiskinan) { window.appState.layers.kemiskinan.eachLayer(function(layer) { if (layer instanceof L.Marker) { // If filter is active, only show markers that are NOT 'in-radius' (which use the greenIcon or have a specific property). // Wait, how do we know if it's in-radius? In loadData we assigned icon: window.greenIcon or window.redIcon. // An easy way is to check the icon URL or just rely on the DOM cards. But markers are drawn. // Let's check the layer.options.icon.options.iconUrl. var isRed = layer.options.icon && layer.options.icon.options && layer.options.icon.options.iconUrl && layer.options.icon.options.iconUrl.includes('red'); if (isActive) { if (isRed) layer.setOpacity(1); else layer.setOpacity(0); // Hide completely } else { layer.setOpacity(1); // Restore } } }); } // Filter Cards in Sidebar var sidebarBody = document.getElementById('sidebar-body'); if (sidebarBody) { var ObjectCards = sidebarBody.getElementsByClassName('card'); for (var i = 0; i < ObjectCards.length; i++) { var c = ObjectCards[i]; // The card has class 'out-radius' if it's red if (isActive) { if (c.classList.contains('out-radius')) { c.style.display = 'block'; } else { // Don't hide masjid cards! Check if it's kemiskinan if (!c.classList.contains('masjid-card')) { c.style.display = 'none'; } } } else { c.style.display = 'block'; } } } }; /* ── Export Data ── */ window.exportToCSV = async function() { Swal.fire({ title: 'Menyiapkan Laporan...', text: 'Mohon tunggu, sistem sedang merangkum data dari database.', allowOutsideClick: false, didOpen: () => { Swal.showLoading(); } }); try { var response = await fetch('php/export_csv.php'); if (!response.ok) throw new Error('Gagal menghubungi server.'); var blob = await response.blob(); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = "Data_Keluarga_Prasejahtera_GeoPoverty.csv"; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); Swal.fire({ icon: 'success', title: 'Unduhan Berhasil!', text: 'File laporan CSV telah tersimpan di komputermu.', timer: 2500, showConfirmButton: false }); } catch (e) { Swal.fire({ icon: 'error', title: 'Ekspor Gagal', text: 'Terjadi kesalahan: ' + e.message }); } }; /* ── Export Map PDF ── */ window.downloadMapPDF = async function() { Swal.fire({ title: 'Mencetak Peta...', text: 'Sistem sedang mengambil tangkapan layar peta. Mohon tunggu...', allowOutsideClick: false, didOpen: () => { Swal.showLoading(); } }); try { var mapElement = document.getElementById('map'); // Hide map controls (zoom, layers, dll) to make it clean var controls = document.querySelectorAll('.leaflet-control-container'); controls.forEach(c => c.style.display = 'none'); // Capture map as JPEG var rect = mapElement.getBoundingClientRect(); var imgData = await domtoimage.toJpeg(mapElement, { quality: 0.95, bgcolor: '#e2e8f0', width: rect.width, height: rect.height, style: { // Ensure no unexpected offset during render transform: 'none', bottom: '0', right: '0' } }); // Restore controls controls.forEach(c => c.style.display = ''); // Setup jsPDF (Landscape A4) var pdf = new jspdf.jsPDF('l', 'mm', 'a4'); var pdfWidth = pdf.internal.pageSize.getWidth(); var pdfHeight = pdf.internal.pageSize.getHeight(); var imgProps = pdf.getImageProperties(imgData); var ratio = imgProps.width / imgProps.height; var renderWidth = pdfWidth; var renderHeight = pdfWidth / ratio; if (renderHeight > pdfHeight) { renderHeight = pdfHeight; renderWidth = pdfHeight * ratio; } // Center image var x = (pdfWidth - renderWidth) / 2; var y = (pdfHeight - renderHeight) / 2; pdf.addImage(imgData, 'JPEG', x, y, renderWidth, renderHeight); pdf.save('Peta_GeoPoverty.pdf'); Swal.fire({ icon: 'success', title: 'Berhasil!', text: 'Peta murni telah berhasil diunduh sebagai PDF.', timer: 2500, showConfirmButton: false }); } catch(e) { var controls = document.querySelectorAll('.leaflet-control-container'); controls.forEach(c => c.style.display = ''); Swal.fire({ icon: 'error', title: 'Gagal Mencetak', text: 'Terjadi kesalahan saat mengekspor peta: ' + e.message }); } };