prepare(" SELECT rc.id, rc.name, rc.worship_type, rc.address, rc.kelurahan, rc.kecamatan, rc.latitude, rc.longitude, rc.radius, rc.contact_person, rc.contact_phone, rc.notes, rc.is_active, rc.created_at, rc.updated_at FROM religious_centers rc WHERE $whereSQL ORDER BY rc.name "); $stmt->execute($params); $rows = $stmt->fetchAll(); // Hitung household_count dan pending_aid_count secara terpisah foreach ($rows as &$r) { $r['latitude'] = (float)$r['latitude']; $r['longitude'] = (float)$r['longitude']; $r['radius'] = (int)$r['radius']; // Hitung jumlah rumah yang dikelola center ini $countStmt = $pdo->prepare(" SELECT COUNT(*) FROM households WHERE managing_center_id = ? AND is_active = 1 "); $countStmt->execute([$r['id']]); $r['household_count'] = (int)$countStmt->fetchColumn(); // Hitung jumlah rumah yang belum terima bantuan $pendingStmt = $pdo->prepare(" SELECT COUNT(*) FROM households WHERE managing_center_id = ? AND is_active = 1 AND aid_status = 'not_yet' "); $pendingStmt->execute([$r['id']]); $r['pending_aid_count'] = (int)$pendingStmt->fetchColumn(); } unset($r); Response::success(['centers' => $rows, 'total' => count($rows)]); break; } case 'GET:show': { if (!$id) Response::error('ID required', 400); $pdo = Database::get(); $stmt = $pdo->prepare(" SELECT * FROM religious_centers WHERE id = ? AND is_active = 1 "); $stmt->execute([$id]); $row = $stmt->fetch(); if (!$row) { Response::notFound('Not found'); } $row['latitude'] = (float)$row['latitude']; $row['longitude'] = (float)$row['longitude']; $row['radius'] = (int)$row['radius']; Response::success($row); break; } case 'GET:nearby': { $lat = (float)($_GET['lat'] ?? 0); $lng = (float)($_GET['lng'] ?? 0); $km = min(50, (float)($_GET['km'] ?? 5)); if ($lat === 0.0 && $lng === 0.0) { Response::error('lat and lng required', 400); } $pdo = Database::get(); $stmt = $pdo->prepare(" SELECT id, name, worship_type, address, latitude, longitude, radius, (6371 * ACOS( COS(RADIANS(?)) * COS(RADIANS(latitude)) * COS(RADIANS(longitude) - RADIANS(?)) + SIN(RADIANS(?)) * SIN(RADIANS(latitude)) )) AS distance_km FROM religious_centers WHERE is_active = 1 HAVING distance_km <= ? ORDER BY distance_km LIMIT 10 "); $stmt->execute([$lat, $lng, $lat, $km]); $rows = $stmt->fetchAll(); foreach ($rows as &$r) { $r['latitude'] = (float)$r['latitude']; $r['longitude'] = (float)$r['longitude']; $r['radius'] = (int)$r['radius']; $r['distance_km'] = round((float)$r['distance_km'], 3); } unset($r); Response::success(['centers' => $rows]); break; } case 'GET:coverage': { if (!$id) Response::error('ID required', 400); $pdo = Database::get(); $center = $pdo->prepare("SELECT * FROM religious_centers WHERE id = ? AND is_active = 1"); $center->execute([$id]); $c = $center->fetch(); if (!$c) { Response::notFound('Center not found'); } $stmt = $pdo->prepare(" SELECT id, head_name, full_address, latitude, longitude, poverty_status, aid_status FROM households WHERE is_active = 1 "); $stmt->execute(); $allHouseholds = $stmt->fetchAll(); $households = []; foreach ($allHouseholds as $h) { $distance = haversineDistance( (float)$c['latitude'], (float)$c['longitude'], (float)$h['latitude'], (float)$h['longitude'] ); if ($distance <= (float)$c['radius']) { $h['distance_m'] = round($distance, 1); $h['latitude'] = (float)$h['latitude']; $h['longitude'] = (float)$h['longitude']; $households[] = $h; } } usort($households, function($a, $b) { return $a['distance_m'] <=> $b['distance_m']; }); Response::success([ 'center' => $c, 'households' => $households, 'count' => count($households) ]); break; } case 'POST:create': { requireAuth(); $data = Validator::json(); $pdo = Database::get(); $stmt = $pdo->prepare(" INSERT INTO religious_centers (name, worship_type, address, latitude, longitude, radius, contact_person, contact_phone, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['name'] ?? '', $data['worship_type'] ?? 'masjid', $data['address'] ?? '', (float)($data['latitude'] ?? 0), (float)($data['longitude'] ?? 0), (int)($data['radius'] ?? 300), $data['contact_person'] ?? null, $data['contact_phone'] ?? null, $data['notes'] ?? null ]); $newId = (int)$pdo->lastInsertId(); AuditLog::record('Tambah Tempat Ibadah', 'religious_centers', $newId, null, $data); Response::created(['id' => $newId], 'Center created'); break; } case 'POST:update': { requireAuth(); if (!$id) Response::error('ID required', 400); $data = Validator::json(); $pdo = Database::get(); $old = $pdo->prepare("SELECT * FROM religious_centers WHERE id = ? AND is_active = 1"); $old->execute([$id]); $oldRow = $old->fetch(); if (!$oldRow) { Response::notFound('Not found'); } $stmt = $pdo->prepare(" UPDATE religious_centers SET name = ?, worship_type = ?, address = ?, latitude = ?, longitude = ?, radius = ?, contact_person = ?, contact_phone = ?, notes = ? WHERE id = ? AND is_active = 1 "); $stmt->execute([ $data['name'] ?? $oldRow['name'], $data['worship_type'] ?? $oldRow['worship_type'], $data['address'] ?? $oldRow['address'], (float)($data['latitude'] ?? $oldRow['latitude']), (float)($data['longitude'] ?? $oldRow['longitude']), (int)($data['radius'] ?? $oldRow['radius']), $data['contact_person'] ?? $oldRow['contact_person'], $data['contact_phone'] ?? $oldRow['contact_phone'], $data['notes'] ?? $oldRow['notes'], $id ]); AuditLog::record('Update Tempat Ibadah', 'religious_centers', $id, $oldRow, $data); Response::success(null, 'Updated'); break; } case 'POST:patch': { requireAuth(); if (!$id) Response::error('ID required', 400); $data = Validator::json(); $fields = []; $params = []; if (isset($data['radius'])) { $fields[] = 'radius = ?'; $params[] = (int)$data['radius']; } if (isset($data['latitude']) && isset($data['longitude'])) { $fields[] = 'latitude = ?'; $params[] = (float)$data['latitude']; $fields[] = 'longitude = ?'; $params[] = (float)$data['longitude']; if (isset($data['address'])) { $fields[] = 'address = ?'; $params[] = $data['address']; } } if (empty($fields)) { Response::error('No fields to update', 400); } $params[] = $id; $sql = "UPDATE religious_centers SET " . implode(', ', $fields) . " WHERE id = ? AND is_active = 1"; $pdo = Database::get(); $pdo->prepare($sql)->execute($params); AuditLog::record('Geser/Resize Tempat Ibadah', 'religious_centers', $id, null, $data); Response::success(null, 'Patched'); break; } case 'POST:delete': { requireAdmin(); if (!$id) Response::error('ID required', 400); $pdo = Database::get(); $old = $pdo->prepare("SELECT * FROM religious_centers WHERE id = ? AND is_active = 1"); $old->execute([$id]); $oldRow = $old->fetch(); if (!$oldRow) { Response::notFound('Not found'); } $pdo->prepare("UPDATE religious_centers SET is_active = 0 WHERE id = ?")->execute([$id]); AuditLog::record('Hapus Tempat Ibadah', 'religious_centers', $id, $oldRow); Response::success(null, 'Deleted'); break; } default: Response::methodNotAllowed(); } } catch (Exception $e) { $message = APP_DEBUG ? $e->getMessage() : 'Internal Server Error'; Response::error($message, 500); } function haversineDistance($lat1, $lon1, $lat2, $lon2) { $R = 6371000; $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2); $c = 2 * atan2(sqrt($a), sqrt(1-$a)); return $R * $c; }