/* ============================================================
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.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
});
}
};