add files

This commit is contained in:
2026-06-10 18:32:52 +07:00
parent 12c04e37eb
commit 3430850d69
45 changed files with 7872 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
<?php
require_once __DIR__ . '/../config.php';
$action = $_GET['action'] ?? '';
switch ($action) {
case 'login':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonErr('Method not allowed', 405);
$b = json_decode(file_get_contents('php://input'), true);
$username = sanitize($b['username'] ?? '');
$password = $b['password'] ?? '';
if (!$username || !$password) jsonErr('Username dan password wajib diisi.');
$db = getDB();
$st = $db->prepare("SELECT u.id,u.username,u.password,u.nama_lengkap,u.telepon,u.aktif,
r.kode as role, r.nama as role_nama, u.rumah_ibadah_id,
ri.nama as ri_nama
FROM user u
JOIN role r ON r.id = u.role_id
LEFT JOIN rumah_ibadah ri ON ri.id = u.rumah_ibadah_id
WHERE u.username = ?");
$st->execute([$username]);
$user = $st->fetch();
if (!$user) jsonErr('Username atau password salah.', 401);
if (!$user['aktif']) jsonErr('Akun ini dinonaktifkan. Hubungi administrator.', 403);
$valid = password_verify($password, $user['password']) || $user['password'] === $password;
if (!$valid) jsonErr('Username atau password salah.', 401);
startSession();
$sess = [
'id' => (int)$user['id'],
'username' => $user['username'],
'nama_lengkap'=> $user['nama_lengkap'],
'role' => $user['role'],
'role_nama' => $user['role_nama'],
'ri_id' => $user['rumah_ibadah_id'] ? (int)$user['rumah_ibadah_id'] : null,
'ri_nama' => $user['ri_nama'],
];
$_SESSION['user'] = $sess;
log_aksi($sess['id'], 'LOGIN', 'user', $sess['id'], 'Login berhasil');
jsonOk($sess, 'Selamat datang, '.$user['nama_lengkap'].'!');
break;
case 'logout':
$u = getCurrentUser();
if ($u) log_aksi($u['id'], 'LOGOUT', 'user', $u['id'], 'Logout');
startSession(); session_destroy();
jsonOk(null, 'Logout berhasil.');
break;
case 'check':
$u = getCurrentUser();
if ($u) jsonOk($u);
else jsonErr('Tidak ada sesi aktif.', 401);
break;
default:
jsonErr('Action tidak dikenali.', 404);
}
+114
View File
@@ -0,0 +1,114 @@
<?php
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? '';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
// ── HISTORI BANTUAN ──
if ($action === 'histori') {
if ($method === 'GET') {
$pid = (int)($_GET['penduduk_id'] ?? 0);
if (!$pid) jsonErr('penduduk_id wajib.');
$st = $db->prepare("SELECT hb.*, b.nama as bantuan_nama, b.jenis, b.satuan, b.sumber,
u.nama_lengkap as disalurkan_nama
FROM histori_bantuan hb
JOIN bantuan b ON b.id=hb.bantuan_id
LEFT JOIN user u ON u.id=hb.disalurkan_oleh
WHERE hb.penduduk_id=? ORDER BY hb.tanggal DESC");
$st->execute([$pid]);
jsonOk($st->fetchAll());
}
if ($method === 'POST') {
requireRole(['superadmin','pengurus']);
$b = json_decode(file_get_contents('php://input'), true);
$pid = (int)($b['penduduk_id'] ?? 0);
$bid = (int)($b['bantuan_id'] ?? 0);
if (!$pid || !$bid) jsonErr('penduduk_id dan bantuan_id wajib.');
$db->prepare("INSERT INTO histori_bantuan (penduduk_id,bantuan_id,disalurkan_oleh,tanggal,jumlah,status,catatan) VALUES (?,?,?,?,?,?,?)")
->execute([$pid,$bid,$user['id'],$b['tanggal']??date('Y-m-d'),(float)($b['jumlah']??1),$b['status']??'disalurkan',sanitize($b['catatan']??'')]);
$newId=(int)$db->lastInsertId();
log_aksi($user['id'],'CREATE','histori_bantuan',$newId,"Catat bantuan penduduk_id:$pid bantuan_id:$bid");
jsonOk(['id'=>$newId],'Histori bantuan dicatat.');
}
if ($method === 'PUT') {
requireRole(['superadmin','pengurus']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("UPDATE histori_bantuan SET status=?,catatan=? WHERE id=?")
->execute([$b['status']??'disalurkan',sanitize($b['catatan']??''),$id]);
jsonOk(null,'Status bantuan diperbarui.');
}
jsonErr('Method tidak didukung.', 405);
}
// ── PRIORITAS OTOMATIS ──
if ($action === 'prioritas') {
$st = $db->query("
SELECT p.id, p.nik, p.nama, p.umur, p.profil_umur, p.kondisi_kesehatan,
p.status_ekonomi, p.penghasilan, p.status_perkawinan,
k.alamat, k.kelurahan, k.lat, k.lng,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') as total_bantuan,
CASE
WHEN p.status_ekonomi='miskin' AND p.kondisi_kesehatan IN ('sakit_berat','disabilitas') THEN 'SANGAT TINGGI'
WHEN p.profil_umur='Lansia' AND p.status_perkawinan IN ('cerai_mati','cerai_hidup') THEN 'SANGAT TINGGI'
WHEN p.status_ekonomi='miskin' AND p.kondisi_kesehatan='sakit_ringan' THEN 'TINGGI'
WHEN p.status_ekonomi='miskin' AND p.profil_umur='Lansia' THEN 'TINGGI'
WHEN p.status_ekonomi='miskin' THEN 'SEDANG'
WHEN p.status_ekonomi='rentan' AND p.kondisi_kesehatan != 'sehat' THEN 'SEDANG'
ELSE 'RENDAH'
END as prioritas
FROM v_penduduk p
JOIN keluarga k ON k.id=p.keluarga_id
WHERE p.status_hidup='hidup'
ORDER BY FIELD(prioritas,'SANGAT TINGGI','TINGGI','SEDANG','RENDAH'), total_bantuan ASC
LIMIT 100
");
jsonOk($st->fetchAll());
}
// ── BELUM TERIMA BANTUAN ──
if ($action === 'belum_terima') {
$st = $db->query("
SELECT p.id, p.nik, p.nama, p.umur, p.profil_umur, p.status_ekonomi,
p.kondisi_kesehatan, k.kelurahan, k.alamat, k.lat, k.lng
FROM v_penduduk p
JOIN keluarga k ON k.id=p.keluarga_id
WHERE p.status_hidup='hidup'
AND p.status_ekonomi IN ('miskin','rentan')
AND p.id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')
ORDER BY p.status_ekonomi DESC, p.umur DESC
");
jsonOk($st->fetchAll());
}
// ── CRUD MASTER BANTUAN ──
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT * FROM bantuan WHERE id=?"); $st->execute([$id]);
jsonOk($st->fetch());
}
jsonOk($db->query("SELECT * FROM bantuan ORDER BY nama")->fetchAll());
break;
case 'POST':
requireRole(['superadmin','pengurus']);
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("INSERT INTO bantuan (nama,jenis,sumber,satuan,nilai_per_satuan,periode) VALUES (?,?,?,?,?,?)")
->execute([sanitize($b['nama']??''),$b['jenis']??'lainnya',sanitize($b['sumber']??''),sanitize($b['satuan']??''),(float)($b['nilai_per_satuan']??0),$b['periode']??'sekali']);
jsonOk(['id'=>(int)$db->lastInsertId()],'Bantuan ditambahkan.');
break;
case 'PUT':
requireRole(['superadmin','pengurus']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("UPDATE bantuan SET nama=?,jenis=?,sumber=?,satuan=?,nilai_per_satuan=?,periode=?,aktif=? WHERE id=?")
->execute([sanitize($b['nama']??''),$b['jenis']??'lainnya',sanitize($b['sumber']??''),sanitize($b['satuan']??''),(float)($b['nilai_per_satuan']??0),$b['periode']??'sekali',(int)($b['aktif']??1),$id]);
jsonOk(null,'Bantuan diperbarui.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+55
View File
@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$db = getDB();
switch ($method) {
case 'GET':
$since = $_GET['since'] ?? '1970-01-01 00:00:00';
// ambil pesan broadcast + pesan ke/dari user ini
$st = $db->prepare("SELECT c.id, c.pesan, c.created_at, c.dibaca,
u.nama_lengkap as dari_nama, u.id as dari_id,
r.kode as dari_role, u.rumah_ibadah_id,
ri.nama as dari_ri,
c.ke_user_id
FROM chat c
JOIN user u ON u.id=c.dari_user_id
JOIN role r ON r.id=u.role_id
LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id
WHERE (c.ke_user_id IS NULL OR c.ke_user_id=? OR c.dari_user_id=?)
AND c.created_at > ?
ORDER BY c.created_at ASC LIMIT 100");
$st->execute([$user['id'], $user['id'], $since]);
$msgs = $st->fetchAll();
// tandai sudah dibaca
$db->prepare("UPDATE chat SET dibaca=1 WHERE ke_user_id=? AND dibaca=0")->execute([$user['id']]);
jsonOk($msgs);
break;
case 'POST':
// hanya pengurus, relawan, superadmin
requireRole(['superadmin','pengurus','relawan']);
$b = json_decode(file_get_contents('php://input'), true);
$pesan = sanitize($b['pesan'] ?? '');
if (!$pesan) jsonErr('Pesan tidak boleh kosong.');
$ke = !empty($b['ke_user_id']) ? (int)$b['ke_user_id'] : null;
$db->prepare("INSERT INTO chat (dari_user_id, ke_user_id, pesan) VALUES (?,?,?)")
->execute([$user['id'], $ke, $pesan]);
$newId = (int)$db->lastInsertId();
// ambil pesan lengkap untuk response
$st = $db->prepare("SELECT c.id, c.pesan, c.created_at, u.nama_lengkap as dari_nama,
r.kode as dari_role, ri.nama as dari_ri, c.ke_user_id
FROM chat c JOIN user u ON u.id=c.dari_user_id
JOIN role r ON r.id=u.role_id
LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id
WHERE c.id=?");
$st->execute([$newId]);
jsonOk($st->fetch(), 'Pesan terkirim.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+107
View File
@@ -0,0 +1,107 @@
<?php
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT k.*, ri.nama as ri_nama, ri.jenis as ri_jenis
FROM keluarga k LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
WHERE k.id=?");
$st->execute([$id]);
$row = $st->fetch();
if (!$row) jsonErr('Tidak ditemukan.', 404);
// anggota keluarga
$an = $db->prepare("SELECT id,nik,nama,jenis_kelamin,umur,profil_umur,
status_keluarga,status_perkawinan,status_ekonomi,
kondisi_kesehatan,pekerjaan,penghasilan,
status_pendidikan,pendidikan_terakhir
FROM penduduk WHERE keluarga_id=? AND status_hidup='hidup' ORDER BY
FIELD(status_keluarga,'kepala','istri','anak','orang_tua','lainnya')");
$an->execute([$id]);
$row['anggota'] = $an->fetchAll();
jsonOk($row);
break;
}
$where = []; $params = [];
if (!empty($_GET['status_ekonomi'])) { $where[] = "k.status_ekonomi=?"; $params[] = $_GET['status_ekonomi']; }
if (!empty($_GET['kelurahan'])) { $where[] = "k.kelurahan=?"; $params[] = $_GET['kelurahan']; }
if (!empty($_GET['rumah_ibadah_id'])) { $where[] = "k.rumah_ibadah_id=?"; $params[] = (int)$_GET['rumah_ibadah_id']; }
if (!empty($_GET['q'])) { $where[] = "(k.nama_kk LIKE ? OR k.no_kk LIKE ?)"; $params[] = '%'.$_GET['q'].'%'; $params[] = '%'.$_GET['q'].'%'; }
$wStr = $where ? 'WHERE '.implode(' AND ',$where) : '';
$st = $db->prepare("SELECT k.id, k.no_kk, k.nama_kk, k.alamat, k.rt, k.rw,
k.kelurahan, k.kecamatan, k.lat, k.lng, k.status_ekonomi,
ri.nama as ri_nama,
(SELECT COUNT(*) FROM penduduk p WHERE p.keluarga_id=k.id AND p.status_hidup='hidup') as jumlah_jiwa,
(SELECT COUNT(*) FROM histori_bantuan hb JOIN penduduk p ON p.id=hb.penduduk_id WHERE p.keluarga_id=k.id AND hb.status='disalurkan') as total_bantuan_diterima
FROM keluarga k LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
$wStr ORDER BY k.nama_kk ASC LIMIT 500");
$st->execute($params);
jsonOk($st->fetchAll());
break;
case 'POST':
requireRole(['superadmin','pengurus','relawan']);
$b = json_decode(file_get_contents('php://input'), true);
$no_kk = sanitize($b['no_kk'] ?? '');
$nama_kk = sanitize($b['nama_kk'] ?? '');
if (!$no_kk || !$nama_kk) jsonErr('No KK dan nama KK wajib diisi.');
if (strlen($no_kk) !== 16) jsonErr('No KK harus 16 digit.');
$chk = $db->prepare("SELECT id FROM keluarga WHERE no_kk=?"); $chk->execute([$no_kk]);
if ($chk->fetch()) jsonErr('No KK sudah terdaftar.');
$db->prepare("INSERT INTO keluarga (no_kk,nama_kk,alamat,rt,rw,kelurahan,kecamatan,lat,lng,rumah_ibadah_id,status_ekonomi,created_by)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)")
->execute([
$no_kk, $nama_kk,
sanitize($b['alamat'] ?? ''),
sanitize($b['rt'] ?? ''), sanitize($b['rw'] ?? ''),
sanitize($b['kelurahan'] ?? ''), sanitize($b['kecamatan'] ?? ''),
isset($b['lat']) ? (float)$b['lat'] : null,
isset($b['lng']) ? (float)$b['lng'] : null,
!empty($b['rumah_ibadah_id']) ? (int)$b['rumah_ibadah_id'] : null,
$b['status_ekonomi'] ?? 'rentan',
$user['id']
]);
$newId = (int)$db->lastInsertId();
log_aksi($user['id'], 'CREATE', 'keluarga', $newId, "Tambah KK: $nama_kk");
jsonOk(['id'=>$newId], 'Keluarga berhasil ditambahkan.');
break;
case 'PUT':
requireRole(['superadmin','pengurus','relawan']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("UPDATE keluarga SET nama_kk=?,alamat=?,rt=?,rw=?,kelurahan=?,kecamatan=?,
lat=?,lng=?,rumah_ibadah_id=?,status_ekonomi=?,updated_at=NOW() WHERE id=?")
->execute([
sanitize($b['nama_kk'] ?? ''), sanitize($b['alamat'] ?? ''),
sanitize($b['rt'] ?? ''), sanitize($b['rw'] ?? ''),
sanitize($b['kelurahan'] ?? ''), sanitize($b['kecamatan'] ?? ''),
isset($b['lat']) ? (float)$b['lat'] : null,
isset($b['lng']) ? (float)$b['lng'] : null,
!empty($b['rumah_ibadah_id']) ? (int)$b['rumah_ibadah_id'] : null,
$b['status_ekonomi'] ?? 'rentan',
$id
]);
log_aksi($user['id'], 'UPDATE', 'keluarga', $id, "Edit KK ID: $id");
jsonOk(null, 'Data keluarga diperbarui.');
break;
case 'DELETE':
requireRole(['superadmin']);
if (!$id) jsonErr('ID tidak valid.');
$db->prepare("DELETE FROM keluarga WHERE id=?")->execute([$id]);
log_aksi($user['id'], 'DELETE', 'keluarga', $id, "Hapus KK ID: $id");
jsonOk(null, 'Data keluarga dihapus.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+74
View File
@@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/../config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT l.*, p.nama as penduduk_nama, u.nama_lengkap as verifikator
FROM laporan l
LEFT JOIN penduduk p ON p.id=l.penduduk_id
LEFT JOIN user u ON u.id=l.diverifikasi_oleh
WHERE l.id=?");
$st->execute([$id]); jsonOk($st->fetch()); break;
}
$where=[]; $params=[];
if (!empty($_GET['status'])) { $where[]="l.status=?"; $params[]=$_GET['status']; }
if (!empty($_GET['urgensi'])) { $where[]="l.urgensi=?"; $params[]=$_GET['urgensi']; }
$w = $where ? 'WHERE '.implode(' AND ',$where) : '';
$st = $db->prepare("SELECT l.id, l.pelapor_nama, l.deskripsi, l.lat, l.lng,
l.alamat_laporan, l.urgensi, l.status, l.foto,
l.created_at, p.nama as penduduk_nama
FROM laporan l LEFT JOIN penduduk p ON p.id=l.penduduk_id
$w ORDER BY FIELD(l.urgensi,'darurat','tinggi','sedang','rendah'), l.created_at DESC");
$st->execute($params); jsonOk($st->fetchAll()); break;
case 'POST':
// masyarakat umum pun bisa lapor (tidak perlu login)
$pelapor = sanitize($_POST['pelapor_nama'] ?? '');
$telp = sanitize($_POST['pelapor_telepon']?? '');
$deskripsi = sanitize($_POST['deskripsi'] ?? '');
$alamat = sanitize($_POST['alamat_laporan'] ?? '');
$lat = isset($_POST['lat']) ? (float)$_POST['lat'] : null;
$lng = isset($_POST['lng']) ? (float)$_POST['lng'] : null;
$urgensi = $_POST['urgensi'] ?? 'sedang';
$pid = !empty($_POST['penduduk_id']) ? (int)$_POST['penduduk_id'] : null;
if (!$deskripsi) jsonErr('Deskripsi laporan wajib diisi.');
// Upload foto
$fotoPath = null;
if (!empty($_FILES['foto']['tmp_name'])) {
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg','jpeg','png','webp'])) jsonErr('Format foto harus JPG/PNG/WebP.');
if ($_FILES['foto']['size'] > 5 * 1024 * 1024) jsonErr('Ukuran foto maks 5MB.');
$fname = 'laporan_' . time() . '_' . rand(1000,9999) . '.' . $ext;
move_uploaded_file($_FILES['foto']['tmp_name'], UPLOAD_DIR . $fname);
$fotoPath = 'uploads/' . $fname;
}
$db->prepare("INSERT INTO laporan (pelapor_nama,pelapor_telepon,penduduk_id,deskripsi,lat,lng,alamat_laporan,foto,urgensi) VALUES (?,?,?,?,?,?,?,?,?)")
->execute([$pelapor,$telp,$pid,$deskripsi,$lat,$lng,$alamat,$fotoPath,$urgensi]);
$newId = (int)$db->lastInsertId();
$u = getCurrentUser();
if ($u) log_aksi($u['id'],'CREATE','laporan',$newId,"Laporan baru: $urgensi");
jsonOk(['id'=>$newId],'Laporan berhasil dikirim. Terima kasih!'); break;
case 'PUT':
$u = requireLogin();
requireRole(['superadmin','pengurus','relawan']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$chk=$db->prepare("SELECT id FROM laporan WHERE id=?"); $chk->execute([$id]);
if (!$chk->fetch()) jsonErr('Laporan tidak ditemukan.',404);
$db->prepare("UPDATE laporan SET status=?,catatan_admin=?,diverifikasi_oleh=?,updated_at=NOW() WHERE id=?")
->execute([$b['status']??'pending',sanitize($b['catatan_admin']??''),$u['id'],$id]);
log_aksi($u['id'],'UPDATE','laporan',$id,"Update status: ".($b['status']??''));
jsonOk(null,'Status laporan diperbarui.'); break;
default: jsonErr('Method tidak didukung.',405);
}
+27
View File
@@ -0,0 +1,27 @@
<?php
// api/log.php — Log Aktivitas
require_once __DIR__ . '/../config.php';
requireRole(['superadmin', 'pimpinan']);
$db = getDB();
$limit = min((int)($_GET['limit'] ?? 100), 500);
$offset = (int)($_GET['offset'] ?? 0);
$tabel = $_GET['tabel'] ?? '';
$uid = (int)($_GET['user_id'] ?? 0);
$where = []; $params = [];
if ($tabel) { $where[] = "l.tabel=?"; $params[] = $tabel; }
if ($uid) { $where[] = "l.user_id=?"; $params[] = $uid; }
$w = $where ? 'WHERE '.implode(' AND ',$where) : '';
$st = getDB()->prepare("SELECT l.id, l.aksi, l.tabel, l.record_id, l.keterangan, l.ip_address, l.created_at,
u.username, u.nama_lengkap, r.kode as role
FROM log_aktivitas l
LEFT JOIN user u ON u.id=l.user_id
LEFT JOIN role r ON r.id=u.role_id
$w ORDER BY l.created_at DESC LIMIT ? OFFSET ?");
$params[] = $limit; $params[] = $offset;
$st->execute($params);
$total = (int)getDB()->query("SELECT COUNT(*) FROM log_aktivitas $w")->fetchColumn();
jsonOk(['rows' => $st->fetchAll(), 'total' => $total, 'limit' => $limit, 'offset' => $offset]);
+95
View File
@@ -0,0 +1,95 @@
<?php
// api/pelatihan.php — Manajemen Pelatihan & Riwayat
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? '';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
// ── RIWAYAT PELATIHAN PER PENDUDUK ──
if ($action === 'riwayat') {
$pid = (int)($_GET['penduduk_id'] ?? 0);
if (!$pid) jsonErr('penduduk_id wajib.');
$st = $db->prepare("SELECT rp.*, p.nama_pelatihan, p.penyelenggara, p.tanggal as tgl_pelatihan
FROM riwayat_pelatihan rp
JOIN pelatihan p ON p.id=rp.pelatihan_id
WHERE rp.penduduk_id=? ORDER BY rp.tanggal_ikut DESC");
$st->execute([$pid]);
jsonOk($st->fetchAll());
}
// ── DAFTARKAN PESERTA ──
if ($action === 'daftar' && $method === 'POST') {
requireRole(['superadmin', 'pengurus', 'relawan']);
$b = json_decode(file_get_contents('php://input'), true);
$pid = (int)($b['penduduk_id'] ?? 0);
$tid = (int)($b['pelatihan_id'] ?? 0);
if (!$pid || !$tid) jsonErr('penduduk_id dan pelatihan_id wajib.');
// validasi: cek umur >= 18 untuk pelatihan kerja
$p = $db->prepare("SELECT umur FROM penduduk WHERE id=?"); $p->execute([$pid]);
$row = $p->fetch();
if ($row && (int)$row['umur'] < 15) jsonErr('Peserta minimal berusia 15 tahun untuk mengikuti pelatihan.');
$db->prepare("INSERT INTO riwayat_pelatihan (penduduk_id, pelatihan_id, tanggal_ikut) VALUES (?,?,?)")
->execute([$pid, $tid, $b['tanggal_ikut'] ?? date('Y-m-d')]);
log_aksi($user['id'], 'CREATE', 'riwayat_pelatihan', (int)$db->lastInsertId(), "Daftar pelatihan pid:$pid tid:$tid");
jsonOk(null, 'Peserta berhasil didaftarkan ke pelatihan.');
}
// ── CRUD MASTER PELATIHAN ──
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT p.*, COUNT(rp.id) as jumlah_peserta FROM pelatihan p
LEFT JOIN riwayat_pelatihan rp ON rp.pelatihan_id=p.id WHERE p.id=? GROUP BY p.id");
$st->execute([$id]);
$row = $st->fetch();
if (!$row) jsonErr('Tidak ditemukan.', 404);
// peserta
$ps = $db->prepare("SELECT rp.id, rp.tanggal_ikut, pd.nama, pd.nik, pd.umur
FROM riwayat_pelatihan rp JOIN v_penduduk pd ON pd.id=rp.penduduk_id
WHERE rp.pelatihan_id=? ORDER BY rp.tanggal_ikut DESC");
$ps->execute([$id]);
$row['peserta'] = $ps->fetchAll();
jsonOk($row);
break;
}
$rows = $db->query("SELECT p.*, COUNT(rp.id) as jumlah_peserta
FROM pelatihan p LEFT JOIN riwayat_pelatihan rp ON rp.pelatihan_id=p.id
GROUP BY p.id ORDER BY p.tanggal DESC")->fetchAll();
jsonOk($rows);
break;
case 'POST':
requireRole(['superadmin', 'pengurus']);
$b = json_decode(file_get_contents('php://input'), true);
$nama = sanitize($b['nama_pelatihan'] ?? '');
if (!$nama) jsonErr('Nama pelatihan wajib diisi.');
$db->prepare("INSERT INTO pelatihan (nama_pelatihan, penyelenggara, tanggal, durasi_hari, deskripsi) VALUES (?,?,?,?,?)")
->execute([$nama, sanitize($b['penyelenggara'] ?? ''), $b['tanggal'] ?? null,
(int)($b['durasi_hari'] ?? 1), sanitize($b['deskripsi'] ?? '')]);
jsonOk(['id' => (int)$db->lastInsertId()], 'Pelatihan ditambahkan.');
break;
case 'PUT':
requireRole(['superadmin', 'pengurus']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("UPDATE pelatihan SET nama_pelatihan=?, penyelenggara=?, tanggal=?, durasi_hari=?, deskripsi=? WHERE id=?")
->execute([sanitize($b['nama_pelatihan'] ?? ''), sanitize($b['penyelenggara'] ?? ''),
$b['tanggal'] ?? null, (int)($b['durasi_hari'] ?? 1),
sanitize($b['deskripsi'] ?? ''), $id]);
jsonOk(null, 'Pelatihan diperbarui.');
break;
case 'DELETE':
requireRole(['superadmin']);
if (!$id) jsonErr('ID tidak valid.');
$db->prepare("DELETE FROM pelatihan WHERE id=?")->execute([$id]);
jsonOk(null, 'Pelatihan dihapus.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+174
View File
@@ -0,0 +1,174 @@
<?php
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
switch ($method) {
// ── GET LIST / SINGLE ──
case 'GET':
if ($id) {
// single dengan histori bantuan & riwayat pelatihan
$st = $db->prepare("SELECT p.*, k.no_kk, k.alamat as alamat_kk, k.rt, k.rw,
k.kelurahan, k.kecamatan, k.rumah_ibadah_id
FROM v_penduduk p
JOIN keluarga k ON k.id = p.keluarga_id
WHERE p.id = ?");
$st->execute([$id]);
$row = $st->fetch();
if (!$row) jsonErr('Data tidak ditemukan.', 404);
// histori bantuan
$hb = $db->prepare("SELECT hb.*, b.nama as bantuan_nama, b.jenis, b.satuan,
u.nama_lengkap as disalurkan_nama
FROM histori_bantuan hb
JOIN bantuan b ON b.id = hb.bantuan_id
LEFT JOIN user u ON u.id = hb.disalurkan_oleh
WHERE hb.penduduk_id = ? ORDER BY hb.tanggal DESC");
$hb->execute([$id]);
$row['histori_bantuan'] = $hb->fetchAll();
// pelatihan
$plt = $db->prepare("SELECT rp.*, p.nama_pelatihan, p.penyelenggara
FROM riwayat_pelatihan rp
JOIN pelatihan p ON p.id = rp.pelatihan_id
WHERE rp.penduduk_id = ?");
$plt->execute([$id]);
$row['riwayat_pelatihan'] = $plt->fetchAll();
// validasi pendidikan
$warn = validasiPendidikan((int)$row['umur'], $row['pendidikan_terakhir'], $row['status_pendidikan']);
$row['peringatan_pendidikan'] = $warn;
jsonOk($row);
}
// list dengan filter
$where = ["p.status_hidup = 'hidup'"]; $params = [];
if (!empty($_GET['status_ekonomi'])) { $where[] = "p.status_ekonomi = ?"; $params[] = $_GET['status_ekonomi']; }
if (!empty($_GET['keluarga_id'])) { $where[] = "p.keluarga_id = ?"; $params[] = (int)$_GET['keluarga_id']; }
if (!empty($_GET['kelurahan'])) { $where[] = "k.kelurahan = ?"; $params[] = $_GET['kelurahan']; }
if (!empty($_GET['q'])) { $where[] = "(p.nama LIKE ? OR p.nik LIKE ?)"; $params[] = '%'.$_GET['q'].'%'; $params[] = '%'.$_GET['q'].'%'; }
$wStr = implode(' AND ', $where);
$st = $db->prepare("SELECT p.id, p.nik, p.nama, p.jenis_kelamin, p.tanggal_lahir, p.umur,
p.profil_umur, p.status_keluarga, p.status_perkawinan,
p.status_ekonomi, p.kondisi_kesehatan, p.pekerjaan,
p.penghasilan, p.lat, p.lng, p.keluarga_id,
k.no_kk, k.kelurahan, k.alamat as alamat_kk,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') as total_bantuan
FROM v_penduduk p JOIN keluarga k ON k.id=p.keluarga_id
WHERE $wStr ORDER BY p.nama ASC LIMIT 500");
$st->execute($params);
jsonOk($st->fetchAll());
break;
// ── POST: Tambah ──
case 'POST':
requireRole(['superadmin','pengurus','relawan']);
$b = json_decode(file_get_contents('php://input'), true);
$nik = sanitize($b['nik'] ?? '');
$nama = sanitize($b['nama'] ?? '');
$tgl = $b['tanggal_lahir'] ?? '';
$kjId = (int)($b['keluarga_id'] ?? 0);
if (!$nik || strlen($nik) !== 16) jsonErr('NIK harus 16 digit.');
if (!$nama) jsonErr('Nama wajib diisi.');
if (!$tgl) jsonErr('Tanggal lahir wajib diisi.');
if (!$kjId) jsonErr('Keluarga (KK) wajib dipilih.');
// cek NIK unik
$chk = $db->prepare("SELECT id FROM penduduk WHERE nik=?"); $chk->execute([$nik]);
if ($chk->fetch()) jsonErr('NIK sudah terdaftar di sistem.');
$umur = hitungUmur($tgl);
$pendid = $b['pendidikan_terakhir'] ?? 'tidak_sekolah';
$stPend = $b['status_pendidikan'] ?? 'tidak_sekolah';
$db->prepare("INSERT INTO penduduk
(nik,nama,jenis_kelamin,tanggal_lahir,status_keluarga,status_perkawinan,status_hidup,
keluarga_id,pendidikan_terakhir,status_pendidikan,pekerjaan,penghasilan,
status_ekonomi,kondisi_kesehatan,lat,lng,catatan,created_by)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
->execute([
$nik, $nama,
$b['jenis_kelamin'] ?? 'L',
$tgl,
$b['status_keluarga'] ?? 'anggota',
$b['status_perkawinan'] ?? 'belum_kawin',
$b['status_hidup'] ?? 'hidup',
$kjId, $pendid, $stPend,
sanitize($b['pekerjaan'] ?? ''),
(float)($b['penghasilan'] ?? 0),
$b['status_ekonomi'] ?? 'rentan',
$b['kondisi_kesehatan'] ?? 'sehat',
isset($b['lat']) ? (float)$b['lat'] : null,
isset($b['lng']) ? (float)$b['lng'] : null,
sanitize($b['catatan'] ?? ''),
$user['id']
]);
$newId = (int)$db->lastInsertId();
log_aksi($user['id'], 'CREATE', 'penduduk', $newId, "Tambah: $nama (NIK: $nik)");
$warn = validasiPendidikan($umur, $pendid, $stPend);
jsonOk(['id'=>$newId,'peringatan'=>$warn], 'Penduduk berhasil ditambahkan.');
break;
// ── PUT: Edit ──
case 'PUT':
requireRole(['superadmin','pengurus','relawan']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$chk = $db->prepare("SELECT id,nik FROM penduduk WHERE id=?"); $chk->execute([$id]);
if (!$chk->fetch()) jsonErr('Data tidak ditemukan.', 404);
$tgl = $b['tanggal_lahir'] ?? '';
$umur = $tgl ? hitungUmur($tgl) : 0;
$pendid = $b['pendidikan_terakhir'] ?? 'tidak_sekolah';
$stPend = $b['status_pendidikan'] ?? 'tidak_sekolah';
$db->prepare("UPDATE penduduk SET
nama=?,jenis_kelamin=?,tanggal_lahir=?,status_keluarga=?,status_perkawinan=?,
status_hidup=?,keluarga_id=?,pendidikan_terakhir=?,status_pendidikan=?,
pekerjaan=?,penghasilan=?,status_ekonomi=?,kondisi_kesehatan=?,
lat=?,lng=?,catatan=?,updated_at=NOW() WHERE id=?")
->execute([
sanitize($b['nama'] ?? ''),
$b['jenis_kelamin'] ?? 'L',
$tgl,
$b['status_keluarga'] ?? 'anggota',
$b['status_perkawinan'] ?? 'belum_kawin',
$b['status_hidup'] ?? 'hidup',
(int)($b['keluarga_id'] ?? 0),
$pendid, $stPend,
sanitize($b['pekerjaan'] ?? ''),
(float)($b['penghasilan'] ?? 0),
$b['status_ekonomi'] ?? 'rentan',
$b['kondisi_kesehatan'] ?? 'sehat',
isset($b['lat']) ? (float)$b['lat'] : null,
isset($b['lng']) ? (float)$b['lng'] : null,
sanitize($b['catatan'] ?? ''),
$id
]);
log_aksi($user['id'], 'UPDATE', 'penduduk', $id, "Edit ID: $id");
$warn = validasiPendidikan($umur, $pendid, $stPend);
jsonOk(['peringatan'=>$warn], 'Data berhasil diperbarui.');
break;
// ── DELETE ──
case 'DELETE':
requireRole(['superadmin']);
if (!$id) jsonErr('ID tidak valid.');
$chk = $db->prepare("SELECT nama FROM penduduk WHERE id=?"); $chk->execute([$id]);
$row = $chk->fetch();
if (!$row) jsonErr('Data tidak ditemukan.', 404);
$db->prepare("DELETE FROM penduduk WHERE id=?")->execute([$id]);
log_aksi($user['id'], 'DELETE', 'penduduk', $id, 'Hapus: '.$row['nama']);
jsonOk(null, 'Data berhasil dihapus.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+52
View File
@@ -0,0 +1,52 @@
<?php
require_once __DIR__ . '/../config.php';
$user = requireLogin();
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT ri.*,
(SELECT COUNT(*) FROM keluarga k WHERE k.rumah_ibadah_id=ri.id) as jumlah_kk,
(SELECT COUNT(*) FROM keluarga k JOIN penduduk p ON p.keluarga_id=k.id WHERE k.rumah_ibadah_id=ri.id AND p.status_hidup='hidup') as jumlah_jiwa,
(SELECT COUNT(*) FROM keluarga k WHERE k.rumah_ibadah_id=ri.id AND k.status_ekonomi='miskin') as kk_miskin
FROM rumah_ibadah ri WHERE ri.id=?");
$st->execute([$id]); jsonOk($st->fetch()); break;
}
$rows = $db->query("SELECT ri.id, ri.nama, ri.jenis, ri.kontak, ri.lat, ri.lng, ri.alamat, ri.kelurahan,
(SELECT COUNT(*) FROM keluarga k WHERE k.rumah_ibadah_id=ri.id) as jumlah_kk,
(SELECT COUNT(*) FROM keluarga k WHERE k.rumah_ibadah_id=ri.id AND k.status_ekonomi='miskin') as kk_miskin
FROM rumah_ibadah ri ORDER BY ri.nama")->fetchAll();
foreach ($rows as &$r) { $r['id']=(int)$r['id']; $r['lat']=(float)$r['lat']; $r['lng']=(float)$r['lng']; }
jsonOk($rows); break;
case 'POST':
requireRole(['superadmin']);
$b = json_decode(file_get_contents('php://input'), true);
$nama = sanitize($b['nama'] ?? '');
if (!$nama) jsonErr('Nama wajib diisi.');
$db->prepare("INSERT INTO rumah_ibadah (nama,jenis,kontak,lat,lng,alamat,kelurahan) VALUES (?,?,?,?,?,?,?)")
->execute([$nama,$b['jenis']??'Masjid',sanitize($b['kontak']??''),(float)($b['lat']??0),(float)($b['lng']??0),sanitize($b['alamat']??''),sanitize($b['kelurahan']??'')]);
jsonOk(['id'=>(int)$db->lastInsertId()],'Rumah ibadah ditambahkan.');
break;
case 'PUT':
requireRole(['superadmin','pengurus']);
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$db->prepare("UPDATE rumah_ibadah SET nama=?,jenis=?,kontak=?,lat=?,lng=?,alamat=?,kelurahan=?,updated_at=NOW() WHERE id=?")
->execute([sanitize($b['nama']??''),$b['jenis']??'Masjid',sanitize($b['kontak']??''),(float)($b['lat']??0),(float)($b['lng']??0),sanitize($b['alamat']??''),sanitize($b['kelurahan']??''),$id]);
jsonOk(null,'Data diperbarui.');
break;
case 'DELETE':
requireRole(['superadmin']);
if (!$id) jsonErr('ID tidak valid.');
$db->prepare("DELETE FROM rumah_ibadah WHERE id=?")->execute([$id]);
jsonOk(null,'Data dihapus.');
break;
default: jsonErr('Method tidak didukung.', 405);
}
+96
View File
@@ -0,0 +1,96 @@
<?php
require_once __DIR__ . '/../config.php';
requireLogin();
$db = getDB();
// KPI Utama
$totalPenduduk = (int)$db->query("SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup'")->fetchColumn();
$totalKeluarga = (int)$db->query("SELECT COUNT(*) FROM keluarga")->fetchColumn();
$totalMiskin = (int)$db->query("SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='miskin' AND status_hidup='hidup'")->fetchColumn();
$totalRentan = (int)$db->query("SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='rentan' AND status_hidup='hidup'")->fetchColumn();
$totalMampu = (int)$db->query("SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='mampu' AND status_hidup='hidup'")->fetchColumn();
$totalLaporan = (int)$db->query("SELECT COUNT(*) FROM laporan WHERE status != 'selesai'")->fetchColumn();
$laporanDarurat= (int)$db->query("SELECT COUNT(*) FROM laporan WHERE urgensi='darurat' AND status NOT IN ('selesai')")->fetchColumn();
$totalBantuan = (int)$db->query("SELECT COUNT(*) FROM histori_bantuan WHERE status='disalurkan'")->fetchColumn();
$belumBantuan = (int)$db->query("SELECT COUNT(*) FROM penduduk WHERE status_ekonomi IN ('miskin','rentan') AND status_hidup='hidup' AND id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')")->fetchColumn();
$unreadChat = (int)$db->query("SELECT COUNT(*) FROM chat WHERE dibaca=0")->fetchColumn();
// Distribusi ekonomi per kelurahan
$byKelurahan = $db->query("SELECT k.kelurahan,
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,
SUM(CASE WHEN k.status_ekonomi='mampu' THEN 1 ELSE 0 END) as mampu,
COUNT(*) as total
FROM keluarga k GROUP BY k.kelurahan ORDER BY miskin DESC LIMIT 10")->fetchAll();
// Distribusi bantuan per jenis
$byBantuan = $db->query("SELECT b.nama, b.jenis, COUNT(hb.id) as total_disalurkan
FROM bantuan b LEFT JOIN histori_bantuan hb ON hb.bantuan_id=b.id AND hb.status='disalurkan'
GROUP BY b.id ORDER BY total_disalurkan DESC")->fetchAll();
// Distribusi per rumah ibadah
$byRI = $db->query("SELECT ri.nama, ri.jenis, ri.lat, ri.lng,
COUNT(DISTINCT k.id) as jumlah_kk,
SUM(CASE WHEN k.status_ekonomi='miskin' THEN 1 ELSE 0 END) as kk_miskin,
(SELECT COUNT(*) FROM v_penduduk p JOIN keluarga k2 ON k2.id=p.keluarga_id WHERE k2.rumah_ibadah_id=ri.id AND p.status_hidup='hidup') as jumlah_jiwa
FROM rumah_ibadah ri LEFT JOIN keluarga k ON k.rumah_ibadah_id=ri.id
GROUP BY ri.id ORDER BY kk_miskin DESC")->fetchAll();
// Profil umur
$byUmur = $db->query("SELECT profil_umur, COUNT(*) as jumlah
FROM v_penduduk WHERE status_hidup='hidup' GROUP BY profil_umur")->fetchAll();
// Laporan per status
$byLaporan = $db->query("SELECT status, urgensi, COUNT(*) as jumlah
FROM laporan GROUP BY status, urgensi ORDER BY FIELD(urgensi,'darurat','tinggi','sedang','rendah')")->fetchAll();
// GeoJSON penduduk untuk peta
$geoRows = $db->query("SELECT p.id, p.nik, p.nama, p.umur, p.profil_umur, p.status_ekonomi,
p.kondisi_kesehatan, p.penghasilan, k.kelurahan, k.lat, k.lng, k.no_kk,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') as total_bantuan
FROM v_penduduk p JOIN keluarga k ON k.id=p.keluarga_id
WHERE p.status_hidup='hidup' AND k.lat IS NOT NULL AND k.lng IS NOT NULL
LIMIT 1000")->fetchAll();
$features = [];
foreach ($geoRows as $r) {
$features[] = [
'type' => 'Feature',
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['lng'],(float)$r['lat']]],
'properties' => $r
];
}
// GeoJSON laporan untuk peta
$laporanRows = $db->query("SELECT id,deskripsi,lat,lng,urgensi,status,created_at,foto FROM laporan WHERE lat IS NOT NULL AND lng IS NOT NULL")->fetchAll();
$laporanFeatures = [];
foreach ($laporanRows as $r) {
$laporanFeatures[] = [
'type' => 'Feature',
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['lng'],(float)$r['lat']]],
'properties' => $r
];
}
jsonOk([
'kpi' => [
'total_penduduk' => $totalPenduduk,
'total_keluarga' => $totalKeluarga,
'total_miskin' => $totalMiskin,
'total_rentan' => $totalRentan,
'total_mampu' => $totalMampu,
'total_laporan_aktif' => $totalLaporan,
'laporan_darurat' => $laporanDarurat,
'total_bantuan_disalurkan' => $totalBantuan,
'belum_dapat_bantuan' => $belumBantuan,
'unread_chat' => $unreadChat,
'pct_miskin' => $totalPenduduk ? round($totalMiskin/$totalPenduduk*100,1) : 0,
],
'by_kelurahan' => $byKelurahan,
'by_bantuan' => $byBantuan,
'by_ri' => $byRI,
'by_umur' => $byUmur,
'by_laporan' => $byLaporan,
'geojson_penduduk' => ['type'=>'FeatureCollection','features'=>$features],
'geojson_laporan' => ['type'=>'FeatureCollection','features'=>$laporanFeatures],
]);
+87
View File
@@ -0,0 +1,87 @@
<?php
// api/user.php — Manajemen User (superadmin only)
require_once __DIR__ . '/../config.php';
$user = requireRole(['superadmin']);
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$db = getDB();
switch ($method) {
case 'GET':
if ($id) {
$st = $db->prepare("SELECT u.id, u.username, u.nama_lengkap, u.telepon, u.aktif,
r.kode as role, r.nama as role_nama, r.id as role_id,
ri.nama as ri_nama, u.rumah_ibadah_id, u.created_at
FROM user u JOIN role r ON r.id=u.role_id
LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id
WHERE u.id=?");
$st->execute([$id]);
jsonOk($st->fetch());
}
$rows = $db->query("SELECT u.id, u.username, u.nama_lengkap, u.telepon, u.aktif,
r.kode as role, r.nama as role_nama, ri.nama as ri_nama, u.created_at
FROM user u JOIN role r ON r.id=u.role_id
LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id
ORDER BY r.id, u.nama_lengkap")->fetchAll();
jsonOk($rows);
break;
case 'POST':
$b = json_decode(file_get_contents('php://input'), true);
$username = sanitize($b['username'] ?? '');
$password = $b['password'] ?? '';
$nama = sanitize($b['nama_lengkap'] ?? '');
$roleId = (int)($b['role_id'] ?? 5);
if (!$username || !$password || !$nama) jsonErr('Username, password, dan nama wajib diisi.');
if (strlen($username) < 4) jsonErr('Username minimal 4 karakter.');
if (strlen($password) < 6) jsonErr('Password minimal 6 karakter.');
$chk = $db->prepare("SELECT id FROM user WHERE username=?"); $chk->execute([$username]);
if ($chk->fetch()) jsonErr('Username sudah digunakan.');
$hash = password_hash($password, PASSWORD_BCRYPT);
$db->prepare("INSERT INTO user (username, password, nama_lengkap, role_id, rumah_ibadah_id, telepon)
VALUES (?,?,?,?,?,?)")
->execute([$username, $hash, $nama, $roleId,
!empty($b['rumah_ibadah_id']) ? (int)$b['rumah_ibadah_id'] : null,
sanitize($b['telepon'] ?? '')]);
$newId = (int)$db->lastInsertId();
log_aksi($user['id'], 'CREATE', 'user', $newId, "Tambah user: $username");
jsonOk(['id' => $newId], 'User berhasil ditambahkan.');
break;
case 'PUT':
if (!$id) jsonErr('ID tidak valid.');
$b = json_decode(file_get_contents('php://input'), true);
$nama = sanitize($b['nama_lengkap'] ?? '');
if (!$nama) jsonErr('Nama wajib diisi.');
$chk = $db->prepare("SELECT id FROM user WHERE id=?"); $chk->execute([$id]);
if (!$chk->fetch()) jsonErr('User tidak ditemukan.', 404);
// Update data dasar
$db->prepare("UPDATE user SET nama_lengkap=?, role_id=?, rumah_ibadah_id=?, telepon=?, aktif=? WHERE id=?")
->execute([$nama, (int)($b['role_id'] ?? 5),
!empty($b['rumah_ibadah_id']) ? (int)$b['rumah_ibadah_id'] : null,
sanitize($b['telepon'] ?? ''), (int)($b['aktif'] ?? 1), $id]);
// Ganti password jika diisi
if (!empty($b['password']) && strlen($b['password']) >= 6) {
$db->prepare("UPDATE user SET password=? WHERE id=?")
->execute([password_hash($b['password'], PASSWORD_BCRYPT), $id]);
}
log_aksi($user['id'], 'UPDATE', 'user', $id, "Edit user ID: $id");
jsonOk(null, 'Data user diperbarui.');
break;
case 'DELETE':
if (!$id) jsonErr('ID tidak valid.');
if ($id === $user['id']) jsonErr('Tidak dapat menghapus akun sendiri.');
$db->prepare("DELETE FROM user WHERE id=?")->execute([$id]);
log_aksi($user['id'], 'DELETE', 'user', $id, "Hapus user ID: $id");
jsonOk(null, 'User dihapus.');
break;
default: jsonErr('Method tidak didukung.', 405);
}