initial commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
jsonResponse(['error' => 'Method tidak didukung'], 405);
|
||||
}
|
||||
|
||||
$admin = requireRole('admin');
|
||||
$db = getDB();
|
||||
|
||||
// Pagination
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = min(100, max(10, (int)($_GET['limit'] ?? 50)));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// Filter
|
||||
$filterAksi = $_GET['aksi'] ?? null;
|
||||
$filterUser = $_GET['user_id'] ?? null;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($filterAksi) {
|
||||
$where[] = "al.aksi LIKE ?";
|
||||
$params[] = "%$filterAksi%";
|
||||
}
|
||||
if ($filterUser) {
|
||||
$where[] = "al.user_id = ?";
|
||||
$params[] = (int)$filterUser;
|
||||
}
|
||||
|
||||
$whereClause = count($where) > 0 ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
// Total count
|
||||
$countSql = "SELECT COUNT(*) FROM audit_logs al $whereClause";
|
||||
$countStmt = $db->prepare($countSql);
|
||||
$countStmt->execute($params);
|
||||
$total = (int)$countStmt->fetchColumn();
|
||||
|
||||
// Data
|
||||
$sql = "
|
||||
SELECT al.*, u.username, u.nama_lengkap
|
||||
FROM audit_logs al
|
||||
LEFT JOIN users u ON u.id = al.user_id
|
||||
$whereClause
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT $limit OFFSET $offset
|
||||
";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
jsonResponse([
|
||||
'data' => $rows,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => ceil($total / $limit),
|
||||
]);
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.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');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
$db = getDB();
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$username = trim($body['username'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
|
||||
if (!$username || !$password) {
|
||||
jsonResponse(['success' => false, 'error' => 'Username dan password wajib diisi'], 422);
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("SELECT * FROM users WHERE username = ? AND is_active = 1");
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !password_verify($password, $user['password'])) {
|
||||
jsonResponse(['success' => false, 'error' => 'Username atau password salah'], 401);
|
||||
}
|
||||
|
||||
// Set session
|
||||
$_SESSION['user'] = [
|
||||
'id' => (int)$user['id'],
|
||||
'username' => $user['username'],
|
||||
'nama_lengkap' => $user['nama_lengkap'],
|
||||
'role' => $user['role'],
|
||||
];
|
||||
|
||||
auditLog($db, (int)$user['id'], 'LOGIN', 'users', (int)$user['id']);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'user' => $_SESSION['user']
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// LOGOUT
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'logout' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$user = getCurrentUser();
|
||||
if ($user) {
|
||||
auditLog($db, $user['id'], 'LOGOUT', 'users', $user['id']);
|
||||
}
|
||||
session_destroy();
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// ME — cek session aktif
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'me' && $_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$user = getCurrentUser();
|
||||
if (!$user) {
|
||||
jsonResponse(['success' => false, 'logged_in' => false], 200);
|
||||
}
|
||||
jsonResponse(['success' => true, 'logged_in' => true, 'user' => $user]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// REGISTER — admin only
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'register' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$admin = requireRole('admin');
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$username = trim($body['username'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
$nama_lengkap = trim($body['nama_lengkap'] ?? '');
|
||||
$role = $body['role'] ?? 'surveyor';
|
||||
|
||||
if (!$username || !$password || !$nama_lengkap) {
|
||||
jsonResponse(['success' => false, 'error' => 'Semua field wajib diisi'], 422);
|
||||
}
|
||||
if (strlen($password) < 6) {
|
||||
jsonResponse(['success' => false, 'error' => 'Password minimal 6 karakter'], 422);
|
||||
}
|
||||
if (!in_array($role, ['admin', 'surveyor', 'pemangku'])) {
|
||||
jsonResponse(['success' => false, 'error' => 'Role tidak valid'], 422);
|
||||
}
|
||||
|
||||
// Cek username unik
|
||||
$check = $db->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$check->execute([$username]);
|
||||
if ($check->fetch()) {
|
||||
jsonResponse(['success' => false, 'error' => 'Username sudah digunakan'], 409);
|
||||
}
|
||||
|
||||
$hashed = password_hash($password, PASSWORD_BCRYPT);
|
||||
$stmt = $db->prepare("INSERT INTO users (username, password, nama_lengkap, role) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $hashed, $nama_lengkap, $role]);
|
||||
$newId = (int)$db->lastInsertId();
|
||||
|
||||
auditLog($db, $admin['id'], 'CREATE_USER', 'users', $newId, null, [
|
||||
'username' => $username, 'nama_lengkap' => $nama_lengkap, 'role' => $role
|
||||
]);
|
||||
|
||||
jsonResponse(['success' => true, 'id' => $newId]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// LIST USERS — admin only
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'users' && $_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
requireRole('admin');
|
||||
$rows = $db->query("SELECT id, username, nama_lengkap, role, is_active, created_at FROM users ORDER BY created_at DESC")->fetchAll();
|
||||
jsonResponse(['data' => $rows]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// UPDATE USER — admin only
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'update' && $_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
$admin = requireRole('admin');
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
if (!$id) jsonResponse(['success' => false, 'error' => 'ID diperlukan'], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
// Ambil data lama
|
||||
$old = $db->prepare("SELECT id, username, nama_lengkap, role, is_active FROM users WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) jsonResponse(['success' => false, 'error' => 'User tidak ditemukan'], 404);
|
||||
|
||||
$fields = []; $vals = [];
|
||||
|
||||
if (!empty($body['nama_lengkap'])) {
|
||||
$fields[] = "nama_lengkap = ?"; $vals[] = trim($body['nama_lengkap']);
|
||||
}
|
||||
if (!empty($body['role']) && in_array($body['role'], ['admin','surveyor','pemangku'])) {
|
||||
$fields[] = "role = ?"; $vals[] = $body['role'];
|
||||
}
|
||||
if (isset($body['is_active'])) {
|
||||
$fields[] = "is_active = ?"; $vals[] = $body['is_active'] ? 1 : 0;
|
||||
}
|
||||
if (!empty($body['password']) && strlen($body['password']) >= 6) {
|
||||
$fields[] = "password = ?"; $vals[] = password_hash($body['password'], PASSWORD_BCRYPT);
|
||||
}
|
||||
|
||||
if (empty($fields)) jsonResponse(['success' => false, 'error' => 'Tidak ada data diubah'], 422);
|
||||
|
||||
$vals[] = $id;
|
||||
$db->prepare("UPDATE users SET " . implode(',', $fields) . " WHERE id = ?")->execute($vals);
|
||||
|
||||
auditLog($db, $admin['id'], 'UPDATE_USER', 'users', $id, $oldData, $body);
|
||||
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// DELETE (deactivate) USER — admin only
|
||||
// ══════════════════════════════════════════════
|
||||
if ($action === 'delete' && $_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
||||
$admin = requireRole('admin');
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
if (!$id) jsonResponse(['success' => false, 'error' => 'ID diperlukan'], 400);
|
||||
|
||||
if ($id === $admin['id']) {
|
||||
jsonResponse(['success' => false, 'error' => 'Tidak bisa menghapus akun sendiri'], 400);
|
||||
}
|
||||
|
||||
$db->prepare("UPDATE users SET is_active = 0 WHERE id = ?")->execute([$id]);
|
||||
|
||||
auditLog($db, $admin['id'], 'DEACTIVATE_USER', 'users', $id);
|
||||
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Action tidak valid'], 400);
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// ── Konfigurasi Database ──────────────────────
|
||||
// Sesuaikan dengan pengaturan XAMPP Anda
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_USER', 'root'); // default XAMPP
|
||||
define('DB_PASS', ''); // default XAMPP (kosong)
|
||||
define('DB_NAME', 'webgis_poverty_mapping');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
// ── Upload Config ─────────────────────────────
|
||||
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
|
||||
define('MAX_UPLOAD_SIZE', 5 * 1024 * 1024); // 5MB
|
||||
|
||||
// ── Session ───────────────────────────────────
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
function getDB(): PDO {
|
||||
static $pdo = null;
|
||||
if ($pdo === null) {
|
||||
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=%s', DB_HOST, DB_NAME, DB_CHARSET);
|
||||
$options = [
|
||||
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, $options);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['success' => false, 'error' => 'Koneksi DB gagal: ' . $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
// ── JSON response helper ──────────────────────
|
||||
function jsonResponse(array $data, int $code = 200): void {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if ($origin) {
|
||||
header('Access-Control-Allow-Origin: ' . $origin);
|
||||
} else {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
}
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Auth Helpers ──────────────────────────────
|
||||
|
||||
/**
|
||||
* Cek apakah user sudah login.
|
||||
* Return data user atau null.
|
||||
*/
|
||||
function getCurrentUser(): ?array {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paksa login. Jika belum login, return 401.
|
||||
*/
|
||||
function requireAuth(): array {
|
||||
$user = getCurrentUser();
|
||||
if (!$user) {
|
||||
jsonResponse(['success' => false, 'error' => 'Unauthorized — silakan login'], 401);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paksa role tertentu. Jika role tidak sesuai, return 403.
|
||||
* @param array|string $roles Role yang diizinkan
|
||||
*/
|
||||
function requireRole($roles): array {
|
||||
$user = requireAuth();
|
||||
if (is_string($roles)) $roles = [$roles];
|
||||
if (!in_array($user['role'], $roles)) {
|
||||
jsonResponse(['success' => false, 'error' => 'Forbidden — Anda tidak memiliki akses'], 403);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
// ── Audit Log Helper ──────────────────────────
|
||||
|
||||
/**
|
||||
* Catat aktivitas ke audit_logs
|
||||
*/
|
||||
function auditLog(PDO $db, int $userId, string $aksi, ?string $targetType = null, ?int $targetId = null, $before = null, $after = null): void {
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO audit_logs (user_id, aksi, target_type, target_id, data_before, data_after, ip_address)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$userId,
|
||||
$aksi,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$before ? json_encode($before, JSON_UNESCAPED_UNICODE) : null,
|
||||
$after ? json_encode($after, JSON_UNESCAPED_UNICODE) : null,
|
||||
$_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// ── Haversine distance (meter) ──────────────
|
||||
function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float {
|
||||
$R = 6371000;
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLng = deg2rad($lng2 - $lng1);
|
||||
$a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2;
|
||||
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
}
|
||||
|
||||
// ── Cari rumah ibadah terdekat yang menjangkau koordinat ──
|
||||
// Hanya rumah ibadah yang sudah verified
|
||||
function findNearestIbadah(PDO $db, float $lat, float $lng): ?int {
|
||||
$ibadahs = $db->query("SELECT id, lat, lng, radius FROM rumah_ibadah WHERE status = 'verified'")->fetchAll();
|
||||
$nearest = null;
|
||||
$minDist = PHP_FLOAT_MAX;
|
||||
foreach ($ibadahs as $ib) {
|
||||
$d = haversine($lat, $lng, (float)$ib['lat'], (float)$ib['lng']);
|
||||
if ($d <= $ib['radius'] && $d < $minDist) {
|
||||
$nearest = (int)$ib['id'];
|
||||
$minDist = $d;
|
||||
}
|
||||
}
|
||||
return $nearest;
|
||||
}
|
||||
|
||||
// ── Re-assign SEMUA warga verified (dipanggil saat radius/ibadah berubah) ──
|
||||
function reassignAllWarga(PDO $db): void {
|
||||
$wargas = $db->query("SELECT id, lat, lng FROM warga_miskin WHERE status = 'verified'")->fetchAll();
|
||||
$stmt = $db->prepare("UPDATE warga_miskin SET ibadah_id = ? WHERE id = ?");
|
||||
foreach ($wargas as $w) {
|
||||
$ibadahId = findNearestIbadah($db, (float)$w['lat'], (float)$w['lng']);
|
||||
$stmt->execute([$ibadahId, $w['id']]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/helpers.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');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
// ── GET: list semua / satu ───────────────────
|
||||
if ($method === 'GET') {
|
||||
$user = getCurrentUser();
|
||||
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT * FROM rumah_ibadah WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
jsonResponse($row ?: ['error' => 'Tidak ditemukan'], $row ? 200 : 404);
|
||||
}
|
||||
|
||||
// Admin, Surveyor, & Pemangku melihat semua, lainnya hanya verified
|
||||
if ($user && ($user['role'] === 'admin' || $user['role'] === 'surveyor' || $user['role'] === 'pemangku')) {
|
||||
$rows = $db->query("
|
||||
SELECT ri.*,
|
||||
COUNT(wm.id) AS jumlah_kk,
|
||||
COALESCE(SUM(wm.jumlah_anggota),0) AS jumlah_jiwa,
|
||||
u.nama_lengkap AS surveyor_nama
|
||||
FROM rumah_ibadah ri
|
||||
LEFT JOIN warga_miskin wm ON wm.ibadah_id = ri.id AND wm.status = 'verified'
|
||||
LEFT JOIN users u ON u.id = ri.surveyor_id
|
||||
GROUP BY ri.id
|
||||
ORDER BY ri.created_at DESC
|
||||
")->fetchAll();
|
||||
} else {
|
||||
// Pemangku / publik: hanya verified
|
||||
$rows = $db->query("
|
||||
SELECT ri.*,
|
||||
COUNT(wm.id) AS jumlah_kk,
|
||||
COALESCE(SUM(wm.jumlah_anggota),0) AS jumlah_jiwa
|
||||
FROM rumah_ibadah ri
|
||||
LEFT JOIN warga_miskin wm ON wm.ibadah_id = ri.id AND wm.status = 'verified'
|
||||
WHERE ri.status = 'verified'
|
||||
GROUP BY ri.id
|
||||
ORDER BY ri.created_at DESC
|
||||
")->fetchAll();
|
||||
}
|
||||
|
||||
jsonResponse(['data' => $rows]);
|
||||
}
|
||||
|
||||
// ── POST: tambah ─────────────────────────────
|
||||
if ($method === 'POST') {
|
||||
$user = requireRole(['admin', 'surveyor']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$required = ['nama','jenis','pic','lat','lng'];
|
||||
foreach ($required as $f) {
|
||||
if (empty($body[$f])) jsonResponse(['success'=>false,'error'=>"Field '$f' wajib diisi"], 422);
|
||||
}
|
||||
|
||||
// Surveyor → pending, Admin → verified
|
||||
$status = ($user['role'] === 'admin') ? 'verified' : 'pending';
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO rumah_ibadah (nama, jenis, alamat, pic, no_wa, radius, lat, lng, foto_path, surveyor_id, status, kecamatan)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$body['nama'],
|
||||
$body['jenis'],
|
||||
$body['alamat'] ?? '',
|
||||
$body['pic'],
|
||||
$body['no_wa'] ?? '',
|
||||
(int)($body['radius'] ?? 500),
|
||||
(float)$body['lat'],
|
||||
(float)$body['lng'],
|
||||
$body['foto_path'] ?? null,
|
||||
$user['id'],
|
||||
$status,
|
||||
$body['kecamatan'] ?? null,
|
||||
]);
|
||||
$newId = (int)$db->lastInsertId();
|
||||
|
||||
// re-assign warga hanya jika langsung verified
|
||||
if ($status === 'verified') {
|
||||
reassignAllWarga($db);
|
||||
}
|
||||
|
||||
auditLog($db, $user['id'], 'CREATE_IBADAH', 'rumah_ibadah', $newId, null, $body);
|
||||
|
||||
jsonResponse(['success'=>true, 'id'=>$newId, 'status'=>$status]);
|
||||
}
|
||||
|
||||
// ── PUT: update ──────────────────────────────
|
||||
if ($method === 'PUT') {
|
||||
$user = requireAuth();
|
||||
if (!$id) jsonResponse(['success'=>false,'error'=>'ID diperlukan'], 400);
|
||||
|
||||
// Cek permission
|
||||
$old = $db->prepare("SELECT * FROM rumah_ibadah WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) jsonResponse(['success'=>false,'error'=>'Tidak ditemukan'], 404);
|
||||
|
||||
// Surveyor hanya bisa edit milik sendiri yang masih pending
|
||||
if ($user['role'] === 'surveyor') {
|
||||
if ((int)$oldData['surveyor_id'] !== $user['id']) {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda hanya bisa edit data milik Anda'], 403);
|
||||
}
|
||||
if ($oldData['status'] !== 'pending') {
|
||||
jsonResponse(['success'=>false,'error'=>'Data yang sudah diverifikasi tidak bisa diedit'], 403);
|
||||
}
|
||||
}
|
||||
// Pemangku tidak boleh edit
|
||||
if ($user['role'] === 'pemangku') {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda tidak memiliki akses edit'], 403);
|
||||
}
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$fields = []; $vals = [];
|
||||
foreach (['nama','jenis','alamat','pic','no_wa','radius','lat','lng','foto_path','kecamatan'] as $f) {
|
||||
if (array_key_exists($f, $body)) {
|
||||
$fields[] = "$f = ?";
|
||||
$vals[] = in_array($f,['lat','lng']) ? (float)$body[$f] : ($f==='radius' ? (int)$body[$f] : $body[$f]);
|
||||
}
|
||||
}
|
||||
if (empty($fields)) jsonResponse(['success'=>false,'error'=>'Tidak ada data diubah'], 422);
|
||||
$vals[] = $id;
|
||||
$db->prepare("UPDATE rumah_ibadah SET ".implode(',',$fields)." WHERE id = ?")->execute($vals);
|
||||
|
||||
// re-assign ulang jika data verified
|
||||
if ($oldData['status'] === 'verified') {
|
||||
reassignAllWarga($db);
|
||||
}
|
||||
|
||||
auditLog($db, $user['id'], 'UPDATE_IBADAH', 'rumah_ibadah', $id, $oldData, $body);
|
||||
|
||||
jsonResponse(['success'=>true]);
|
||||
}
|
||||
|
||||
// ── DELETE ───────────────────────────────────
|
||||
if ($method === 'DELETE') {
|
||||
$user = requireAuth();
|
||||
if (!$id) jsonResponse(['success'=>false,'error'=>'ID diperlukan'], 400);
|
||||
|
||||
$old = $db->prepare("SELECT * FROM rumah_ibadah WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) jsonResponse(['success'=>false,'error'=>'Tidak ditemukan'], 404);
|
||||
|
||||
// Surveyor bisa hapus milik sendiri
|
||||
if ($user['role'] === 'surveyor') {
|
||||
if ((int)$oldData['surveyor_id'] !== $user['id']) {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda hanya bisa hapus data milik Anda'], 403);
|
||||
}
|
||||
}
|
||||
if ($user['role'] === 'pemangku') {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda tidak memiliki akses hapus'], 403);
|
||||
}
|
||||
|
||||
// warga_miskin.ibadah_id akan SET NULL (FK ON DELETE SET NULL)
|
||||
$db->prepare("DELETE FROM rumah_ibadah WHERE id = ?")->execute([$id]);
|
||||
|
||||
// Jika rumah ibadah terverifikasi dihapus, reassign warga sekitar
|
||||
if ($oldData['status'] === 'verified') {
|
||||
reassignAllWarga($db);
|
||||
}
|
||||
|
||||
auditLog($db, $user['id'], 'DELETE_IBADAH', 'rumah_ibadah', $id, $oldData);
|
||||
|
||||
jsonResponse(['success'=>true]);
|
||||
}
|
||||
|
||||
jsonResponse(['error'=>'Method tidak didukung'], 405);
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
|
||||
$db = getDB();
|
||||
$user = getCurrentUser();
|
||||
|
||||
// Statistik utama — hanya data verified (digabung dalam 1 query)
|
||||
$stats = $db->query("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM rumah_ibadah WHERE status = 'verified') AS ibadah,
|
||||
COUNT(*) AS kk_total,
|
||||
COALESCE(SUM(CASE WHEN ibadah_id IS NOT NULL THEN 1 ELSE 0 END), 0) AS kk_terlayani,
|
||||
COALESCE(SUM(jumlah_anggota), 0) AS jiwa_total,
|
||||
COALESCE(SUM(CASE WHEN ibadah_id IS NOT NULL THEN jumlah_anggota ELSE 0 END), 0) AS jiwa_terlayani,
|
||||
COALESCE(SUM(CASE WHEN bantuan_diterima = 'sembako' THEN 1 ELSE 0 END), 0) AS bantuan_sembako,
|
||||
COALESCE(SUM(CASE WHEN bantuan_diterima = 'beasiswa' THEN 1 ELSE 0 END), 0) AS bantuan_beasiswa,
|
||||
COALESCE(SUM(CASE WHEN bantuan_diterima = 'modal' THEN 1 ELSE 0 END), 0) AS bantuan_modal,
|
||||
COALESCE(SUM(CASE WHEN bantuan_diterima = 'tunai' THEN 1 ELSE 0 END), 0) AS bantuan_tunai,
|
||||
COALESCE(SUM(CASE WHEN bantuan_diterima = 'tidak_ada' THEN 1 ELSE 0 END), 0) AS bantuan_tidak_ada
|
||||
FROM warga_miskin
|
||||
WHERE status = 'verified'
|
||||
")->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$totalIbadah = (int)($stats['ibadah'] ?? 0);
|
||||
$totalWarga = (int)($stats['kk_total'] ?? 0);
|
||||
$totalCovered = (int)($stats['kk_terlayani'] ?? 0);
|
||||
$totalJiwa = (int)($stats['jiwa_total'] ?? 0);
|
||||
$jiwaLayan = (int)($stats['jiwa_terlayani'] ?? 0);
|
||||
|
||||
$response = [
|
||||
'ibadah' => $totalIbadah,
|
||||
'kk_total' => $totalWarga,
|
||||
'kk_terlayani' => $totalCovered,
|
||||
'kk_belum' => $totalWarga - $totalCovered,
|
||||
'jiwa_total' => $totalJiwa,
|
||||
'jiwa_terlayani' => $jiwaLayan,
|
||||
'bantuan_sembako' => (int)($stats['bantuan_sembako'] ?? 0),
|
||||
'bantuan_beasiswa' => (int)($stats['bantuan_beasiswa'] ?? 0),
|
||||
'bantuan_modal' => (int)($stats['bantuan_modal'] ?? 0),
|
||||
'bantuan_tunai' => (int)($stats['bantuan_tunai'] ?? 0),
|
||||
'bantuan_tidak_ada' => (int)($stats['bantuan_tidak_ada'] ?? 0),
|
||||
];
|
||||
|
||||
// Data pending — hanya untuk admin (digabung dalam 1 query)
|
||||
if ($user && $user['role'] === 'admin') {
|
||||
$adminStats = $db->query("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM rumah_ibadah WHERE status = 'pending') AS pending_ibadah,
|
||||
(SELECT COUNT(*) FROM warga_miskin WHERE status = 'pending') AS pending_warga,
|
||||
(SELECT COUNT(*) FROM users WHERE is_active = 1) AS total_users
|
||||
")->fetch(PDO::FETCH_ASSOC);
|
||||
$response['pending_ibadah'] = (int)($adminStats['pending_ibadah'] ?? 0);
|
||||
$response['pending_warga'] = (int)($adminStats['pending_warga'] ?? 0);
|
||||
$response['total_users'] = (int)($adminStats['total_users'] ?? 0);
|
||||
}
|
||||
|
||||
// Data pending milik surveyor (digabung dalam 1 query)
|
||||
if ($user && $user['role'] === 'surveyor') {
|
||||
$stmt = $db->prepare("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM warga_miskin WHERE surveyor_id = ? AND status = 'pending') AS my_pending_warga,
|
||||
(SELECT COUNT(*) FROM rumah_ibadah WHERE surveyor_id = ? AND status = 'pending') AS my_pending_ibadah
|
||||
");
|
||||
$stmt->execute([$user['id'], $user['id']]);
|
||||
$surveyorStats = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$response['my_pending_warga'] = (int)($surveyorStats['my_pending_warga'] ?? 0);
|
||||
$response['my_pending_ibadah'] = (int)($surveyorStats['my_pending_ibadah'] ?? 0);
|
||||
}
|
||||
|
||||
jsonResponse($response);
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['error' => 'Method tidak didukung'], 405);
|
||||
}
|
||||
|
||||
$user = requireRole(['admin', 'surveyor']);
|
||||
|
||||
// ── Validasi file ────────────────────────────
|
||||
if (!isset($_FILES['foto']) || $_FILES['foto']['error'] !== UPLOAD_ERR_OK) {
|
||||
jsonResponse(['success' => false, 'error' => 'File foto wajib diunggah'], 422);
|
||||
}
|
||||
|
||||
$file = $_FILES['foto'];
|
||||
|
||||
// Cek ukuran
|
||||
if ($file['size'] > MAX_UPLOAD_SIZE) {
|
||||
jsonResponse(['success' => false, 'error' => 'Ukuran file melebihi batas 5MB'], 422);
|
||||
}
|
||||
|
||||
// Cek tipe file
|
||||
$allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($mimeType, $allowedTypes)) {
|
||||
jsonResponse(['success' => false, 'error' => 'Hanya file JPEG dan PNG yang diizinkan'], 422);
|
||||
}
|
||||
|
||||
// ── Buat folder uploads jika belum ada ───────
|
||||
if (!is_dir(UPLOAD_DIR)) {
|
||||
mkdir(UPLOAD_DIR, 0755, true);
|
||||
}
|
||||
|
||||
// ── Generate nama file unik ──────────────────
|
||||
$ext = ($mimeType === 'image/png') ? 'png' : 'jpg';
|
||||
$filename = date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
|
||||
$destPath = UPLOAD_DIR . $filename;
|
||||
$webPath = 'uploads/' . $filename;
|
||||
|
||||
// ── Pindahkan file ───────────────────────────
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
jsonResponse(['success' => false, 'error' => 'Gagal menyimpan file'], 500);
|
||||
}
|
||||
|
||||
// ── Extract EXIF GPS ─────────────────────────
|
||||
$lat = null;
|
||||
$lng = null;
|
||||
$hasGps = false;
|
||||
|
||||
if ($mimeType === 'image/jpeg' || $mimeType === 'image/jpg') {
|
||||
$exif = @exif_read_data($destPath, 'GPS', true);
|
||||
if ($exif && isset($exif['GPS']['GPSLatitude']) && isset($exif['GPS']['GPSLongitude'])) {
|
||||
$lat = exifGpsToDecimal(
|
||||
$exif['GPS']['GPSLatitude'],
|
||||
$exif['GPS']['GPSLatitudeRef'] ?? 'N'
|
||||
);
|
||||
$lng = exifGpsToDecimal(
|
||||
$exif['GPS']['GPSLongitude'],
|
||||
$exif['GPS']['GPSLongitudeRef'] ?? 'E'
|
||||
);
|
||||
$hasGps = true;
|
||||
}
|
||||
}
|
||||
|
||||
auditLog(getDB(), $user['id'], 'UPLOAD_FOTO', null, null, null, [
|
||||
'filename' => $filename, 'has_gps' => $hasGps
|
||||
]);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'foto_path' => $webPath,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'has_gps' => $hasGps,
|
||||
]);
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// EXIF GPS Helper
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Konversi EXIF GPS coordinate (DMS) ke desimal
|
||||
* @param array $coord Array of 3 fractions [degrees, minutes, seconds]
|
||||
* @param string $ref Hemisphere reference (N/S/E/W)
|
||||
*/
|
||||
function exifGpsToDecimal(array $coord, string $ref): float {
|
||||
$degrees = exifFractionToFloat($coord[0]);
|
||||
$minutes = exifFractionToFloat($coord[1]);
|
||||
$seconds = exifFractionToFloat($coord[2]);
|
||||
|
||||
$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);
|
||||
|
||||
if ($ref === 'S' || $ref === 'W') {
|
||||
$decimal *= -1;
|
||||
}
|
||||
|
||||
return round($decimal, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konversi fraction string "num/den" ke float
|
||||
*/
|
||||
function exifFractionToFloat(string $fraction): float {
|
||||
$parts = explode('/', $fraction);
|
||||
if (count($parts) === 2 && (float)$parts[1] !== 0.0) {
|
||||
return (float)$parts[0] / (float)$parts[1];
|
||||
}
|
||||
return (float)$parts[0];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, PUT, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$type = $_GET['type'] ?? ''; // 'ibadah' atau 'warga'
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
if (!in_array($type, ['ibadah', 'warga'])) {
|
||||
jsonResponse(['error' => 'Parameter type harus "ibadah" atau "warga"'], 400);
|
||||
}
|
||||
|
||||
$table = $type === 'ibadah' ? 'rumah_ibadah' : 'warga_miskin';
|
||||
|
||||
// ── GET: list data pending ───────────────────
|
||||
if ($method === 'GET') {
|
||||
$admin = requireRole('admin');
|
||||
|
||||
$status = $_GET['status'] ?? 'pending';
|
||||
if (!in_array($status, ['pending', 'verified', 'rejected'])) $status = 'pending';
|
||||
|
||||
if ($type === 'ibadah') {
|
||||
$stmt = $db->prepare("
|
||||
SELECT ri.*, u.nama_lengkap AS surveyor_nama
|
||||
FROM rumah_ibadah ri
|
||||
LEFT JOIN users u ON u.id = ri.surveyor_id
|
||||
WHERE ri.status = ?
|
||||
ORDER BY ri.created_at DESC
|
||||
");
|
||||
} else {
|
||||
$stmt = $db->prepare("
|
||||
SELECT wm.*,
|
||||
ri.nama AS ibadah_nama,
|
||||
u.nama_lengkap AS surveyor_nama
|
||||
FROM warga_miskin wm
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = wm.ibadah_id
|
||||
LEFT JOIN users u ON u.id = wm.surveyor_id
|
||||
WHERE wm.status = ?
|
||||
ORDER BY wm.created_at DESC
|
||||
");
|
||||
}
|
||||
$stmt->execute([$status]);
|
||||
jsonResponse(['data' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
// ── PUT: approve / reject ────────────────────
|
||||
if ($method === 'PUT') {
|
||||
$admin = requireRole('admin');
|
||||
if (!$id) jsonResponse(['success' => false, 'error' => 'ID diperlukan'], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$newStatus = $body['status'] ?? '';
|
||||
if (!in_array($newStatus, ['verified', 'rejected'])) {
|
||||
jsonResponse(['success' => false, 'error' => 'Status harus "verified" atau "rejected"'], 422);
|
||||
}
|
||||
|
||||
// Ambil data lama
|
||||
$old = $db->prepare("SELECT * FROM $table WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) {
|
||||
jsonResponse(['success' => false, 'error' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
// Update status
|
||||
$alasan = ($newStatus === 'rejected') ? ($body['alasan_penolakan'] ?? '') : null;
|
||||
$stmt = $db->prepare("UPDATE $table SET status = ?, alasan_penolakan = ? WHERE id = ?");
|
||||
$stmt->execute([$newStatus, $alasan, $id]);
|
||||
|
||||
// Jika diverifikasi, proses spasial
|
||||
if ($newStatus === 'verified') {
|
||||
if ($type === 'warga') {
|
||||
// Cari ibadah terdekat untuk warga yang baru verified
|
||||
$ibadahId = findNearestIbadah($db, (float)$oldData['lat'], (float)$oldData['lng']);
|
||||
$db->prepare("UPDATE warga_miskin SET ibadah_id = ? WHERE id = ?")->execute([$ibadahId, $id]);
|
||||
} elseif ($type === 'ibadah') {
|
||||
// Ibadah baru verified → re-assign semua warga
|
||||
reassignAllWarga($db);
|
||||
}
|
||||
}
|
||||
|
||||
$aksi = ($newStatus === 'verified') ? 'VERIFY_' : 'REJECT_';
|
||||
$aksi .= strtoupper($type);
|
||||
auditLog($db, $admin['id'], $aksi, $table, $id, $oldData, [
|
||||
'status' => $newStatus, 'alasan_penolakan' => $alasan
|
||||
]);
|
||||
|
||||
jsonResponse(['success' => true, 'status' => $newStatus]);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Method tidak didukung'], 405);
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/helpers.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');
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
// ── GET ───────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
$user = getCurrentUser();
|
||||
|
||||
// Filter tambahan: kecamatan
|
||||
$filterKec = $_GET['kecamatan'] ?? null;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($filterKec) { $where[] = "wm.kecamatan = ?"; $params[] = $filterKec; }
|
||||
|
||||
if ($user && ($user['role'] === 'admin' || $user['role'] === 'surveyor' || $user['role'] === 'pemangku')) {
|
||||
// Admin, Surveyor, & Pemangku lihat semua data (untuk mencegah input ganda & analisis lengkap)
|
||||
$statusFilter = "";
|
||||
} else {
|
||||
// Publik: hanya verified
|
||||
$statusFilter = "AND wm.status = 'verified'";
|
||||
}
|
||||
|
||||
$whereClause = count($where) > 0 ? 'WHERE ' . implode(' AND ', $where) . ' ' . $statusFilter : ($statusFilter ? 'WHERE 1=1 ' . $statusFilter : '');
|
||||
|
||||
$sql = "
|
||||
SELECT wm.*,
|
||||
ri.nama AS ibadah_nama,
|
||||
ri.jenis AS ibadah_jenis,
|
||||
u.nama_lengkap AS surveyor_nama
|
||||
FROM warga_miskin wm
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = wm.ibadah_id
|
||||
LEFT JOIN users u ON u.id = wm.surveyor_id
|
||||
$whereClause
|
||||
ORDER BY wm.created_at DESC
|
||||
";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
jsonResponse(['data' => $rows]);
|
||||
}
|
||||
|
||||
// ── POST: tambah warga ───────────────────────
|
||||
if ($method === 'POST') {
|
||||
$user = requireRole(['admin', 'surveyor']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
foreach (['nama_kk','lat','lng'] as $f) {
|
||||
if (empty($body[$f])) jsonResponse(['success'=>false,'error'=>"Field '$f' wajib diisi"], 422);
|
||||
}
|
||||
|
||||
// Validasi NIK: harus 16 digit angka jika diisi
|
||||
$nik = $body['nik'] ?? null;
|
||||
if ($nik !== null && $nik !== '') {
|
||||
$nik = trim($nik);
|
||||
if (!preg_match('/^\d{16}$/', $nik)) {
|
||||
jsonResponse(['success'=>false,'error'=>'NIK harus tepat 16 digit angka'], 422);
|
||||
}
|
||||
// Cek duplikasi NIK
|
||||
$chk = $db->prepare("SELECT id FROM warga_miskin WHERE nik = ?");
|
||||
$chk->execute([$nik]);
|
||||
if ($chk->fetch()) {
|
||||
jsonResponse(['success'=>false,'error'=>'NIK Kepala Keluarga sudah terdaftar di sistem'], 422);
|
||||
}
|
||||
} else {
|
||||
$nik = null;
|
||||
}
|
||||
|
||||
$lat = (float)$body['lat'];
|
||||
$lng = (float)$body['lng'];
|
||||
|
||||
// Surveyor → pending, Admin → verified
|
||||
$status = ($user['role'] === 'admin') ? 'verified' : 'pending';
|
||||
|
||||
// otomatis cari rumah ibadah terdekat (hanya jika verified)
|
||||
$ibadahId = ($status === 'verified') ? findNearestIbadah($db, $lat, $lng) : null;
|
||||
|
||||
$bantuan = $body['bantuan_diterima'] ?? 'tidak_ada';
|
||||
if (!in_array($bantuan, ['tidak_ada', 'sembako', 'beasiswa', 'modal', 'tunai'])) {
|
||||
$bantuan = 'tidak_ada';
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO warga_miskin (nama_kk, jumlah_anggota, keterangan, lat, lng, ibadah_id, foto_path, surveyor_id, status, alamat, nik, kecamatan, bantuan_diterima)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$body['nama_kk'],
|
||||
(int)($body['jumlah_anggota'] ?? 1),
|
||||
$body['keterangan'] ?? '',
|
||||
$lat, $lng, $ibadahId,
|
||||
$body['foto_path'] ?? null,
|
||||
$user['id'],
|
||||
$status,
|
||||
$body['alamat'] ?? null,
|
||||
$nik,
|
||||
$body['kecamatan'] ?? null,
|
||||
$bantuan,
|
||||
]);
|
||||
|
||||
$newId = (int)$db->lastInsertId();
|
||||
|
||||
// ambil nama ibadah untuk respon
|
||||
$ibadahNama = null;
|
||||
if ($ibadahId) {
|
||||
$r = $db->prepare("SELECT nama FROM rumah_ibadah WHERE id = ?");
|
||||
$r->execute([$ibadahId]);
|
||||
$ibadahNama = $r->fetchColumn();
|
||||
}
|
||||
|
||||
auditLog($db, $user['id'], 'CREATE_WARGA', 'warga_miskin', $newId, null, $body);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'id' => $newId,
|
||||
'status' => $status,
|
||||
'ibadah_id' => $ibadahId,
|
||||
'ibadah_nama' => $ibadahNama,
|
||||
]);
|
||||
}
|
||||
|
||||
// ── PUT: update warga ────────────────────────
|
||||
if ($method === 'PUT') {
|
||||
$user = requireAuth();
|
||||
if (!$id) jsonResponse(['success'=>false,'error'=>'ID diperlukan'], 400);
|
||||
|
||||
$old = $db->prepare("SELECT * FROM warga_miskin WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) jsonResponse(['success'=>false,'error'=>'Tidak ditemukan'], 404);
|
||||
|
||||
// Surveyor hanya bisa edit milik sendiri yang masih pending
|
||||
if ($user['role'] === 'surveyor') {
|
||||
if ((int)$oldData['surveyor_id'] !== $user['id']) {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda hanya bisa edit data milik Anda'], 403);
|
||||
}
|
||||
if ($oldData['status'] !== 'pending') {
|
||||
jsonResponse(['success'=>false,'error'=>'Data yang sudah diverifikasi tidak bisa diedit'], 403);
|
||||
}
|
||||
}
|
||||
if ($user['role'] === 'pemangku') {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda tidak memiliki akses edit'], 403);
|
||||
}
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
// Validasi NIK jika diubah
|
||||
if (array_key_exists('nik', $body) && $body['nik'] !== null && $body['nik'] !== '') {
|
||||
$nikVal = trim($body['nik']);
|
||||
if (!preg_match('/^\d{16}$/', $nikVal)) {
|
||||
jsonResponse(['success'=>false,'error'=>'NIK harus tepat 16 digit angka'], 422);
|
||||
}
|
||||
// Cek duplikasi NIK (kecuali data sendiri)
|
||||
$chk = $db->prepare("SELECT id FROM warga_miskin WHERE nik = ? AND id != ?");
|
||||
$chk->execute([$nikVal, $id]);
|
||||
if ($chk->fetch()) {
|
||||
jsonResponse(['success'=>false,'error'=>'NIK Kepala Keluarga sudah terdaftar di sistem'], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$fields = []; $vals = [];
|
||||
foreach (['nama_kk','jumlah_anggota','keterangan','lat','lng','foto_path','alamat','nik','kecamatan','bantuan_diterima'] as $f) {
|
||||
if (array_key_exists($f, $body)) {
|
||||
$fields[] = "$f = ?";
|
||||
if ($f === 'nik') {
|
||||
$nikVal = $body['nik'] !== null ? trim($body['nik']) : '';
|
||||
$vals[] = ($nikVal === '') ? null : $nikVal;
|
||||
} else {
|
||||
$vals[] = in_array($f,['lat','lng']) ? (float)$body[$f]
|
||||
: ($f==='jumlah_anggota' ? (int)$body[$f] : $body[$f]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($fields)) jsonResponse(['success'=>false,'error'=>'Tidak ada data diubah'], 422);
|
||||
|
||||
// jika koordinat berubah dan data verified, cari ulang ibadah terdekat
|
||||
if ($oldData['status'] === 'verified' && (array_key_exists('lat', $body) || array_key_exists('lng', $body))) {
|
||||
$lat = array_key_exists('lat',$body) ? (float)$body['lat'] : (float)$oldData['lat'];
|
||||
$lng = array_key_exists('lng',$body) ? (float)$body['lng'] : (float)$oldData['lng'];
|
||||
$ibadahId = findNearestIbadah($db, $lat, $lng);
|
||||
$fields[] = "ibadah_id = ?";
|
||||
$vals[] = $ibadahId;
|
||||
}
|
||||
|
||||
$vals[] = $id;
|
||||
$db->prepare("UPDATE warga_miskin SET ".implode(',',$fields)." WHERE id = ?")->execute($vals);
|
||||
|
||||
auditLog($db, $user['id'], 'UPDATE_WARGA', 'warga_miskin', $id, $oldData, $body);
|
||||
|
||||
jsonResponse(['success'=>true]);
|
||||
}
|
||||
|
||||
// ── DELETE ───────────────────────────────────
|
||||
if ($method === 'DELETE') {
|
||||
$user = requireAuth();
|
||||
if (!$id) jsonResponse(['success'=>false,'error'=>'ID diperlukan'], 400);
|
||||
|
||||
$old = $db->prepare("SELECT * FROM warga_miskin WHERE id = ?");
|
||||
$old->execute([$id]);
|
||||
$oldData = $old->fetch();
|
||||
if (!$oldData) jsonResponse(['success'=>false,'error'=>'Tidak ditemukan'], 404);
|
||||
|
||||
// Surveyor bisa hapus milik sendiri
|
||||
if ($user['role'] === 'surveyor') {
|
||||
if ((int)$oldData['surveyor_id'] !== $user['id']) {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda hanya bisa hapus data milik Anda'], 403);
|
||||
}
|
||||
}
|
||||
if ($user['role'] === 'pemangku') {
|
||||
jsonResponse(['success'=>false,'error'=>'Anda tidak memiliki akses hapus'], 403);
|
||||
}
|
||||
|
||||
$db->prepare("DELETE FROM warga_miskin WHERE id = ?")->execute([$id]);
|
||||
|
||||
auditLog($db, $user['id'], 'DELETE_WARGA', 'warga_miskin', $id, $oldData);
|
||||
|
||||
jsonResponse(['success'=>true]);
|
||||
}
|
||||
|
||||
jsonResponse(['error'=>'Method tidak didukung'], 405);
|
||||
Reference in New Issue
Block a user