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 warga_ekstrem, (SELECT COUNT(*) FROM rumah_ibadah WHERE is_active=1) as total_ri, (SELECT COUNT(*) FROM laporan_masyarakat) as total_laporan, (SELECT COUNT(*) FROM laporan_masyarakat WHERE status='menunggu') as laporan_pending, (SELECT COUNT(*) FROM histori_bantuan WHERE status='disalurkan') as total_bantuan, (SELECT COUNT(*) FROM warga_miskin WHERE is_blind_spot=1 AND status='aktif') as blind_spot, (SELECT COUNT(*) FROM users WHERE is_active=1) as total_user "); // Data per kecamatan $perKecamatan = $this->db->fetchAll(" SELECT 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=1) 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 "); // Tren bantuan 6 bulan terakhir $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 6 MONTH) GROUP BY bulan ORDER BY bulan "); $aktivitas = $this->db->fetchAll("SELECT * FROM aktivitas_sistem ORDER BY created_at DESC LIMIT 15"); $this->view('admin.dashboard', [ 'title' => 'Dashboard Super Admin', 'stats' => $stats, 'perKecamatan'=> $perKecamatan, 'trenBantuan' => $trenBantuan, 'aktivitas' => $aktivitas, 'user' => Auth::user() ], 'admin'); } public function users(): void { $users = $this->db->fetchAll(" SELECT u.*, r.nama as role_nama, r.slug as role_slug, k.nama as kelurahan_nama, 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 rumah_ibadah ri ON ri.id_user = u.id ORDER BY u.id_role, u.nama "); $roles = $this->db->fetchAll("SELECT * FROM roles"); $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"); $this->view('admin.users', [ 'title' => 'Manajemen User', 'users' => $users, 'roles' => $roles, 'kelurahan' => $kelurahan, 'user' => Auth::user() ], 'admin'); } public function storeUser(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/users'); $data = [ 'id_role' => (int)$_POST['id_role'], 'id_kelurahan' => !empty($_POST['id_kelurahan']) ? (int)$_POST['id_kelurahan'] : null, 'nama' => trim($_POST['nama']), 'username' => trim($_POST['username']), 'email' => trim($_POST['email']), 'password' => password_hash($_POST['password'], PASSWORD_BCRYPT), 'no_hp' => trim($_POST['no_hp'] ?? ''), 'is_active' => 1 ]; try { $this->db->insert('users', $data); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'superadmin', 'Tambah User', 'users', 'Menambah user: ' . $data['username']); $this->flash('success', 'User berhasil ditambahkan.'); } catch (Exception $e) { $this->flash('error', 'Gagal menambah user: username/email sudah digunakan.'); } $this->redirect(APP_URL . '/admin/users'); } public function updateUser(int $id): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/users'); $data = [ 'id_role' => (int)$_POST['id_role'], 'id_kelurahan' => !empty($_POST['id_kelurahan']) ? (int)$_POST['id_kelurahan'] : null, 'nama' => trim($_POST['nama']), 'username' => trim($_POST['username']), 'email' => trim($_POST['email']), 'no_hp' => trim($_POST['no_hp'] ?? ''), 'is_active' => (int)$_POST['is_active'] ]; if (!empty($_POST['password'])) { $data['password'] = password_hash($_POST['password'], PASSWORD_BCRYPT); } try { $this->db->update('users', $data, 'id = ?', [$id]); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'superadmin', 'Update User', 'users', "Update user ID $id"); $this->flash('success', 'User berhasil diperbarui.'); } catch (Exception $e) { $this->flash('error', 'Gagal memperbarui user: username atau email sudah digunakan.'); } $this->redirect(APP_URL . '/admin/users'); } public function resetUserPassword(int $id): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/users'); if ($id === Auth::id()) { $this->flash('error', 'Tidak bisa reset password akun sendiri dari halaman ini.'); $this->redirect(APP_URL . '/admin/users'); } $target = $this->db->fetchOne(" SELECT u.id, u.nama, u.username, u.email FROM users u WHERE u.id = ? ", [$id]); if (!$target) { $this->flash('error', 'User tidak ditemukan.'); $this->redirect(APP_URL . '/admin/users'); } $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'], 'superadmin', 'Reset Password User', '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 . '/admin/users'); } public function deleteUser(int $id): void { if ($id === Auth::id()) { $this->flash('error', 'Tidak bisa menghapus akun sendiri.'); $this->redirect(APP_URL . '/admin/users'); } $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'], 'superadmin', 'Nonaktifkan User', 'users', "Nonaktifkan user ID $id"); $this->flash('success', 'User berhasil dinonaktifkan.'); $this->redirect(APP_URL . '/admin/users'); } public function rumahIbadah(): void { $data = $this->db->fetchAll(" SELECT ri.*, ji.nama as jenis, k.nama as kelurahan, kec.nama as kecamatan, u.nama as pengurus_nama, (SELECT COUNT(*) FROM warga_miskin w WHERE w.id_rumah_ibadah=ri.id AND w.status='aktif') 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 users u ON ri.id_user = u.id ORDER BY kec.nama, k.nama, ri.nama "); $jenis = $this->db->fetchAll("SELECT * FROM jenis_rumah_ibadah"); $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"); $pengurusAvail = $this->db->fetchAll("SELECT u.id, u.nama FROM users u LEFT JOIN rumah_ibadah ri ON ri.id_user=u.id WHERE u.id_role=3 AND ri.id IS NULL"); $this->view('admin.rumah_ibadah', [ 'title' => 'Manajemen Rumah Ibadah', 'data' => $data, 'jenis' => $jenis, 'kelurahan' => $kelurahan, 'pengurusAvail' => $pengurusAvail, 'user' => Auth::user() ], 'admin'); } public function storeRumahIbadah(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/rumah-ibadah'); $foto = null; if (!empty($_FILES['foto']['name'])) { $foto = $this->uploadFile($_FILES['foto'], 'rumah_ibadah'); } $data = [ 'id_user' => !empty($_POST['id_user']) ? (int)$_POST['id_user'] : null, 'id_kelurahan' => (int)$_POST['id_kelurahan'], 'id_jenis' => (int)$_POST['id_jenis'], 'nama' => trim($_POST['nama']), 'alamat' => trim($_POST['alamat']), 'lat' => (float)$_POST['lat'], 'lng' => (float)$_POST['lng'], 'radius_meter' => (int)($_POST['radius_meter'] ?? 500), 'nama_ketua' => trim($_POST['nama_ketua'] ?? ''), 'no_hp_pengurus'=> trim($_POST['no_hp_pengurus'] ?? ''), 'foto' => $foto, 'is_active' => 1 ]; $this->db->insert('rumah_ibadah', $data); Auth::logActivity(Auth::id(), Auth::user()['nama'], 'superadmin', 'Tambah Rumah Ibadah', 'rumah_ibadah', 'Menambah: ' . $data['nama']); $this->flash('success', 'Rumah ibadah berhasil ditambahkan.'); $this->redirect(APP_URL . '/admin/rumah-ibadah'); } public function updateRumahIbadah(int $id): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/rumah-ibadah'); $data = [ 'id_user' => !empty($_POST['id_user']) ? (int)$_POST['id_user'] : null, 'id_kelurahan' => (int)$_POST['id_kelurahan'], 'id_jenis' => (int)$_POST['id_jenis'], 'nama' => trim($_POST['nama']), 'alamat' => trim($_POST['alamat']), 'lat' => (float)$_POST['lat'], 'lng' => (float)$_POST['lng'], 'radius_meter' => (int)($_POST['radius_meter'] ?? 500), 'nama_ketua' => trim($_POST['nama_ketua'] ?? ''), 'no_hp_pengurus'=> trim($_POST['no_hp_pengurus'] ?? ''), 'is_active' => (int)$_POST['is_active'] ]; if (!empty($_FILES['foto']['name'])) { $data['foto'] = $this->uploadFile($_FILES['foto'], 'rumah_ibadah'); } $this->db->update('rumah_ibadah', $data, 'id = ?', [$id]); $this->flash('success', 'Rumah ibadah berhasil diperbarui.'); $this->redirect(APP_URL . '/admin/rumah-ibadah'); } public function deleteRumahIbadah(int $id): void { $this->db->update('rumah_ibadah', ['is_active' => 0], 'id = ?', [$id]); $this->flash('success', 'Rumah ibadah berhasil dinonaktifkan.'); $this->redirect(APP_URL . '/admin/rumah-ibadah'); } public function wilayah(): void { $kecamatan = $this->db->fetchAll("SELECT k.*, COUNT(kel.id) as jml_kel FROM kecamatan k LEFT JOIN kelurahan kel ON kel.id_kecamatan=k.id GROUP BY k.id ORDER BY k.nama"); $kelurahan = $this->db->fetchAll("SELECT kel.*, kec.nama as kecamatan FROM kelurahan kel JOIN kecamatan kec ON kel.id_kecamatan=kec.id ORDER BY kec.nama, kel.nama"); $this->view('admin.wilayah', [ 'title' => 'Manajemen Wilayah', 'kecamatan' => $kecamatan, 'kelurahan' => $kelurahan, 'user' => Auth::user() ], 'admin'); } public function storeKecamatan(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/wilayah'); $this->db->insert('kecamatan', [ 'nama' => trim($_POST['nama']), 'kode' => trim($_POST['kode'] ?? ''), 'lat' => (float)($_POST['lat'] ?? 0), 'lng' => (float)($_POST['lng'] ?? 0) ]); $this->flash('success', 'Kecamatan berhasil ditambahkan.'); $this->redirect(APP_URL . '/admin/wilayah'); } public function storeKelurahan(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/wilayah'); $this->db->insert('kelurahan', [ 'id_kecamatan' => (int)$_POST['id_kecamatan'], 'nama' => trim($_POST['nama']), 'kode' => trim($_POST['kode'] ?? ''), 'lat' => (float)($_POST['lat'] ?? 0), 'lng' => (float)($_POST['lng'] ?? 0) ]); $this->flash('success', 'Kelurahan berhasil ditambahkan.'); $this->redirect(APP_URL . '/admin/wilayah'); } public function kategoriBantuan(): void { $data = $this->db->fetchAll("SELECT * FROM kategori_bantuan ORDER BY nama"); $this->view('admin.kategori_bantuan', [ 'title' => 'Manajemen Kategori Bantuan', 'data' => $data, 'user' => Auth::user() ], 'admin'); } public function storeKategoriBantuan(): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/kategori-bantuan'); $this->db->insert('kategori_bantuan', [ 'nama' => trim($_POST['nama']), 'deskripsi' => trim($_POST['deskripsi'] ?? ''), 'satuan' => trim($_POST['satuan'] ?? ''), 'is_active' => 1 ]); $this->flash('success', 'Kategori bantuan berhasil ditambahkan.'); $this->redirect(APP_URL . '/admin/kategori-bantuan'); } public function updateKategoriBantuan(int $id): void { if (!$this->verifyCsrf()) $this->redirect(APP_URL . '/admin/kategori-bantuan'); $this->db->update('kategori_bantuan', [ 'nama' => trim($_POST['nama']), 'deskripsi' => trim($_POST['deskripsi'] ?? ''), 'satuan' => trim($_POST['satuan'] ?? ''), 'is_active' => (int)$_POST['is_active'] ], 'id = ?', [$id]); $this->flash('success', 'Kategori bantuan berhasil diperbarui.'); $this->redirect(APP_URL . '/admin/kategori-bantuan'); } public function monitoring(): void { $warga = $this->db->fetchAll(" SELECT w.*, k.nama as kelurahan, kec.nama as kecamatan, ri.nama as rumah_ibadah_nama 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' ORDER BY w.level_prioritas DESC, w.skor_prioritas DESC "); $this->view('admin.monitoring', [ 'title' => 'Monitoring Data Kemiskinan', 'warga' => $warga, 'user' => Auth::user() ], 'admin'); } public function blindSpot(): void { $data = $this->db->fetchAll(" SELECT w.*, k.nama as kelurahan, kec.nama as kecamatan FROM warga_miskin w JOIN kelurahan k ON w.id_kelurahan = k.id JOIN kecamatan kec ON k.id_kecamatan = kec.id WHERE w.is_blind_spot = 1 AND w.status = 'aktif' ORDER BY w.level_prioritas DESC "); $this->view('admin.blind_spot', [ 'title' => 'Monitoring Blind Spot', 'data' => $data, 'user' => Auth::user() ], 'admin'); } public function aktivitas(): void { $data = $this->db->fetchAll(" SELECT * FROM aktivitas_sistem ORDER BY created_at DESC LIMIT 200 "); $this->view('admin.aktivitas', [ 'title' => 'Aktivitas Sistem', 'data' => $data, 'user' => Auth::user() ], 'admin'); } // API: Stats untuk dashboard public function apiStats(): void { $data = $this->db->fetchOne(" SELECT (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif') as total_warga, (SELECT COUNT(*) FROM warga_miskin WHERE status='aktif' AND level_prioritas='Tinggi') as prioritas_tinggi, (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' AND MONTH(tanggal_penyaluran)=MONTH(CURDATE())) as bantuan_bulan_ini, (SELECT COUNT(*) FROM laporan_masyarakat WHERE status='menunggu') as laporan_pending "); $this->json(['success' => true, 'data' => $data]); } // API: Data peta public function apiPeta(): void { $warga = $this->db->fetchAll(" SELECT w.id, w.nama, w.lat, w.lng, w.status_kemiskinan, w.level_prioritas, w.is_blind_spot, k.nama as kelurahan, ri.nama as rumah_ibadah FROM warga_miskin w JOIN kelurahan k ON w.id_kelurahan = k.id LEFT JOIN rumah_ibadah ri ON w.id_rumah_ibadah = ri.id WHERE w.status = 'aktif' "); $ri = $this->db->fetchAll(" SELECT ri.id, ri.nama, ri.lat, ri.lng, ri.radius_meter, ri.alamat, ji.nama as jenis, k.nama as kelurahan FROM rumah_ibadah ri JOIN jenis_rumah_ibadah ji ON ri.id_jenis = ji.id JOIN kelurahan k ON ri.id_kelurahan = k.id WHERE ri.is_active = 1 "); $laporan = $this->db->fetchAll(" SELECT id, kode_laporan, nama_warga, lat, lng, status, created_at FROM laporan_masyarakat WHERE lat != 0 AND lng != 0 AND status IN ('menunggu','diproses') "); $this->json(['warga' => $warga, 'rumah_ibadah' => $ri, 'laporan' => $laporan]); } }