add files and folders
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Autentikasi
|
||||
// POST ?action=login
|
||||
// POST ?action=logout
|
||||
// GET ?action=me
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
if ($method === 'POST' && $action === 'login') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$username = sanitizeStr($body['username'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
|
||||
if (!$username || !$password) {
|
||||
jsonResponse(false, 'Username dan password wajib diisi.', [], 400);
|
||||
}
|
||||
|
||||
$result = doLogin($pdo, $username, $password);
|
||||
if (!$result['success']) {
|
||||
jsonResponse(false, $result['message'], [], 401);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Login berhasil.', [
|
||||
'role' => $result['role'],
|
||||
'user_id' => $_SESSION['user_id'],
|
||||
'nama' => $_SESSION['user_nama'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $action === 'logout') {
|
||||
doLogout($pdo);
|
||||
jsonResponse(true, 'Logout berhasil.');
|
||||
}
|
||||
|
||||
if ($method === 'GET' && $action === 'me') {
|
||||
$user = getApiUser($pdo);
|
||||
if (!$user) jsonResponse(false, 'Belum login.', [], 401);
|
||||
$detail = $pdo->prepare("SELECT u.id, u.nama, u.username, u.email, r.nama AS role, u.rumah_ibadah_id, ri.nama AS nama_ri FROM users u JOIN roles r ON r.id=u.role_id LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id WHERE u.id=:id");
|
||||
$detail->execute([':id' => $user['id']]);
|
||||
jsonResponse(true, 'OK', ['data' => $detail->fetch()]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Bantuan & Histori Bantuan
|
||||
// GET ?action=jenis|histori&nik=...
|
||||
// POST action=salurkan
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'jenis') {
|
||||
$rows = $pdo->query("SELECT * FROM jenis_bantuan WHERE aktif=1 ORDER BY kategori, nama")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
|
||||
if ($action === 'histori') {
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
|
||||
if ($nik && validateNIK($nik)) {
|
||||
// Histori per penduduk
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT hb.*, jb.nama AS nama_bantuan, jb.kategori, jb.satuan,
|
||||
ri.nama AS nama_rumah_ibadah, u.nama AS disalurkan_oleh_nama
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
LEFT JOIN users u ON u.id = hb.disalurkan_oleh
|
||||
WHERE hb.nik = :nik
|
||||
ORDER BY hb.tanggal DESC
|
||||
");
|
||||
$stmt->execute([':nik' => $nik]);
|
||||
$data = $stmt->fetchAll();
|
||||
|
||||
// Ringkasan
|
||||
$summary = [
|
||||
'total_bantuan' => count($data),
|
||||
'kategori' => [],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$summary['kategori'][$row['kategori']] = ($summary['kategori'][$row['kategori']] ?? 0) + 1;
|
||||
}
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $data, 'summary' => $summary]);
|
||||
}
|
||||
|
||||
// List semua histori (admin/pengurus)
|
||||
if (!canViewAll($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($_GET['bulan'])) { $where[] = 'MONTH(hb.tanggal) = :bln'; $params[':bln'] = (int)$_GET['bulan']; }
|
||||
if (!empty($_GET['tahun'])) { $where[] = 'YEAR(hb.tanggal) = :thn'; $params[':thn'] = (int)$_GET['tahun']; }
|
||||
if (!empty($_GET['kategori'])) { $where[] = 'jb.kategori = :kat'; $params[':kat'] = $_GET['kategori']; }
|
||||
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT hb.id, hb.nik, p.nama_lengkap, hb.tanggal,
|
||||
jb.nama AS nama_bantuan, jb.kategori,
|
||||
hb.jumlah, hb.nilai_rupiah, hb.status_penyaluran,
|
||||
ri.nama AS nama_ri
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik = hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
$whereStr
|
||||
ORDER BY hb.tanggal DESC
|
||||
LIMIT 1000
|
||||
");
|
||||
$stmt->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'belum_dibantu') {
|
||||
// Warga miskin yang BELUM pernah menerima bantuan
|
||||
$rows = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.pekerjaan, p.alamat, p.latitude, p.longitude,
|
||||
k.nama_kk, k.no_kk, ri.nama AS nama_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
WHERE p.status_hidup = 'hidup'
|
||||
AND p.status_ekonomi IN ('miskin', 'rentan')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM histori_bantuan hb
|
||||
WHERE hb.nik = p.nik AND hb.status_penyaluran = 'disalurkan'
|
||||
)
|
||||
ORDER BY
|
||||
CASE p.status_ekonomi WHEN 'miskin' THEN 1 ELSE 2 END,
|
||||
CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 WHEN 'disabilitas' THEN 2 ELSE 3 END
|
||||
")->fetchAll();
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
if ($action === 'dashboard_stats') {
|
||||
// Statistik untuk dashboard
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='miskin' AND status_hidup='hidup') AS total_miskin,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='rentan' AND status_hidup='hidup') AS total_rentan,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='mampu' AND status_hidup='hidup') AS total_mampu,
|
||||
(SELECT COUNT(*) FROM keluarga) AS total_keluarga,
|
||||
(SELECT COUNT(*) FROM rumah_ibadah) AS total_rumah_ibadah,
|
||||
(SELECT COUNT(*) FROM laporan WHERE status='pending') AS laporan_pending,
|
||||
(SELECT COUNT(*) FROM laporan WHERE status='diproses') AS laporan_diproses,
|
||||
(SELECT COUNT(*) FROM histori_bantuan WHERE MONTH(tanggal)=MONTH(CURDATE()) AND status_penyaluran='disalurkan') AS bantuan_bulan_ini,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi IN ('miskin','rentan')
|
||||
AND NOT EXISTS (SELECT 1 FROM histori_bantuan hb WHERE hb.nik=penduduk.nik AND hb.status_penyaluran='disalurkan')
|
||||
) AS belum_pernah_dibantu
|
||||
")->fetch();
|
||||
|
||||
// Bantuan per kategori bulan ini
|
||||
$per_kategori = $pdo->query("
|
||||
SELECT jb.kategori, COUNT(*) AS jumlah
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
WHERE MONTH(hb.tanggal) = MONTH(CURDATE())
|
||||
AND hb.status_penyaluran = 'disalurkan'
|
||||
GROUP BY jb.kategori
|
||||
")->fetchAll();
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $stats, 'per_kategori' => $per_kategori]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST: salurkan bantuan
|
||||
// ============================================================
|
||||
if ($method === 'POST' && $action === 'salurkan') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$nik = sanitizeStr($body['nik'] ?? '');
|
||||
$idJenis = (int)($body['id_jenis_bantuan'] ?? 0);
|
||||
$tanggal = sanitizeStr($body['tanggal'] ?? date('Y-m-d'));
|
||||
$jumlah = sanitizeStr($body['jumlah'] ?? '');
|
||||
$status = sanitizeStr($body['status_penyaluran'] ?? 'dijadwalkan');
|
||||
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
if ($idJenis <= 0) jsonResponse(false, 'Jenis bantuan harus dipilih.', [], 400);
|
||||
if (!strtotime($tanggal)) jsonResponse(false, 'Format tanggal tidak valid.', [], 400);
|
||||
|
||||
// Cek penduduk ada
|
||||
$cek = $pdo->prepare("SELECT nik, id_keluarga FROM penduduk WHERE nik = :nik AND status_hidup='hidup'");
|
||||
$cek->execute([':nik' => $nik]);
|
||||
$pddk = $cek->fetch();
|
||||
if (!$pddk) jsonResponse(false, 'Penduduk tidak ditemukan atau sudah meninggal.', [], 404);
|
||||
|
||||
// Cek jenis bantuan ada
|
||||
$cekJb = $pdo->prepare("SELECT id FROM jenis_bantuan WHERE id = :id AND aktif=1");
|
||||
$cekJb->execute([':id' => $idJenis]);
|
||||
if (!$cekJb->fetch()) jsonResponse(false, 'Jenis bantuan tidak ditemukan.', [], 404);
|
||||
|
||||
$buktiNama = null;
|
||||
if (!empty($_FILES['bukti']['name'])) {
|
||||
try { $buktiNama = uploadFile($_FILES['bukti'], UPLOAD_BUKTI); }
|
||||
catch (RuntimeException $e) { jsonResponse(false, $e->getMessage(), [], 400); }
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO histori_bantuan
|
||||
(nik, id_jenis_bantuan, id_keluarga, rumah_ibadah_id, tanggal,
|
||||
jumlah, nilai_rupiah, status_penyaluran, catatan, bukti_file, disalurkan_oleh)
|
||||
VALUES
|
||||
(:nik,:jb,:kk,:ri,:tgl,:jml,:rp,:status,:cat,:bukti,:user)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nik' => $nik,
|
||||
':jb' => $idJenis,
|
||||
':kk' => $pddk['id_keluarga'],
|
||||
':ri' => $currentUser['rumah_ibadah_id'],
|
||||
':tgl' => $tanggal,
|
||||
':jml' => $jumlah ?: null,
|
||||
':rp' => !empty($body['nilai_rupiah']) ? (float)$body['nilai_rupiah'] : null,
|
||||
':status' => in_array($status, ['dijadwalkan','disalurkan','dibatalkan']) ? $status : 'dijadwalkan',
|
||||
':cat' => sanitizeStr($body['catatan'] ?? '') ?: null,
|
||||
':bukti' => $buktiNama,
|
||||
':user' => $currentUser['id'],
|
||||
]);
|
||||
|
||||
$hbId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id'], 'CREATE', 'histori_bantuan', (string)$hbId, null, $body);
|
||||
|
||||
// Notifikasi ke pimpinan
|
||||
$pimpinan = $pdo->query("SELECT id FROM users WHERE role_id=3")->fetchAll();
|
||||
foreach ($pimpinan as $pm) {
|
||||
$pdo->prepare("INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id) VALUES (:u,:j,:i,'bantuan',:r)")
|
||||
->execute([':u' => $pm['id'], ':j' => 'Bantuan Disalurkan', ':i' => "Bantuan untuk NIK $nik telah dicatat.", ':r' => $hbId]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Bantuan berhasil dicatat.', ['id' => $hbId], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: update status penyaluran
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$status = sanitizeStr($body['status_penyaluran'] ?? '');
|
||||
|
||||
if (!in_array($status, ['dijadwalkan','disalurkan','dibatalkan'])) {
|
||||
jsonResponse(false, 'Status tidak valid.', [], 400);
|
||||
}
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM histori_bantuan WHERE id = :id");
|
||||
$old->execute([':id' => $id]);
|
||||
if (!$old->fetch()) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$pdo->prepare("UPDATE histori_bantuan SET status_penyaluran=:s WHERE id=:id")
|
||||
->execute([':s' => $status, ':id' => $id]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE', 'histori_bantuan', (string)$id);
|
||||
jsonResponse(true, 'Status penyaluran diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Keluarga
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($_GET['status_ekonomi'])) {
|
||||
$where[] = 'k.status_ekonomi = :se';
|
||||
$params[':se'] = $_GET['status_ekonomi'];
|
||||
}
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$rows = $pdo->prepare("
|
||||
SELECT k.*,
|
||||
ri.nama AS nama_ri, ri.jenis AS jenis_ri,
|
||||
COUNT(p.nik) AS jumlah_anggota_db,
|
||||
SUM(CASE WHEN p.kondisi_kesehatan IN ('sakit_parah','disabilitas') THEN 1 ELSE 0 END) AS anggota_sakit
|
||||
FROM keluarga k
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
LEFT JOIN penduduk p ON p.id_keluarga = k.id AND p.status_hidup='hidup'
|
||||
$whereStr
|
||||
GROUP BY k.id
|
||||
ORDER BY CASE k.status_ekonomi WHEN 'miskin' THEN 1 WHEN 'rentan' THEN 2 ELSE 3 END ASC
|
||||
");
|
||||
$rows->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $rows->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("SELECT k.*, ri.nama AS nama_ri FROM keluarga k LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id WHERE k.id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$kk = $stmt->fetch();
|
||||
if (!$kk) jsonResponse(false, 'Keluarga tidak ditemukan.', [], 404);
|
||||
|
||||
$anggota = $pdo->prepare("SELECT * FROM penduduk WHERE id_keluarga=:id ORDER BY CASE status_keluarga WHEN 'kepala_keluarga' THEN 1 ELSE 2 END, tanggal_lahir");
|
||||
$anggota->execute([':id' => $id]);
|
||||
|
||||
$histori = $pdo->prepare("
|
||||
SELECT hb.tanggal, jb.nama AS bantuan, hb.jumlah, hb.status_penyaluran, p.nama_lengkap
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik=hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id=hb.id_jenis_bantuan
|
||||
WHERE hb.id_keluarga=:id
|
||||
ORDER BY hb.tanggal DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$histori->execute([':id' => $id]);
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $kk, 'anggota' => $anggota->fetchAll(), 'histori' => $histori->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT k.id, k.no_kk, k.nama_kk, k.status_ekonomi, k.latitude, k.longitude,
|
||||
COUNT(p.nik) AS jml_anggota, ri.nama AS nama_ri
|
||||
FROM keluarga k
|
||||
LEFT JOIN penduduk p ON p.id_keluarga=k.id AND p.status_hidup='hidup'
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
WHERE k.latitude IS NOT NULL AND k.longitude IS NOT NULL
|
||||
GROUP BY k.id
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['longitude'],(float)$r['latitude']]],
|
||||
'properties' => array_merge($r, [
|
||||
'color' => match($r['status_ekonomi']){'miskin'=>'#ef4444','rentan'=>'#f59e0b',default=>'#22c55e'},
|
||||
'layer' => 'keluarga'
|
||||
])
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type'=>'FeatureCollection','features'=>$features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$noKk = sanitizeStr($body['no_kk'] ?? '');
|
||||
$namaKk = sanitizeStr($body['nama_kk'] ?? '');
|
||||
if (strlen($noKk) !== 16) jsonResponse(false, 'No KK harus 16 digit.', [], 400);
|
||||
if (strlen($namaKk) < 3) jsonResponse(false, 'Nama KK terlalu pendek.', [], 400);
|
||||
|
||||
$cek = $pdo->prepare("SELECT id FROM keluarga WHERE no_kk=:nkk");
|
||||
$cek->execute([':nkk' => $noKk]);
|
||||
if ($cek->fetch()) jsonResponse(false, 'No KK sudah terdaftar.', [], 409);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO keluarga (no_kk, nama_kk, alamat, kelurahan, kecamatan, kota, latitude, longitude, status_ekonomi, rumah_ibadah_id)
|
||||
VALUES (:nkk,:nkk_nama,:almt,:kel,:kec,:kota,:lat,:lng,:se,:ri)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nkk' => $noKk,
|
||||
':nkk_nama' => $namaKk,
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
':kota' => sanitizeStr($body['kota'] ?? '') ?: null,
|
||||
':lat' => !empty($body['latitude']) ? (float)$body['latitude'] : null,
|
||||
':lng' => !empty($body['longitude']) ? (float)$body['longitude'] : null,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':ri' => !empty($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : null,
|
||||
]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'CREATE', 'keluarga', (string)$newId, null, $body);
|
||||
jsonResponse(true, 'Keluarga berhasil ditambahkan.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
if ($id <= 0) jsonResponse(false, 'ID tidak valid.', [], 400);
|
||||
|
||||
if ($action === 'update_koordinat') {
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat tidak valid.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE keluarga SET latitude=:lat, longitude=:lng WHERE id=:id")
|
||||
->execute([':lat'=>$lat, ':lng'=>$lng, ':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'keluarga', (string)$id, null, ['lat'=>$lat,'lng'=>$lng]);
|
||||
jsonResponse(true, 'Koordinat diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
$pdo->prepare("
|
||||
UPDATE keluarga SET
|
||||
nama_kk=:nkk, alamat=:almt, kelurahan=:kel, kecamatan=:kec,
|
||||
kota=:kota, latitude=:lat, longitude=:lng,
|
||||
status_ekonomi=:se, rumah_ibadah_id=:ri
|
||||
WHERE id=:id
|
||||
")->execute([
|
||||
':nkk' => sanitizeStr($body['nama_kk'] ?? ''),
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
':kota' => sanitizeStr($body['kota'] ?? '') ?: null,
|
||||
':lat' => !empty($body['latitude']) ? (float)$body['latitude'] : null,
|
||||
':lng' => !empty($body['longitude']) ? (float)$body['longitude'] : null,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':ri' => !empty($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : null,
|
||||
':id' => $id,
|
||||
]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'keluarga', (string)$id);
|
||||
jsonResponse(true, 'Keluarga diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin yang bisa menghapus keluarga.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
// Cek apakah masih ada anggota
|
||||
$jml = (int)$pdo->prepare("SELECT COUNT(*) FROM penduduk WHERE id_keluarga=:id")->execute([':id'=>$id]);
|
||||
if ($jml > 0) jsonResponse(false, 'Tidak bisa dihapus: masih ada anggota keluarga.', [], 409);
|
||||
$pdo->prepare("DELETE FROM keluarga WHERE id=:id")->execute([':id' => $id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'DELETE', 'keluarga', (string)$id);
|
||||
jsonResponse(true, 'Keluarga dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Laporan Masyarakat
|
||||
// GET ?action=list|detail&id=...
|
||||
// POST action=create (multipart untuk foto)
|
||||
// PUT action=update_status&id=...
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET: list laporan
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status'])) {
|
||||
$where[] = 'l.status = :status';
|
||||
$params[':status'] = $_GET['status'];
|
||||
}
|
||||
if (!empty($_GET['urgensi'])) {
|
||||
$where[] = 'l.tingkat_urgensi = :urgensi';
|
||||
$params[':urgensi'] = $_GET['urgensi'];
|
||||
}
|
||||
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$rows = $pdo->prepare("
|
||||
SELECT l.*,
|
||||
p.nama_lengkap AS nama_terdampak,
|
||||
u.nama AS nama_verifikator
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik = l.nik_terdampak
|
||||
LEFT JOIN users u ON u.id = l.diverifikasi_oleh
|
||||
$whereStr
|
||||
ORDER BY
|
||||
CASE l.tingkat_urgensi WHEN 'darurat' THEN 1 WHEN 'tinggi' THEN 2 WHEN 'sedang' THEN 3 ELSE 4 END ASC,
|
||||
CASE l.status WHEN 'pending' THEN 1 WHEN 'diverifikasi' THEN 2 ELSE 3 END ASC,
|
||||
l.created_at DESC
|
||||
LIMIT 500
|
||||
");
|
||||
$rows->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $rows->fetchAll(), 'total' => $rows->rowCount()]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT l.*, p.nama_lengkap AS nama_terdampak, p.status_ekonomi,
|
||||
p.kondisi_kesehatan, p.alamat AS alamat_penduduk
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik = l.nik_terdampak
|
||||
WHERE l.id = :id
|
||||
");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$data = $stmt->fetch();
|
||||
if (!$data) jsonResponse(false, 'Laporan tidak ditemukan.', [], 404);
|
||||
jsonResponse(true, 'OK', ['data' => $data]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
// Untuk layer marker laporan di peta
|
||||
$stmt = $pdo->query("
|
||||
SELECT id, nama_pelapor, pelapor_id, kategori, tingkat_urgensi, status,
|
||||
latitude, longitude, alamat, created_at
|
||||
FROM laporan
|
||||
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
|
||||
AND status NOT IN ('selesai','ditolak')
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$color = match($r['tingkat_urgensi']) {
|
||||
'darurat' => '#dc2626',
|
||||
'tinggi' => '#ea580c',
|
||||
'sedang' => '#ca8a04',
|
||||
default => '#2563eb',
|
||||
};
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type' => 'Point', 'coordinates' => [(float)$r['longitude'], (float)$r['latitude']]],
|
||||
'properties' => array_merge($r, ['color' => $color, 'layer' => 'laporan'])
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type' => 'FeatureCollection', 'features' => $features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'stats') {
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
SUM(status='pending') AS pending,
|
||||
SUM(status='diverifikasi') AS diverifikasi,
|
||||
SUM(status='diproses') AS diproses,
|
||||
SUM(status='selesai') AS selesai,
|
||||
SUM(tingkat_urgensi='darurat') AS darurat,
|
||||
COUNT(*) AS total
|
||||
FROM laporan
|
||||
")->fetch();
|
||||
jsonResponse(true, 'OK', ['data' => $stats]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST: buat laporan baru (bisa dilakukan masyarakat umum)
|
||||
// ============================================================
|
||||
if ($method === 'POST') {
|
||||
$deskripsi = sanitizeStr($_POST['deskripsi'] ?? '', 2000);
|
||||
$kategori = sanitizeStr($_POST['kategori'] ?? 'butuh_bantuan_lain');
|
||||
$urgensi = sanitizeStr($_POST['tingkat_urgensi'] ?? 'sedang');
|
||||
$lat = !empty($_POST['latitude']) ? (float)$_POST['latitude'] : null;
|
||||
$lng = !empty($_POST['longitude']) ? (float)$_POST['longitude'] : null;
|
||||
|
||||
if (strlen($deskripsi) < 10) jsonResponse(false, 'Deskripsi laporan terlalu singkat.', [], 400);
|
||||
|
||||
$fotoNama = null;
|
||||
if (!empty($_FILES['foto']['name'])) {
|
||||
try {
|
||||
$fotoNama = uploadFile($_FILES['foto'], UPLOAD_LAPORAN, ALLOWED_IMG_EXT);
|
||||
} catch (RuntimeException $e) {
|
||||
jsonResponse(false, $e->getMessage(), [], 400);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO laporan
|
||||
(nama_pelapor, pelapor_id, nik_terdampak, deskripsi, kategori, tingkat_urgensi,
|
||||
latitude, longitude, alamat, foto, status)
|
||||
VALUES
|
||||
(:np, :pid, :nik, :dsk, :kat, :urg, :lat, :lng, :almt, :foto, 'pending')
|
||||
");
|
||||
$stmt->execute([
|
||||
':np' => sanitizeStr($_POST['nama_pelapor'] ?? '') ?: null,
|
||||
':pid' => $currentUser['id'] ?? null,
|
||||
':nik' => sanitizeStr($_POST['nik_terdampak'] ?? '') ?: null,
|
||||
':dsk' => $deskripsi,
|
||||
':kat' => $kategori,
|
||||
':urg' => $urgensi,
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':almt' => sanitizeStr($_POST['alamat'] ?? '') ?: null,
|
||||
':foto' => $fotoNama,
|
||||
]);
|
||||
|
||||
$laporanId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Kirim notifikasi ke pengurus jika darurat
|
||||
if (in_array($urgensi, ['darurat', 'tinggi'])) {
|
||||
$pengurus = $pdo->query("SELECT id FROM users WHERE role_id = 2")->fetchAll();
|
||||
foreach ($pengurus as $p) {
|
||||
$pdo->prepare("
|
||||
INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id)
|
||||
VALUES (:uid, :judul, :isi, 'laporan', :ref)
|
||||
")->execute([
|
||||
':uid' => $p['id'],
|
||||
':judul' => '🚨 Laporan ' . strtoupper($urgensi) . ' masuk!',
|
||||
':isi' => "Ada laporan baru kategori '$kategori'. Segera ditangani.",
|
||||
':ref' => $laporanId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'LAPORAN', 'laporan', (string)$laporanId);
|
||||
|
||||
jsonResponse(true, 'Laporan berhasil dikirim. Terima kasih!', ['id' => $laporanId], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: Update status laporan (hanya pengurus/admin)
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canVerify($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if ($id <= 0) jsonResponse(false, 'ID tidak valid.', [], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$status = sanitizeStr($body['status'] ?? '');
|
||||
$valid = ['diverifikasi', 'diproses', 'selesai', 'ditolak'];
|
||||
|
||||
if (!in_array($status, $valid)) jsonResponse(false, 'Status tidak valid.', [], 400);
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM laporan WHERE id = :id");
|
||||
$old->execute([':id' => $id]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Laporan tidak ditemukan.', [], 404);
|
||||
|
||||
$sets = ['status = :status', 'updated_at = NOW()'];
|
||||
$params = [':id' => $id, ':status' => $status];
|
||||
|
||||
if ($status === 'diverifikasi' && $currentUser) {
|
||||
$sets[] = 'diverifikasi_oleh = :verif';
|
||||
$params[':verif'] = $currentUser['id'];
|
||||
}
|
||||
if ($status === 'diproses' && $currentUser) {
|
||||
$sets[] = 'ditangani_oleh = :tangani';
|
||||
$params[':tangani'] = $currentUser['id'];
|
||||
}
|
||||
if ($status === 'selesai') {
|
||||
$sets[] = 'tanggal_selesai = NOW()';
|
||||
}
|
||||
if (!empty($body['catatan_verifikasi'])) {
|
||||
$sets[] = 'catatan_verifikasi = :catatan';
|
||||
$params[':catatan'] = sanitizeStr($body['catatan_verifikasi'], 1000);
|
||||
}
|
||||
|
||||
$pdo->prepare("UPDATE laporan SET " . implode(', ', $sets) . " WHERE id = :id")->execute($params);
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE', 'laporan', (string)$id, $dataLama, $body);
|
||||
|
||||
jsonResponse(true, "Status laporan diperbarui menjadi '$status'.", ['id' => $id]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: Update koordinat (via drag map)
|
||||
// ============================================================
|
||||
if ($method === 'PUT' && $action === 'update_koordinat') {
|
||||
if (!canVerify($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$lat = isset($body['latitude']) ? (float)$body['latitude'] : null;
|
||||
$lng = isset($body['longitude']) ? (float)$body['longitude'] : null;
|
||||
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Latitude dan Longitude wajib diisi.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE laporan SET latitude = :lat, longitude = :lng, updated_at = NOW() WHERE id = :id")->execute([
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE_KOORDINAT', 'laporan', (string)$id);
|
||||
jsonResponse(true, 'Koordinat laporan berhasil diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Penduduk
|
||||
// GET ?action=list|detail&nik=...
|
||||
// POST action=create
|
||||
// PUT action=update&nik=...
|
||||
// DELETE action=delete&nik=...
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = ['p.status_hidup = "hidup"'];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status_ekonomi'])) {
|
||||
$where[] = 'p.status_ekonomi = :se';
|
||||
$params[':se'] = $_GET['status_ekonomi'];
|
||||
}
|
||||
if (!empty($_GET['id_keluarga'])) {
|
||||
$where[] = 'p.id_keluarga = :kk';
|
||||
$params[':kk'] = (int)$_GET['id_keluarga'];
|
||||
}
|
||||
if (!empty($_GET['belum_dibantu'])) {
|
||||
$where[] = 'NOT EXISTS (SELECT 1 FROM histori_bantuan hb WHERE hb.nik = p.nik AND hb.status_penyaluran="disalurkan")';
|
||||
}
|
||||
if (!empty($_GET['kondisi_kesehatan'])) {
|
||||
$where[] = 'p.kondisi_kesehatan = :kk2';
|
||||
$params[':kk2'] = $_GET['kondisi_kesehatan'];
|
||||
}
|
||||
if (!empty($_GET['search'])) {
|
||||
$where[] = '(p.nama_lengkap LIKE :q OR p.nik LIKE :q2)';
|
||||
$params[':q'] = '%' . $_GET['search'] . '%';
|
||||
$params[':q2'] = '%' . $_GET['search'] . '%';
|
||||
}
|
||||
|
||||
$whereStr = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
p.nik, p.nama_lengkap, p.jenis_kelamin, p.tanggal_lahir,
|
||||
p.umur, p.status_keluarga, p.status_perkawinan,
|
||||
p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.pekerjaan, p.penghasilan,
|
||||
p.pendidikan_terakhir, p.status_pendidikan,
|
||||
p.alamat, p.latitude, p.longitude, p.foto,
|
||||
p.id_keluarga,
|
||||
k.no_kk, k.nama_kk, k.status_ekonomi AS status_ekonomi_kk,
|
||||
ri.nama AS nama_rumah_ibadah, ri.jenis AS jenis_rumah_ibadah,
|
||||
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.nik = p.nik AND hb.status_penyaluran='disalurkan') AS jumlah_bantuan_diterima,
|
||||
(SELECT MAX(hb2.tanggal) FROM histori_bantuan hb2 WHERE hb2.nik = p.nik AND hb2.status_penyaluran='disalurkan') AS terakhir_dibantu
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
$whereStr
|
||||
ORDER BY
|
||||
CASE p.status_ekonomi WHEN 'miskin' THEN 1 WHEN 'rentan' THEN 2 ELSE 3 END ASC,
|
||||
CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 WHEN 'disabilitas' THEN 2 WHEN 'sakit_ringan' THEN 3 ELSE 4 END ASC
|
||||
LIMIT 1000
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
// Hitung prioritas untuk setiap penduduk
|
||||
foreach ($rows as &$r) {
|
||||
$belumDibantu = (int)$r['jumlah_bantuan_diterima'] === 0;
|
||||
$r['prioritas_bantuan'] = hitungPrioritasBantuan($r['status_ekonomi'], $r['kondisi_kesehatan'], $belumDibantu);
|
||||
}
|
||||
unset($r);
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, k.no_kk, k.nama_kk, ri.nama AS nama_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
WHERE p.nik = :nik
|
||||
");
|
||||
$stmt->execute([':nik' => $nik]);
|
||||
$penduduk = $stmt->fetch();
|
||||
if (!$penduduk) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
// Histori bantuan
|
||||
$stmtHB = $pdo->prepare("
|
||||
SELECT hb.*, jb.nama AS nama_bantuan, jb.kategori, ri.nama AS nama_ri
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
WHERE hb.nik = :nik
|
||||
ORDER BY hb.tanggal DESC
|
||||
");
|
||||
$stmtHB->execute([':nik' => $nik]);
|
||||
$histori = $stmtHB->fetchAll();
|
||||
|
||||
// Pelatihan
|
||||
$stmtPl = $pdo->prepare("SELECT * FROM riwayat_pelatihan WHERE nik = :nik ORDER BY tanggal_mulai DESC");
|
||||
$stmtPl->execute([':nik' => $nik]);
|
||||
$pelatihan = $stmtPl->fetchAll();
|
||||
|
||||
// Validasi pendidikan
|
||||
$validasi = validasiPendidikanUmur(
|
||||
(int)$penduduk['umur'],
|
||||
$penduduk['pendidikan_terakhir'],
|
||||
$penduduk['status_pendidikan']
|
||||
);
|
||||
|
||||
$belumDibantu = empty($histori);
|
||||
$penduduk['prioritas_bantuan'] = hitungPrioritasBantuan($penduduk['status_ekonomi'], $penduduk['kondisi_kesehatan'], $belumDibantu);
|
||||
|
||||
jsonResponse(true, 'OK', [
|
||||
'data' => $penduduk,
|
||||
'histori' => $histori,
|
||||
'pelatihan' => $pelatihan,
|
||||
'validasi' => $validasi,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
// Untuk layer Leaflet
|
||||
$stmt = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.latitude, p.longitude,
|
||||
k.nama_kk,
|
||||
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan') AS jml_bantuan
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
WHERE p.latitude IS NOT NULL AND p.longitude IS NOT NULL
|
||||
AND p.status_hidup = 'hidup'
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$color = match($r['status_ekonomi']) {
|
||||
'miskin' => '#ef4444',
|
||||
'rentan' => '#f59e0b',
|
||||
default => '#22c55e'
|
||||
};
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => [
|
||||
'type' => 'Point',
|
||||
'coordinates' => [(float)$r['longitude'], (float)$r['latitude']]
|
||||
],
|
||||
'properties' => array_merge($r, ['color' => $color])
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type' => 'FeatureCollection', 'features' => $features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST - Create
|
||||
// ============================================================
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$nik = sanitizeStr($body['nik'] ?? '');
|
||||
$nama = sanitizeStr($body['nama_lengkap'] ?? '');
|
||||
$jk = sanitizeStr($body['jenis_kelamin'] ?? '');
|
||||
$tgl = sanitizeStr($body['tanggal_lahir'] ?? '');
|
||||
$idKk = !empty($body['id_keluarga']) ? (int)$body['id_keluarga'] : null;
|
||||
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'Format NIK harus 16 digit angka.', [], 400);
|
||||
if (strlen($nama) < 3) jsonResponse(false, 'Nama terlalu pendek.', [], 400);
|
||||
if (!in_array($jk, ['L','P'])) jsonResponse(false, 'Jenis kelamin tidak valid.', [], 400);
|
||||
if (!strtotime($tgl)) jsonResponse(false, 'Format tanggal lahir tidak valid.', [], 400);
|
||||
|
||||
// Cek duplikat NIK
|
||||
$cek = $pdo->prepare("SELECT nik FROM penduduk WHERE nik = :nik");
|
||||
$cek->execute([':nik' => $nik]);
|
||||
if ($cek->fetch()) jsonResponse(false, 'NIK sudah terdaftar.', [], 409);
|
||||
|
||||
// Hitung umur untuk validasi pendidikan
|
||||
$umur = (int)(new DateTime())->diff(new DateTime($tgl))->y;
|
||||
$statusPendidikan = sanitizeStr($body['status_pendidikan'] ?? 'tidak_sekolah');
|
||||
$pendidikan = sanitizeStr($body['pendidikan_terakhir'] ?? 'tidak_sekolah');
|
||||
$validasi = validasiPendidikanUmur($umur, $pendidikan, $statusPendidikan);
|
||||
|
||||
$lat = !empty($body['latitude']) ? (float)$body['latitude'] : null;
|
||||
$lng = !empty($body['longitude']) ? (float)$body['longitude'] : null;
|
||||
if ($lat !== null && !validateCoord((string)$lat, 'lat')) jsonResponse(false, 'Latitude tidak valid.', [], 400);
|
||||
if ($lng !== null && !validateCoord((string)$lng, 'lng')) jsonResponse(false, 'Longitude tidak valid.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO penduduk
|
||||
(nik, id_keluarga, nama_lengkap, jenis_kelamin, tanggal_lahir,
|
||||
status_keluarga, status_perkawinan, status_hidup,
|
||||
pendidikan_terakhir, status_pendidikan,
|
||||
pekerjaan, penghasilan, status_ekonomi,
|
||||
kondisi_kesehatan, catatan_kesehatan,
|
||||
no_telp, alamat, latitude, longitude)
|
||||
VALUES
|
||||
(:nik,:kk,:nama,:jk,:tgl,
|
||||
:sk,:sp,:sh,
|
||||
:ptk,:spd,
|
||||
:pkr,:pgn,:se,
|
||||
:kkh,:ckkh,
|
||||
:tlp,:almt,:lat,:lng)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nik' => $nik,
|
||||
':kk' => $idKk,
|
||||
':nama' => $nama,
|
||||
':jk' => $jk,
|
||||
':tgl' => $tgl,
|
||||
':sk' => sanitizeStr($body['status_keluarga'] ?? 'anggota_lain'),
|
||||
':sp' => sanitizeStr($body['status_perkawinan'] ?? 'belum_kawin'),
|
||||
':sh' => sanitizeStr($body['status_hidup'] ?? 'hidup'),
|
||||
':ptk' => $pendidikan,
|
||||
':spd' => $statusPendidikan,
|
||||
':pkr' => sanitizeStr($body['pekerjaan'] ?? '') ?: null,
|
||||
':pgn' => !empty($body['penghasilan']) ? (float)$body['penghasilan'] : 0,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':kkh' => sanitizeStr($body['kondisi_kesehatan'] ?? 'sehat'),
|
||||
':ckkh' => sanitizeStr($body['catatan_kesehatan'] ?? '') ?: null,
|
||||
':tlp' => sanitizeStr($body['no_telp'] ?? '') ?: null,
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'CREATE', 'penduduk', $nik, null, $body);
|
||||
|
||||
jsonResponse(true, 'Penduduk berhasil ditambahkan.', [
|
||||
'nik' => $nik,
|
||||
'validasi' => $validasi
|
||||
], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT - Update
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
// Ambil data lama
|
||||
$old = $pdo->prepare("SELECT * FROM penduduk WHERE nik = :nik");
|
||||
$old->execute([':nik' => $nik]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$sets = [];
|
||||
$params = [':nik' => $nik];
|
||||
|
||||
$fieldMap = [
|
||||
'id_keluarga' => ':kk', 'nama_lengkap' => ':nama', 'jenis_kelamin' => ':jk',
|
||||
'tanggal_lahir' => ':tgl', 'status_keluarga' => ':sk', 'status_perkawinan' => ':sp',
|
||||
'status_hidup' => ':sh', 'pendidikan_terakhir' => ':ptk', 'status_pendidikan' => ':spd',
|
||||
'pekerjaan' => ':pkr', 'penghasilan' => ':pgn', 'status_ekonomi' => ':se',
|
||||
'kondisi_kesehatan' => ':kkh', 'catatan_kesehatan' => ':ckkh',
|
||||
'no_telp' => ':tlp', 'alamat' => ':almt', 'latitude' => ':lat', 'longitude' => ':lng',
|
||||
];
|
||||
|
||||
foreach ($fieldMap as $field => $param) {
|
||||
if (array_key_exists($field, $body)) {
|
||||
$sets[] = "$field = $param";
|
||||
$params[$param] = $body[$field] === '' ? null : $body[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sets)) jsonResponse(false, 'Tidak ada data yang diubah.', [], 400);
|
||||
|
||||
$sql = "UPDATE penduduk SET " . implode(', ', $sets) . " WHERE nik = :nik";
|
||||
$pdo->prepare($sql)->execute($params);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'UPDATE', 'penduduk', $nik, $dataLama, $body);
|
||||
|
||||
jsonResponse(true, 'Data penduduk berhasil diperbarui.', ['nik' => $nik]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DELETE
|
||||
// ============================================================
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin yang bisa menghapus data.', [], 403);
|
||||
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM penduduk WHERE nik = :nik");
|
||||
$old->execute([':nik' => $nik]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$pdo->prepare("DELETE FROM penduduk WHERE nik = :nik")->execute([':nik' => $nik]);
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'DELETE', 'penduduk', $nik, $dataLama, null);
|
||||
|
||||
jsonResponse(true, 'Data penduduk dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Pesan / Chat antar Pengurus Rumah Ibadah
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// Harus login untuk fitur chat
|
||||
if (!$currentUser) jsonResponse(false, 'Harus login.', [], 401);
|
||||
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'inbox') {
|
||||
$riId = !empty($_GET['rumah_ibadah_id']) ? (int)$_GET['rumah_ibadah_id'] : null;
|
||||
|
||||
if ($riId) {
|
||||
// Chat diskusi per rumah ibadah
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, u.nama AS pengirim, u.role_id,
|
||||
ri.nama AS nama_ri
|
||||
FROM pesan p
|
||||
JOIN users u ON u.id = p.dari_user_id
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = p.ke_rumah_ibadah_id
|
||||
WHERE p.ke_rumah_ibadah_id = :ri
|
||||
ORDER BY p.created_at ASC
|
||||
LIMIT 200
|
||||
");
|
||||
$stmt->execute([':ri' => $riId]);
|
||||
} else {
|
||||
// Pesan langsung ke user
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, u.nama AS pengirim
|
||||
FROM pesan p
|
||||
JOIN users u ON u.id = p.dari_user_id
|
||||
WHERE p.ke_user_id = :uid OR p.dari_user_id = :uid2
|
||||
ORDER BY p.created_at ASC
|
||||
LIMIT 200
|
||||
");
|
||||
$stmt->execute([':uid' => $currentUser['id'], ':uid2' => $currentUser['id']]);
|
||||
}
|
||||
$pesan = $stmt->fetchAll();
|
||||
|
||||
// Tandai sudah dibaca
|
||||
if ($riId) {
|
||||
$pdo->prepare("UPDATE pesan SET dibaca=1 WHERE ke_rumah_ibadah_id=:ri AND dari_user_id != :uid")
|
||||
->execute([':ri' => $riId, ':uid' => $currentUser['id']]);
|
||||
} else {
|
||||
$pdo->prepare("UPDATE pesan SET dibaca=1 WHERE ke_user_id=:uid")
|
||||
->execute([':uid' => $currentUser['id']]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $pesan]);
|
||||
}
|
||||
|
||||
if ($action === 'notifikasi') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT * FROM notifikasi
|
||||
WHERE user_id = :uid
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 30
|
||||
");
|
||||
$stmt->execute([':uid' => $currentUser['id']]);
|
||||
$notif = $stmt->fetchAll();
|
||||
$unread = (int)$pdo->prepare("SELECT COUNT(*) FROM notifikasi WHERE user_id=:uid AND dibaca=0")
|
||||
->execute([':uid' => $currentUser['id']]);
|
||||
jsonResponse(true, 'OK', ['data' => $notif, 'unread' => array_sum(array_column($notif, 'dibaca') === array_map(fn($r) => $r['dibaca'] == 0, $notif))]);
|
||||
}
|
||||
|
||||
if ($action === 'pengurus_list') {
|
||||
// Daftar pengurus untuk pilihan chat
|
||||
$rows = $pdo->query("
|
||||
SELECT u.id, u.nama, u.email, ri.nama AS nama_ri, ri.jenis AS jenis_ri
|
||||
FROM users u
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = u.rumah_ibadah_id
|
||||
WHERE u.role_id IN (1,2) AND u.aktif=1
|
||||
ORDER BY u.nama
|
||||
")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
// Kirim pesan
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$isi = sanitizeStr($body['isi'] ?? '', 2000);
|
||||
if (strlen(trim($isi)) < 1) jsonResponse(false, 'Pesan tidak boleh kosong.', [], 400);
|
||||
|
||||
$keUserId = !empty($body['ke_user_id']) ? (int)$body['ke_user_id'] : null;
|
||||
$keRiId = !empty($body['ke_rumah_ibadah_id']) ? (int)$body['ke_rumah_ibadah_id'] : null;
|
||||
|
||||
if (!$keUserId && !$keRiId) jsonResponse(false, 'Tujuan pesan harus diisi.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO pesan (dari_user_id, ke_user_id, ke_rumah_ibadah_id, isi)
|
||||
VALUES (:dari, :ke_user, :ke_ri, :isi)
|
||||
");
|
||||
$stmt->execute([
|
||||
':dari' => $currentUser['id'],
|
||||
':ke_user' => $keUserId,
|
||||
':ke_ri' => $keRiId,
|
||||
':isi' => $isi,
|
||||
]);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Notifikasi ke penerima
|
||||
if ($keUserId) {
|
||||
$pdo->prepare("INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id) VALUES (:u,:j,:i,'pesan',:r)")
|
||||
->execute([':u'=>$keUserId, ':j'=>"Pesan dari {$currentUser['role']}", ':i'=>substr($isi,0,100), ':r'=>$newId]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Pesan terkirim.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Rumah Ibadah
|
||||
// ============================================================
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
if ($method === 'GET') {
|
||||
if ($action === 'list') {
|
||||
$rows = $pdo->query("
|
||||
SELECT ri.*,
|
||||
COUNT(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.rumah_ibadah_id = ri.id
|
||||
GROUP BY ri.id
|
||||
ORDER BY ri.nama
|
||||
")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
if ($action === 'geojson') {
|
||||
$rows = $pdo->query("SELECT * FROM rumah_ibadah")->fetchAll();
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['longitude'],(float)$r['latitude']]],
|
||||
'properties' => $r
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type'=>'FeatureCollection','features'=>$features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$nama = sanitizeStr($body['nama'] ?? '');
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (strlen($nama) < 3) jsonResponse(false, 'Nama terlalu pendek.', [], 400);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat wajib diisi.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO rumah_ibadah (nama, jenis, kontak, radius_meter, latitude, longitude, alamat, kelurahan, kecamatan)
|
||||
VALUES (:nama,:jenis,:kontak,:radius,:lat,:lng,:alamat,:kel,:kec)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':jenis' => sanitizeStr($body['jenis'] ?? 'Lainnya'),
|
||||
':kontak' => sanitizeStr($body['kontak'] ?? '') ?: null,
|
||||
':radius' => (float)($body['radius_meter'] ?? 300),
|
||||
':lat' => $lat, ':lng' => $lng,
|
||||
':alamat' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'CREATE', 'rumah_ibadah', (string)$newId, null, $body);
|
||||
jsonResponse(true, 'Rumah ibadah ditambahkan.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
if ($action === 'update_koordinat') {
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat tidak valid.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE rumah_ibadah SET latitude=:lat, longitude=:lng WHERE id=:id")
|
||||
->execute([':lat'=>$lat, ':lng'=>$lng, ':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'rumah_ibadah', (string)$id, null, ['lat'=>$lat,'lng'=>$lng]);
|
||||
jsonResponse(true, 'Koordinat diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
$pdo->prepare("
|
||||
UPDATE rumah_ibadah SET nama=:nama,jenis=:jenis,kontak=:kontak,radius_meter=:radius,
|
||||
latitude=:lat,longitude=:lng,alamat=:alamat,kelurahan=:kel,kecamatan=:kec WHERE id=:id
|
||||
")->execute([
|
||||
':nama'=>sanitizeStr($body['nama']??''), ':jenis'=>sanitizeStr($body['jenis']??'Lainnya'),
|
||||
':kontak'=>sanitizeStr($body['kontak']??'')?: null, ':radius'=>(float)($body['radius_meter']??300),
|
||||
':lat'=>(float)($body['latitude']??0), ':lng'=>(float)($body['longitude']??0),
|
||||
':alamat'=>sanitizeStr($body['alamat']??'')?: null, ':kel'=>sanitizeStr($body['kelurahan']??'')?: null,
|
||||
':kec'=>sanitizeStr($body['kecamatan']??'')?: null, ':id'=>$id,
|
||||
]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'rumah_ibadah', (string)$id);
|
||||
jsonResponse(true, 'Rumah ibadah diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$pdo->prepare("DELETE FROM rumah_ibadah WHERE id=:id")->execute([':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'DELETE', 'rumah_ibadah', (string)$id);
|
||||
jsonResponse(true, 'Dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// Konfigurasi & Koneksi Database
|
||||
// WebGIS Pengentasan Kemiskinan v2.0
|
||||
// ============================================================
|
||||
|
||||
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
|
||||
define('DB_PORT', getenv('DB_PORT') ?: '3306');
|
||||
define('DB_NAME', getenv('DB_NAME') ?: 'webgis_kemiskinan');
|
||||
define('DB_USER', getenv('DB_USER') ?: 'root');
|
||||
define('DB_PASS', getenv('DB_PASSWORD') ?: '');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
define('APP_NAME', 'WebGIS Pengentasan Kemiskinan');
|
||||
define('APP_VERSION', '2.0.0');
|
||||
define('APP_URL', 'http://localhost/webgis-kemiskinan');
|
||||
define('APP_ENV', 'development'); // Ganti ke 'production' saat deploy
|
||||
|
||||
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
|
||||
define('UPLOAD_BUKTI', UPLOAD_PATH . 'bukti/');
|
||||
define('UPLOAD_LAPORAN', UPLOAD_PATH . 'foto_laporan/');
|
||||
define('UPLOAD_FOTO', UPLOAD_PATH . 'foto_profil/');
|
||||
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5 MB
|
||||
define('ALLOWED_IMG_EXT', ['jpg','jpeg','png','webp']);
|
||||
define('ALLOWED_FILE_EXT', ['jpg','jpeg','png','webp','pdf']);
|
||||
|
||||
// Buat folder upload jika belum ada
|
||||
foreach ([UPLOAD_BUKTI, UPLOAD_LAPORAN, UPLOAD_FOTO] as $dir) {
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Koneksi PDO
|
||||
// ============================================================
|
||||
|
||||
$dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||
|
||||
$pdoOptions = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, DB_USER, DB_PASS, $pdoOptions);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
// Jangan expose detail error di produksi
|
||||
$errMsg = (defined('APP_ENV') && APP_ENV === 'development') ? $e->getMessage() : 'Hubungi administrator sistem.';
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Koneksi database gagal: ' . DB_NAME . '@' . DB_HOST . ' tidak ditemukan. Pastikan database sudah dibuat dan nama database benar.',
|
||||
'error' => $errMsg
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Response JSON
|
||||
// ============================================================
|
||||
|
||||
function jsonResponse(bool $success, string $message, array $data = [], int $httpCode = 200): void {
|
||||
http_response_code($httpCode);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
echo json_encode(array_merge(['success' => $success, 'message' => $message], $data), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Sanitasi & Validasi
|
||||
// ============================================================
|
||||
|
||||
function h(string $s): string {
|
||||
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function sanitizeStr(?string $v, int $max = 255): string {
|
||||
return mb_substr(trim($v ?? ''), 0, $max);
|
||||
}
|
||||
|
||||
function validateNIK(string $nik): bool {
|
||||
return preg_match('/^\d{16}$/', $nik) === 1;
|
||||
}
|
||||
|
||||
function validateCoord(string $val, string $type = 'lat'): bool {
|
||||
$f = (float)$val;
|
||||
return $type === 'lat' ? ($f >= -90 && $f <= 90) : ($f >= -180 && $f <= 180);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Upload File
|
||||
// ============================================================
|
||||
|
||||
function uploadFile(array $file, string $destDir, array $allowedExt = null): ?string {
|
||||
if ($allowedExt === null) $allowedExt = ALLOWED_FILE_EXT;
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) return null;
|
||||
if ($file['size'] > MAX_FILE_SIZE) throw new RuntimeException('Ukuran file melebihi 5MB.');
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowedExt, true)) throw new RuntimeException("Format file .$ext tidak diizinkan.");
|
||||
$filename = uniqid('img_', true) . '.' . $ext;
|
||||
$dest = rtrim($destDir, '/') . '/' . $filename;
|
||||
if (!move_uploaded_file($file['tmp_name'], $dest)) throw new RuntimeException('Gagal menyimpan file.');
|
||||
return $filename;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Log Aktivitas
|
||||
// ============================================================
|
||||
|
||||
function logAktivitas(PDO $pdo, ?int $userId, string $aksi, string $tabel = '', string $recordId = '', $dataLama = null, $dataBaru = null): void {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO log_aktivitas (user_id, aksi, tabel, record_id, data_lama, data_baru, ip_address, user_agent)
|
||||
VALUES (:u, :a, :t, :r, :dl, :db, :ip, :ua)
|
||||
");
|
||||
$stmt->execute([
|
||||
':u' => $userId,
|
||||
':a' => $aksi,
|
||||
':t' => $tabel,
|
||||
':r' => $recordId,
|
||||
':dl' => $dataLama ? json_encode($dataLama, JSON_UNESCAPED_UNICODE) : null,
|
||||
':db' => $dataBaru ? json_encode($dataBaru, JSON_UNESCAPED_UNICODE) : null,
|
||||
':ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
':ua' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
|
||||
]);
|
||||
} catch (Exception) { /* log gagal tidak menghentikan proses */ }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Validasi Logika Pendidikan vs Umur
|
||||
// ============================================================
|
||||
|
||||
function validasiPendidikanUmur(int $umur, string $pendidikan, string $statusPendidikan): array {
|
||||
$warnings = [];
|
||||
$errors = [];
|
||||
|
||||
if ($umur >= 7 && $umur <= 12) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SD.";
|
||||
}
|
||||
} elseif ($umur >= 13 && $umur <= 15) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMP.";
|
||||
}
|
||||
} elseif ($umur >= 16 && $umur <= 18) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMA/SMK.";
|
||||
}
|
||||
}
|
||||
// Umur > 18: bebas, boleh tidak sekolah, boleh pelatihan
|
||||
|
||||
return ['warnings' => $warnings, 'errors' => $errors];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Prioritas Bantuan
|
||||
// ============================================================
|
||||
|
||||
function hitungPrioritasBantuan(string $statusEkonomi, string $kondisiKesehatan, bool $belumPernahDibantu): string {
|
||||
if ($statusEkonomi === 'miskin' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) {
|
||||
return 'KRITIS';
|
||||
}
|
||||
if ($statusEkonomi === 'miskin' && $belumPernahDibantu) {
|
||||
return 'TINGGI';
|
||||
}
|
||||
if ($statusEkonomi === 'miskin') {
|
||||
return 'SEDANG';
|
||||
}
|
||||
if ($statusEkonomi === 'rentan' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) {
|
||||
return 'SEDANG';
|
||||
}
|
||||
return 'RENDAH';
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// Middleware Autentikasi & Otorisasi
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../koneksi.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start([
|
||||
'cookie_httponly' => true,
|
||||
'cookie_samesite' => 'Strict',
|
||||
'use_strict_mode' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Fungsi login / logout
|
||||
// ============================================================
|
||||
|
||||
function doLogin(PDO $pdo, string $username, string $password): array {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT u.*, r.nama AS role_nama
|
||||
FROM users u
|
||||
JOIN roles r ON r.id = u.role_id
|
||||
WHERE u.username = :u AND u.aktif = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':u' => $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
return ['success' => false, 'message' => 'Username atau password salah.'];
|
||||
}
|
||||
|
||||
// Update last_login
|
||||
$pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = :id")
|
||||
->execute([':id' => $user['id']]);
|
||||
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_nama'] = $user['nama'];
|
||||
$_SESSION['role'] = $user['role_nama'];
|
||||
$_SESSION['role_id'] = $user['role_id'];
|
||||
$_SESSION['rumah_ibadah_id'] = $user['rumah_ibadah_id'];
|
||||
session_regenerate_id(true);
|
||||
|
||||
logAktivitas($pdo, $user['id'], 'LOGIN', 'users', (string)$user['id']);
|
||||
|
||||
return ['success' => true, 'role' => $user['role_nama']];
|
||||
}
|
||||
|
||||
function doLogout(PDO $pdo): void {
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
logAktivitas($pdo, $_SESSION['user_id'], 'LOGOUT', 'users', (string)$_SESSION['user_id']);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guard: wajib login
|
||||
// ============================================================
|
||||
|
||||
function requireLogin(array $allowedRoles = []): array {
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
// Jika request API
|
||||
if (!empty($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
jsonResponse(false, 'Sesi habis. Silakan login kembali.', [], 401);
|
||||
}
|
||||
header('Location: ' . APP_URL . '/admin/login.php');
|
||||
exit;
|
||||
}
|
||||
if (!empty($allowedRoles) && !in_array($_SESSION['role'], $allowedRoles, true)) {
|
||||
if (!empty($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
jsonResponse(false, 'Tidak punya akses.', [], 403);
|
||||
}
|
||||
header('Location: ' . APP_URL . '/admin/forbidden.php');
|
||||
exit;
|
||||
}
|
||||
return [
|
||||
'id' => (int)$_SESSION['user_id'],
|
||||
'nama' => $_SESSION['user_nama'],
|
||||
'role' => $_SESSION['role'],
|
||||
'role_id' => (int)$_SESSION['role_id'],
|
||||
'rumah_ibadah_id' => $_SESSION['rumah_ibadah_id'] ? (int)$_SESSION['rumah_ibadah_id'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guard API: token-based (opsional, untuk mobile app nanti)
|
||||
// ============================================================
|
||||
|
||||
function getApiUser(PDO $pdo): ?array {
|
||||
// Cek session dulu
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
return [
|
||||
'id' => (int)$_SESSION['user_id'],
|
||||
'role' => $_SESSION['role'],
|
||||
'role_id' => (int)$_SESSION['role_id'],
|
||||
'rumah_ibadah_id' => $_SESSION['rumah_ibadah_id'] ? (int)$_SESSION['rumah_ibadah_id'] : null,
|
||||
];
|
||||
}
|
||||
return null; // belum login = null
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Cek izin per fitur
|
||||
// ============================================================
|
||||
|
||||
function canEdit(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus'], true);
|
||||
}
|
||||
|
||||
function canVerify(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus'], true);
|
||||
}
|
||||
|
||||
function canViewAll(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus', 'pimpinan'], true);
|
||||
}
|
||||
|
||||
function isAdmin(?array $user): bool {
|
||||
return $user && $user['role'] === 'admin';
|
||||
}
|
||||
Reference in New Issue
Block a user