Add fitur Dashboard Analisis Penerima Bantuan

This commit is contained in:
2026-06-09 17:46:57 +07:00
parent 36a02dad66
commit ec4749b123
7 changed files with 673 additions and 20 deletions
+212 -14
View File
@@ -19,6 +19,11 @@ var drawnItems = new L.FeatureGroup().addTo(map);
var rumahIbadahLayer = L.layerGroup().addTo(map);
var radiusLayer = L.layerGroup().addTo(map);
var pendudukMiskinLayer = L.layerGroup().addTo(map);
var pendudukMiskinCluster = L.markerClusterGroup({
showCoverageOnHover: false,
zoomToBoundsOnClick: true,
maxClusterRadius: 50
}).addTo(map);
// Inisialisasi Heatmap kosong (akan diisi datanya saat load data)
var povertyHeatLayer = L.heatLayer([], { radius: 22, blur: 18, maxZoom: 17 }).addTo(map);
@@ -27,6 +32,7 @@ var povertyHeatLayer = L.heatLayer([], { radius: 22, blur: 18, maxZoom: 17 }).ad
var overlayMaps = {
"🕌 Rumah Ibadah": rumahIbadahLayer,
"🔥 Kepadatan Kemiskinan (Heatmap)": povertyHeatLayer,
"👥 Semua Penduduk Miskin": pendudukMiskinCluster,
"👥 Penduduk Terdekat (Analisis)": pendudukMiskinLayer
};
L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map);
@@ -148,6 +154,62 @@ function loadRumahIbadah() {
]);
povertyHeatLayer.setLatLngs(heatPoints);
// Marker Clustering untuk semua penduduk miskin
pendudukMiskinCluster.clearLayers();
dataPendudukMiskin.forEach(penduduk => {
var latPenduduk = parseFloat(penduduk.lat || penduduk.latitude);
var lngPenduduk = parseFloat(penduduk.lng || penduduk.longitude);
var statusBantuan = penduduk.bantuan || 'Belum Menerima';
var poorFamilyIcon = L.divIcon({
className: '',
html: `<div style="
width:14px; height:14px;
background: ${statusBantuan === 'Sudah Menerima' ? '#10b981' : '#ef4444'};
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 6px rgba(0,0,0,0.5);
"></div>`,
iconSize: [14, 14],
iconAnchor: [7, 7],
popupAnchor: [0, -7]
});
var actionButtons = '';
if (IS_ADMIN) {
actionButtons = `
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditPenduduk('${penduduk.id}', '${penduduk.nama_kk}', '${penduduk.alamat}', '${statusBantuan}', ${latPenduduk}, ${lngPenduduk})">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('penduduk_miskin', '${penduduk.id}')">🗑️ Hapus</button>
</div>
`;
}
var htmlIsi = `
<div class="popup-container" id="popup-penduduk-${penduduk.id}">
<div class="popup-header" style="background: linear-gradient(135deg, ${statusBantuan === 'Sudah Menerima' ? '#065f46, #10b981' : '#7f1d1d, #ef4444'});">
<h4>${penduduk.nama_kk}</h4>
<span class="popup-type-badge">👥 Data Sosial</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Alamat Lengkap</span>
<span class="info-value" style="font-size:11.5px; line-height:1.4;">${penduduk.alamat}</span>
</div>
<div class="info-row">
<span class="info-label">Status Bantuan</span>
<span class="info-value" style="color: ${statusBantuan === 'Sudah Menerima' ? '#34d399' : '#f87171'}; font-weight: 600;">${statusBantuan}</span>
</div>
${actionButtons}
</div>
</div>`;
var marker = L.marker([latPenduduk, lngPenduduk], { icon: poorFamilyIcon })
.bindPopup(htmlIsi);
pendudukMiskinCluster.addLayer(marker);
});
dataRumahIbadah.forEach(ibadah => {
var latIbadah = ibadah.lat || ibadah.latitude;
var lngIbadah = ibadah.lng || ibadah.longitude;
@@ -234,6 +296,11 @@ function updatePopupRadius(ibadahId, lat, lng) {
areaRadius.on('click', function (evt) { L.DomEvent.stopPropagation(evt); });
var totalKK = 0;
var sudahBantuan = 0;
var belumBantuan = 0;
var familiesInRadius = [];
// Saring dan tampilkan penduduk miskin yang berada dalam radius baru
databasePendudukMiskin.forEach(penduduk => {
var latPenduduk = penduduk.lat || penduduk.latitude;
@@ -242,12 +309,23 @@ function updatePopupRadius(ibadahId, lat, lng) {
var jarak = centerPoint.distanceTo(pendudukLatLng);
if (jarak <= newRadius) {
totalKK++;
var statusBantuan = penduduk.bantuan || 'Belum Menerima';
if (statusBantuan === 'Sudah Menerima') {
sudahBantuan++;
} else {
belumBantuan++;
}
familiesInRadius.push(penduduk);
var warnaDot = statusBantuan === 'Sudah Menerima' ? '#10b981' : '#ef4444';
var redDot = L.circleMarker(pendudukLatLng, {
radius: 6,
color: '#dc2626',
fillColor: '#ef4444',
radius: 7,
color: '#ffffff',
fillColor: warnaDot,
fillOpacity: 1,
weight: 2
weight: 1.5
}).addTo(pendudukMiskinLayer);
redDot.on('click', function (e) {
@@ -263,15 +341,18 @@ function updatePopupRadius(ibadahId, lat, lng) {
if (IS_ADMIN) {
actionButtons = `
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditPenduduk('${pendudukId}', '${namaKeluarga}', '${alamatLengkap}', ${latAngka}, ${lngAngka})">✏️ Edit</button>
<button class="btn btn-edit" onclick="formEditPenduduk('${penduid}', '${namaKeluarga}', '${alamatLengkap}', '${statusBantuan}', ${latAngka}, ${lngAngka})">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('penduduk_miskin', '${pendudukId}')">🗑️ Hapus</button>
</div>
`;
}
// Typo fix: penduid -> pendudukId
actionButtons = actionButtons.replace('\'${penduid}\'', `'${pendudukId}'`);
var htmlIsi = `
<div class="popup-container" id="popup-penduduk-${pendudukId}">
<div class="popup-header" style="background: linear-gradient(135deg, #7f1d1d, #ef4444);">
<div class="popup-header" style="background: linear-gradient(135deg, ${statusBantuan === 'Sudah Menerima' ? '#065f46, #10b981' : '#7f1d1d, #ef4444'});">
<h4>${namaKeluarga}</h4>
<span class="popup-type-badge">👥 Data Sosial</span>
</div>
@@ -280,6 +361,10 @@ function updatePopupRadius(ibadahId, lat, lng) {
<span class="info-label">Alamat Lengkap</span>
<span class="info-value" style="font-size:11.5px; line-height:1.4;">${alamatLengkap}</span>
</div>
<div class="info-row">
<span class="info-label">Status Bantuan</span>
<span class="info-value" style="color: ${statusBantuan === 'Sudah Menerima' ? '#34d399' : '#f87171'}; font-weight: 600;">${statusBantuan}</span>
</div>
${actionButtons}
</div>
</div>`;
@@ -288,12 +373,19 @@ function updatePopupRadius(ibadahId, lat, lng) {
});
}
});
// Cari nama rumah ibadah dari databasePencarian
var ibadahObj = databasePencarian.find(item => item.layer && item.layer._popup && item.layer._popup.getContent().includes('popup-ibadah-' + ibadahId));
var ibadahName = ibadahObj ? ibadahObj.namaAsli : "Rumah Ibadah";
showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius);
}
map.on('click', function () {
if (sidebarPickMode) return; // Ditangani oleh listener sidebar
radiusLayer.clearLayers();
pendudukMiskinLayer.clearLayers();
closeDashboard();
});
loadRumahIbadah();
@@ -509,7 +601,7 @@ function formEditRumahIbadah(id, nama, lat, lng) {
}, 50);
}
function formEditPenduduk(id, nama, alamat, lat, lng) {
function formEditPenduduk(id, nama, alamat, statusBantuan, lat, lng) {
setTimeout(function () {
document.getElementById(`popup-penduduk-${id}`).innerHTML = `
<div class="popup-header" style="background: linear-gradient(135deg, #7f1d1d, #ef4444); padding:14px 16px 12px; border-radius: 0;">
@@ -518,9 +610,12 @@ function formEditPenduduk(id, nama, alamat, lat, lng) {
<div class="popup-body" style="padding:14px 16px;">
<form onsubmit="updateData(event, 'penduduk_miskin', '${id}')">
<input type="text" id="edit_nama_penduduk" value="${nama}" required>
<textarea id="edit_alamat_penduduk" rows="2" required>${alamat}</textarea>
<textarea id="edit_alamat_penduduk" rows="3" required>${alamat}</textarea>
<select id="edit_status_bantuan" style="margin-bottom:12px; width:100%; padding:8px 10px; background-color:#0f172a; border:1px solid rgba(255,255,255,0.2); border-radius:6px; color:#f8fafc; font-size:13px; cursor:pointer;">
<option value="Belum Menerima" style="background-color:#1e293b; color:#f8fafc;" ${statusBantuan === 'Belum Menerima' ? 'selected' : ''}>Belum Menerima</option>
<option value="Sudah Menerima" style="background-color:#1e293b; color:#f8fafc;" ${statusBantuan === 'Sudah Menerima' ? 'selected' : ''}>Sudah Menerima</option>
</select>
<input type="hidden" id="edit_lat_penduduk" value="${lat}">
<input type="hidden" id="edit_lng_penduduk" value="${lng}">
@@ -549,6 +644,7 @@ function updateData(e, tabel, id) {
} else if (tabel === 'penduduk_miskin') {
formData.append('nama', document.getElementById('edit_nama_penduduk').value);
formData.append('alamat', document.getElementById('edit_alamat_penduduk').value);
formData.append('bantuan', document.getElementById('edit_status_bantuan').value);
formData.append('latitude', document.getElementById('edit_lat_penduduk').value);
formData.append('longitude', document.getElementById('edit_lng_penduduk').value);
}
@@ -818,14 +914,16 @@ function savePendudukSidebar() {
if (coords === 'Belum dipilih') { alert('Pilih lokasi di peta terlebih dahulu!'); return; }
var nama = document.getElementById('sb_nama_penduduk').value.trim();
var alamat = document.getElementById('sb_alamat_penduduk').value.trim(); // Ambil nilai alamat
var alamat = document.getElementById('sb_alamat_penduduk').value.trim();
var statusBantuan = document.getElementById('sb_status_bantuan').value;
if (!nama) { alert('Nama kepala keluarga wajib diisi!'); return; }
if (!alamat) { alert('Alamat wajib diisi!'); return; } // Validasi alamat
if (!alamat) { alert('Alamat wajib diisi!'); return; }
var formData = new FormData();
formData.append('nama', nama);
formData.append('alamat', alamat); // Masukkan ke dalam data form
formData.append('alamat', alamat);
formData.append('bantuan', statusBantuan);
formData.append('latitude', sidebarTempLat);
formData.append('longitude', sidebarTempLng);
@@ -833,7 +931,8 @@ function savePendudukSidebar() {
.then(r => r.text()).then(res => {
alert(res);
document.getElementById('sb_nama_penduduk').value = '';
document.getElementById('sb_alamat_penduduk').value = ''; // Hanya kosongkan value
document.getElementById('sb_alamat_penduduk').value = '';
document.getElementById('sb_status_bantuan').value = 'Belum Menerima';
document.getElementById('coords-penduduk-val').textContent = 'Belum dipilih';
document.getElementById('coords-penduduk-val').classList.remove('has-coords');
sidebarTempLat = 0; sidebarTempLng = 0;
@@ -882,4 +981,103 @@ document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('click', function () {
options.classList.remove('show');
});
});
});
// ==========================================
// 15. DYNAMIC STATS DASHBOARD SIDEBAR (CHART.JS)
// ==========================================
var myPovertyChart = null;
function showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius) {
document.getElementById('dashboard-target-name').textContent = ibadahName;
document.getElementById('stat-total-kk').textContent = totalKK;
document.getElementById('stat-sudah-bantuan').textContent = sudahBantuan;
document.getElementById('stat-belum-bantuan').textContent = belumBantuan;
// Tampilkan sidebar kanan
document.getElementById('dashboard-sidebar').classList.add('open');
// Buat daftar KK miskin dalam radius
var listContainer = document.getElementById('dashboard-families-list');
listContainer.innerHTML = '';
if (familiesInRadius.length === 0) {
listContainer.innerHTML = '<div class="families-empty">Tidak ada KK Miskin dalam radius ini</div>';
} else {
familiesInRadius.forEach(f => {
var item = document.createElement('div');
item.className = 'family-item';
var statusClass = f.bantuan === 'Sudah Menerima' ? 'badge-success' : 'badge-danger';
var statusText = f.bantuan === 'Sudah Menerima' ? 'Sudah' : 'Belum';
item.innerHTML = `
<div class="family-details">
<div class="family-name">${f.nama_kk}</div>
<div class="family-address">${f.alamat || 'Alamat tidak diisi'}</div>
</div>
<span class="family-badge ${statusClass}">${statusText}</span>
`;
// Klik untuk zoom/fly ke marker
item.onclick = function () {
var lat = parseFloat(f.lat || f.latitude);
var lng = parseFloat(f.lng || f.longitude);
map.flyTo([lat, lng], 18, { animate: true });
};
listContainer.appendChild(item);
});
}
// Gambar Chart.js
var ctx = document.getElementById('povertyChart').getContext('2d');
if (myPovertyChart) {
myPovertyChart.destroy();
}
var chartLabels = ['Sudah Menerima', 'Belum Menerima'];
var chartData = [sudahBantuan, belumBantuan];
var bgColors = ['#10b981', '#ef4444'];
if (totalKK === 0) {
chartLabels = ['Tidak Ada Data'];
chartData = [1];
bgColors = ['#475569'];
}
myPovertyChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: chartLabels,
datasets: [{
data: chartData,
backgroundColor: bgColors,
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
color: '#cbd5e1',
font: {
family: 'Plus Jakarta Sans',
size: 10
},
boxWidth: 12
}
}
},
cutout: '65%'
}
});
}
function closeDashboard() {
document.getElementById('dashboard-sidebar').classList.remove('open');
}