Files

296 lines
13 KiB
PHP

<?php
// ================================================================
// Admin Dashboard
// ================================================================
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$pageTitle = 'Dashboard';
$extraHead = '<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>';
require_once __DIR__ . '/../includes/header.php';
$db = getDB();
// Statistik utama
$stats = [
'ibadah' => (int)$db->query("SELECT COUNT(*) FROM rumah_ibadah")->fetchColumn(),
'miskin' => (int)$db->query("SELECT COUNT(*) FROM penduduk_miskin")->fetchColumn(),
'sudah' => (int)$db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='sudah'")->fetchColumn(),
'belum' => (int)$db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='belum'")->fetchColumn(),
'menunggu' => (int)$db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='menunggu'")->fetchColumn(),
'darurat' => (int)$db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='darurat'")->fetchColumn(),
'anggota' => (int)$db->query("SELECT SUM(jumlah_anggota) FROM penduduk_miskin")->fetchColumn(),
'nominal' => (float)$db->query("SELECT COALESCE(SUM(nominal_bantuan),0) FROM penduduk_miskin WHERE status_bantuan IN ('sudah','darurat') AND nominal_bantuan IS NOT NULL")->fetchColumn(),
];
// Per jenis ibadah
$jenisStats = $db->query("SELECT jenis, COUNT(*) AS total FROM rumah_ibadah GROUP BY jenis ORDER BY total DESC")->fetchAll();
// Per kecamatan (top 7)
$kecamatanStats = $db->query("
SELECT kecamatan, COUNT(*) AS total,
SUM(CASE WHEN status_bantuan='sudah' THEN 1 ELSE 0 END) AS sudah,
SUM(CASE WHEN status_bantuan='belum' THEN 1 ELSE 0 END) AS belum
FROM penduduk_miskin WHERE kecamatan IS NOT NULL AND kecamatan != ''
GROUP BY kecamatan ORDER BY total DESC LIMIT 7
")->fetchAll();
// Data terbaru 6 baris
$recentMiskin = $db->query("
SELECT pm.id, pm.kk_nama, pm.jumlah_anggota, pm.status_bantuan,
pm.kecamatan, pm.created_at, ri.nama AS ibadah_nama
FROM penduduk_miskin pm
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
ORDER BY pm.created_at DESC LIMIT 6
")->fetchAll();
// Tren bulanan (6 bulan terakhir)
$trenBulan = $db->query("
SELECT DATE_FORMAT(created_at,'%Y-%m') AS bulan,
COUNT(*) AS total,
SUM(CASE WHEN status_bantuan='sudah' THEN 1 ELSE 0 END) AS sudah
FROM penduduk_miskin
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 6 MONTH)
GROUP BY bulan ORDER BY bulan ASC
")->fetchAll();
?>
<!-- ── Page Header ─────────────────────────────────────────── -->
<div class="page-header">
<div class="page-title-block">
<h1>📊 Dashboard</h1>
<div class="page-subtitle">Ringkasan data WebGIS Sebaran Penduduk Miskin · <?= date('d M Y, H:i') ?> WIB</div>
</div>
<div class="page-actions">
<a href="<?= BASE_URL ?>/admin/import.php" class="btn btn-ghost btn-sm">
<i class="bi bi-upload"></i> Import
</a>
<a href="<?= BASE_URL ?>/admin/export.php" class="btn btn-outline-accent btn-sm">
<i class="bi bi-download"></i> Export
</a>
<a href="<?= BASE_URL ?>/" target="_blank" class="btn btn-primary btn-sm">
<i class="bi bi-map-fill"></i> Buka Peta
</a>
</div>
</div>
<!-- ── Stat Cards ──────────────────────────────────────────── -->
<div class="stats-row">
<div class="stat-card blue">
<div class="stat-icon">🏛️</div>
<div class="stat-value"><?= $stats['ibadah'] ?></div>
<div class="stat-label">Rumah Ibadah</div>
</div>
<div class="stat-card yellow">
<div class="stat-icon">👥</div>
<div class="stat-value"><?= $stats['miskin'] ?></div>
<div class="stat-label">KK Terdaftar</div>
</div>
<div class="stat-card green">
<div class="stat-icon">✅</div>
<div class="stat-value"><?= $stats['sudah'] ?></div>
<div class="stat-label">Sudah Dibantu</div>
</div>
<div class="stat-card red">
<div class="stat-icon">❌</div>
<div class="stat-value"><?= $stats['belum'] ?></div>
<div class="stat-label">Belum Dibantu</div>
</div>
<div class="stat-card yellow" style="--yellow:#f59e0b">
<div class="stat-icon">⏳</div>
<div class="stat-value"><?= $stats['menunggu'] ?></div>
<div class="stat-label">Menunggu Verif.</div>
</div>
<div class="stat-card purple" style="--purple:#a855f7">
<div class="stat-icon">🚨</div>
<div class="stat-value"><?= $stats['darurat'] ?></div>
<div class="stat-label">Bantuan Darurat (Musibah)</div>
</div>
<div class="stat-card blue">
<div class="stat-icon">👤</div>
<div class="stat-value"><?= number_format($stats['anggota'], 0, ',', '.') ?></div>
<div class="stat-label">Total Jiwa</div>
</div>
<div class="stat-card green" style="grid-column: span 2; display:flex; flex-direction:row; justify-content:flex-start; align-items:center; gap:16px;">
<div class="stat-icon" style="margin-bottom:0; font-size:32px;">💰</div>
<div>
<div class="stat-value" style="font-size:20px; font-family:'JetBrains Mono',monospace;">Rp <?= number_format($stats['nominal'], 0, ',', '.') ?></div>
<div class="stat-label" style="text-align:left;">Total Bantuan Disalurkan</div>
</div>
</div>
</div>
<!-- ── Charts Row ──────────────────────────────────────────── -->
<div style="display:grid;grid-template-columns:1fr 1fr 1.2fr;gap:16px;margin-bottom:20px;">
<!-- Chart: Status Bantuan (Doughnut) -->
<div class="card">
<div class="card-header">
<div class="card-title">📊 Status Bantuan</div>
</div>
<div class="card-body" style="padding:14px;">
<div class="chart-container" style="height:200px;">
<canvas id="chart-status"></canvas>
</div>
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:10px;font-size:11px;">
<span style="color:#22c55e">● Sudah: <?= $stats['sudah'] ?></span>
<span style="color:#ef4444">● Belum: <?= $stats['belum'] ?></span>
<span style="color:#f59e0b">● Menunggu: <?= $stats['menunggu'] ?></span>
<span style="color:#a855f7">● Darurat: <?= $stats['darurat'] ?></span>
</div>
</div>
</div>
<!-- Chart: Per Jenis Ibadah (Pie) -->
<div class="card">
<div class="card-header">
<div class="card-title">🕌 Jenis Ibadah</div>
</div>
<div class="card-body" style="padding:14px;">
<div class="chart-container" style="height:200px;">
<canvas id="chart-jenis"></canvas>
</div>
</div>
</div>
<!-- Chart: Tren Bulanan (Line) -->
<div class="card">
<div class="card-header">
<div class="card-title">📈 Tren 6 Bulan</div>
</div>
<div class="card-body" style="padding:14px;">
<div class="chart-container" style="height:200px;">
<canvas id="chart-tren"></canvas>
</div>
</div>
</div>
</div>
<!-- ── Kecamatan + Recent ──────────────────────────────────── -->
<div style="display:grid;grid-template-columns:1fr 1.5fr;gap:16px;margin-bottom:20px;">
<!-- Per Kecamatan Bar -->
<div class="card">
<div class="card-header">
<div class="card-title">📍 Top Kecamatan</div>
</div>
<div class="card-body" style="padding:14px;">
<div class="chart-container" style="height:240px;">
<canvas id="chart-kecamatan"></canvas>
</div>
</div>
</div>
<!-- Data Terbaru -->
<div class="card">
<div class="card-header">
<div class="card-title">🕐 Data Terbaru</div>
<a href="<?= BASE_URL ?>/admin/penduduk_miskin.php" class="btn btn-ghost btn-sm">Lihat Semua</a>
</div>
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>Nama KK</th>
<th>Kecamatan</th>
<th>Status</th>
<th>Ibadah</th>
<th>Tanggal</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentMiskin as $r): ?>
<tr>
<td><a href="<?= BASE_URL ?>/admin/penduduk_miskin.php?edit=<?= $r['id'] ?>" style="color:var(--text);font-weight:600;text-decoration:none;"><?= htmlspecialchars($r['kk_nama']) ?></a></td>
<td style="color:var(--text2);font-size:12px;"><?= htmlspecialchars($r['kecamatan'] ?: '—') ?></td>
<td><span class="badge-status badge-<?= $r['status_bantuan'] ?>"><?= ucfirst($r['status_bantuan']) ?></span></td>
<td style="font-size:11px;color:var(--text2);"><?= htmlspecialchars($r['ibadah_nama'] ?: '—') ?></td>
<td style="font-size:11px;color:var(--text3);"><?= formatTanggal($r['created_at']) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($recentMiskin)): ?>
<tr><td colspan="5" style="text-align:center;color:var(--text3);padding:24px;">Belum ada data</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ── Chart.js Init ───────────────────────────────────────── -->
<script>
const cOpts = { responsive: true, maintainAspectRatio: false,
plugins: { legend: { labels: { color:'#8890b0', font:{ size:11 } } } } };
// Status Bantuan — Doughnut
new Chart(document.getElementById('chart-status').getContext('2d'), {
type: 'doughnut',
data: {
labels: ['Sudah Dibantu','Belum Dibantu','Menunggu','Bantuan Darurat'],
datasets: [{ data: [<?= $stats['sudah'] ?>,<?= $stats['belum'] ?>,<?= $stats['menunggu'] ?>,<?= $stats['darurat'] ?>],
backgroundColor: ['rgba(34,197,94,0.8)','rgba(239,68,68,0.8)','rgba(245,158,11,0.8)','rgba(168,85,247,0.8)'],
borderColor: ['#161b27'], borderWidth: 2 }]
},
options: { ...cOpts, cutout:'65%' }
});
// Jenis Ibadah — Pie
const jenisLabels = <?= json_encode(array_column($jenisStats, 'jenis')) ?>;
const jenisData = <?= json_encode(array_column($jenisStats, 'total')) ?>;
const jenisColors = ['rgba(91,127,255,.8)','rgba(124,91,255,.8)','rgba(34,197,94,.8)','rgba(245,158,11,.8)','rgba(239,68,68,.8)','rgba(96,165,250,.8)','rgba(251,191,36,.8)','rgba(139,92,246,.8)'];
new Chart(document.getElementById('chart-jenis').getContext('2d'), {
type: 'pie',
data: { labels: jenisLabels, datasets: [{ data: jenisData,
backgroundColor: jenisColors.slice(0, jenisLabels.length),
borderColor: '#161b27', borderWidth: 2 }] },
options: { ...cOpts }
});
// Tren Bulanan — Line
const trenLabels = <?= json_encode(array_column($trenBulan,'bulan')) ?>;
const trenTotal = <?= json_encode(array_column($trenBulan,'total')) ?>;
const trenSudah = <?= json_encode(array_column($trenBulan,'sudah')) ?>;
new Chart(document.getElementById('chart-tren').getContext('2d'), {
type: 'line',
data: {
labels: trenLabels,
datasets: [
{ label:'Total KK', data: trenTotal, borderColor:'#5b7fff', backgroundColor:'rgba(91,127,255,0.1)', tension:0.4, fill:true, pointRadius:4 },
{ label:'Dibantu', data: trenSudah, borderColor:'#22c55e', backgroundColor:'rgba(34,197,94,0.07)', tension:0.4, fill:true, pointRadius:4 }
]
},
options: { ...cOpts, scales: {
x: { ticks:{ color:'#565e80', font:{size:10} }, grid:{color:'rgba(255,255,255,0.04)'} },
y: { ticks:{ color:'#565e80', font:{size:10} }, grid:{color:'rgba(255,255,255,0.04)'} }
}}
});
// Kecamatan — Bar
const kecLabels = <?= json_encode(array_column($kecamatanStats,'kecamatan')) ?>;
const kecTotal = <?= json_encode(array_column($kecamatanStats,'total')) ?>;
const kecSudah = <?= json_encode(array_column($kecamatanStats,'sudah')) ?>;
const kecBelum = <?= json_encode(array_column($kecamatanStats,'belum')) ?>;
new Chart(document.getElementById('chart-kecamatan').getContext('2d'), {
type: 'bar',
data: {
labels: kecLabels,
datasets: [
{ label:'Sudah', data: kecSudah, backgroundColor:'rgba(34,197,94,0.75)', borderRadius:4 },
{ label:'Belum', data: kecBelum, backgroundColor:'rgba(239,68,68,0.7)', borderRadius:4 }
]
},
options: { ...cOpts, plugins:{...cOpts.plugins, legend:{...cOpts.plugins.legend}},
scales:{
x:{ ticks:{color:'#565e80',font:{size:10}}, grid:{color:'rgba(255,255,255,0.04)'}, stacked:true },
y:{ ticks:{color:'#565e80',font:{size:10}}, grid:{color:'rgba(255,255,255,0.04)'}, stacked:true }
}
}
});
</script>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>