Files
2026-06-11 21:18:48 +07:00

1106 lines
47 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
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" ? "<i class='fa-solid fa-check'></i>" : type === "error" ? "<i class='fa-solid fa-xmark'></i>" : "<i class='fa-solid fa-circle-info'></i>";
toast.innerHTML = icon + " " + message;
container.appendChild(toast);
setTimeout(function () {
toast.classList.add("removing");
setTimeout(function () { toast.remove(); }, 300);
}, 3500);
};
/* ── Label helpers ── */
window.labelStatusKeluarga = {
aktif: '<i class="fa-solid fa-check"></i> Aktif', meninggal: '<i class="fa-solid fa-heart-crack"></i> Meninggal', pindah: '<i class="fa-solid fa-box-open"></i> Pindah', sudah_mampu: '<i class="fa-solid fa-arrow-trend-up"></i> 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: '<i class="fa-solid fa-check"></i> Aktif Membutuhkan Bantuan',
meninggal: '<i class="fa-solid fa-heart-crack"></i> Meninggal',
pindah: '<i class="fa-solid fa-box-open"></i> Pindah',
sudah_mampu: '<i class="fa-solid fa-arrow-trend-up"></i> 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 = '<i class="fa-solid fa-triangle-exclamation"></i> 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 = '<i class="fa-solid fa-pen"></i> 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 = '<div class="loading">Memuat laporan...</div>';
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 =
'<div class="l-head">' +
'<span class="l-cat">' + (window.labelKategoriLaporan[l.kategori] || l.kategori) + '</span>' +
'<span class="chip chip-' + { menunggu:'amber', diproses:'blue', selesai:'green', ditolak:'gray' }[l.status_laporan] + '">' +
(window.labelStatusLaporan[l.status_laporan] || l.status_laporan) + '</span>' +
'</div>' +
'<div class="l-desc">' + window.escapeHtml(l.deskripsi) + '</div>' +
'<div class="l-meta">👤 ' + window.escapeHtml(l.nama_pelapor) + ' · 🕐 ' + l.created_fmt + '</div>';
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 1005000 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 1005000 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 = '<span class="sec-hd-label">Filter Layer</span>';
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 = '<div class="loading">Memuat data...</div>';
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 = '<div class="empty">Terjadi kendala saat membaca data.<br>' + window.escapeHtml(err.message) + '</div>';
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 = '<span class="sec-hd-label">Keluarga Prasejahtera</span>' +
'<span class="sec-count">' + dataKem.length + '</span>';
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(
'<b>' + window.escapeHtml(item.nama_kepala_keluarga) + '</b><br>' +
'Status: ' + (window.labelStatusKeluarga[status] || status) + '<br>' +
'Anggota: ' + item.jumlah_kk + ' jiwa<br>' +
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 ? '<div class="card-ts"><i class="fa-regular fa-clock"></i> ' + item.updated_at_fmt + '</div>' : '';
card.innerHTML =
'<div class="card-name">' + window.escapeHtml(item.nama_kepala_keluarga) + '</div>' +
'<div class="card-sub">' + item.jumlah_kk + ' anggota keluarga</div>' +
'<div class="card-addr">' + window.escapeHtml(item.alamat) + '</div>' +
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 = '<i class="fa-solid fa-location-dot"></i>';
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 = '<i class="fa-solid fa-eye"></i> 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 = '<i class="fa-solid fa-triangle-exclamation"></i>';
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 = '<i class="fa-solid fa-pen"></i>';
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 = '<i class="fa-solid fa-trash"></i>';
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: '<i class="fa-solid fa-mosque"></i>', gereja: '<i class="fa-solid fa-church"></i>', vihara: '<i class="fa-solid fa-vihara"></i>', pura: '<i class="fa-solid fa-om"></i>',
klenteng: '<i class="fa-solid fa-vihara"></i>', kapel: '<i class="fa-solid fa-church"></i>', lainnya: '<i class="fa-solid fa-place-of-worship"></i>'
};
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 = '<span class="sec-hd-label">Rumah Ibadah</span>' +
'<span class="sec-count">' + dataMsj.length + '</span>';
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] || '<i class="fa-solid fa-place-of-worship"></i>';
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,
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#' +
({ masjid: 128332, gereja: 9962, vihara: 127983, pura: 128469, klenteng: 127982, kapel: 9769, lainnya: 127963 }[jenis] || 127963) +
';</text>'
);
var popup = '<b>' + emoji + ' ' + window.escapeHtml(item.nama_masjid) + '</b><br>' +
'Jenis: ' + label + '<br>' +
'PIC: ' + window.escapeHtml(item.pic_masjid) + '<br>' +
'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 =
'<div class="card-name">' + emoji + ' ' + window.escapeHtml(item.nama_masjid) + '</div>' +
'<div class="card-sub">PIC: ' + window.escapeHtml(item.pic_masjid) + '</div>' +
'<div class="card-addr">' + window.escapeHtml(item.alamat) + '</div>';
/* Radius row */
var canEdit = window.appAuth && window.appAuth.role === 'pengurus_masjid';
var rr = document.createElement('div');
rr.className = 'radius-row';
rr.innerHTML = '<label>Radius</label>';
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 = '<i class="fa-solid fa-location-dot"></i>';
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 = '<i class="fa-solid fa-pen"></i>';
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 = '<i class="fa-solid fa-trash"></i>';
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
});
}
};