Initial commit
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
||||
<div>
|
||||
<h2 style="font-size:1.2rem;font-weight:700">📦 Monitoring Bantuan</h2>
|
||||
<p class="text-muted text-sm"><?= count($data) ?> penyaluran pada bulan <?= e($bulan) ?></p>
|
||||
</div>
|
||||
<form method="GET" style="display:flex;gap:8px;align-items:center">
|
||||
<label class="text-sm">Bulan:</label>
|
||||
<input type="month" name="bulan" class="form-control" value="<?= e($bulan) ?>" onchange="this.form.submit()">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Stats Bulan Ini -->
|
||||
<div class="stats-grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:20px">
|
||||
<div class="stat-card"><div class="stat-card-icon green">📦</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= count($data) ?></div><div class="stat-card-label">Total Penyaluran</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon blue">👥</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= count(array_unique(array_column($data,'id_warga'))) ?></div><div class="stat-card-label">Warga Terbantu</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon orange">⛪</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= count(array_unique(array_column($data,'id_rumah_ibadah'))) ?></div><div class="stat-card-label">Rumah Ibadah Aktif</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon purple">📊</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= count($perKategori) ?></div><div class="stat-card-label">Jenis Bantuan</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart + Tabel -->
|
||||
<div style="display:grid;grid-template-columns:1fr 280px;gap:20px;margin-bottom:20px">
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📋</span><h3>Daftar Penyaluran Bantuan</h3></div>
|
||||
<div class="card-body no-pad">
|
||||
<div class="filters-bar" style="margin:0;border:none;border-bottom:1px solid var(--clr-border);border-radius:0">
|
||||
<input type="text" id="search-bantuan" class="form-control" placeholder="🔍 Cari nama warga..." style="max-width:280px">
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="table-bantuan">
|
||||
<thead>
|
||||
<tr><th>Kode</th><th>Warga</th><th>Kategori</th><th>Jumlah</th><th>Rumah Ibadah</th><th>Wilayah</th><th>Tanggal</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($data as $b): ?>
|
||||
<tr>
|
||||
<td><code style="font-size:0.72rem;background:#f1f5f9;padding:1px 5px;border-radius:3px"><?= e($b['kode_bantuan']) ?></code></td>
|
||||
<td style="font-weight:600;font-size:0.875rem"><?= e($b['warga']) ?></td>
|
||||
<td><span class="badge badge-green"><?= e($b['kategori']) ?></span></td>
|
||||
<td class="text-sm">
|
||||
<?= $b['jumlah'] ? ($b['satuan']==='rupiah' ? formatRupiah($b['jumlah']) : number_format($b['jumlah']).' '.$b['satuan']) : '—' ?>
|
||||
</td>
|
||||
<td class="text-sm"><?= e($b['rumah_ibadah']) ?></td>
|
||||
<td class="text-xs text-muted"><?= e($b['kecamatan']) ?></td>
|
||||
<td class="text-xs text-muted"><?= formatTanggal($b['tanggal_penyaluran']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if(empty($data)): ?>
|
||||
<div class="empty-state"><div class="empty-state-icon">📭</div><h3>Tidak ada penyaluran</h3><p>Belum ada bantuan yang disalurkan pada periode ini</p></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Donut per Kategori -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">🥧</span><h3>Per Kategori</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="chart-wrap" style="height:200px"><canvas id="chart-kategori"></canvas></div>
|
||||
<div style="margin-top:12px">
|
||||
<?php foreach($perKategori as $k): ?>
|
||||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid #f1f5f9;font-size:0.8rem">
|
||||
<span><?= e($k['nama']) ?></span>
|
||||
<strong><?= number_format($k['jumlah']) ?></strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
App.initTableSearch('search-bantuan','table-bantuan');
|
||||
Charts.donut('chart-kategori',
|
||||
<?= json_encode(array_column($perKategori,'nama')) ?>,
|
||||
<?= json_encode(array_column($perKategori,'jumlah')) ?>
|
||||
);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
$formatJarak = function ($meter): string {
|
||||
if ($meter === null || $meter === '') return '-';
|
||||
$meter = (float)$meter;
|
||||
if ($meter >= 1000) {
|
||||
return number_format($meter / 1000, 1, ',', '.') . ' km';
|
||||
}
|
||||
return number_format($meter, 0, ',', '.') . ' m';
|
||||
};
|
||||
|
||||
$jsonFlags = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
||||
$totalBlind = count($data);
|
||||
$sudahDitugaskan = (int)($ringkasanPenugasan['sudah'] ?? 0);
|
||||
$belumDitugaskan = (int)($ringkasanPenugasan['belum'] ?? 0);
|
||||
$totalRumahIbadah = (int)($ringkasanPenugasan['total_ri'] ?? 0);
|
||||
?>
|
||||
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:20px">
|
||||
<div>
|
||||
<h2 style="font-size:1.2rem;font-weight:700">Monitoring Blind Spot</h2>
|
||||
<p class="text-muted text-sm">Atur penugasan rumah ibadah untuk warga yang berada di luar radius pelayanan.</p>
|
||||
</div>
|
||||
<?php if (!empty($perKec)): ?>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end">
|
||||
<?php foreach ($perKec as $k): ?>
|
||||
<div style="text-align:center;padding:8px 14px;background:#f3e8ff;border-radius:8px;border:1px solid #d8b4fe;min-width:110px">
|
||||
<div style="font-size:1.05rem;font-weight:800;color:#7c3aed"><?= number_format((int)$k['jumlah']) ?></div>
|
||||
<div style="font-size:0.7rem;color:#6d28d9"><?= e($k['nama']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid" style="grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin-bottom:20px">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon purple">BS</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($totalBlind) ?></div>
|
||||
<div class="stat-card-label">Total blind spot</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon red">!</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($belumDitugaskan) ?></div>
|
||||
<div class="stat-card-label">Belum ditugaskan</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon green">OK</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($sudahDitugaskan) ?></div>
|
||||
<div class="stat-card-label">Sudah ditugaskan</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon blue">RI</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($totalRumahIbadah) ?></div>
|
||||
<div class="stat-card-label">Rumah ibadah aktif</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom:20px">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">MAP</span>
|
||||
<h3>Peta Blind Spot dan Rumah Ibadah</h3>
|
||||
</div>
|
||||
<div class="card-body no-pad">
|
||||
<div id="blind-map" style="height:360px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">BS</span>
|
||||
<h3>Daftar Warga Blind Spot (<?= number_format($totalBlind) ?>)</h3>
|
||||
<div class="card-actions">
|
||||
<input type="text" id="search-blind" class="form-control" placeholder="Cari warga atau rumah ibadah..." style="width:260px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body no-pad">
|
||||
<div class="table-wrap">
|
||||
<table id="table-blind">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Warga</th>
|
||||
<th>Lokasi</th>
|
||||
<th>Prioritas</th>
|
||||
<th>Rekomendasi Terdekat</th>
|
||||
<th>Penugasan</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($data as $i => $w): ?>
|
||||
<?php
|
||||
$rowRekomendasi = $rekomendasi[(int)$w['id']] ?? [];
|
||||
$top = $rowRekomendasi[0] ?? null;
|
||||
$currentRiId = (int)($w['id_rumah_ibadah'] ?? 0);
|
||||
$isTopAssigned = $top && $currentRiId === (int)$top['id'];
|
||||
$modalPayload = [
|
||||
'id' => (int)$w['id'],
|
||||
'nama' => $w['nama'],
|
||||
'alamat' => $w['alamat'] ?? '',
|
||||
'lokasi' => trim(($w['kecamatan'] ?? '') . ' / ' . ($w['kelurahan'] ?? ''), ' /'),
|
||||
'current_ri_id' => $currentRiId
|
||||
];
|
||||
?>
|
||||
<tr id="warga-row-<?= (int)$w['id'] ?>">
|
||||
<td class="text-xs text-muted"><?= $i + 1 ?></td>
|
||||
<td>
|
||||
<div style="font-weight:700"><?= e($w['nama']) ?></div>
|
||||
<div class="text-xs text-muted" style="max-width:220px"><?= e($w['alamat'] ?? '') ?></div>
|
||||
<div class="text-xs text-muted" style="font-family:var(--font-mono);margin-top:3px">
|
||||
<?= e((string)$w['lat']) ?>, <?= e((string)$w['lng']) ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-sm">
|
||||
<div><?= e($w['kecamatan'] ?? '') ?></div>
|
||||
<div class="text-xs text-muted"><?= e($w['kelurahan'] ?? '') ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php $sc = ['Miskin Ekstrem' => 'badge-red', 'Miskin' => 'badge-orange', 'Rentan Miskin' => 'badge-yellow']; ?>
|
||||
<?php $pc = ['Tinggi' => 'badge-red', 'Sedang' => 'badge-yellow', 'Rendah' => 'badge-green']; ?>
|
||||
<div style="display:flex;flex-direction:column;gap:5px;align-items:flex-start">
|
||||
<span class="badge <?= $sc[$w['status_kemiskinan']] ?? 'badge-gray' ?>"><?= e($w['status_kemiskinan']) ?></span>
|
||||
<span class="badge <?= $pc[$w['level_prioritas']] ?? 'badge-gray' ?>">Prioritas <?= e($w['level_prioritas']) ?></span>
|
||||
<span class="text-xs text-muted">Skor <?= number_format((int)$w['skor_prioritas']) ?></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($top): ?>
|
||||
<div style="min-width:260px">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<strong><?= e($top['nama']) ?></strong>
|
||||
<span class="badge badge-blue"><?= e($formatJarak($top['jarak_meter'])) ?></span>
|
||||
</div>
|
||||
<div class="text-xs text-muted" style="margin-top:3px">
|
||||
<?= e($top['jenis']) ?> · <?= e($top['kelurahan']) ?>, <?= e($top['kecamatan']) ?>
|
||||
</div>
|
||||
<div class="text-xs text-muted" style="margin-top:3px">
|
||||
Radius <?= e($formatJarak($top['radius_meter'])) ?> · <?= number_format((int)$top['jml_warga']) ?> warga binaan
|
||||
</div>
|
||||
<?php if (count($rowRekomendasi) > 1): ?>
|
||||
<div class="text-xs text-muted" style="margin-top:6px">
|
||||
Alternatif:
|
||||
<?php foreach (array_slice($rowRekomendasi, 1) as $alt): ?>
|
||||
<span><?= e($alt['nama']) ?> (<?= e($formatJarak($alt['jarak_meter'])) ?>)</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-sm text-muted">Koordinat warga atau rumah ibadah belum lengkap.</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($w['assigned_rumah_ibadah'])): ?>
|
||||
<div style="min-width:210px">
|
||||
<span class="badge badge-green">Sudah ditugaskan</span>
|
||||
<div style="font-weight:700;margin-top:6px"><?= e($w['assigned_rumah_ibadah']) ?></div>
|
||||
<div class="text-xs text-muted"><?= e($w['assigned_jenis'] ?? 'Rumah Ibadah') ?></div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="min-width:180px">
|
||||
<span class="badge badge-red">Belum ditugaskan</span>
|
||||
<div class="text-xs text-muted" style="margin-top:6px">Gunakan rekomendasi atau pilih manual.</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;flex-direction:column;gap:8px;min-width:175px">
|
||||
<?php if ($top && !$isTopAssigned): ?>
|
||||
<form method="POST" action="<?= APP_URL ?>/dinsos/blind-spot/<?= (int)$w['id'] ?>/assign" onsubmit="return confirm('Gunakan rekomendasi rumah ibadah terdekat untuk warga ini?')">
|
||||
<?= csrfField() ?>
|
||||
<input type="hidden" name="id_rumah_ibadah" value="<?= (int)$top['id'] ?>">
|
||||
<button type="submit" class="btn btn-success btn-sm" style="width:100%;justify-content:center">Gunakan rekomendasi</button>
|
||||
</form>
|
||||
<?php elseif ($top): ?>
|
||||
<span class="badge badge-green" style="justify-content:center;padding:6px 10px">Rekomendasi aktif</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
style="width:100%;justify-content:center"
|
||||
onclick='openAssignModal(<?= json_encode($modalPayload, $jsonFlags) ?>)'
|
||||
<?= empty($rumahIbadah) ? 'disabled' : '' ?>>
|
||||
Pilih manual
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (empty($data)): ?>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">OK</div>
|
||||
<h3>Tidak Ada Blind Spot</h3>
|
||||
<p>Semua warga sudah berada dalam radius pelayanan rumah ibadah.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="modal-assign">
|
||||
<div class="modal" style="max-width:620px">
|
||||
<div class="modal-header">
|
||||
<h3>Atur Penugasan Blind Spot</h3>
|
||||
<button class="modal-close" onclick="App.hideModal('modal-assign')">×</button>
|
||||
</div>
|
||||
<form method="POST" id="assign-form">
|
||||
<?= csrfField() ?>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Warga</label>
|
||||
<div style="padding:12px 14px;border:1px solid var(--clr-border);border-radius:var(--radius-sm);background:#f8fafc">
|
||||
<div id="assign-warga-name" style="font-weight:700"></div>
|
||||
<div id="assign-warga-location" class="text-xs text-muted" style="margin-top:3px"></div>
|
||||
<div id="assign-warga-address" class="text-xs text-muted" style="margin-top:3px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rekomendasi terdekat</label>
|
||||
<div id="assign-recommendations" style="display:flex;flex-direction:column;gap:8px"></div>
|
||||
<div class="form-hint">Klik salah satu rekomendasi untuk mengisi pilihan manual di bawah.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Pilih rumah ibadah <span class="required">*</span></label>
|
||||
<select name="id_rumah_ibadah" id="assign-ri" class="form-control" required>
|
||||
<option value="">-- Pilih rumah ibadah --</option>
|
||||
<?php foreach ($rumahIbadah as $ri): ?>
|
||||
<option value="<?= (int)$ri['id'] ?>">
|
||||
<?= e($ri['nama']) ?> - <?= e($ri['jenis']) ?>, <?= e($ri['kelurahan']) ?> (<?= number_format((int)$ri['jml_warga']) ?> warga)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-hint">Pilihan manual akan mengganti penugasan sebelumnya jika warga sudah pernah ditugaskan.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ghost" onclick="App.hideModal('modal-assign')">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Simpan Penugasan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const recommendationsByWarga = <?= json_encode($rekomendasi, $jsonFlags) ?>;
|
||||
const blindSpotAppUrl = <?= json_encode(APP_URL, $jsonFlags) ?>;
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '').replace(/[&<>"']/g, ch => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[ch]));
|
||||
}
|
||||
|
||||
function formatJarakClient(meter) {
|
||||
const value = Number(meter || 0);
|
||||
if (!value) return '-';
|
||||
if (value >= 1000) return (value / 1000).toLocaleString('id-ID', { maximumFractionDigits: 1 }) + ' km';
|
||||
return value.toLocaleString('id-ID') + ' m';
|
||||
}
|
||||
|
||||
function openAssignModal(warga) {
|
||||
const form = document.getElementById('assign-form');
|
||||
const select = document.getElementById('assign-ri');
|
||||
const list = document.getElementById('assign-recommendations');
|
||||
|
||||
form.action = `${blindSpotAppUrl}/dinsos/blind-spot/${warga.id}/assign`;
|
||||
document.getElementById('assign-warga-name').textContent = warga.nama || '-';
|
||||
document.getElementById('assign-warga-location').textContent = warga.lokasi || '-';
|
||||
document.getElementById('assign-warga-address').textContent = warga.alamat || '-';
|
||||
|
||||
const rekomendasi = recommendationsByWarga[warga.id] || [];
|
||||
select.value = warga.current_ri_id ? String(warga.current_ri_id) : '';
|
||||
if (!select.value && rekomendasi[0]) {
|
||||
select.value = String(rekomendasi[0].id);
|
||||
}
|
||||
|
||||
list.innerHTML = '';
|
||||
if (!rekomendasi.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'text-sm text-muted';
|
||||
empty.textContent = 'Belum ada rekomendasi karena koordinat belum lengkap.';
|
||||
list.appendChild(empty);
|
||||
} else {
|
||||
rekomendasi.forEach((ri, index) => {
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = 'btn btn-ghost btn-sm';
|
||||
item.style.justifyContent = 'space-between';
|
||||
item.style.width = '100%';
|
||||
item.innerHTML = `<span>${index + 1}. ${escapeHtml(ri.nama)}</span><span>${formatJarakClient(ri.jarak_meter)}</span>`;
|
||||
item.addEventListener('click', () => {
|
||||
select.value = String(ri.id);
|
||||
});
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
App.showModal('modal-assign');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
App.initTableSearch('search-blind', 'table-blind');
|
||||
|
||||
const map = GISMap.init('blind-map', <?= MAP_DEFAULT_LAT ?>, <?= MAP_DEFAULT_LNG ?>, 12);
|
||||
const riData = await App.fetchJSON('<?= APP_URL ?>/api/rumah-ibadah');
|
||||
if (riData) GISMap.addRILayer(riData);
|
||||
|
||||
const blindData = <?= json_encode(array_map(function ($w) use ($rekomendasi) {
|
||||
$top = $rekomendasi[(int)$w['id']][0] ?? null;
|
||||
return [
|
||||
'id' => (int)$w['id'],
|
||||
'lat' => (float)$w['lat'],
|
||||
'lng' => (float)$w['lng'],
|
||||
'nama' => $w['nama'],
|
||||
'status_kemiskinan' => $w['status_kemiskinan'],
|
||||
'level_prioritas' => $w['level_prioritas'],
|
||||
'assigned' => $w['assigned_rumah_ibadah'] ?? '',
|
||||
'rekomendasi' => $top ? [
|
||||
'nama' => $top['nama'],
|
||||
'lat' => (float)$top['lat'],
|
||||
'lng' => (float)$top['lng'],
|
||||
'jarak_meter' => (int)$top['jarak_meter']
|
||||
] : null
|
||||
];
|
||||
}, array_filter($data, fn($w) => !empty($w['lat']) && !empty($w['lng']))), $jsonFlags) ?>;
|
||||
|
||||
blindData.forEach(w => {
|
||||
const marker = L.circleMarker([w.lat, w.lng], {
|
||||
radius: 10,
|
||||
fillColor: '#7c3aed',
|
||||
color: '#fff',
|
||||
weight: 2,
|
||||
fillOpacity: 0.9
|
||||
});
|
||||
|
||||
const popup = `
|
||||
<div style="min-width:210px;font-family:Inter,system-ui,sans-serif">
|
||||
<strong>Blind Spot</strong><br>
|
||||
${escapeHtml(w.nama)}<br>
|
||||
<span style="font-size:11px;color:#6b7280">${escapeHtml(w.status_kemiskinan)} - Prioritas ${escapeHtml(w.level_prioritas)}</span>
|
||||
${w.assigned ? `<br><span style="font-size:11px;color:#15803d;font-weight:700">Ditugaskan: ${escapeHtml(w.assigned)}</span>` : '<br><span style="font-size:11px;color:#dc2626;font-weight:700">Belum ditugaskan</span>'}
|
||||
${w.rekomendasi ? `<br><span style="font-size:11px;color:#0284c7">Rekomendasi: ${escapeHtml(w.rekomendasi.nama)} (${formatJarakClient(w.rekomendasi.jarak_meter)})</span>` : ''}
|
||||
</div>`;
|
||||
|
||||
marker.bindPopup(popup).addTo(map);
|
||||
GISMap._allMarkerBounds.push([w.lat, w.lng]);
|
||||
|
||||
if (w.rekomendasi && w.rekomendasi.lat && w.rekomendasi.lng) {
|
||||
L.polyline([[w.lat, w.lng], [w.rekomendasi.lat, w.rekomendasi.lng]], {
|
||||
color: '#0284c7',
|
||||
weight: 2,
|
||||
opacity: 0.5,
|
||||
dashArray: '5,7'
|
||||
}).addTo(map);
|
||||
}
|
||||
});
|
||||
|
||||
GISMap.autoFitBounds();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// Ekstrak semua data sebelum heredoc agar tidak terjadi Array to string conversion
|
||||
$mapLat = MAP_DEFAULT_LAT;
|
||||
$mapLng = MAP_DEFAULT_LNG;
|
||||
$appUrl = APP_URL;
|
||||
|
||||
$jsonKec = json_encode(array_column($perKecamatan ?? [], 'nama'));
|
||||
$jsonTotals = json_encode(array_column($perKecamatan ?? [], 'total'));
|
||||
$jsonEkstrems = json_encode(array_column($perKecamatan ?? [], 'ekstrem'));
|
||||
|
||||
$jsonBulan = json_encode(array_column($trenBantuan ?? [], 'bulan'));
|
||||
$jsonJmlBantuan = json_encode(array_column($trenBantuan ?? [], 'jumlah'));
|
||||
$jsonWargaTerbantu = json_encode(array_column($trenBantuan ?? [], 'warga_terbantu'));
|
||||
|
||||
$extraJs = <<<JS
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Peta dengan heatmap
|
||||
const map = GISMap.init('dinsos-map', {$mapLat}, {$mapLng}, 12);
|
||||
|
||||
// Load semua data
|
||||
const [heatData, petaData] = await Promise.all([
|
||||
App.fetchJSON('{$appUrl}/dinsos/api/heatmap'),
|
||||
App.fetchJSON('{$appUrl}/admin/api/peta')
|
||||
]);
|
||||
|
||||
if(petaData) {
|
||||
const riLayer = GISMap.addRILayer(petaData.rumah_ibadah||[]);
|
||||
const wLayer = GISMap.addWargaLayer(petaData.warga||[]);
|
||||
L.control.layers(GISMap.layers, {
|
||||
'⛪ Rumah Ibadah': riLayer,
|
||||
'👥 Warga Miskin': wLayer
|
||||
}, {position:'topright', collapsed:true}).addTo(map);
|
||||
}
|
||||
if(heatData) GISMap.addHeatmap(heatData);
|
||||
// Auto zoom ke seluruh Kota Pontianak
|
||||
GISMap.autoFitBounds();
|
||||
|
||||
// Chart per kecamatan
|
||||
const kec = {$jsonKec};
|
||||
const totals = {$jsonTotals};
|
||||
const ekstrems = {$jsonEkstrems};
|
||||
Charts.bar('chart-kecamatan', kec, [
|
||||
{label:'Total Warga', data:totals},
|
||||
{label:'Miskin Ekstrem', data:ekstrems}
|
||||
]);
|
||||
|
||||
// Chart tren bantuan
|
||||
const bulan = {$jsonBulan};
|
||||
const jmlBantuan = {$jsonJmlBantuan};
|
||||
const wargaTerbantu = {$jsonWargaTerbantu};
|
||||
Charts.line('chart-tren', bulan, [
|
||||
{label:'Bantuan Disalurkan', data:jmlBantuan},
|
||||
{label:'Warga Terbantu', data:wargaTerbantu}
|
||||
]);
|
||||
});
|
||||
</script>
|
||||
JS; ?>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon blue">👥</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['total_warga']??0) ?></div>
|
||||
<div class="stat-card-label">Total Warga Miskin</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon red">💀</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['ekstrem']??0) ?></div>
|
||||
<div class="stat-card-label">Miskin Ekstrem</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon orange">⚠</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['miskin']??0) ?></div>
|
||||
<div class="stat-card-label">Miskin</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon yellow">📊</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['rentan']??0) ?></div>
|
||||
<div class="stat-card-label">Rentan Miskin</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon purple">⚠️</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['blind_spot']??0) ?></div>
|
||||
<div class="stat-card-label">Blind Spot</div>
|
||||
<div class="stat-card-sub"><a href="<?= APP_URL ?>/dinsos/blind-spot" style="color:var(--clr-primary-l)">Detail →</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon green">📦</div>
|
||||
<div class="stat-card-body">
|
||||
<div class="stat-card-value"><?= number_format($stats['total_bantuan']??0) ?></div>
|
||||
<div class="stat-card-label">Total Bantuan</div>
|
||||
<div class="stat-card-sub"><?= number_format($stats['bantuan_bulan_ini']??0) ?> bulan ini</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid: Peta Besar + Info -->
|
||||
<div class="dashboard-grid" style="margin-bottom:20px">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">🌡</span><h3>Heatmap Kemiskinan Kota Pontianak</h3>
|
||||
<div class="card-actions">
|
||||
<a href="<?= APP_URL ?>/dinsos/kemiskinan" class="btn btn-ghost btn-sm">Detail Peta</a>
|
||||
<a href="<?= APP_URL ?>/dinsos/blind-spot" class="btn btn-danger btn-sm">⚠ Blind Spot</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body no-pad" style="position:relative">
|
||||
<div id="dinsos-map" style="height:460px"></div>
|
||||
<div class="map-legend">
|
||||
<h4>Intensitas Kemiskinan</h4>
|
||||
<div style="height:12px;border-radius:6px;background:linear-gradient(to right,#fef9c3,#fb923c,#ef4444,#7f1d1d);margin-bottom:6px"></div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:0.65rem;color:var(--clr-text-muted)">
|
||||
<span>Rendah</span><span>Tinggi</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar: Per kec + Top RI -->
|
||||
<div style="display:flex;flex-direction:column;gap:16px">
|
||||
<!-- Peringkat Kecamatan -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">🗾</span><h3>Kemiskinan per Kecamatan</h3></div>
|
||||
<div class="card-body no-pad">
|
||||
<?php foreach($perKecamatan as $i=>$k): ?>
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-weight:600;font-size:0.82rem"><?= $i+1 ?>. <?= e($k['nama']) ?></span>
|
||||
<span style="font-weight:700;color:var(--clr-primary)"><?= number_format($k['total']) ?></span>
|
||||
</div>
|
||||
<div style="height:5px;background:#f1f5f9;border-radius:3px;overflow:hidden">
|
||||
<?php $maxTotal = max(array_column($perKecamatan,'total')?:[1]); ?>
|
||||
<div style="height:100%;background:var(--clr-danger);width:<?= $maxTotal>0?round($k['total']/$maxTotal*100):0 ?>%;border-radius:3px"></div>
|
||||
</div>
|
||||
<div style="font-size:0.7rem;color:var(--clr-text-muted);margin-top:2px">
|
||||
Ekstrem: <?= $k['ekstrem'] ?> · Blind: <?= $k['blind_spot']??0 ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Rumah Ibadah Aktif -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">🏆</span><h3>Rumah Ibadah Paling Aktif</h3></div>
|
||||
<div class="card-body no-pad">
|
||||
<?php foreach($topRi as $i=>$r): ?>
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;display:flex;gap:12px;align-items:center">
|
||||
<div style="width:28px;height:28px;border-radius:50%;background:var(--clr-accent);color:var(--clr-primary-d);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:0.85rem;flex-shrink:0"><?= $i+1 ?></div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-weight:600;font-size:0.82rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"><?= e($r['nama']) ?></div>
|
||||
<div style="font-size:0.72rem;color:var(--clr-text-muted)"><?= number_format($r['jml_bantuan']) ?> bantuan · <?= number_format($r['jml_warga']) ?> warga</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📊</span><h3>Perbandingan per Kecamatan</h3></div>
|
||||
<div class="card-body"><div class="chart-wrap"><canvas id="chart-kecamatan"></canvas></div></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📈</span><h3>Tren Bantuan 6 Bulan</h3></div>
|
||||
<div class="card-body"><div class="chart-wrap"><canvas id="chart-tren"></canvas></div></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,137 @@
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
||||
<div>
|
||||
<h2 style="font-size:1.2rem;font-weight:700">🗺 Monitoring Kemiskinan</h2>
|
||||
<p class="text-muted text-sm"><?= count($data) ?> warga terfilter · Klik marker pada peta untuk detail</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<form method="GET" action="<?= APP_URL ?>/dinsos/kemiskinan">
|
||||
<div class="filters-bar" style="margin-bottom:16px">
|
||||
<select name="kecamatan" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">— Semua Kecamatan —</option>
|
||||
<?php foreach($kecamatan as $k): ?>
|
||||
<option value="<?= $k['id'] ?>" <?= $filter_kec==$k['id']?'selected':'' ?>><?= e($k['nama']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if(!empty($kelurahan)): ?>
|
||||
<select name="kelurahan" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">— Semua Kelurahan —</option>
|
||||
<?php foreach($kelurahan as $k): ?>
|
||||
<option value="<?= $k['id'] ?>" <?= $filter_kel==$k['id']?'selected':'' ?>><?= e($k['nama']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php endif; ?>
|
||||
<select name="status" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">— Semua Status —</option>
|
||||
<?php foreach(['Miskin Ekstrem','Miskin','Rentan Miskin'] as $s): ?>
|
||||
<option value="<?= $s ?>" <?= $filter_status===$s?'selected':'' ?>><?= $s ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="prioritas" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">— Semua Prioritas —</option>
|
||||
<?php foreach(['Tinggi','Sedang','Rendah'] as $p): ?>
|
||||
<option value="<?= $p ?>" <?= $filter_prioritas===$p?'selected':'' ?>><?= $p ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if($filter_kec||$filter_kel||$filter_status||$filter_prioritas): ?>
|
||||
<a href="<?= APP_URL ?>/dinsos/kemiskinan" class="btn btn-ghost btn-sm">✕ Reset</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Grid: Peta + Daftar -->
|
||||
<div style="display:grid;grid-template-columns:1fr 360px;gap:16px">
|
||||
<!-- PETA -->
|
||||
<div class="card">
|
||||
<div class="card-body no-pad" style="position:relative">
|
||||
<div id="kemiskinan-map" style="height:600px"></div>
|
||||
<div class="map-legend">
|
||||
<h4>Legenda</h4>
|
||||
<div class="map-legend-item"><div class="map-legend-dot legend-dot-ekstrem"></div> Miskin Ekstrem</div>
|
||||
<div class="map-legend-item"><div class="map-legend-dot legend-dot-miskin"></div> Miskin</div>
|
||||
<div class="map-legend-item"><div class="map-legend-dot legend-dot-rentan"></div> Rentan Miskin</div>
|
||||
<div class="map-legend-item"><div class="map-legend-dot legend-dot-ri"></div> Rumah Ibadah</div>
|
||||
<div class="map-legend-item"><div class="map-legend-dot legend-dot-blind"></div> Blind Spot</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DAFTAR WARGA -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">👥</span>
|
||||
<h3>Daftar Warga (<?= count($data) ?>)</h3>
|
||||
</div>
|
||||
<div style="overflow-y:auto;max-height:560px">
|
||||
<?php foreach($data as $w): ?>
|
||||
<div class="sidebar-panel-item" id="warga-item-<?= $w['id'] ?>"
|
||||
onclick="highlightWarga(<?= $w['id'] ?>,<?= (float)$w['lat'] ?>,<?= (float)$w['lng'] ?>)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<h4><?= e($w['nama']) ?></h4>
|
||||
<div style="display:flex;gap:4px;align-items:center">
|
||||
<?php if($w['is_blind_spot']): ?><span class="badge badge-purple" style="font-size:0.62rem">Blind</span><?php endif; ?>
|
||||
<?php $pc=['Tinggi'=>'badge-red','Sedang'=>'badge-yellow','Rendah'=>'badge-green']; ?>
|
||||
<span class="badge <?= $pc[$w['level_prioritas']]??'badge-gray' ?>" style="font-size:0.62rem"><?= $w['level_prioritas'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<p><?= e($w['kecamatan']) ?> › <?= e($w['kelurahan']) ?></p>
|
||||
<div style="font-size:0.72rem;color:var(--clr-text-muted);margin-top:2px">
|
||||
<?= e($w['status_kemiskinan']) ?> · <?= formatRupiah($w['penghasilan_bulanan']??0) ?>/bln
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php if(empty($data)): ?>
|
||||
<div class="empty-state"><div class="empty-state-icon">👥</div><h3>Tidak ada data</h3></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let wargaMarkers = {};
|
||||
|
||||
function highlightWarga(id, lat, lng) {
|
||||
document.querySelectorAll('.sidebar-panel-item').forEach(el => el.classList.remove('active'));
|
||||
document.getElementById('warga-item-'+id)?.classList.add('active');
|
||||
if (wargaMarkers[id]) {
|
||||
wargaMarkers[id].openPopup();
|
||||
GISMap.map.setView([lat,lng],16);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const map = GISMap.init('kemiskinan-map', <?= MAP_DEFAULT_LAT ?>, <?= MAP_DEFAULT_LNG ?>, 12);
|
||||
|
||||
const riData = await App.fetchJSON('<?= APP_URL ?>/api/rumah-ibadah');
|
||||
if(riData) GISMap.addRILayer(riData);
|
||||
// Zoom ke Pontianak terlebih dahulu
|
||||
GISMap.autoFitBounds();
|
||||
|
||||
const wData = <?= json_encode(array_map(fn($w) => [
|
||||
'id'=>$w['id'],'nama'=>$w['nama'],'lat'=>(float)$w['lat'],'lng'=>(float)$w['lng'],
|
||||
'status_kemiskinan'=>$w['status_kemiskinan'],'level_prioritas'=>$w['level_prioritas'],
|
||||
'is_blind_spot'=>$w['is_blind_spot'],'kelurahan'=>$w['kelurahan']??'',
|
||||
'kecamatan'=>$w['kecamatan']??'','penghasilan_bulanan'=>$w['penghasilan_bulanan']??0
|
||||
], array_filter($data, fn($w)=>$w['lat']&&$w['lng']))) ?>;
|
||||
|
||||
const colors = {'Miskin Ekstrem':'#dc2626','Miskin':'#ea580c','Rentan Miskin':'#ca8a04'};
|
||||
|
||||
wData.forEach(w => {
|
||||
const color = w.is_blind_spot ? '#7c3aed' : (colors[w.status_kemiskinan]||'#6b7280');
|
||||
const m = L.circleMarker([w.lat,w.lng],{radius:9,fillColor:color,color:'#fff',weight:2,fillOpacity:0.9})
|
||||
.bindPopup(`<div style="min-width:180px"><strong>${w.nama}</strong><br>
|
||||
<span style="font-size:12px;color:#6b7280">${w.kecamatan} › ${w.kelurahan}</span><br>
|
||||
<span style="color:${color};font-weight:700;font-size:11px">${w.status_kemiskinan}</span>
|
||||
${w.is_blind_spot?'<br><span style="color:#7c3aed;font-weight:700;font-size:11px">⚠ Blind Spot</span>':''}
|
||||
</div>`)
|
||||
.addTo(map);
|
||||
wargaMarkers[w.id] = m;
|
||||
m.on('click', () => {
|
||||
document.querySelectorAll('.sidebar-panel-item').forEach(el=>el.classList.remove('active'));
|
||||
document.getElementById('warga-item-'+w.id)?.classList.add('active');
|
||||
document.getElementById('warga-item-'+w.id)?.scrollIntoView({behavior:'smooth',block:'nearest'});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,123 @@
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
||||
<div>
|
||||
<h2 style="font-size:1.2rem;font-weight:700">🏆 Kinerja Rumah Ibadah</h2>
|
||||
<p class="text-muted text-sm">Monitoring efektivitas penyaluran bantuan oleh seluruh rumah ibadah</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="stats-grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:20px">
|
||||
<?php
|
||||
$totalBantuan = array_sum(array_column($data,'jml_bantuan'));
|
||||
$totalWarga = array_sum(array_column($data,'jml_warga'));
|
||||
$aktif = count(array_filter($data, fn($d)=>$d['jml_bantuan']>0));
|
||||
?>
|
||||
<div class="stat-card"><div class="stat-card-icon green">⛪</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= count($data) ?></div><div class="stat-card-label">Total Rumah Ibadah</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon blue">✅</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= $aktif ?></div><div class="stat-card-label">Aktif Menyalurkan</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon teal">📦</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= number_format($totalBantuan) ?></div><div class="stat-card-label">Total Bantuan</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon orange">👥</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= number_format($totalWarga) ?></div><div class="stat-card-label">Total Warga Binaan</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts + Table -->
|
||||
<div style="display:grid;grid-template-columns:1fr 300px;gap:20px;margin-bottom:20px">
|
||||
<!-- Bar chart -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📊</span><h3>Ranking Bantuan per Rumah Ibadah (Top 10)</h3></div>
|
||||
<div class="card-body"><div class="chart-wrap" style="height:300px"><canvas id="chart-kinerja"></canvas></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Donut: aktif vs tidak -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">🥧</span><h3>Status Partisipasi</h3></div>
|
||||
<div class="card-body"><div class="chart-wrap" style="height:200px"><canvas id="chart-partisipasi"></canvas></div>
|
||||
<div style="text-align:center;margin-top:12px">
|
||||
<div style="font-size:0.8rem;color:var(--clr-text-muted)">
|
||||
<span style="color:#16a34a;font-weight:700"><?= $aktif ?></span> aktif |
|
||||
<span style="color:#dc2626;font-weight:700"><?= count($data)-$aktif ?></span> belum aktif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabel lengkap -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📋</span><h3>Detail Kinerja Semua Rumah Ibadah</h3></div>
|
||||
<div class="card-body no-pad">
|
||||
<div class="filters-bar" style="margin:0;border:none;border-bottom:1px solid var(--clr-border);border-radius:0">
|
||||
<input type="text" id="search-kinerja" class="form-control" placeholder="🔍 Cari nama rumah ibadah..." style="max-width:280px">
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="table-kinerja">
|
||||
<thead>
|
||||
<tr><th>#</th><th>Nama</th><th>Jenis</th><th>Wilayah</th><th>Warga Binaan</th><th>Bantuan Disalurkan</th><th>Miskin Ekstrem</th><th>Bantuan Terakhir</th><th>Status</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($data as $i=>$r): ?>
|
||||
<tr>
|
||||
<td class="text-xs text-muted"><?= $i+1 ?></td>
|
||||
<td>
|
||||
<div style="font-weight:600;font-size:0.875rem"><?= e($r['nama']) ?></div>
|
||||
<div class="text-xs text-muted">Radius: <?= number_format($r['radius_meter']) ?>m</div>
|
||||
</td>
|
||||
<td><span class="badge badge-green"><?= e($r['jenis']) ?></span></td>
|
||||
<td class="text-sm"><?= e($r['kecamatan']) ?> › <?= e($r['kelurahan']) ?></td>
|
||||
<td class="text-sm text-center"><strong><?= number_format($r['jml_warga']) ?></strong></td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<strong style="color:var(--clr-success)"><?= number_format($r['jml_bantuan']) ?></strong>
|
||||
<div class="priority-bar" style="width:60px">
|
||||
<?php $mx=max(array_column($data,'jml_bantuan')?:[1]); ?>
|
||||
<div class="priority-bar-fill low" style="width:<?= $mx>0?round($r['jml_bantuan']/$mx*100):0 ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-sm text-center">
|
||||
<?php if($r['jml_ekstrem']>0): ?>
|
||||
<span class="badge badge-red"><?= number_format($r['jml_ekstrem']) ?></span>
|
||||
<?php else: ?><span class="text-muted">—</span><?php endif; ?>
|
||||
</td>
|
||||
<td class="text-xs text-muted"><?= $r['bantuan_terakhir'] ? formatTanggal($r['bantuan_terakhir']) : '—' ?></td>
|
||||
<td>
|
||||
<?php if($r['jml_bantuan']>0): ?>
|
||||
<span class="badge badge-green">✅ Aktif</span>
|
||||
<?php elseif($r['jml_warga']>0): ?>
|
||||
<span class="badge badge-yellow">⏳ Ada Binaan</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-gray">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
App.initTableSearch('search-kinerja','table-kinerja');
|
||||
|
||||
const top10 = <?= json_encode(array_slice($data,0,10)) ?>;
|
||||
Charts.bar('chart-kinerja',
|
||||
top10.map(r=>r.nama.length>20?r.nama.slice(0,20)+'...':r.nama),
|
||||
[{label:'Bantuan Disalurkan', data:top10.map(r=>r.jml_bantuan)},
|
||||
{label:'Warga Binaan', data:top10.map(r=>r.jml_warga)}]
|
||||
);
|
||||
|
||||
Charts.donut('chart-partisipasi',
|
||||
['Aktif Menyalurkan','Belum Aktif'],
|
||||
[<?= $aktif ?>, <?= count($data)-$aktif ?>],
|
||||
['#16a34a','#dc2626']
|
||||
);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
||||
<div>
|
||||
<h2 style="font-size:1.2rem;font-weight:700">📄 Laporan Tahunan</h2>
|
||||
<p class="text-muted text-sm">Rekap penyaluran bantuan tahun <?= $tahun ?></p>
|
||||
</div>
|
||||
<form method="GET" style="display:flex;gap:8px;align-items:center">
|
||||
<label class="text-sm">Tahun:</label>
|
||||
<select name="tahun" class="form-control" onchange="this.form.submit()">
|
||||
<?php for($y=date('Y');$y>=2020;$y--): ?>
|
||||
<option value="<?= $y ?>" <?= $tahun==$y?'selected':'' ?>><?= $y ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<button type="button" class="btn btn-ghost btn-sm" onclick="window.print()">🖨 Cetak</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Ringkasan -->
|
||||
<div class="stats-grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:20px">
|
||||
<div class="stat-card"><div class="stat-card-icon green">📦</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= number_format($ringkasan['total_bantuan']??0) ?></div><div class="stat-card-label">Total Penyaluran Bantuan</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon blue">👥</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= number_format($ringkasan['total_warga_terbantu']??0) ?></div><div class="stat-card-label">Warga Mendapat Bantuan</div></div>
|
||||
</div>
|
||||
<div class="stat-card"><div class="stat-card-icon orange">⛪</div>
|
||||
<div class="stat-card-body"><div class="stat-card-value"><?= number_format($ringkasan['total_ri_aktif']??0) ?></div><div class="stat-card-label">Rumah Ibadah Aktif</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart bulanan -->
|
||||
<div class="card" style="margin-bottom:20px">
|
||||
<div class="card-header"><span class="card-icon">📊</span><h3>Grafik Penyaluran Bantuan Bulan per Bulan Tahun <?= $tahun ?></h3></div>
|
||||
<div class="card-body"><div class="chart-wrap" style="height:280px"><canvas id="chart-bulanan"></canvas></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Tabel per bulan -->
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-icon">📋</span><h3>Rekap per Bulan</h3></div>
|
||||
<div class="card-body no-pad">
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Bulan</th><th>Bantuan Disalurkan</th><th>Warga Terbantu</th><th>Rata-rata/Warga</th></tr></thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$namaBulan=['','Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];
|
||||
$totalBulanan=0; $totalWarga=0;
|
||||
foreach($perBulan as $b):
|
||||
$totalBulanan+=$b['bantuan'];
|
||||
$totalWarga+=$b['warga_terbantu'];
|
||||
$avg=$b['warga_terbantu']>0?round($b['bantuan']/$b['warga_terbantu'],1):0;
|
||||
?>
|
||||
<tr>
|
||||
<td style="font-weight:600"><?= $namaBulan[(int)$b['bulan']] ?></td>
|
||||
<td><strong><?= number_format($b['bantuan']) ?></strong></td>
|
||||
<td><?= number_format($b['warga_terbantu']) ?> warga</td>
|
||||
<td class="text-sm text-muted"><?= $avg ?> kali/warga</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<!-- Total -->
|
||||
<tr style="background:#f0f9ff;font-weight:700">
|
||||
<td>TOTAL</td>
|
||||
<td><?= number_format($totalBulanan) ?></td>
|
||||
<td><?= number_format($totalWarga) ?> (unik)</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if(empty($perBulan)): ?>
|
||||
<div class="empty-state"><div class="empty-state-icon">📭</div><h3>Tidak ada data</h3><p>Belum ada penyaluran pada tahun <?= $tahun ?></p></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const bulanData = <?= json_encode($perBulan) ?>;
|
||||
const namaBulan = ['','Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];
|
||||
Charts.bar('chart-bulanan',
|
||||
bulanData.map(b=>namaBulan[parseInt(b.bulan)]),
|
||||
[{label:'Bantuan Disalurkan', data:bulanData.map(b=>b.bantuan)},
|
||||
{label:'Warga Terbantu', data:bulanData.map(b=>b.warga_terbantu)}]
|
||||
);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user