db->fetchOne(" SELECT (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif') as total_warga, (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif' AND status_kemiskinan='Miskin Ekstrem') as ekstrem, (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif' AND status_kemiskinan='Miskin') as miskin, (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif' AND status_kemiskinan='Rentan Miskin') as rentan, (SELECT COUNT(*) FROM warga_miskin WHERE is_blind_spot=1 AND status='aktif') as blind_spot, (SELECT COUNT(*) FROM rumah_ibadah WHERE is_active=1) as total_ri, (SELECT COUNT(*) FROM histori_bantuan WHERE status='disalurkan') as total_bantuan, (SELECT COUNT(*) FROM histori_bantuan WHERE status='disalurkan' AND MONTH(tanggal_penyaluran)=MONTH(CURDATE())) as bantuan_bulan_ini, (SELECT COUNT(*) FROM laporan_masyarakat WHERE status='menunggu') as laporan_pending "); $perKecamatan = $this->db->fetchAll(" SELECT kec.id, kec.nama, COUNT(w.id) as total, SUM(w.status_kemiskinan='Miskin Ekstrem') as ekstrem, SUM(w.status_kemiskinan='Miskin') as miskin, SUM(w.status_kemiskinan='Rentan Miskin') as rentan, SUM(w.is_blind_spot) as blind_spot FROM kecamatan kec LEFT JOIN kelurahan k ON k.id_kecamatan = kec.id LEFT JOIN warga_miskin w ON w.id_kelurahan = k.id AND w.status='aktif' GROUP BY kec.id, kec.nama ORDER BY total DESC "); $trenBantuan = $this->db->fetchAll(" SELECT DATE_FORMAT(tanggal_penyaluran,'%Y-%m') as bulan, COUNT(*) as jumlah, COUNT(DISTINCT id_warga) as warga_terbantu FROM histori_bantuan WHERE status='disalurkan' AND tanggal_penyaluran >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY bulan ORDER BY bulan "); $topRi = $this->db->fetchAll(" SELECT ri.nama, ji.nama as jenis, COUNT(hb.id) as jml_bantuan, COUNT(DISTINCT hb.id_warga) as jml_warga FROM rumah_ibadah ri JOIN jenis_rumah_ibadah ji ON ri.id_jenis = ji.id LEFT JOIN histori_bantuan hb ON hb.id_rumah_ibadah = ri.id AND hb.status='disalurkan' WHERE ri.is_active = 1 GROUP BY ri.id, ri.nama, ji.nama ORDER BY jml_bantuan DESC LIMIT 5 "); $this->view('dinsos.dashboard', [ 'title' => 'Dashboard Dinas Sosial', 'stats' => $stats, 'perKecamatan' => $perKecamatan, 'trenBantuan' => $trenBantuan, 'topRi' => $topRi, 'user' => Auth::user() ], 'dinsos'); } public function kemiskinan(): void { $filter_kec = $_GET['kecamatan'] ?? ''; $filter_kel = $_GET['kelurahan'] ?? ''; $filter_status = $_GET['status'] ?? ''; $filter_prioritas = $_GET['prioritas'] ?? ''; $sql = "SELECT w.*, k.nama as kelurahan, kec.nama as kecamatan, ri.nama as rumah_ibadah FROM warga_miskin w JOIN kelurahan k ON w.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN rumah_ibadah ri ON w.id_rumah_ibadah = ri.id WHERE w.status = 'aktif'"; $params = []; if ($filter_kec) { $sql .= " AND kec.id = ?"; $params[] = $filter_kec; } if ($filter_kel) { $sql .= " AND k.id = ?"; $params[] = $filter_kel; } if ($filter_status) { $sql .= " AND w.status_kemiskinan = ?"; $params[] = $filter_status; } if ($filter_prioritas) { $sql .= " AND w.level_prioritas = ?"; $params[] = $filter_prioritas; } $sql .= " ORDER BY w.skor_prioritas DESC"; $data = $this->db->fetchAll($sql, $params); $kecamatan = $this->db->fetchAll("SELECT * FROM kecamatan ORDER BY nama"); $kelurahan = !empty($filter_kec) ? $this->db->fetchAll("SELECT * FROM kelurahan WHERE id_kecamatan = ? ORDER BY nama", [$filter_kec]) : []; $this->view('dinsos.kemiskinan', [ 'title' => 'Monitoring Kemiskinan', 'data' => $data, 'kecamatan' => $kecamatan, 'kelurahan' => $kelurahan, 'filter_kec' => $filter_kec, 'filter_kel' => $filter_kel, 'filter_status' => $filter_status, 'filter_prioritas'=> $filter_prioritas, 'user' => Auth::user() ], 'dinsos'); } public function bantuan(): void { $bulan = $_GET['bulan'] ?? date('Y-m'); [$tahun, $bln] = explode('-', $bulan); $data = $this->db->fetchAll(" SELECT hb.*, kb.nama as kategori, wm.nama as warga, ri.nama as rumah_ibadah, k.nama as kelurahan, kec.nama as kecamatan FROM histori_bantuan hb JOIN kategori_bantuan kb ON hb.id_kategori_bantuan = kb.id JOIN warga_miskin wm ON hb.id_warga = wm.id JOIN rumah_ibadah ri ON hb.id_rumah_ibadah = ri.id JOIN kelurahan k ON wm.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id WHERE hb.status = 'disalurkan' AND YEAR(hb.tanggal_penyaluran) = ? AND MONTH(hb.tanggal_penyaluran) = ? ORDER BY hb.tanggal_penyaluran DESC ", [$tahun, $bln]); $perKategori = $this->db->fetchAll(" SELECT kb.nama, COUNT(hb.id) as jumlah FROM histori_bantuan hb JOIN kategori_bantuan kb ON hb.id_kategori_bantuan = kb.id WHERE hb.status = 'disalurkan' AND YEAR(hb.tanggal_penyaluran) = ? AND MONTH(hb.tanggal_penyaluran) = ? GROUP BY kb.id, kb.nama ORDER BY jumlah DESC ", [$tahun, $bln]); $this->view('dinsos.bantuan', [ 'title' => 'Monitoring Bantuan', 'data' => $data, 'perKategori' => $perKategori, 'bulan' => $bulan, 'user' => Auth::user() ], 'dinsos'); } public function akun(): void { $users = $this->db->fetchAll(" SELECT u.*, r.nama as role_nama, r.slug as role_slug, CONCAT(kec.nama, ' - ', k.nama) as kelurahan_nama, ri.id as rumah_ibadah_id, ri.nama as rumah_ibadah_nama FROM users u JOIN roles r ON u.id_role = r.id LEFT JOIN kelurahan k ON u.id_kelurahan = k.id LEFT JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN rumah_ibadah ri ON ri.id_user = u.id WHERE r.slug IN ('verifikator', 'rumah_ibadah') ORDER BY r.id, u.nama "); $roles = $this->db->fetchAll(" SELECT id, nama, slug FROM roles WHERE slug IN ('verifikator', 'rumah_ibadah') ORDER BY id "); $kelurahan = $this->db->fetchAll(" SELECT k.id, CONCAT(kec.nama, ' - ', k.nama) as nama FROM kelurahan k JOIN kecamatan kec ON k.id_kecamatan = kec.id ORDER BY kec.nama, k.nama "); $rumahIbadah = $this->db->fetchAll(" SELECT ri.id, ri.nama, ri.id_user, CONCAT(kec.nama, ' - ', k.nama) as wilayah, ji.nama as jenis, u.nama as pengurus_nama FROM rumah_ibadah ri JOIN jenis_rumah_ibadah ji ON ri.id_jenis = ji.id JOIN kelurahan k ON ri.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN users u ON ri.id_user = u.id WHERE ri.is_active = 1 ORDER BY kec.nama, k.nama, ri.nama "); $this->view('dinsos.akun', [ 'title' => 'Manajemen Akun Pengguna', 'users' => $users, 'roles' => $roles, 'kelurahan' => $kelurahan, 'rumahIbadah' => $rumahIbadah, 'user' => Auth::user() ], 'dinsos'); } public function storeAkun(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/dinsos/akun'); $roleSlug = $_POST['role_slug'] ?? ''; if (!in_array($roleSlug, ['verifikator', 'rumah_ibadah'], true)) { $this->flash('error', 'Peran pengguna tidak valid.'); $this->redirect(APP_URL . '/dinsos/akun'); } $role = $this->db->fetchOne("SELECT id, nama FROM roles WHERE slug = ?", [$roleSlug]); if (!$role) { $this->flash('error', 'Data peran tidak ditemukan.'); $this->redirect(APP_URL . '/dinsos/akun'); } $idKelurahan = null; $idRumahIbadah = null; if ($roleSlug === 'verifikator') { $idKelurahan = (int)($_POST['id_kelurahan'] ?? 0); if ($idKelurahan <= 0) { $this->flash('error', 'Kelurahan tanggung jawab verifikator wajib dipilih.'); $this->redirect(APP_URL . '/dinsos/akun'); } } else { $idRumahIbadah = (int)($_POST['id_rumah_ibadah'] ?? 0); $ri = $this->db->fetchOne("SELECT id, id_kelurahan, id_user, nama FROM rumah_ibadah WHERE id = ? AND is_active = 1", [$idRumahIbadah]); if (!$ri) { $this->flash('error', 'Rumah ibadah tidak ditemukan atau tidak aktif.'); $this->redirect(APP_URL . '/dinsos/akun'); } if (!empty($ri['id_user'])) { $this->flash('error', 'Rumah ibadah tersebut sudah memiliki pengurus.'); $this->redirect(APP_URL . '/dinsos/akun'); } $idKelurahan = (int)$ri['id_kelurahan']; } $password = generatePasswordAkun(); $data = [ 'id_role' => (int)$role['id'], 'id_kelurahan' => $idKelurahan, 'nama' => trim($_POST['nama'] ?? ''), 'username' => trim($_POST['username'] ?? ''), 'email' => trim($_POST['email'] ?? ''), 'password' => password_hash($password, PASSWORD_BCRYPT), 'no_hp' => trim($_POST['no_hp'] ?? ''), 'is_active' => 1 ]; if (!$data['nama'] || !$data['username'] || !$data['email']) { $this->flash('error', 'Nama lengkap, username, dan email wajib diisi.'); $this->redirect(APP_URL . '/dinsos/akun'); } try { $idUser = $this->db->insert('users', $data); if ($roleSlug === 'rumah_ibadah') { $this->db->update('rumah_ibadah', ['id_user' => $idUser], 'id = ?', [$idRumahIbadah]); } $emailSent = kirimEmailAkun($data['email'], $data['nama'], $data['username'], $password, 'dibuat'); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'dinsos', 'Tambah Akun Pengguna', 'users', 'Menambah user: ' . $data['username']); if ($emailSent) { $this->flash('success', 'Akun berhasil disimpan dan kredensial dikirim ke email pengguna.'); } else { $this->flash('warning', 'Akun berhasil disimpan, tetapi email belum terkirim. Password awal: ' . $password . ''); } } catch (Exception $e) { $this->flash('error', 'Gagal menyimpan akun. Username atau email mungkin sudah digunakan.'); } $this->redirect(APP_URL . '/dinsos/akun'); } public function updateAkun(string $id): void { $id = (int)$id; if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/dinsos/akun'); $target = $this->managedUser($id); if (!$target) { $this->flash('error', 'Akun tidak ditemukan atau tidak dapat dikelola Dinsos.'); $this->redirect(APP_URL . '/dinsos/akun'); } $roleSlug = $_POST['role_slug'] ?? ''; if (!in_array($roleSlug, ['verifikator', 'rumah_ibadah'], true)) { $this->flash('error', 'Peran pengguna tidak valid.'); $this->redirect(APP_URL . '/dinsos/akun'); } $role = $this->db->fetchOne("SELECT id FROM roles WHERE slug = ?", [$roleSlug]); $idKelurahan = null; $idRumahIbadah = null; if ($roleSlug === 'verifikator') { $idKelurahan = (int)($_POST['id_kelurahan'] ?? 0); if ($idKelurahan <= 0) { $this->flash('error', 'Kelurahan tanggung jawab verifikator wajib dipilih.'); $this->redirect(APP_URL . '/dinsos/akun'); } } else { $idRumahIbadah = (int)($_POST['id_rumah_ibadah'] ?? 0); $ri = $this->db->fetchOne("SELECT id, id_kelurahan, id_user FROM rumah_ibadah WHERE id = ? AND is_active = 1", [$idRumahIbadah]); if (!$ri) { $this->flash('error', 'Rumah ibadah tidak ditemukan atau tidak aktif.'); $this->redirect(APP_URL . '/dinsos/akun'); } if (!empty($ri['id_user']) && (int)$ri['id_user'] !== $id) { $this->flash('error', 'Rumah ibadah tersebut sudah memiliki pengurus.'); $this->redirect(APP_URL . '/dinsos/akun'); } $idKelurahan = (int)$ri['id_kelurahan']; } $data = [ 'id_role' => (int)$role['id'], 'id_kelurahan' => $idKelurahan, 'nama' => trim($_POST['nama'] ?? ''), 'username' => trim($_POST['username'] ?? ''), 'email' => trim($_POST['email'] ?? ''), 'no_hp' => trim($_POST['no_hp'] ?? ''), 'is_active' => (int)($_POST['is_active'] ?? 1) ]; try { $this->db->update('users', $data, 'id = ?', [$id]); $this->db->update('rumah_ibadah', ['id_user' => null], 'id_user = ?', [$id]); if ($roleSlug === 'rumah_ibadah' && $data['is_active']) { $this->db->update('rumah_ibadah', ['id_user' => $id], 'id = ?', [$idRumahIbadah]); } Auth::logActivity(Auth::id(), Auth::user()['nama'], 'dinsos', 'Update Akun Pengguna', 'users', "Update user ID $id"); $this->flash('success', 'Akun pengguna berhasil diperbarui.'); } catch (Exception $e) { $this->flash('error', 'Gagal memperbarui akun. Username atau email mungkin sudah digunakan.'); } $this->redirect(APP_URL . '/dinsos/akun'); } public function resetAkunPassword(string $id): void { $id = (int)$id; if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/dinsos/akun'); $target = $this->managedUser($id); if (!$target) { $this->flash('error', 'Akun tidak ditemukan atau tidak dapat dikelola Dinsos.'); $this->redirect(APP_URL . '/dinsos/akun'); } $password = generatePasswordAkun(); $this->db->update('users', [ 'password' => password_hash($password, PASSWORD_BCRYPT) ], 'id = ?', [$id]); $emailSent = kirimEmailAkun($target['email'], $target['nama'], $target['username'], $password, 'direset passwordnya'); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'dinsos', 'Reset Password Akun', 'users', "Reset password user ID $id"); if ($emailSent) { $this->flash('success', 'Password berhasil direset dan dikirim ke email pengguna.'); } else { $this->flash('warning', 'Password berhasil direset, tetapi email belum terkirim. Password baru: ' . $password . ''); } $this->redirect(APP_URL . '/dinsos/akun'); } public function deactivateAkun(string $id): void { $id = (int)$id; if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/dinsos/akun'); $target = $this->managedUser($id); if (!$target) { $this->flash('error', 'Akun tidak ditemukan atau tidak dapat dikelola Dinsos.'); $this->redirect(APP_URL . '/dinsos/akun'); } $this->db->update('users', ['is_active' => 0], 'id = ?', [$id]); $this->db->update('rumah_ibadah', ['id_user' => null], 'id_user = ?', [$id]); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'dinsos', 'Nonaktifkan Akun', 'users', "Nonaktifkan user ID $id"); $this->flash('success', 'Akun pengguna berhasil dinonaktifkan.'); $this->redirect(APP_URL . '/dinsos/akun'); } private function managedUser(int $id): ?array { return $this->db->fetchOne(" SELECT u.*, r.slug as role_slug FROM users u JOIN roles r ON u.id_role = r.id WHERE u.id = ? AND r.slug IN ('verifikator', 'rumah_ibadah') ", [$id]); } public function blindSpot(): void { $data = $this->db->fetchAll(" SELECT w.*, k.nama as kelurahan, kec.nama as kecamatan, ri.id as assigned_ri_id, ri.nama as assigned_rumah_ibadah, assigned_ji.nama as assigned_jenis FROM warga_miskin w JOIN kelurahan k ON w.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN rumah_ibadah ri ON w.id_rumah_ibadah = ri.id LEFT JOIN jenis_rumah_ibadah assigned_ji ON ri.id_jenis = assigned_ji.id WHERE w.is_blind_spot = 1 AND w.status = 'aktif' ORDER BY CASE WHEN w.id_rumah_ibadah IS NULL THEN 0 ELSE 1 END, CASE w.level_prioritas WHEN 'Tinggi' THEN 1 WHEN 'Sedang' THEN 2 ELSE 3 END, w.skor_prioritas DESC "); $rumahIbadah = $this->db->fetchAll(" SELECT ri.id, ri.nama, ri.alamat, ri.lat, ri.lng, ri.radius_meter, ji.nama as jenis, k.nama as kelurahan, kec.nama as kecamatan, COUNT(wm.id) as jml_warga FROM rumah_ibadah ri JOIN jenis_rumah_ibadah ji ON ri.id_jenis = ji.id JOIN kelurahan k ON ri.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN warga_miskin wm ON wm.id_rumah_ibadah = ri.id AND wm.status = 'aktif' WHERE ri.is_active = 1 GROUP BY ri.id, ri.nama, ri.alamat, ri.lat, ri.lng, ri.radius_meter, ji.nama, k.nama, kec.nama ORDER BY kec.nama, k.nama, ri.nama "); $rekomendasi = $this->buildRekomendasiRumahIbadah($data, $rumahIbadah); $sudahDitugaskan = count(array_filter($data, fn($w) => !empty($w['id_rumah_ibadah']))); $perKec = $this->db->fetchAll(" SELECT kec.nama, COUNT(w.id) as jumlah FROM kecamatan kec JOIN kelurahan k ON k.id_kecamatan = kec.id JOIN warga_miskin w ON w.id_kelurahan = k.id WHERE w.is_blind_spot = 1 AND w.status = 'aktif' GROUP BY kec.id, kec.nama ORDER BY jumlah DESC "); $this->view('dinsos.blind_spot', [ 'title' => 'Monitoring Blind Spot', 'data' => $data, 'perKec' => $perKec, 'rumahIbadah' => $rumahIbadah, 'rekomendasi' => $rekomendasi, 'ringkasanPenugasan' => [ 'sudah' => $sudahDitugaskan, 'belum' => count($data) - $sudahDitugaskan, 'total_ri' => count($rumahIbadah) ], 'user' => Auth::user() ], 'dinsos'); } public function assignBlindSpot(string $id): void { $id = (int) $id; if (!$this->verifyCsrf()) { $this->flash('error', 'Token tidak valid. Silakan coba lagi.'); $this->redirect(APP_URL . '/dinsos/blind-spot'); } $idRumahIbadah = (int)($_POST['id_rumah_ibadah'] ?? 0); if ($id <= 0 || $idRumahIbadah <= 0) { $this->flash('error', 'Pilih rumah ibadah penanggung jawab terlebih dahulu.'); $this->redirect(APP_URL . '/dinsos/blind-spot'); } $warga = $this->db->fetchOne(" SELECT id, nama FROM warga_miskin WHERE id = ? AND is_blind_spot = 1 AND status = 'aktif' ", [$id]); if (!$warga) { $this->flash('error', 'Data warga blind spot tidak ditemukan.'); $this->redirect(APP_URL . '/dinsos/blind-spot'); } $rumahIbadah = $this->db->fetchOne(" SELECT id, nama FROM rumah_ibadah WHERE id = ? AND is_active = 1 ", [$idRumahIbadah]); if (!$rumahIbadah) { $this->flash('error', 'Rumah ibadah tidak aktif atau tidak ditemukan.'); $this->redirect(APP_URL . '/dinsos/blind-spot'); } $this->db->update('warga_miskin', [ 'id_rumah_ibadah' => $idRumahIbadah ], 'id = ? AND is_blind_spot = 1 AND status = ?', [$id, 'aktif']); Auth::logActivity( Auth::id(), Auth::user()['nama'], 'dinsos', 'Atur Penugasan Blind Spot', 'blind_spot', 'Menugaskan ' . $warga['nama'] . ' ke ' . $rumahIbadah['nama'] ); $this->flash('success', 'Penugasan warga blind spot berhasil disimpan.'); $this->redirect(APP_URL . '/dinsos/blind-spot'); } private function buildRekomendasiRumahIbadah(array $wargaList, array $rumahIbadahList): array { $hasil = []; foreach ($wargaList as $warga) { $lat = (float)($warga['lat'] ?? 0); $lng = (float)($warga['lng'] ?? 0); $idWarga = (int)($warga['id'] ?? 0); $hasil[$idWarga] = []; if (!$lat || !$lng) { continue; } $kandidat = []; foreach ($rumahIbadahList as $ri) { if (empty($ri['lat']) || empty($ri['lng'])) { continue; } $jarak = haversine($lat, $lng, (float)$ri['lat'], (float)$ri['lng']); $item = $ri; $item['jarak_meter'] = (int)round($jarak); $item['di_dalam_radius'] = $jarak <= (float)$ri['radius_meter']; $kandidat[] = $item; } usort($kandidat, fn($a, $b) => $a['jarak_meter'] <=> $b['jarak_meter']); $hasil[$idWarga] = array_slice($kandidat, 0, 3); } return $hasil; } public function kinerja(): void { $data = $this->db->fetchAll(" SELECT ri.id, ri.nama, ji.nama as jenis, k.nama as kelurahan, kec.nama as kecamatan, ri.radius_meter, COUNT(DISTINCT wm.id) as jml_warga, COUNT(DISTINCT hb.id) as jml_bantuan, COUNT(DISTINCT CASE WHEN wm.status_kemiskinan='Miskin Ekstrem' THEN wm.id END) as jml_ekstrem, MAX(hb.tanggal_penyaluran) as bantuan_terakhir FROM rumah_ibadah ri JOIN jenis_rumah_ibadah ji ON ri.id_jenis = ji.id JOIN kelurahan k ON ri.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id LEFT JOIN warga_miskin wm ON wm.id_rumah_ibadah = ri.id AND wm.status='aktif' LEFT JOIN histori_bantuan hb ON hb.id_rumah_ibadah = ri.id AND hb.status='disalurkan' WHERE ri.is_active = 1 GROUP BY ri.id, ri.nama, ji.nama, k.nama, kec.nama, ri.radius_meter ORDER BY jml_bantuan DESC "); $this->view('dinsos.kinerja', [ 'title' => 'Kinerja Rumah Ibadah', 'data' => $data, 'user' => Auth::user() ], 'dinsos'); } public function laporan(): void { $tahun = (int)($_GET['tahun'] ?? date('Y')); $perBulan = $this->db->fetchAll(" SELECT MONTH(tanggal_penyaluran) as bulan, COUNT(*) as bantuan, COUNT(DISTINCT id_warga) as warga_terbantu FROM histori_bantuan WHERE status='disalurkan' AND YEAR(tanggal_penyaluran) = ? GROUP BY MONTH(tanggal_penyaluran) ORDER BY bulan ", [$tahun]); $ringkasan = $this->db->fetchOne(" SELECT COUNT(*) as total_bantuan, COUNT(DISTINCT id_warga) as total_warga_terbantu, COUNT(DISTINCT id_rumah_ibadah) as total_ri_aktif FROM histori_bantuan WHERE status='disalurkan' AND YEAR(tanggal_penyaluran) = ? ", [$tahun]); $this->view('dinsos.laporan', [ 'title' => 'Laporan Tahunan', 'perBulan' => $perBulan, 'ringkasan' => $ringkasan, 'tahun' => $tahun, 'user' => Auth::user() ], 'dinsos'); } public function apiHeatmap(): void { $data = $this->db->fetchAll(" SELECT lat, lng, status_kemiskinan, CASE status_kemiskinan WHEN 'Miskin Ekstrem' THEN 1.0 WHEN 'Miskin' THEN 0.7 WHEN 'Rentan Miskin' THEN 0.4 ELSE 0.3 END as intensity FROM warga_miskin WHERE status = 'aktif' AND lat != 0 "); $this->json($data); } public function apiStats(): void { $perKec = $this->db->fetchAll(" SELECT kec.nama, COUNT(w.id) as total, SUM(w.status_kemiskinan='Miskin Ekstrem') as ekstrem, SUM(w.is_blind_spot) as blind_spot FROM kecamatan kec LEFT JOIN kelurahan k ON k.id_kecamatan = kec.id LEFT JOIN warga_miskin w ON w.id_kelurahan = k.id AND w.status='aktif' GROUP BY kec.id, kec.nama "); $trenBantuan = $this->db->fetchAll(" SELECT DATE_FORMAT(tanggal_penyaluran,'%Y-%m') as bulan, COUNT(*) as jumlah FROM histori_bantuan WHERE status='disalurkan' AND tanggal_penyaluran >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY bulan ORDER BY bulan "); $this->json(['perKecamatan' => $perKec, 'trenBantuan' => $trenBantuan]); } }