edit files

This commit is contained in:
2026-06-10 19:48:00 +07:00
parent 8d26d3842d
commit 13cc0c9bb7
63 changed files with 8750 additions and 0 deletions
@@ -0,0 +1,40 @@
<?php
// api/controllers/AuthController.php
class AuthController {
public function index(): void { jsonError('Method tidak diizinkan', 405); }
public function show(string $id): void { jsonError('Method tidak diizinkan', 405); }
public function update(string $id): void { jsonError('Method tidak diizinkan', 405); }
public function destroy(string $id): void { jsonError('Method tidak diizinkan', 405); }
/** POST /api/auth — login */
public function store(): void {
$body = getBody();
$errs = required($body, ['username', 'password']);
if ($errs) jsonError(implode('; ', $errs));
$user = login(sanitize($body['username']), $body['password']);
if (!$user) jsonError('Username atau password salah', 401);
startSession();
$_SESSION['user'] = $user;
logActivity('login', 'user', $user['id']);
jsonSuccess($user, 'Login berhasil');
}
/** DELETE /api/auth/:any — logout */
public function logout(): void {
startSession();
$user = currentUser();
if ($user) logActivity('logout', 'user', $user['id']);
session_destroy();
jsonSuccess([], 'Logout berhasil');
}
/** GET /api/auth/me */
public function me(): void {
$user = currentUser();
if (!$user) jsonError('Tidak login', 401);
jsonSuccess($user);
}
}
@@ -0,0 +1,61 @@
<?php
// api/controllers/BantuanController.php
class BantuanController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$rows = $db->query("SELECT * FROM bantuan ORDER BY kategori, nama_bantuan")->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$stmt = $db->prepare("SELECT * FROM bantuan WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) jsonError('Jenis bantuan tidak ditemukan', 404);
jsonSuccess($row);
}
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$errs = required($body, ['nama_bantuan', 'kategori']);
if ($errs) jsonError(implode('; ', $errs));
$db = getDB();
$db->prepare(
"INSERT INTO bantuan (nama_bantuan, kategori, sumber, deskripsi, satuan)
VALUES (?,?,?,?,?)"
)->execute([
sanitize($body['nama_bantuan']),
$body['kategori'],
sanitize($body['sumber'] ?? ''),
sanitize($body['deskripsi'] ?? ''),
sanitize($body['satuan'] ?? ''),
]);
jsonSuccess(['id' => getDB()->lastInsertId()], 'Jenis bantuan ditambahkan', 201);
}
public function update(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$db = getDB();
$db->prepare(
"UPDATE bantuan SET nama_bantuan=?, kategori=?, sumber=?, deskripsi=?, satuan=?, aktif=?
WHERE id=?"
)->execute([
sanitize($body['nama_bantuan'] ?? ''),
$body['kategori'] ?? 'lainnya',
sanitize($body['sumber'] ?? ''),
sanitize($body['deskripsi'] ?? ''),
sanitize($body['satuan'] ?? ''),
(int)($body['aktif'] ?? 1),
$id,
]);
jsonSuccess([], 'Jenis bantuan diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$db->prepare("DELETE FROM bantuan WHERE id = ?")->execute([$id]);
jsonSuccess([], 'Jenis bantuan dihapus');
}
}
@@ -0,0 +1,44 @@
<?php
// api/controllers/ChatController.php
class ChatController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$roomId = $_GET['room'] ?? null;
$sql = "SELECT c.id, c.pesan, c.created_at, u.nama_lengkap, u.id_role,
cr.nama AS nama_room
FROM chat c JOIN user u ON c.id_user=u.id
LEFT JOIN chat_room cr ON c.id_room=cr.id
WHERE " . ($roomId ? "c.id_room=?" : "c.id_room IS NULL") .
" ORDER BY c.created_at DESC LIMIT 50";
$stmt = $db->prepare($sql);
$roomId ? $stmt->execute([$roomId]) : $stmt->execute();
jsonSuccess(array_reverse($stmt->fetchAll()));
}
public function show(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$rows = $db->query("SELECT id, nama, deskripsi FROM chat_room ORDER BY nama")->fetchAll();
jsonSuccess($rows);
}
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$user = currentUser();
$body = getBody();
$errs = required($body, ['pesan']);
if ($errs) jsonError(implode('; ',$errs));
$db = getDB();
$db->prepare("INSERT INTO chat (id_user, id_room, pesan) VALUES (?,?,?)")->execute([
$user['id'],
$body['id_room'] ?? null,
sanitize($body['pesan']),
]);
jsonSuccess(['id' => $db->lastInsertId()], 'Pesan terkirim', 201);
}
public function update(string $id): void { jsonError('N/A',405); }
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
getDB()->prepare("DELETE FROM chat WHERE id=?")->execute([$id]);
jsonSuccess([],'Pesan dihapus');
}
}
@@ -0,0 +1,80 @@
<?php
// api/controllers/DashboardController.php
class DashboardController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
// Ringkasan utama
$stat = $db->query(
"SELECT
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup') AS total_penduduk,
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi='miskin') AS total_miskin,
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi='rentan') AS total_rentan,
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi='mampu') AS total_mampu,
(SELECT COUNT(*) FROM keluarga) AS total_kk,
(SELECT COUNT(*) FROM rumah_ibadah WHERE aktif=1) AS total_ri,
(SELECT COUNT(*) FROM laporan WHERE status='pending') AS laporan_pending,
(SELECT COUNT(*) FROM laporan WHERE urgensi='darurat' AND status NOT IN('selesai')) AS laporan_darurat,
(SELECT COUNT(DISTINCT nik) FROM histori_bantuan WHERE status_penyaluran='disalurkan') AS sudah_dibantu,
(SELECT COUNT(*) FROM histori_bantuan WHERE MONTH(tanggal)=MONTH(CURDATE()) AND YEAR(tanggal)=YEAR(CURDATE())) AS bantuan_bulan_ini"
)->fetch();
// Sebaran per kecamatan
$kec = $db->query(
"SELECT k.kecamatan,
COUNT(DISTINCT k.id) AS jumlah_kk,
SUM(CASE WHEN k.status_ekonomi='miskin' THEN 1 ELSE 0 END) AS miskin,
SUM(CASE WHEN k.status_ekonomi='rentan' THEN 1 ELSE 0 END) AS rentan
FROM keluarga k GROUP BY k.kecamatan ORDER BY miskin DESC"
)->fetchAll();
// Trend bantuan 6 bulan
$trend = $db->query(
"SELECT DATE_FORMAT(tanggal,'%Y-%m') AS bulan, COUNT(*) AS jumlah
FROM histori_bantuan
WHERE tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY bulan ORDER BY bulan"
)->fetchAll();
// Belum pernah dibantu (miskin)
$belum = $db->query(
"SELECT COUNT(*) AS c FROM penduduk p
WHERE p.status_ekonomi IN ('miskin','rentan')
AND p.status_hidup='hidup'
AND NOT EXISTS (SELECT 1 FROM histori_bantuan hb WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan')"
)->fetch()['c'];
// Prioritas darurat
$prioritas = $db->query(
"SELECT * FROM v_prioritas_bantuan
WHERE level_prioritas IN ('SANGAT TINGGI','TINGGI')
LIMIT 10"
)->fetchAll();
// Beban per RI
$bebanRI = $db->query(
"SELECT ri.nama, ri.jenis,
COUNT(DISTINCT k.id) AS jumlah_kk,
SUM(CASE WHEN k.status_ekonomi='miskin' THEN 1 ELSE 0 END) AS kk_miskin
FROM rumah_ibadah ri
LEFT JOIN keluarga k ON k.id_rumah_ibadah=ri.id
WHERE ri.aktif=1
GROUP BY ri.id ORDER BY kk_miskin DESC"
)->fetchAll();
jsonSuccess([
'statistik' => $stat,
'sebaran_kecamatan' => $kec,
'trend_bantuan' => $trend,
'belum_dibantu' => (int)$belum,
'prioritas_darurat' => $prioritas,
'beban_per_ri' => $bebanRI,
]);
}
public function show(string $id): void { jsonError('Not implemented', 501); }
public function store(): void { jsonError('Not implemented', 501); }
public function update(string $id): void { jsonError('Not implemented', 501); }
public function destroy(string $id): void { jsonError('Not implemented', 501); }
}
@@ -0,0 +1,121 @@
<?php
// api/controllers/HistoriController.php
class HistoriController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$sql = "SELECT hb.*, b.nama_bantuan, b.kategori,
p.nama_lengkap, p.status_ekonomi,
ri.nama AS nama_ri
FROM histori_bantuan hb
JOIN bantuan b ON hb.id_bantuan = b.id
JOIN penduduk p ON hb.nik = p.nik
LEFT JOIN rumah_ibadah ri ON hb.id_rumah_ibadah = ri.id
WHERE 1=1";
$params = [];
if (!empty($_GET['nik'])) {
$sql .= " AND hb.nik = ?"; $params[] = $_GET['nik'];
}
if (!empty($_GET['id_bantuan'])) {
$sql .= " AND hb.id_bantuan = ?"; $params[] = (int)$_GET['id_bantuan'];
}
if (!empty($_GET['status'])) {
$sql .= " AND hb.status_penyaluran = ?"; $params[] = $_GET['status'];
}
$sql .= " ORDER BY hb.tanggal DESC LIMIT 200";
$stmt = $db->prepare($sql);
$stmt->execute($params);
jsonSuccess($stmt->fetchAll());
}
public function show(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$stmt = $db->prepare(
"SELECT hb.*, b.nama_bantuan, b.kategori, p.nama_lengkap, ri.nama AS nama_ri
FROM histori_bantuan hb
JOIN bantuan b ON hb.id_bantuan=b.id
JOIN penduduk p ON hb.nik=p.nik
LEFT JOIN rumah_ibadah ri ON hb.id_rumah_ibadah=ri.id
WHERE hb.id=?"
);
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) jsonError('Histori tidak ditemukan', 404);
jsonSuccess($row);
}
/** POST /api/histori — catat penyaluran bantuan */
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$errs = required($body, ['nik', 'id_bantuan', 'tanggal']);
if ($errs) jsonError(implode('; ', $errs));
$user = currentUser();
$db = getDB();
// Cek penduduk ada
$chk = $db->prepare("SELECT nik FROM penduduk WHERE nik = ?");
$chk->execute([$body['nik']]);
if (!$chk->fetch()) jsonError('NIK penduduk tidak ditemukan', 404);
$db->prepare(
"INSERT INTO histori_bantuan
(nik, id_bantuan, id_rumah_ibadah, id_user, tanggal, jumlah, keterangan, status_penyaluran)
VALUES (?,?,?,?,?,?,?,?)"
)->execute([
$body['nik'],
(int)$body['id_bantuan'],
$body['id_rumah_ibadah'] ?? null,
$user['id'],
$body['tanggal'],
(float)($body['jumlah'] ?? 1),
sanitize($body['keterangan'] ?? ''),
$body['status_penyaluran'] ?? 'disalurkan',
]);
$newId = $db->lastInsertId();
logActivity('create_histori_bantuan', 'histori_bantuan', $newId, [], $body);
jsonSuccess(['id' => $newId], 'Histori bantuan dicatat', 201);
}
public function update(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$db = getDB();
$db->prepare(
"UPDATE histori_bantuan SET status_penyaluran=?, keterangan=?, jumlah=? WHERE id=?"
)->execute([
$body['status_penyaluran'] ?? 'disalurkan',
sanitize($body['keterangan'] ?? ''),
(float)($body['jumlah'] ?? 1),
$id,
]);
logActivity('update_histori_bantuan', 'histori_bantuan', $id, [], $body);
jsonSuccess([], 'Histori bantuan diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$db->prepare("DELETE FROM histori_bantuan WHERE id=?")->execute([$id]);
jsonSuccess([], 'Histori bantuan dihapus');
}
/** GET /api/histori/:nik/cek */
public function cek(string $nik): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$stmt = $db->prepare(
"SELECT COUNT(*) AS total,
GROUP_CONCAT(DISTINCT b.nama_bantuan ORDER BY b.nama_bantuan SEPARATOR ', ') AS jenis_bantuan,
MAX(hb.tanggal) AS terakhir_dibantu
FROM histori_bantuan hb
JOIN bantuan b ON hb.id_bantuan=b.id
WHERE hb.nik=? AND hb.status_penyaluran='disalurkan'"
);
$stmt->execute([$nik]);
jsonSuccess($stmt->fetch());
}
}
@@ -0,0 +1,123 @@
<?php
// api/controllers/KeluargaController.php
class KeluargaController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$sql = "SELECT k.*, ri.nama AS nama_ri,
COUNT(p.nik) AS jumlah_anggota,
SUM(p.penghasilan) AS total_penghasilan,
COUNT(DISTINCT hb.id) AS total_bantuan
FROM keluarga k
LEFT JOIN rumah_ibadah ri ON k.id_rumah_ibadah = ri.id
LEFT JOIN penduduk p ON p.id_keluarga = k.id AND p.status_hidup='hidup'
LEFT JOIN histori_bantuan hb ON hb.nik = p.nik
WHERE 1=1";
$params = [];
if (!empty($_GET['status_ekonomi'])) {
$sql .= " AND k.status_ekonomi = ?"; $params[] = $_GET['status_ekonomi'];
}
if (!empty($_GET['kecamatan'])) {
$sql .= " AND k.kecamatan = ?"; $params[] = $_GET['kecamatan'];
}
$sql .= " GROUP BY k.id ORDER BY k.status_ekonomi, k.nama_kepala";
$stmt = $db->prepare($sql);
$stmt->execute($params);
jsonSuccess($stmt->fetchAll());
}
public function show(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$stmt = $db->prepare(
"SELECT k.*, ri.nama AS nama_ri FROM keluarga k
LEFT JOIN rumah_ibadah ri ON k.id_rumah_ibadah = ri.id
WHERE k.id = ?"
);
$stmt->execute([$id]);
$kk = $stmt->fetch();
if (!$kk) jsonError('Keluarga tidak ditemukan', 404);
$anggota = $db->prepare(
"SELECT nik, nama_lengkap, jenis_kelamin, status_keluarga,
TIMESTAMPDIFF(YEAR, tanggal_lahir, CURDATE()) AS umur,
pekerjaan, penghasilan, status_ekonomi, kondisi_kesehatan,
pendidikan_terakhir, status_pendidikan, status_hidup
FROM penduduk WHERE id_keluarga = ? ORDER BY status_keluarga"
);
$anggota->execute([$id]);
$kk['anggota'] = $anggota->fetchAll();
jsonSuccess($kk);
}
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$errs = required($body, ['no_kk', 'nama_kepala']);
if ($errs) jsonError(implode('; ', $errs));
if (!preg_match('/^\d{16}$/', $body['no_kk'])) jsonError('No KK harus 16 digit');
$db = getDB();
$chk = $db->prepare("SELECT id FROM keluarga WHERE no_kk = ?");
$chk->execute([$body['no_kk']]);
if ($chk->fetch()) jsonError('No KK sudah terdaftar', 409);
$db->prepare(
"INSERT INTO keluarga (no_kk, id_rumah_ibadah, nama_kepala, alamat, rt, rw,
kelurahan, kecamatan, lat, lng, status_ekonomi)
VALUES (?,?,?,?,?,?,?,?,?,?,?)"
)->execute([
$body['no_kk'],
$body['id_rumah_ibadah'] ?? null,
sanitize($body['nama_kepala']),
sanitize($body['alamat'] ?? ''),
sanitize($body['rt'] ?? ''),
sanitize($body['rw'] ?? ''),
sanitize($body['kelurahan'] ?? ''),
sanitize($body['kecamatan'] ?? ''),
$body['lat'] ?? null,
$body['lng'] ?? null,
$body['status_ekonomi'] ?? 'rentan',
]);
$newId = $db->lastInsertId();
logActivity('create_keluarga', 'keluarga', $newId, [], $body);
jsonSuccess(['id' => $newId], 'Data keluarga berhasil disimpan', 201);
}
public function update(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$body = getBody();
$db->prepare(
"UPDATE keluarga SET id_rumah_ibadah=?, nama_kepala=?, alamat=?,
rt=?, rw=?, kelurahan=?, kecamatan=?, lat=?, lng=?, status_ekonomi=?
WHERE id=?"
)->execute([
$body['id_rumah_ibadah'] ?? null,
sanitize($body['nama_kepala'] ?? ''),
sanitize($body['alamat'] ?? ''),
sanitize($body['rt'] ?? ''),
sanitize($body['rw'] ?? ''),
sanitize($body['kelurahan'] ?? ''),
sanitize($body['kecamatan'] ?? ''),
$body['lat'] ?? null,
$body['lng'] ?? null,
$body['status_ekonomi'] ?? 'rentan',
$id,
]);
logActivity('update_keluarga', 'keluarga', $id, [], $body);
jsonSuccess([], 'Data keluarga diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$chk = $db->prepare("SELECT COUNT(*) AS c FROM penduduk WHERE id_keluarga = ?");
$chk->execute([$id]);
if ($chk->fetch()['c'] > 0) jsonError('Tidak bisa hapus — masih ada anggota keluarga', 409);
$db->prepare("DELETE FROM keluarga WHERE id = ?")->execute([$id]);
logActivity('delete_keluarga', 'keluarga', $id);
jsonSuccess([], 'Data keluarga dihapus');
}
}
@@ -0,0 +1,91 @@
<?php
// api/controllers/LaporanController.php
class LaporanController {
public function index(): void {
$user = currentUser();
// Masyarakat bisa lihat laporan mereka sendiri; pengurus+ lihat semua
$db = getDB();
$sql = "SELECT l.*, ri.nama AS nama_ri, p.nama_lengkap AS nama_terdampak
FROM laporan l
LEFT JOIN rumah_ibadah ri ON l.id_rumah_ibadah=ri.id
LEFT JOIN penduduk p ON l.nik_terdampak=p.nik
WHERE 1=1";
$params = [];
if (!empty($_GET['status'])) { $sql .= " AND l.status=?"; $params[] = $_GET['status']; }
if (!empty($_GET['urgensi'])) { $sql .= " AND l.urgensi=?"; $params[] = $_GET['urgensi']; }
$sql .= " ORDER BY FIELD(l.urgensi,'darurat','tinggi','sedang','rendah'), l.created_at DESC LIMIT 100";
$stmt = $db->prepare($sql);
$stmt->execute($params);
jsonSuccess($stmt->fetchAll());
}
public function show(string $id): void {
$db = getDB();
$stmt = $db->prepare(
"SELECT l.*, ri.nama AS nama_ri, p.nama_lengkap AS nama_terdampak
FROM laporan l
LEFT JOIN rumah_ibadah ri ON l.id_rumah_ibadah=ri.id
LEFT JOIN penduduk p ON l.nik_terdampak=p.nik
WHERE l.id=?"
);
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) jsonError('Laporan tidak ditemukan', 404);
jsonSuccess($row);
}
/** POST /api/laporan — siapa saja bisa lapor */
public function store(): void {
$body = getBody();
$errs = required($body, ['deskripsi']);
if ($errs) jsonError(implode('; ', $errs));
$db = getDB();
$db->prepare(
"INSERT INTO laporan
(nama_pelapor, no_hp_pelapor, nik_terdampak, id_rumah_ibadah,
deskripsi, lat, lng, alamat_laporan, urgensi)
VALUES (?,?,?,?,?,?,?,?,?)"
)->execute([
sanitize($body['nama_pelapor'] ?? ''),
sanitize($body['no_hp_pelapor'] ?? ''),
$body['nik_terdampak'] ?? null,
$body['id_rumah_ibadah'] ?? null,
sanitize($body['deskripsi']),
$body['lat'] ?? null,
$body['lng'] ?? null,
sanitize($body['alamat_laporan'] ?? ''),
$body['urgensi'] ?? 'sedang',
]);
$newId = $db->lastInsertId();
jsonSuccess(['id' => $newId], 'Laporan berhasil dikirim. Terima kasih!', 201);
}
/** PUT /api/laporan/:id — verifikasi / update status */
public function update(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$user = currentUser();
$body = getBody();
$db = getDB();
$db->prepare(
"UPDATE laporan SET status=?, catatan_verifikasi=?,
id_verifikator=?, tgl_verifikasi=NOW()
WHERE id=?"
)->execute([
$body['status'] ?? 'diverifikasi',
sanitize($body['catatan_verifikasi'] ?? ''),
$user['id'],
$id,
]);
logActivity('update_laporan', 'laporan', $id, [], $body);
jsonSuccess([], 'Status laporan diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
getDB()->prepare("DELETE FROM laporan WHERE id=?")->execute([$id]);
jsonSuccess([], 'Laporan dihapus');
}
}
@@ -0,0 +1,18 @@
<?php
// api/controllers/LogController.php
class LogController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PIMPINAN]);
$db = getDB();
$rows = $db->query(
"SELECT l.*, u.nama_lengkap FROM log_aktivitas l
LEFT JOIN user u ON l.id_user=u.id
ORDER BY l.created_at DESC LIMIT 100"
)->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void { jsonError('N/A',501); }
public function store(): void { jsonError('N/A',405); }
public function update(string $id): void { jsonError('N/A',405); }
public function destroy(string $id): void { jsonError('N/A',405); }
}
@@ -0,0 +1,239 @@
<?php
// api/controllers/PendudukController.php
class PendudukController {
/** GET /api/penduduk */
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$where = ['p.status_hidup = ?'];
$params = ['hidup'];
// Filter
if (!empty($_GET['status_ekonomi'])) {
$where[] = 'p.status_ekonomi = ?';
$params[] = $_GET['status_ekonomi'];
}
if (!empty($_GET['kecamatan'])) {
$where[] = 'k.kecamatan = ?';
$params[] = $_GET['kecamatan'];
}
if (!empty($_GET['id_keluarga'])) {
$where[] = 'p.id_keluarga = ?';
$params[] = (int)$_GET['id_keluarga'];
}
if (!empty($_GET['q'])) {
$where[] = '(p.nama_lengkap LIKE ? OR p.nik LIKE ?)';
$q = '%' . $_GET['q'] . '%';
$params[] = $q; $params[] = $q;
}
$sql = "SELECT p.nik, p.nama_lengkap, p.jenis_kelamin, p.tanggal_lahir,
TIMESTAMPDIFF(YEAR, p.tanggal_lahir, CURDATE()) AS umur,
p.status_keluarga, p.status_perkawinan, p.status_hidup,
p.agama, p.pekerjaan, p.penghasilan, p.status_ekonomi,
p.pendidikan_terakhir, p.status_pendidikan, p.kondisi_kesehatan,
p.lat, p.lng, p.id_keluarga, p.updated_at,
k.no_kk, k.nama_kepala, k.alamat, k.rt, k.rw, k.kelurahan, k.kecamatan,
ri.nama AS nama_ri, ri.jenis AS jenis_ri
FROM penduduk p
JOIN keluarga k ON p.id_keluarga = k.id
LEFT JOIN rumah_ibadah ri ON k.id_rumah_ibadah = ri.id
WHERE " . implode(' AND ', $where) . "
ORDER BY p.status_ekonomi, p.nama_lengkap";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
jsonSuccess($rows);
}
/** GET /api/penduduk/:nik */
public function show(string $nik): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$stmt = $db->prepare(
"SELECT p.*, TIMESTAMPDIFF(YEAR, p.tanggal_lahir, CURDATE()) AS umur,
k.no_kk, k.nama_kepala, k.alamat, k.rt, k.rw, k.kelurahan, k.kecamatan,
ri.nama AS nama_ri, ri.jenis AS jenis_ri
FROM penduduk p
JOIN keluarga k ON p.id_keluarga = k.id
LEFT JOIN rumah_ibadah ri ON k.id_rumah_ibadah = ri.id
WHERE p.nik = ?"
);
$stmt->execute([$nik]);
$row = $stmt->fetch();
if (!$row) jsonError('Penduduk tidak ditemukan', 404);
// Histori bantuan
$stmt2 = $db->prepare(
"SELECT hb.*, b.nama_bantuan, b.kategori, ri.nama AS nama_ri
FROM histori_bantuan hb
JOIN bantuan b ON hb.id_bantuan = b.id
LEFT JOIN rumah_ibadah ri ON hb.id_rumah_ibadah = ri.id
WHERE hb.nik = ? ORDER BY hb.tanggal DESC"
);
$stmt2->execute([$nik]);
$row['histori_bantuan'] = $stmt2->fetchAll();
// Riwayat pendidikan
$stmt3 = $db->prepare("SELECT * FROM riwayat_pendidikan WHERE nik = ? ORDER BY tahun_masuk DESC");
$stmt3->execute([$nik]);
$row['riwayat_pendidikan'] = $stmt3->fetchAll();
// Peristiwa
$stmt4 = $db->prepare("SELECT * FROM peristiwa_kependudukan WHERE nik = ? ORDER BY tanggal DESC");
$stmt4->execute([$nik]);
$row['peristiwa'] = $stmt4->fetchAll();
jsonSuccess($row);
}
/** POST /api/penduduk */
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$errs = required($body, ['nik','id_keluarga','nama_lengkap','jenis_kelamin','tanggal_lahir']);
if ($errs) jsonError(implode('; ', $errs));
// Validasi NIK 16 digit
if (!preg_match('/^\d{16}$/', $body['nik'])) jsonError('NIK harus 16 digit angka');
$db = getDB();
// Cek duplikat
$chk = $db->prepare("SELECT nik FROM penduduk WHERE nik = ?");
$chk->execute([$body['nik']]);
if ($chk->fetch()) jsonError('NIK sudah terdaftar', 409);
// Hitung umur
$tgl = new DateTime($body['tanggal_lahir']);
$umur = (new DateTime())->diff($tgl)->y;
// Validasi pendidikan
$warnings = validasiPendidikan(
$umur,
$body['pendidikan_terakhir'] ?? 'tidak_sekolah',
$body['status_pendidikan'] ?? 'tidak_sekolah'
);
$db->prepare(
"INSERT INTO penduduk
(nik, id_keluarga, nama_lengkap, jenis_kelamin, tanggal_lahir, tempat_lahir,
status_keluarga, status_perkawinan, status_hidup, agama, pekerjaan, penghasilan,
status_ekonomi, pendidikan_terakhir, status_pendidikan, kondisi_kesehatan,
keterangan_kesehatan, lat, lng)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
)->execute([
$body['nik'],
(int)$body['id_keluarga'],
sanitize($body['nama_lengkap']),
$body['jenis_kelamin'],
$body['tanggal_lahir'],
sanitize($body['tempat_lahir'] ?? ''),
$body['status_keluarga'] ?? 'anak',
$body['status_perkawinan'] ?? 'belum_kawin',
$body['status_hidup'] ?? 'hidup',
$body['agama'] ?? null,
sanitize($body['pekerjaan'] ?? ''),
(int)($body['penghasilan'] ?? 0),
$body['status_ekonomi'] ?? 'rentan',
$body['pendidikan_terakhir'] ?? 'tidak_sekolah',
$body['status_pendidikan'] ?? 'tidak_sekolah',
$body['kondisi_kesehatan'] ?? 'sehat',
sanitize($body['keterangan_kesehatan'] ?? ''),
$body['lat'] ?? null,
$body['lng'] ?? null,
]);
logActivity('create_penduduk', 'penduduk', $body['nik'], [], $body);
jsonSuccess(['nik' => $body['nik'], 'warnings' => $warnings], 'Data penduduk berhasil disimpan', 201);
}
/** PUT /api/penduduk/:nik */
public function update(string $nik): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$old = $db->prepare("SELECT * FROM penduduk WHERE nik = ?");
$old->execute([$nik]);
$existing = $old->fetch();
if (!$existing) jsonError('Penduduk tidak ditemukan', 404);
$body = getBody();
$tgl = new DateTime($body['tanggal_lahir'] ?? $existing['tanggal_lahir']);
$umur = (new DateTime())->diff($tgl)->y;
$warnings = validasiPendidikan(
$umur,
$body['pendidikan_terakhir'] ?? $existing['pendidikan_terakhir'],
$body['status_pendidikan'] ?? $existing['status_pendidikan']
);
$db->prepare(
"UPDATE penduduk SET
nama_lengkap=?, jenis_kelamin=?, tanggal_lahir=?, tempat_lahir=?,
status_keluarga=?, status_perkawinan=?, status_hidup=?, agama=?,
pekerjaan=?, penghasilan=?, status_ekonomi=?,
pendidikan_terakhir=?, status_pendidikan=?,
kondisi_kesehatan=?, keterangan_kesehatan=?, lat=?, lng=?
WHERE nik=?"
)->execute([
sanitize($body['nama_lengkap'] ?? $existing['nama_lengkap']),
$body['jenis_kelamin'] ?? $existing['jenis_kelamin'],
$body['tanggal_lahir'] ?? $existing['tanggal_lahir'],
sanitize($body['tempat_lahir'] ?? $existing['tempat_lahir']),
$body['status_keluarga'] ?? $existing['status_keluarga'],
$body['status_perkawinan'] ?? $existing['status_perkawinan'],
$body['status_hidup'] ?? $existing['status_hidup'],
$body['agama'] ?? $existing['agama'],
sanitize($body['pekerjaan'] ?? $existing['pekerjaan']),
(int)($body['penghasilan'] ?? $existing['penghasilan']),
$body['status_ekonomi'] ?? $existing['status_ekonomi'],
$body['pendidikan_terakhir'] ?? $existing['pendidikan_terakhir'],
$body['status_pendidikan'] ?? $existing['status_pendidikan'],
$body['kondisi_kesehatan'] ?? $existing['kondisi_kesehatan'],
sanitize($body['keterangan_kesehatan'] ?? $existing['keterangan_kesehatan']),
$body['lat'] ?? $existing['lat'],
$body['lng'] ?? $existing['lng'],
$nik,
]);
logActivity('update_penduduk', 'penduduk', $nik, $existing, $body);
jsonSuccess(['nik' => $nik, 'warnings' => $warnings], 'Data penduduk berhasil diperbarui');
}
/** DELETE /api/penduduk/:nik */
public function destroy(string $nik): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$stmt = $db->prepare("SELECT nik FROM penduduk WHERE nik = ?");
$stmt->execute([$nik]);
if (!$stmt->fetch()) jsonError('Penduduk tidak ditemukan', 404);
$db->prepare("DELETE FROM penduduk WHERE nik = ?")->execute([$nik]);
logActivity('delete_penduduk', 'penduduk', $nik);
jsonSuccess([], 'Data penduduk dihapus');
}
/** GET /api/penduduk/:nik/validasi-pendidikan */
public function validasiPendidikan(string $nik): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$db = getDB();
$stmt = $db->prepare(
"SELECT nik, nama_lengkap,
TIMESTAMPDIFF(YEAR, tanggal_lahir, CURDATE()) AS umur,
pendidikan_terakhir, status_pendidikan
FROM penduduk WHERE nik = ?"
);
$stmt->execute([$nik]);
$row = $stmt->fetch();
if (!$row) jsonError('Penduduk tidak ditemukan', 404);
$warnings = validasiPendidikan($row['umur'], $row['pendidikan_terakhir'], $row['status_pendidikan']);
jsonSuccess(['penduduk' => $row, 'warnings' => $warnings]);
}
}
@@ -0,0 +1,53 @@
<?php
// api/controllers/PeristiwaController.php
class PeristiwaController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$rows = $db->query(
"SELECT pk.*, p.nama_lengkap FROM peristiwa_kependudukan pk
JOIN penduduk p ON pk.nik=p.nik
ORDER BY pk.tanggal DESC LIMIT 100"
)->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void { jsonError('N/A',501); }
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$user = currentUser();
$body = getBody();
$errs = required($body, ['nik','jenis','tanggal']);
if ($errs) jsonError(implode('; ',$errs));
$db = getDB();
$db->prepare(
"INSERT INTO peristiwa_kependudukan (nik,jenis,tanggal,keterangan,id_user)
VALUES (?,?,?,?,?)"
)->execute([
$body['nik'], $body['jenis'], $body['tanggal'],
sanitize($body['keterangan'] ?? ''), $user['id'],
]);
// Otomatis update status penduduk
switch ($body['jenis']) {
case 'kematian':
$db->prepare("UPDATE penduduk SET status_hidup='meninggal' WHERE nik=?")->execute([$body['nik']]);
break;
case 'pernikahan':
$db->prepare("UPDATE penduduk SET status_perkawinan='kawin' WHERE nik=?")->execute([$body['nik']]);
break;
case 'perceraian':
$db->prepare("UPDATE penduduk SET status_perkawinan='cerai_hidup' WHERE nik=?")->execute([$body['nik']]);
break;
}
logActivity('peristiwa_'.$body['jenis'], 'penduduk', $body['nik'], [], $body);
jsonSuccess(['id' => $db->lastInsertId()], 'Peristiwa dicatat', 201);
}
public function update(string $id): void { jsonError('N/A',405); }
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
getDB()->prepare("DELETE FROM peristiwa_kependudukan WHERE id=?")->execute([$id]);
jsonSuccess([],'Peristiwa dihapus');
}
}
@@ -0,0 +1,80 @@
<?php
// api/controllers/PetaController.php
// Returns GeoJSON-ready data for Leaflet
class PetaController {
public function index(): void {
$db = getDB();
$layer = $_GET['layer'] ?? 'penduduk';
if ($layer === 'rumah_ibadah') {
$rows = $db->query("SELECT id,nama,jenis,lat,lng,radius_meter,alamat FROM rumah_ibadah WHERE aktif=1 AND lat IS NOT NULL")->fetchAll();
$features = [];
foreach ($rows as $r) {
$features[] = [
'type' => 'Feature',
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['lng'],(float)$r['lat']]],
'properties' => $r,
];
}
jsonSuccess(['type'=>'FeatureCollection','features'=>$features]);
return;
}
if ($layer === 'laporan') {
$rows = $db->query(
"SELECT id,nama_pelapor,deskripsi,lat,lng,urgensi,status,alamat_laporan FROM laporan
WHERE lat IS NOT NULL ORDER BY FIELD(urgensi,'darurat','tinggi','sedang','rendah')"
)->fetchAll();
$features = [];
foreach ($rows as $r) {
$features[] = [
'type' => 'Feature',
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['lng'],(float)$r['lat']]],
'properties' => $r,
];
}
jsonSuccess(['type'=>'FeatureCollection','features'=>$features]);
return;
}
// Default: penduduk (keluarga sebagai titik)
$sql = "SELECT k.id, k.no_kk, k.nama_kepala, k.alamat, k.kecamatan,
k.lat, k.lng, k.status_ekonomi,
ri.nama AS nama_ri,
COUNT(p.nik) AS jumlah_anggota,
COUNT(DISTINCT hb.id) AS total_bantuan
FROM keluarga k
LEFT JOIN rumah_ibadah ri ON k.id_rumah_ibadah=ri.id
LEFT JOIN penduduk p ON p.id_keluarga=k.id AND p.status_hidup='hidup'
LEFT JOIN histori_bantuan hb ON hb.nik=p.nik
WHERE k.lat IS NOT NULL";
$params = [];
if (!empty($_GET['status_ekonomi'])) {
$sql .= " AND k.status_ekonomi=?"; $params[] = $_GET['status_ekonomi'];
}
$sql .= " GROUP BY k.id";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
$features = [];
foreach ($rows as $r) {
$color = match($r['status_ekonomi']) {
'miskin' => '#ef4444',
'rentan' => '#f59e0b',
default => '#22c55e',
};
$r['color'] = $color;
$features[] = [
'type' => 'Feature',
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['lng'],(float)$r['lat']]],
'properties' => $r,
];
}
jsonSuccess(['type'=>'FeatureCollection','features'=>$features]);
}
public function show(string $id): void { jsonError('Not implemented', 501); }
public function store(): void { jsonError('Not implemented', 501); }
public function update(string $id): void { jsonError('Not implemented', 501); }
public function destroy(string $id): void { jsonError('Not implemented', 501); }
}
@@ -0,0 +1,19 @@
<?php
// api/controllers/PrioritasController.php
class PrioritasController {
public function index(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS, ROLE_PIMPINAN]);
$db = getDB();
$rows = $db->query("SELECT * FROM v_prioritas_bantuan ORDER BY FIELD(level_prioritas,'SANGAT TINGGI','TINGGI','SEDANG','RENDAH'), jumlah_dibantu ASC")->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void { jsonError('N/A',501); }
public function store(): void { jsonError('N/A',501); }
public function update(string $id): void { jsonError('N/A',501); }
public function destroy(string $id): void { jsonError('N/A',501); }
}
// ----------------------------------------------------------------
// api/controllers/ChatController.php (in same file for brevity)
// ----------------------------------------------------------------
// Normally in its own file — included separately below
@@ -0,0 +1,73 @@
<?php
// api/controllers/RumahIbadahController.php
class RumahIbadahController {
public function index(): void {
$db = getDB();
$rows = $db->query(
"SELECT ri.*, u.nama_lengkap AS nama_pengurus,
COUNT(DISTINCT k.id) AS jumlah_kk,
COUNT(DISTINCT p.nik) AS jumlah_penduduk
FROM rumah_ibadah ri
LEFT JOIN user u ON ri.id_user=u.id
LEFT JOIN keluarga k ON k.id_rumah_ibadah=ri.id
LEFT JOIN penduduk p ON p.id_keluarga=k.id AND p.status_hidup='hidup'
WHERE ri.aktif=1
GROUP BY ri.id ORDER BY ri.jenis, ri.nama"
)->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void {
$db = getDB();
$stmt = $db->prepare("SELECT ri.*, u.nama_lengkap AS nama_pengurus FROM rumah_ibadah ri LEFT JOIN user u ON ri.id_user=u.id WHERE ri.id=?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) jsonError('Rumah ibadah tidak ditemukan', 404);
jsonSuccess($row);
}
public function store(): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$errs = required($body, ['nama','jenis','lat','lng']);
if ($errs) jsonError(implode('; ',$errs));
$db = getDB();
$db->prepare(
"INSERT INTO rumah_ibadah (id_user,nama,jenis,alamat,lat,lng,radius_meter,kontak,kecamatan,kelurahan)
VALUES (?,?,?,?,?,?,?,?,?,?)"
)->execute([
$body['id_user'] ?? null,
sanitize($body['nama']),
$body['jenis'],
sanitize($body['alamat'] ?? ''),
$body['lat'], $body['lng'],
(int)($body['radius_meter'] ?? 300),
sanitize($body['kontak'] ?? ''),
sanitize($body['kecamatan'] ?? ''),
sanitize($body['kelurahan'] ?? ''),
]);
jsonSuccess(['id' => $db->lastInsertId()], 'Rumah ibadah ditambahkan', 201);
}
public function update(string $id): void {
requireAuth([ROLE_ADMIN, ROLE_PENGURUS]);
$body = getBody();
$db = getDB();
$db->prepare(
"UPDATE rumah_ibadah SET nama=?,jenis=?,alamat=?,lat=?,lng=?,radius_meter=?,kontak=?,kecamatan=?,kelurahan=? WHERE id=?"
)->execute([
sanitize($body['nama'] ?? ''),
$body['jenis'] ?? 'Masjid',
sanitize($body['alamat'] ?? ''),
$body['lat'] ?? 0, $body['lng'] ?? 0,
(int)($body['radius_meter'] ?? 300),
sanitize($body['kontak'] ?? ''),
sanitize($body['kecamatan'] ?? ''),
sanitize($body['kelurahan'] ?? ''),
$id,
]);
jsonSuccess([], 'Rumah ibadah diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
getDB()->prepare("UPDATE rumah_ibadah SET aktif=0 WHERE id=?")->execute([$id]);
jsonSuccess([], 'Rumah ibadah dinonaktifkan');
}
}
@@ -0,0 +1,74 @@
<?php
// api/controllers/UserController.php
class UserController {
public function index(): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$rows = $db->query(
"SELECT u.id, u.username, u.nama_lengkap, u.email, u.no_hp,
u.aktif, u.last_login, u.created_at, r.nama_role
FROM user u JOIN role r ON u.id_role=r.id ORDER BY r.id, u.nama_lengkap"
)->fetchAll();
jsonSuccess($rows);
}
public function show(string $id): void {
requireAuth([ROLE_ADMIN]);
$db = getDB();
$stmt = $db->prepare(
"SELECT u.id, u.username, u.nama_lengkap, u.email, u.no_hp, u.aktif, r.nama_role
FROM user u JOIN role r ON u.id_role=r.id WHERE u.id=?"
);
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) jsonError('User tidak ditemukan',404);
jsonSuccess($row);
}
public function store(): void {
requireAuth([ROLE_ADMIN]);
$body = getBody();
$errs = required($body, ['username','password','nama_lengkap','id_role']);
if ($errs) jsonError(implode('; ',$errs));
if (strlen($body['password']) < 6) jsonError('Password minimal 6 karakter');
$db = getDB();
$chk = $db->prepare("SELECT id FROM user WHERE username=?");
$chk->execute([$body['username']]);
if ($chk->fetch()) jsonError('Username sudah dipakai',409);
$db->prepare(
"INSERT INTO user (id_role,username,password_hash,nama_lengkap,email,no_hp)
VALUES (?,?,?,?,?,?)"
)->execute([
(int)$body['id_role'],
sanitize($body['username']),
password_hash($body['password'], PASSWORD_BCRYPT),
sanitize($body['nama_lengkap']),
sanitize($body['email'] ?? ''),
sanitize($body['no_hp'] ?? ''),
]);
jsonSuccess(['id' => $db->lastInsertId()], 'User dibuat',201);
}
public function update(string $id): void {
requireAuth([ROLE_ADMIN]);
$body = getBody();
$db = getDB();
$sets = ["nama_lengkap=?","email=?","no_hp=?","id_role=?","aktif=?"];
$vals = [
sanitize($body['nama_lengkap'] ?? ''),
sanitize($body['email'] ?? ''),
sanitize($body['no_hp'] ?? ''),
(int)($body['id_role'] ?? 4),
(int)($body['aktif'] ?? 1),
];
if (!empty($body['password'])) {
$sets[] = "password_hash=?";
$vals[] = password_hash($body['password'], PASSWORD_BCRYPT);
}
$vals[] = $id;
$db->prepare("UPDATE user SET " . implode(',',$sets) . " WHERE id=?")->execute($vals);
jsonSuccess([],'User diperbarui');
}
public function destroy(string $id): void {
requireAuth([ROLE_ADMIN]);
getDB()->prepare("UPDATE user SET aktif=0 WHERE id=?")->execute([$id]);
jsonSuccess([],'User dinonaktifkan');
}
}