Add fitur Dashboard Analisis Penerima Bantuan
This commit is contained in:
@@ -12,6 +12,8 @@ $isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
|
||||
<title>Sistem WebGIS Terpadu — Kota Pontianak</title>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap"
|
||||
rel="stylesheet">
|
||||
@@ -267,6 +269,13 @@ $isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
|
||||
<label class="sfield-label">Nama Kepala Keluarga <span class="required">*</span></label>
|
||||
<input type="text" id="sb_nama_penduduk" class="sinput" placeholder="Contoh: Siti Rahayu">
|
||||
</div>
|
||||
<div class="sfield">
|
||||
<label class="sfield-label">Status Bantuan <span class="required">*</span></label>
|
||||
<select id="sb_status_bantuan" class="sselect">
|
||||
<option value="Belum Menerima">Belum Menerima Bantuan</option>
|
||||
<option value="Sudah Menerima">Sudah Menerima Bantuan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn-save-sidebar btn-red-solid" onclick="savePendudukSidebar()">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||||
@@ -279,6 +288,65 @@ $isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
|
||||
</div><!-- end sidebar-body -->
|
||||
</div><!-- end input-sidebar -->
|
||||
|
||||
<!-- ==============================
|
||||
DASHBOARD STATISTIK (RIGHT SIDEBAR)
|
||||
============================== -->
|
||||
<div class="dashboard-sidebar" id="dashboard-sidebar">
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-title">
|
||||
<div class="dashboard-title-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11 3.055A9.003 9.003 0 1020.945 13H11V3.055z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dashboard-title-text">Dashboard Analisis</div>
|
||||
<div class="dashboard-title-sub" id="dashboard-target-name">Rumah Ibadah</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="dashboard-close" id="dashboard-close-btn" onclick="closeDashboard()" title="Tutup">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-body">
|
||||
<!-- Stats Summary Cards -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num" id="stat-total-kk">0</div>
|
||||
<div class="stat-label">Total KK Miskin</div>
|
||||
</div>
|
||||
<div class="stat-card stat-success">
|
||||
<div class="stat-num" id="stat-sudah-bantuan">0</div>
|
||||
<div class="stat-label">Sudah Menerima</div>
|
||||
</div>
|
||||
<div class="stat-card stat-danger">
|
||||
<div class="stat-num" id="stat-belum-bantuan">0</div>
|
||||
<div class="stat-label">Belum Menerima</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<div class="chart-section">
|
||||
<div class="chart-title">Distribusi Penerima Bantuan</div>
|
||||
<div class="chart-container" style="position: relative; height: 180px; width: 100%;">
|
||||
<canvas id="povertyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List of Families inside Radius -->
|
||||
<div class="families-section">
|
||||
<div class="families-title">Daftar KK Miskin Terdekat</div>
|
||||
<div class="families-list" id="dashboard-families-list">
|
||||
<!-- Dynamic List Items -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overlay saat mode pilih lokasi aktif -->
|
||||
<div class="pick-mode-overlay" id="pick-overlay">
|
||||
<div class="pick-mode-banner">
|
||||
@@ -294,7 +362,9 @@ $isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
|
||||
<div id="map" style="padding-top: 56px; height: 100vh; box-sizing: border-box;"></div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
// Variabel global untuk mendeteksi status admin di script.js
|
||||
|
||||
@@ -15,10 +15,11 @@ $nama = $_POST['nama'];
|
||||
$alamat = $_POST['alamat'];
|
||||
$latitude = $_POST['latitude'];
|
||||
$longitude = $_POST['longitude'];
|
||||
$bantuan = isset($_POST['bantuan']) ? $_POST['bantuan'] : 'Belum Menerima';
|
||||
|
||||
// Gunakan Prepared Statement untuk mencegah SQL Injection
|
||||
$query = $conn->prepare("INSERT INTO penduduk_miskin (nama_kk, alamat, lat, lng) VALUES (?, ?, ?, ?)");
|
||||
$query->bind_param("ssdd", $nama, $alamat, $latitude, $longitude);
|
||||
$query = $conn->prepare("INSERT INTO penduduk_miskin (nama_kk, alamat, lat, lng, bantuan) VALUES (?, ?, ?, ?, ?)");
|
||||
$query->bind_param("ssdds", $nama, $alamat, $latitude, $longitude, $bantuan);
|
||||
|
||||
// Eksekusi dan kirim respon ke Javascript
|
||||
if ($query->execute()) {
|
||||
|
||||
+212
-14
@@ -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');
|
||||
}
|
||||
@@ -2004,4 +2004,343 @@ body {
|
||||
.logout-link:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
POPUP CLOSE BUTTON OVERRIDES
|
||||
========================================== */
|
||||
.leaflet-popup-close-button {
|
||||
position: absolute !important;
|
||||
top: 12px !important;
|
||||
right: 12px !important;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
line-height: 20px !important;
|
||||
text-align: center !important;
|
||||
color: rgba(255, 255, 255, 0.75) !important;
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-radius: 50% !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: normal !important;
|
||||
z-index: 1000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button:hover {
|
||||
color: #ffffff !important;
|
||||
background: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
DASHBOARD STATISTIK (RIGHT SIDEBAR)
|
||||
========================================== */
|
||||
.dashboard-sidebar {
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
right: -340px;
|
||||
width: 330px;
|
||||
height: calc(100vh - 56px);
|
||||
background: rgba(13, 20, 36, 0.97);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.08);
|
||||
z-index: 1040;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: right 0.32s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: -4px 0 32px rgba(0, 0, 0, 0.4);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.dashboard-sidebar.open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-title-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboard-title-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dashboard-title-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dashboard-title-sub {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
margin-top: 1px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.dashboard-close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 7px;
|
||||
color: #94a3b8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboard-close:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dashboard-close svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.dashboard-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Stats Grid cards */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 8px;
|
||||
padding: 10px 6px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #e2e8f0;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 9px;
|
||||
color: #64748b;
|
||||
margin-top: 4px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.stat-card.stat-success {
|
||||
border-color: rgba(16, 185, 129, 0.2);
|
||||
background: rgba(16, 185, 129, 0.03);
|
||||
}
|
||||
.stat-card.stat-success .stat-num {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.stat-card.stat-danger {
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
background: rgba(239, 68, 68, 0.03);
|
||||
}
|
||||
.stat-card.stat-danger .stat-num {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* Chart Section */
|
||||
.chart-section {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Families List Section */
|
||||
.families-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.families-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.families-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
max-height: 250px;
|
||||
}
|
||||
|
||||
.family-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.family-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.family-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.family-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.family-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #f1f5f9;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.family-address {
|
||||
font-size: 10px;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.family-badge {
|
||||
font-size: 8.5px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.family-badge.badge-success {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
.family-badge.badge-danger {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.families-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
MARKER CLUSTER CUSTOM STYLING
|
||||
========================================== */
|
||||
.marker-cluster-small {
|
||||
background-color: rgba(59, 130, 246, 0.4) !important;
|
||||
}
|
||||
.marker-cluster-small div {
|
||||
background-color: rgba(37, 99, 235, 0.95) !important;
|
||||
color: white !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.marker-cluster-medium {
|
||||
background-color: rgba(37, 99, 235, 0.4) !important;
|
||||
}
|
||||
.marker-cluster-medium div {
|
||||
background-color: rgba(29, 78, 216, 0.95) !important;
|
||||
color: white !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.marker-cluster-large {
|
||||
background-color: rgba(30, 58, 138, 0.4) !important;
|
||||
}
|
||||
.marker-cluster-large div {
|
||||
background-color: rgba(30, 64, 175, 0.95) !important;
|
||||
color: white !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.marker-cluster div {
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
box-shadow: 0 0 10px rgba(37, 99, 235, 0.5) !important;
|
||||
}
|
||||
@@ -27,8 +27,9 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$alamat = $_POST['alamat'];
|
||||
$lat = $_POST['latitude'];
|
||||
$lng = $_POST['longitude'];
|
||||
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_kk=?, alamat=?, lat=?, lng=? WHERE id=?");
|
||||
$stmt->bind_param("ssddi", $nama, $alamat, $lat, $lng, $id);
|
||||
$bantuan = isset($_POST['bantuan']) ? $_POST['bantuan'] : 'Belum Menerima';
|
||||
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_kk=?, alamat=?, lat=?, lng=?, bantuan=? WHERE id=?");
|
||||
$stmt->bind_param("ssddsi", $nama, $alamat, $lat, $lng, $bantuan, $id);
|
||||
}
|
||||
|
||||
if ($stmt) {
|
||||
|
||||
+23
-1
@@ -860,8 +860,30 @@ body {
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button {
|
||||
position: absolute !important;
|
||||
top: 12px !important;
|
||||
right: 12px !important;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
line-height: 20px !important;
|
||||
text-align: center !important;
|
||||
color: rgba(255, 255, 255, 0.75) !important;
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-radius: 50% !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: normal !important;
|
||||
z-index: 1000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button:hover {
|
||||
color: #ffffff !important;
|
||||
padding: 12px !important;
|
||||
background: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
|
||||
+23
-1
@@ -842,8 +842,30 @@ body {
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button {
|
||||
position: absolute !important;
|
||||
top: 12px !important;
|
||||
right: 12px !important;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
line-height: 20px !important;
|
||||
text-align: center !important;
|
||||
color: rgba(255, 255, 255, 0.75) !important;
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-radius: 50% !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: normal !important;
|
||||
z-index: 1000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button:hover {
|
||||
color: #ffffff !important;
|
||||
padding: 12px !important;
|
||||
background: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
|
||||
Reference in New Issue
Block a user