feat: complete WebGIS implementation with nominal bantuan and cache busting
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Anggota Keluarga
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
$pid = isset($_GET['penduduk_id']) ? (int)$_GET['penduduk_id'] : null;
|
||||
|
||||
if ($method === 'GET') {
|
||||
if (!$pid) jsonResponse(['success' => false, 'message' => 'penduduk_id wajib'], 400);
|
||||
$stmt = $db->prepare("SELECT * FROM anggota_keluarga WHERE penduduk_id = ? ORDER BY id");
|
||||
$stmt->execute([$pid]);
|
||||
jsonResponse(['success' => true, 'data' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
if (empty($body['penduduk_id']) || empty($body['nama'])) {
|
||||
jsonResponse(['success' => false, 'message' => 'penduduk_id dan nama wajib'], 422);
|
||||
}
|
||||
$stmt = $db->prepare("INSERT INTO anggota_keluarga (penduduk_id,nama,hubungan,umur,pekerjaan,keterangan) VALUES (?,?,?,?,?,?)");
|
||||
$stmt->execute([
|
||||
(int)$body['penduduk_id'],
|
||||
sanitize($body['nama'] ?? ''),
|
||||
sanitize($body['hubungan'] ?? ''),
|
||||
!empty($body['umur']) ? (int)$body['umur'] : null,
|
||||
sanitize($body['pekerjaan'] ?? ''),
|
||||
sanitize($body['keterangan'] ?? ''),
|
||||
]);
|
||||
jsonResponse(['success' => true, 'id' => $db->lastInsertId()], 201);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'id wajib'], 400);
|
||||
$stmt = $db->prepare("DELETE FROM anggota_keluarga WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => false, 'message' => 'Method tidak didukung'], 405);
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Export Data ke CSV atau HTML Print
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
// Wajib login untuk export
|
||||
session_name(SESSION_NAME);
|
||||
session_start();
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$type = $_GET['type'] ?? 'penduduk'; // penduduk | ibadah
|
||||
$format = $_GET['format'] ?? 'csv'; // csv | print
|
||||
|
||||
// ── Query Penduduk Miskin ──────────────────────────────────────
|
||||
if ($type === 'penduduk') {
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status'])) { $where[] = 'pm.status_bantuan = ?'; $params[] = $_GET['status']; }
|
||||
if (!empty($_GET['kecamatan'])) { $where[] = 'pm.kecamatan = ?'; $params[] = $_GET['kecamatan']; }
|
||||
if (!empty($_GET['ibadah_id'])) { $where[] = 'pm.rumah_ibadah_id = ?'; $params[] = (int)$_GET['ibadah_id']; }
|
||||
if (!empty($_GET['dari'])) { $where[] = 'DATE(pm.created_at) >= ?'; $params[] = $_GET['dari']; }
|
||||
if (!empty($_GET['sampai'])) { $where[] = 'DATE(pm.created_at) <= ?'; $params[] = $_GET['sampai']; }
|
||||
|
||||
$ws = implode(' AND ', $where);
|
||||
$stmt = $db->prepare("
|
||||
SELECT pm.id, pm.kk_nama, pm.nik, pm.jumlah_anggota, pm.alamat,
|
||||
pm.kelurahan, pm.kecamatan, pm.lat, pm.lng,
|
||||
pm.status_bantuan, pm.jenis_bantuan, pm.tanggal_bantuan, pm.nominal_bantuan, pm.jarak_ke_ibadah,
|
||||
pm.created_at, ri.nama AS ibadah_nama, ri.jenis AS ibadah_jenis
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
WHERE $ws ORDER BY pm.kecamatan, pm.kk_nama
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
if ($format === 'csv') {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="penduduk_miskin_' . date('Ymd_His') . '.csv"');
|
||||
$out = fopen('php://output', 'w');
|
||||
// BOM for Excel UTF-8
|
||||
fputs($out, "\xEF\xBB\xBF");
|
||||
fputcsv($out, ['No','Nama KK','NIK','Jumlah Anggota','Alamat','Kelurahan','Kecamatan',
|
||||
'Latitude','Longitude','Status Bantuan','Jenis Bantuan','Tanggal Bantuan','Nominal Bantuan',
|
||||
'Jarak ke Ibadah (m)','Rumah Ibadah','Jenis Ibadah','Terdaftar']);
|
||||
foreach ($rows as $i => $row) {
|
||||
fputcsv($out, [
|
||||
$i + 1,
|
||||
$row['kk_nama'],
|
||||
$row['nik'] ?: '',
|
||||
$row['jumlah_anggota'],
|
||||
$row['alamat'] ?: '',
|
||||
$row['kelurahan'] ?: '',
|
||||
$row['kecamatan'] ?: '',
|
||||
$row['lat'],
|
||||
$row['lng'],
|
||||
$row['status_bantuan'],
|
||||
$row['jenis_bantuan'] ?: '',
|
||||
$row['tanggal_bantuan'] ?: '',
|
||||
$row['nominal_bantuan'] ? $row['nominal_bantuan'] : '',
|
||||
$row['jarak_ke_ibadah'] ? round($row['jarak_ke_ibadah']) : '',
|
||||
$row['ibadah_nama'] ?: '',
|
||||
$row['ibadah_jenis'] ?: '',
|
||||
$row['created_at'],
|
||||
]);
|
||||
}
|
||||
fclose($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Print / PDF view
|
||||
$title = 'Data Penduduk Miskin';
|
||||
$subtitle = 'WebGIS Sebaran Penduduk Miskin — ' . APP_NAME;
|
||||
$cols = ['No', 'Nama KK', 'NIK', 'Anggota', 'Kecamatan', 'Status', 'Nominal Bantuan', 'Rumah Ibadah', 'Jarak'];
|
||||
$tableRows = array_map(function($r, $i) {
|
||||
$sc = ['sudah' => '#22c55e', 'menunggu' => '#f59e0b', 'belum' => '#ef4444'][$r['status_bantuan']] ?? '#ef4444';
|
||||
return [$i + 1, $r['kk_nama'], $r['nik'] ?: '—', $r['jumlah_anggota'],
|
||||
$r['kecamatan'] ?: '—',
|
||||
"<span style='color:{$sc};font-weight:700'>".ucfirst($r['status_bantuan']).'</span>',
|
||||
$r['nominal_bantuan'] ? 'Rp '.number_format($r['nominal_bantuan'],0,',','.') : '—',
|
||||
$r['ibadah_nama'] ?: '—',
|
||||
$r['jarak_ke_ibadah'] ? round($r['jarak_ke_ibadah']).' m' : '—'];
|
||||
}, $rows, array_keys($rows));
|
||||
}
|
||||
|
||||
// ── Query Rumah Ibadah ────────────────────────────────────────
|
||||
if ($type === 'ibadah') {
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
if (!empty($_GET['jenis'])) { $where[] = 'jenis = ?'; $params[] = $_GET['jenis']; }
|
||||
if (!empty($_GET['kecamatan'])) { $where[] = 'kecamatan = ?'; $params[] = $_GET['kecamatan']; }
|
||||
|
||||
$ws = implode(' AND ', $where);
|
||||
$stmt = $db->prepare("
|
||||
SELECT ri.*,
|
||||
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id) AS jumlah_binaan
|
||||
FROM rumah_ibadah ri WHERE $ws ORDER BY jenis, nama
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
if ($format === 'csv') {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="rumah_ibadah_' . date('Ymd_His') . '.csv"');
|
||||
$out = fopen('php://output', 'w');
|
||||
fputs($out, "\xEF\xBB\xBF");
|
||||
fputcsv($out, ['No','Nama','Jenis','Kontak','Alamat','Kelurahan','Kecamatan',
|
||||
'Latitude','Longitude','Radius (m)','Jumlah Binaan','Terdaftar']);
|
||||
foreach ($rows as $i => $row) {
|
||||
fputcsv($out, [
|
||||
$i + 1, $row['nama'], $row['jenis'], $row['kontak'] ?: '',
|
||||
$row['alamat'] ?: '', $row['kelurahan'] ?: '', $row['kecamatan'] ?: '',
|
||||
$row['lat'], $row['lng'], $row['radius'], $row['jumlah_binaan'], $row['created_at']
|
||||
]);
|
||||
}
|
||||
fclose($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
$title = 'Data Rumah Ibadah';
|
||||
$subtitle = 'WebGIS Sebaran Penduduk Miskin — ' . APP_NAME;
|
||||
$cols = ['No', 'Nama', 'Jenis', 'Kontak', 'Kecamatan', 'Radius', 'Binaan'];
|
||||
$tableRows = array_map(function($r, $i) {
|
||||
return [$i + 1, $r['nama'], $r['jenis'], $r['kontak'] ?: '—',
|
||||
$r['kecamatan'] ?: '—', formatRadius($r['radius']), $r['jumlah_binaan']];
|
||||
}, $rows, array_keys($rows));
|
||||
}
|
||||
|
||||
// ── HTML Print View ───────────────────────────────────────────
|
||||
$total = count($rows);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $title ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Plus Jakarta Sans', sans-serif; background: #fff; color: #1a1a2e; padding: 20px; font-size: 12px; }
|
||||
.print-header { text-align: center; margin-bottom: 24px; border-bottom: 2px solid #5b7fff; padding-bottom: 16px; }
|
||||
.print-header h1 { font-size: 20px; font-weight: 800; color: #1a1a2e; }
|
||||
.print-header p { font-size: 12px; color: #666; margin-top: 4px; }
|
||||
.print-meta { display: flex; gap: 16px; justify-content: center; margin-top: 8px; flex-wrap: wrap; font-size: 11px; color: #555; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
|
||||
th { background: #5b7fff; color: #fff; padding: 8px 10px; text-align: left; font-size: 11px; font-weight: 600; }
|
||||
td { padding: 6px 10px; border-bottom: 1px solid #eee; font-size: 11px; vertical-align: top; }
|
||||
tr:nth-child(even) td { background: #f8f9ff; }
|
||||
.print-footer { margin-top: 20px; text-align: right; font-size: 10px; color: #999; }
|
||||
.no-print { position: fixed; top: 20px; right: 20px; display: flex; gap: 8px; z-index: 999; }
|
||||
.btn-print { background: #5b7fff; color: #fff; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-family: inherit; font-weight: 600; font-size: 13px; }
|
||||
.btn-close-p { background: #ef4444; color: #fff; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-family: inherit; font-weight: 600; font-size: 13px; }
|
||||
@media print { .no-print { display: none; } body { padding: 0; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="no-print">
|
||||
<button class="btn-print" onclick="window.print()">🖨️ Cetak / Save PDF</button>
|
||||
<button class="btn-close-p" onclick="window.close()">✕ Tutup</button>
|
||||
</div>
|
||||
|
||||
<div class="print-header">
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
<p><?= htmlspecialchars($subtitle) ?></p>
|
||||
<div class="print-meta">
|
||||
<span>📅 Dicetak: <?= date('d F Y, H:i') ?> WIB</span>
|
||||
<span>📊 Total: <?= $total ?> data</span>
|
||||
<?php if (!empty($_GET['status'])): ?>
|
||||
<span>🔍 Status: <?= htmlspecialchars(ucfirst($_GET['status'])) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($_GET['kecamatan'])): ?>
|
||||
<span>📍 Kecamatan: <?= htmlspecialchars($_GET['kecamatan']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><?php foreach ($cols as $c) echo "<th>$c</th>"; ?></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($tableRows as $tr): ?>
|
||||
<tr><?php foreach ($tr as $td) echo "<td>$td</td>"; ?></tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($tableRows)): ?>
|
||||
<tr><td colspan="<?= count($cols) ?>" style="text-align:center;padding:20px;color:#999;">Tidak ada data</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="print-footer">
|
||||
<?= APP_NAME ?> v<?= APP_VERSION ?> — <?= BASE_URL ?>
|
||||
</div>
|
||||
<script>
|
||||
// Auto-trigger print dialog for print format
|
||||
<?php if ($format === 'print'): ?>
|
||||
window.addEventListener('load', () => setTimeout(() => {}, 500));
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Reverse Geocoding Proxy (Nominatim)
|
||||
// ================================================================
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$lat = isset($_GET['lat']) ? (float)$_GET['lat'] : null;
|
||||
$lng = isset($_GET['lng']) ? (float)$_GET['lng'] : null;
|
||||
|
||||
if (!$lat || !$lng) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'lat dan lng wajib']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = "https://nominatim.openstreetmap.org/reverse?format=json&lat={$lat}&lon={$lng}&addressdetails=1&accept-language=id";
|
||||
|
||||
$opts = ['http' => ['header' => "User-Agent: WebGIS-PovertyMap/2.0 (https://github.com)\r\n", 'timeout' => 10]];
|
||||
$context = stream_context_create($opts);
|
||||
$result = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($result === false) {
|
||||
echo json_encode(['display_name' => "{$lat}, {$lng}", 'fallback' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode($result, true);
|
||||
$addr = $data['display_name'] ?? "{$lat}, {$lng}";
|
||||
|
||||
// Ekstrak komponen alamat
|
||||
$components = $data['address'] ?? [];
|
||||
$kelurahan = $components['suburb'] ?? $components['village'] ?? $components['neighbourhood'] ?? '';
|
||||
$kecamatan = $components['city_district'] ?? $components['district'] ?? '';
|
||||
$kota = $components['city'] ?? $components['town'] ?? $components['county'] ?? '';
|
||||
|
||||
echo json_encode([
|
||||
'display_name' => $addr,
|
||||
'kelurahan' => $kelurahan,
|
||||
'kecamatan' => $kecamatan,
|
||||
'kota' => $kota,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
]);
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Import Data Massal dari CSV
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Hanya POST
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'message' => 'Method tidak didukung']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$type = $_POST['type'] ?? ''; // 'penduduk' | 'ibadah'
|
||||
|
||||
if (!in_array($type, ['penduduk', 'ibadah'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Tipe import tidak valid']); exit;
|
||||
}
|
||||
|
||||
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
echo json_encode(['success' => false, 'message' => 'File tidak valid atau tidak ada']); exit;
|
||||
}
|
||||
|
||||
// Baca isi file
|
||||
$content = file_get_contents($_FILES['file']['tmp_name']);
|
||||
if ($content === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal membaca file']); exit;
|
||||
}
|
||||
|
||||
// Normalize line endings & parse CSV
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
$content = str_replace("\r", "\n", $content);
|
||||
$lines = array_filter(explode("\n", $content), 'strlen');
|
||||
$lines = array_values($lines);
|
||||
|
||||
if (empty($lines)) {
|
||||
echo json_encode(['success' => false, 'message' => 'File kosong']); exit;
|
||||
}
|
||||
|
||||
// Parse CSV baris per baris
|
||||
function parseCSVLine(string $line): array {
|
||||
$cols = [];
|
||||
$cur = '';
|
||||
$inQ = false;
|
||||
for ($i = 0; $i < strlen($line); $i++) {
|
||||
$ch = $line[$i];
|
||||
if ($ch === '"') { $inQ = !$inQ; }
|
||||
elseif ($ch === ',' && !$inQ) { $cols[] = trim($cur, " \t\""); $cur = ''; }
|
||||
else { $cur .= $ch; }
|
||||
}
|
||||
$cols[] = trim($cur, " \t\"");
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// Deteksi header row
|
||||
$firstLine = strtolower($lines[0]);
|
||||
$hasHeader = str_contains($firstLine, 'nama') || str_contains($firstLine, 'lat') || str_contains($firstLine, 'kk_nama');
|
||||
$dataLines = $hasHeader ? array_slice($lines, 1) : $lines;
|
||||
|
||||
$imported = 0;
|
||||
$failed = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
|
||||
// ── Import Penduduk Miskin ─────────────────────────────────────
|
||||
if ($type === 'penduduk') {
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO penduduk_miskin
|
||||
(kk_nama, nik, jumlah_anggota, alamat, kelurahan, kecamatan, lat, lng,
|
||||
rumah_ibadah_id, jarak_ke_ibadah, status_bantuan, jenis_bantuan, tanggal_bantuan, nominal_bantuan)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
");
|
||||
|
||||
foreach ($dataLines as $lineNum => $line) {
|
||||
if (!trim($line)) continue;
|
||||
$row = parseCSVLine($line);
|
||||
|
||||
// kk_nama (0), nik (1), jumlah_anggota (2), alamat (3), kelurahan (4),
|
||||
// kecamatan (5), lat (6), lng (7), status_bantuan (8), jenis_bantuan (9),
|
||||
// tanggal_bantuan (10), nominal_bantuan (11)
|
||||
if (count($row) < 8 || empty($row[0]) || empty($row[6]) || empty($row[7])) {
|
||||
$failed++;
|
||||
$errors[] = "Baris " . ($lineNum + ($hasHeader ? 2 : 1)) . ": kolom tidak lengkap atau koordinat kosong";
|
||||
if (count($errors) > 10) { $errors[] = '... dan lebih banyak error'; break; }
|
||||
continue;
|
||||
}
|
||||
|
||||
$lat = (float)$row[6];
|
||||
$lng = (float)$row[7];
|
||||
if ($lat === 0.0 && $lng === 0.0) { $skipped++; continue; }
|
||||
|
||||
$status = in_array($row[8] ?? '', ['sudah','belum','menunggu']) ? $row[8] : 'belum';
|
||||
$tanggal_bantuan = !empty($row[10]) ? sanitize($row[10]) : null;
|
||||
$nominal_bantuan = isset($row[11]) && $row[11] !== '' ? (float)str_replace(['.', ','], ['', '.'], $row[11]) : null;
|
||||
|
||||
$nearest = findNearestIbadah($lat, $lng);
|
||||
$ibadahId = $nearest ? (int)$nearest['id'] : null;
|
||||
$jarakIbadah = $nearest ? (float)$nearest['jarak'] : null;
|
||||
|
||||
try {
|
||||
$stmt->execute([
|
||||
sanitize($row[0]),
|
||||
sanitize($row[1] ?? ''),
|
||||
max(1, (int)($row[2] ?? 1)),
|
||||
sanitize($row[3] ?? ''),
|
||||
sanitize($row[4] ?? ''),
|
||||
sanitize($row[5] ?? ''),
|
||||
$lat, $lng,
|
||||
$ibadahId,
|
||||
$jarakIbadah,
|
||||
$status,
|
||||
sanitize($row[9] ?? ''),
|
||||
$tanggal_bantuan,
|
||||
$nominal_bantuan,
|
||||
]);
|
||||
$imported++;
|
||||
} catch (PDOException $e) {
|
||||
$failed++;
|
||||
$errors[] = "Baris " . ($lineNum + ($hasHeader ? 2 : 1)) . ": " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Import Rumah Ibadah ───────────────────────────────────────
|
||||
if ($type === 'ibadah') {
|
||||
$validJenis = ['Masjid','Musholla','Gereja','Katolik','Pura','Vihara','Klenteng','Lainnya'];
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO rumah_ibadah (nama, jenis, kontak, alamat, kelurahan, kecamatan, lat, lng, radius)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
foreach ($dataLines as $lineNum => $line) {
|
||||
if (!trim($line)) continue;
|
||||
$row = parseCSVLine($line);
|
||||
|
||||
// nama (0), jenis (1), kontak (2), alamat (3), kelurahan (4),
|
||||
// kecamatan (5), lat (6), lng (7), radius (8)
|
||||
if (count($row) < 8 || empty($row[0]) || empty($row[6]) || empty($row[7])) {
|
||||
$failed++;
|
||||
$errors[] = "Baris " . ($lineNum + ($hasHeader ? 2 : 1)) . ": kolom tidak lengkap";
|
||||
if (count($errors) > 10) { $errors[] = '... dan lebih banyak error'; break; }
|
||||
continue;
|
||||
}
|
||||
|
||||
$lat = (float)$row[6];
|
||||
$lng = (float)$row[7];
|
||||
if ($lat === 0.0 && $lng === 0.0) { $skipped++; continue; }
|
||||
|
||||
$jenis = in_array($row[1] ?? '', $validJenis) ? $row[1] : 'Lainnya';
|
||||
$radius = max(100, min(5000, (int)($row[8] ?? RADIUS_DEFAULT)));
|
||||
|
||||
try {
|
||||
$stmt->execute([
|
||||
sanitize($row[0]),
|
||||
$jenis,
|
||||
sanitize($row[2] ?? ''),
|
||||
sanitize($row[3] ?? ''),
|
||||
sanitize($row[4] ?? ''),
|
||||
sanitize($row[5] ?? ''),
|
||||
$lat, $lng, $radius,
|
||||
]);
|
||||
$imported++;
|
||||
} catch (PDOException $e) {
|
||||
$failed++;
|
||||
$errors[] = "Baris " . ($lineNum + ($hasHeader ? 2 : 1)) . ": " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'imported' => $imported,
|
||||
'failed' => $failed,
|
||||
'skipped' => $skipped,
|
||||
'errors' => array_slice($errors, 0, 15),
|
||||
'message' => "Import selesai: $imported berhasil, $failed gagal, $skipped dilewati",
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Notify — Cek apakah ada data baru sejak timestamp tertentu
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$db = getDB();
|
||||
$since = $_GET['since'] ?? date('Y-m-d H:i:s', strtotime('-1 minute'));
|
||||
|
||||
// Sanitasi sederhana
|
||||
$since = preg_replace('/[^0-9\-: ]/', '', $since);
|
||||
|
||||
$newMiskin = (int)$db->prepare("SELECT COUNT(*) FROM penduduk_miskin WHERE created_at > ?")->execute([$since]) ? 0 : 0;
|
||||
$stmtM = $db->prepare("SELECT COUNT(*) FROM penduduk_miskin WHERE created_at > ?");
|
||||
$stmtM->execute([$since]);
|
||||
$newMiskin = (int)$stmtM->fetchColumn();
|
||||
|
||||
$stmtI = $db->prepare("SELECT COUNT(*) FROM rumah_ibadah WHERE created_at > ?");
|
||||
$stmtI->execute([$since]);
|
||||
$newIbadah = (int)$stmtI->fetchColumn();
|
||||
|
||||
$stmtTotal = $db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='belum'");
|
||||
$belumDibantu = (int)$stmtTotal->fetchColumn();
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'new_miskin' => $newMiskin,
|
||||
'new_ibadah' => $newIbadah,
|
||||
'belum_dibantu' => $belumDibantu,
|
||||
'has_new' => ($newMiskin + $newIbadah) > 0,
|
||||
'server_time' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Penduduk Miskin
|
||||
// Methods: GET, POST, PUT, DELETE
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.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') {
|
||||
http_response_code(204); exit;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
// ── GET ────────────────────────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
if ($id) {
|
||||
// Detail lengkap termasuk anggota keluarga dan info ibadah
|
||||
$stmt = $db->prepare("
|
||||
SELECT pm.*,
|
||||
ri.nama AS ibadah_nama, ri.jenis AS ibadah_jenis,
|
||||
ri.kontak AS ibadah_kontak, ri.lat AS ibadah_lat, ri.lng AS ibadah_lng
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
WHERE pm.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) jsonResponse(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
// Anggota keluarga
|
||||
$stmtAk = $db->prepare("SELECT * FROM anggota_keluarga WHERE penduduk_id = ? ORDER BY id");
|
||||
$stmtAk->execute([$id]);
|
||||
$row['anggota'] = $stmtAk->fetchAll();
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $row]);
|
||||
}
|
||||
|
||||
// List dengan filter + search
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status'])) {
|
||||
$where[] = 'pm.status_bantuan = ?';
|
||||
$params[] = $_GET['status'];
|
||||
}
|
||||
if (!empty($_GET['kecamatan'])) {
|
||||
$where[] = 'pm.kecamatan = ?';
|
||||
$params[] = $_GET['kecamatan'];
|
||||
}
|
||||
if (!empty($_GET['kelurahan'])) {
|
||||
$where[] = 'pm.kelurahan = ?';
|
||||
$params[] = $_GET['kelurahan'];
|
||||
}
|
||||
if (!empty($_GET['ibadah_id'])) {
|
||||
$where[] = 'pm.rumah_ibadah_id = ?';
|
||||
$params[] = (int)$_GET['ibadah_id'];
|
||||
}
|
||||
if (!empty($_GET['search'])) {
|
||||
$where[] = '(pm.kk_nama LIKE ? OR pm.alamat LIKE ? OR pm.nik LIKE ?)';
|
||||
$q = '%' . $_GET['search'] . '%';
|
||||
$params = array_merge($params, [$q, $q, $q]);
|
||||
}
|
||||
|
||||
$sql = "SELECT pm.*,
|
||||
ri.nama AS ibadah_nama,
|
||||
ri.jenis AS ibadah_jenis
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
WHERE " . implode(' AND ', $where) . "
|
||||
ORDER BY pm.created_at DESC";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
// ── POST ───────────────────────────────────────────────────────
|
||||
if ($method === 'POST') {
|
||||
$isMultipart = str_contains($_SERVER['CONTENT_TYPE'] ?? '', 'multipart/form-data');
|
||||
$body = $isMultipart ? $_POST : (json_decode(file_get_contents('php://input'), true) ?? []);
|
||||
|
||||
$required = ['kk_nama', 'lat', 'lng'];
|
||||
foreach ($required as $f) {
|
||||
if (empty($body[$f])) jsonResponse(['success' => false, 'message' => "Field '$f' wajib diisi"], 422);
|
||||
}
|
||||
|
||||
$lat = (float)$body['lat'];
|
||||
$lng = (float)$body['lng'];
|
||||
|
||||
// Auto-cari rumah ibadah terdekat
|
||||
$nearest = findNearestIbadah($lat, $lng);
|
||||
$ibadahId = $nearest ? (int)$nearest['id'] : null;
|
||||
$jarakIbadah = $nearest ? (float)$nearest['jarak'] : null;
|
||||
|
||||
// Handle upload bukti
|
||||
$buktiFile = null;
|
||||
if (!empty($_FILES['bukti_file']) && $_FILES['bukti_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$buktiFile = handleUpload($_FILES['bukti_file'], 'bukti');
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO penduduk_miskin
|
||||
(kk_nama, nik, jumlah_anggota, alamat, kelurahan, kecamatan, lat, lng,
|
||||
rumah_ibadah_id, jarak_ke_ibadah, status_bantuan, jenis_bantuan,
|
||||
tanggal_bantuan, nominal_bantuan, bukti_file)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
");
|
||||
$stmt->execute([
|
||||
sanitize($body['kk_nama']),
|
||||
sanitize($body['nik'] ?? ''),
|
||||
(int)($body['jumlah_anggota'] ?? 1),
|
||||
sanitize($body['alamat'] ?? ''),
|
||||
sanitize($body['kelurahan'] ?? ''),
|
||||
sanitize($body['kecamatan'] ?? ''),
|
||||
$lat, $lng,
|
||||
$ibadahId,
|
||||
$jarakIbadah,
|
||||
$body['status_bantuan'] ?? 'belum',
|
||||
sanitize($body['jenis_bantuan'] ?? ''),
|
||||
!empty($body['tanggal_bantuan']) ? $body['tanggal_bantuan'] : null,
|
||||
!empty($body['nominal_bantuan']) ? (float)str_replace(['.', ','], ['', '.'], $body['nominal_bantuan']) : null,
|
||||
$buktiFile,
|
||||
]);
|
||||
$newId = $db->lastInsertId();
|
||||
|
||||
// Simpan anggota keluarga jika ada (anggota bisa berupa JSON string dari FormData)
|
||||
$anggotaData = $body['anggota'] ?? null;
|
||||
if (is_string($anggotaData)) $anggotaData = json_decode($anggotaData, true);
|
||||
if (!empty($anggotaData) && is_array($anggotaData)) {
|
||||
saveAnggota($db, $newId, $anggotaData);
|
||||
}
|
||||
|
||||
// Fetch full data
|
||||
$new = $db->query("
|
||||
SELECT pm.*, ri.nama AS ibadah_nama, ri.jenis AS ibadah_jenis
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
WHERE pm.id = $newId
|
||||
")->fetch();
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'Data penduduk berhasil ditambahkan', 'data' => $new], 201);
|
||||
}
|
||||
|
||||
// ── PUT ────────────────────────────────────────────────────────
|
||||
if ($method === 'PUT') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
|
||||
// Cek apakah multipart (ada file) atau JSON
|
||||
$isMultipart = str_contains($_SERVER['CONTENT_TYPE'] ?? '', 'multipart/form-data');
|
||||
$body = $isMultipart ? $_POST : (json_decode(file_get_contents('php://input'), true) ?? []);
|
||||
|
||||
$current = $db->prepare("SELECT * FROM penduduk_miskin WHERE id = ?");
|
||||
$current->execute([$id]);
|
||||
$existing = $current->fetch();
|
||||
if (!$existing) jsonResponse(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
$lat = (float)($body['lat'] ?? $existing['lat']);
|
||||
$lng = (float)($body['lng'] ?? $existing['lng']);
|
||||
|
||||
// Re-assign ibadah terdekat jika koordinat berubah
|
||||
$coordChanged = ($lat != (float)$existing['lat'] || $lng != (float)$existing['lng']);
|
||||
if ($coordChanged || isset($body['force_reassign'])) {
|
||||
$nearest = findNearestIbadah($lat, $lng);
|
||||
$ibadahId = $nearest ? (int)$nearest['id'] : null;
|
||||
$jarakIbadah = $nearest ? (float)$nearest['jarak'] : null;
|
||||
} else {
|
||||
$ibadahId = (int)($body['rumah_ibadah_id'] ?? $existing['rumah_ibadah_id']);
|
||||
$jarakIbadah = (float)($existing['jarak_ke_ibadah'] ?? 0);
|
||||
}
|
||||
|
||||
// Handle upload bukti baru
|
||||
$buktiFile = $existing['bukti_file'];
|
||||
if (!empty($_FILES['bukti_file']) && $_FILES['bukti_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$newFile = handleUpload($_FILES['bukti_file'], 'bukti');
|
||||
if ($newFile) $buktiFile = $newFile;
|
||||
}
|
||||
|
||||
$status_bantuan = $body['status_bantuan'] ?? $existing['status_bantuan'];
|
||||
if ($status_bantuan === 'belum') {
|
||||
$jenis_bantuan = null;
|
||||
$tanggal_bantuan = null;
|
||||
$nominal_bantuan = null;
|
||||
} else {
|
||||
$jenis_bantuan = isset($body['jenis_bantuan']) ? sanitize($body['jenis_bantuan']) : $existing['jenis_bantuan'];
|
||||
$tanggal_bantuan = isset($body['tanggal_bantuan']) ? (!empty($body['tanggal_bantuan']) ? $body['tanggal_bantuan'] : null) : $existing['tanggal_bantuan'];
|
||||
if (isset($body['nominal_bantuan'])) {
|
||||
$nominal_bantuan = $body['nominal_bantuan'] !== '' ? (float)str_replace(['.', ','], ['', '.'], $body['nominal_bantuan']) : null;
|
||||
} else {
|
||||
$nominal_bantuan = $existing['nominal_bantuan'];
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
UPDATE penduduk_miskin
|
||||
SET kk_nama=?, nik=?, jumlah_anggota=?, alamat=?, kelurahan=?, kecamatan=?,
|
||||
lat=?, lng=?, rumah_ibadah_id=?, jarak_ke_ibadah=?,
|
||||
status_bantuan=?, jenis_bantuan=?, tanggal_bantuan=?, nominal_bantuan=?, bukti_file=?
|
||||
WHERE id=?
|
||||
");
|
||||
$stmt->execute([
|
||||
sanitize($body['kk_nama'] ?? $existing['kk_nama']),
|
||||
sanitize($body['nik'] ?? $existing['nik'] ?? ''),
|
||||
(int)($body['jumlah_anggota'] ?? $existing['jumlah_anggota']),
|
||||
sanitize($body['alamat'] ?? $existing['alamat'] ?? ''),
|
||||
sanitize($body['kelurahan'] ?? $existing['kelurahan'] ?? ''),
|
||||
sanitize($body['kecamatan'] ?? $existing['kecamatan'] ?? ''),
|
||||
$lat, $lng,
|
||||
$ibadahId, $jarakIbadah,
|
||||
$status_bantuan,
|
||||
$jenis_bantuan,
|
||||
$tanggal_bantuan,
|
||||
$nominal_bantuan,
|
||||
$buktiFile,
|
||||
$id,
|
||||
]);
|
||||
|
||||
// Update anggota keluarga (anggota bisa berupa JSON string dari FormData)
|
||||
if (isset($body['anggota'])) {
|
||||
$anggotaDataPut = $body['anggota'];
|
||||
if (is_string($anggotaDataPut)) $anggotaDataPut = json_decode($anggotaDataPut, true);
|
||||
if (is_array($anggotaDataPut)) {
|
||||
$db->prepare("DELETE FROM anggota_keluarga WHERE penduduk_id = ?")->execute([$id]);
|
||||
saveAnggota($db, $id, $anggotaDataPut);
|
||||
}
|
||||
}
|
||||
|
||||
$updated = $db->query("
|
||||
SELECT pm.*, ri.nama AS ibadah_nama, ri.jenis AS ibadah_jenis
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
WHERE pm.id = $id
|
||||
")->fetch();
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'Data berhasil diperbarui', 'data' => $updated]);
|
||||
}
|
||||
|
||||
// ── DELETE ─────────────────────────────────────────────────────
|
||||
if ($method === 'DELETE') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
$stmt = $db->prepare("DELETE FROM penduduk_miskin WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->rowCount() === 0) jsonResponse(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
jsonResponse(['success' => true, 'message' => 'Data berhasil dihapus']);
|
||||
}
|
||||
|
||||
// ── Helper ─────────────────────────────────────────────────────
|
||||
function saveAnggota(PDO $db, int $pendudukId, array $list): void {
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO anggota_keluarga (penduduk_id, nama, hubungan, umur, pekerjaan, keterangan)
|
||||
VALUES (?,?,?,?,?,?)
|
||||
");
|
||||
foreach ($list as $a) {
|
||||
if (empty($a['nama'])) continue;
|
||||
$stmt->execute([
|
||||
$pendudukId,
|
||||
sanitize($a['nama'] ?? ''),
|
||||
sanitize($a['hubungan'] ?? ''),
|
||||
!empty($a['umur']) ? (int)$a['umur'] : null,
|
||||
sanitize($a['pekerjaan'] ?? ''),
|
||||
sanitize($a['keterangan'] ?? ''),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse(['success' => false, 'message' => 'Method tidak didukung'], 405);
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Rumah Ibadah
|
||||
// Methods: GET, POST, PUT, DELETE
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.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') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
// ── GET ────────────────────────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
if ($id) {
|
||||
// Single record
|
||||
$stmt = $db->prepare("
|
||||
SELECT ri.*,
|
||||
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id) AS jumlah_binaan
|
||||
FROM rumah_ibadah ri WHERE ri.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) jsonResponse(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
jsonResponse(['success' => true, 'data' => $row]);
|
||||
}
|
||||
|
||||
// List dengan filter
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['jenis'])) {
|
||||
$where[] = 'jenis = ?';
|
||||
$params[] = $_GET['jenis'];
|
||||
}
|
||||
if (!empty($_GET['kecamatan'])) {
|
||||
$where[] = 'kecamatan = ?';
|
||||
$params[] = $_GET['kecamatan'];
|
||||
}
|
||||
if (!empty($_GET['search'])) {
|
||||
$where[] = 'nama LIKE ?';
|
||||
$params[] = '%' . $_GET['search'] . '%';
|
||||
}
|
||||
|
||||
$sql = "SELECT ri.*,
|
||||
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id) AS jumlah_binaan
|
||||
FROM rumah_ibadah ri
|
||||
WHERE " . implode(' AND ', $where) . "
|
||||
ORDER BY ri.nama ASC";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
// ── POST ───────────────────────────────────────────────────────
|
||||
if ($method === 'POST') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$required = ['nama', 'jenis', 'lat', 'lng'];
|
||||
foreach ($required as $f) {
|
||||
if (empty($body[$f])) jsonResponse(['success' => false, 'message' => "Field '$f' wajib diisi"], 422);
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO rumah_ibadah (nama, jenis, kontak, alamat, kelurahan, kecamatan, lat, lng, radius)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
sanitize($body['nama']),
|
||||
$body['jenis'],
|
||||
sanitize($body['kontak'] ?? ''),
|
||||
sanitize($body['alamat'] ?? ''),
|
||||
sanitize($body['kelurahan'] ?? ''),
|
||||
sanitize($body['kecamatan'] ?? ''),
|
||||
(float)$body['lat'],
|
||||
(float)$body['lng'],
|
||||
(int)($body['radius'] ?? RADIUS_DEFAULT),
|
||||
]);
|
||||
$newId = $db->lastInsertId();
|
||||
$new = $db->query("SELECT * FROM rumah_ibadah WHERE id = $newId")->fetch();
|
||||
jsonResponse(['success' => true, 'message' => 'Rumah ibadah berhasil ditambahkan', 'data' => $new], 201);
|
||||
}
|
||||
|
||||
// ── PUT ────────────────────────────────────────────────────────
|
||||
if ($method === 'PUT') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$stmt = $db->prepare("
|
||||
UPDATE rumah_ibadah
|
||||
SET nama=?, jenis=?, kontak=?, alamat=?, kelurahan=?, kecamatan=?, lat=?, lng=?, radius=?
|
||||
WHERE id=?
|
||||
");
|
||||
$stmt->execute([
|
||||
sanitize($body['nama'] ?? ''),
|
||||
$body['jenis'] ?? 'Masjid',
|
||||
sanitize($body['kontak'] ?? ''),
|
||||
sanitize($body['alamat'] ?? ''),
|
||||
sanitize($body['kelurahan'] ?? ''),
|
||||
sanitize($body['kecamatan'] ?? ''),
|
||||
(float)($body['lat'] ?? 0),
|
||||
(float)($body['lng'] ?? 0),
|
||||
(int)($body['radius'] ?? RADIUS_DEFAULT),
|
||||
$id,
|
||||
]);
|
||||
|
||||
// Re-assign penduduk yang terhubung jika radius berubah
|
||||
reassignPendudukAfterIbadahUpdate();
|
||||
|
||||
$updated = $db->query("SELECT * FROM rumah_ibadah WHERE id = $id")->fetch();
|
||||
jsonResponse(['success' => true, 'message' => 'Data berhasil diperbarui', 'data' => $updated]);
|
||||
}
|
||||
|
||||
// ── DELETE ─────────────────────────────────────────────────────
|
||||
if ($method === 'DELETE') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
$stmt = $db->prepare("DELETE FROM rumah_ibadah WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->rowCount() === 0) jsonResponse(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
jsonResponse(['success' => true, 'message' => 'Data berhasil dihapus']);
|
||||
}
|
||||
|
||||
function reassignPendudukAfterIbadahUpdate(): void {
|
||||
$db = getDB();
|
||||
$penduduk = $db->query("SELECT id, lat, lng FROM penduduk_miskin")->fetchAll();
|
||||
foreach ($penduduk as $p) {
|
||||
$nearest = findNearestIbadah((float)$p['lat'], (float)$p['lng']);
|
||||
if ($nearest) {
|
||||
$stmt = $db->prepare("UPDATE penduduk_miskin SET rumah_ibadah_id=?, jarak_ke_ibadah=? WHERE id=?");
|
||||
$stmt->execute([$nearest['id'], $nearest['jarak'], $p['id']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse(['success' => false, 'message' => 'Method tidak didukung'], 405);
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: Statistics
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Total counts
|
||||
$totalIbadah = $db->query("SELECT COUNT(*) FROM rumah_ibadah")->fetchColumn();
|
||||
$totalMiskin = $db->query("SELECT COUNT(*) FROM penduduk_miskin")->fetchColumn();
|
||||
$sudahDibantu = $db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='sudah'")->fetchColumn();
|
||||
$belumDibantu = $db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='belum'")->fetchColumn();
|
||||
$menunggu = $db->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_bantuan='menunggu'")->fetchColumn();
|
||||
$totalNominal = $db->query("SELECT COALESCE(SUM(nominal_bantuan),0) FROM penduduk_miskin WHERE status_bantuan='sudah' AND nominal_bantuan IS NOT NULL")->fetchColumn();
|
||||
$totalNominalAll = $db->query("SELECT COALESCE(SUM(nominal_bantuan),0) FROM penduduk_miskin WHERE nominal_bantuan IS NOT NULL")->fetchColumn();
|
||||
|
||||
// Per jenis ibadah
|
||||
$jenisStats = $db->query("
|
||||
SELECT jenis, COUNT(*) as total,
|
||||
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id) AS binaan
|
||||
FROM rumah_ibadah ri
|
||||
GROUP BY jenis ORDER BY total DESC
|
||||
")->fetchAll();
|
||||
|
||||
// Per kecamatan (top 5)
|
||||
$kecamatanStats = $db->query("
|
||||
SELECT kecamatan, COUNT(*) as total,
|
||||
SUM(CASE WHEN status_bantuan='sudah' THEN 1 ELSE 0 END) AS sudah,
|
||||
SUM(CASE WHEN status_bantuan='belum' THEN 1 ELSE 0 END) AS belum
|
||||
FROM penduduk_miskin
|
||||
WHERE kecamatan != '' AND kecamatan IS NOT NULL
|
||||
GROUP BY kecamatan ORDER BY total DESC LIMIT 5
|
||||
")->fetchAll();
|
||||
|
||||
// Data terbaru (5 data)
|
||||
$recentMiskin = $db->query("
|
||||
SELECT pm.id, pm.kk_nama, pm.status_bantuan, pm.created_at,
|
||||
ri.nama AS ibadah_nama
|
||||
FROM penduduk_miskin pm
|
||||
LEFT JOIN rumah_ibadah ri ON pm.rumah_ibadah_id = ri.id
|
||||
ORDER BY pm.created_at DESC LIMIT 5
|
||||
")->fetchAll();
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'total_ibadah' => (int)$totalIbadah,
|
||||
'total_miskin' => (int)$totalMiskin,
|
||||
'sudah_dibantu' => (int)$sudahDibantu,
|
||||
'belum_dibantu' => (int)$belumDibantu,
|
||||
'menunggu' => (int)$menunggu,
|
||||
'total_nominal' => (float)$totalNominal,
|
||||
'total_nominal_all' => (float)$totalNominalAll,
|
||||
'jenis_stats' => $jenisStats,
|
||||
'kecamatan_stats' => $kecamatanStats,
|
||||
'recent_miskin' => $recentMiskin,
|
||||
]
|
||||
]);
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: File Upload
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'message' => 'Method harus POST'], 405);
|
||||
}
|
||||
|
||||
if (empty($_FILES['file'])) {
|
||||
jsonResponse(['success' => false, 'message' => 'Tidak ada file yang diupload'], 400);
|
||||
}
|
||||
|
||||
$file = $_FILES['file'];
|
||||
$prefix = sanitize($_POST['prefix'] ?? 'upload');
|
||||
$filename = handleUpload($file, $prefix);
|
||||
|
||||
if (!$filename) {
|
||||
jsonResponse(['success' => false, 'message' => 'Upload gagal. Pastikan tipe file dan ukuran sesuai (maks 10MB, tipe: jpg/jpeg/png/gif/pdf/doc/docx)'], 422);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'filename' => $filename,
|
||||
'url' => UPLOAD_URL . $filename,
|
||||
'message' => 'File berhasil diupload',
|
||||
]);
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
// ================================================================
|
||||
// API: User Management (CRUD)
|
||||
// Hanya bisa diakses oleh admin
|
||||
// ================================================================
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/functions.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') { http_response_code(204); exit; }
|
||||
|
||||
// Auth check — hanya admin
|
||||
session_name(SESSION_NAME);
|
||||
session_start();
|
||||
$sessionRole = $_SESSION['user']['role'] ?? $_SESSION['user_role'] ?? null;
|
||||
$sessionUserId = $_SESSION['user']['id'] ?? $_SESSION['user_id'] ?? null;
|
||||
if (!$sessionUserId || $sessionRole !== 'admin') {
|
||||
jsonResponse(['success' => false, 'message' => 'Akses ditolak: hanya admin'], 403);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
// ── GET ────────────────────────────────────────────────────────
|
||||
if ($method === 'GET') {
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT id, nama, email, role, created_at FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) jsonResponse(['success' => false, 'message' => 'User tidak ditemukan'], 404);
|
||||
jsonResponse(['success' => true, 'data' => $row]);
|
||||
}
|
||||
$rows = $db->query("SELECT id, nama, email, role, created_at FROM users ORDER BY role DESC, nama")->fetchAll();
|
||||
jsonResponse(['success' => true, 'data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
// ── POST (Tambah user baru) ────────────────────────────────────
|
||||
if ($method === 'POST') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$nama = trim($body['nama'] ?? '');
|
||||
$email = trim($body['email'] ?? '');
|
||||
$pwd = $body['password'] ?? '';
|
||||
$role = in_array($body['role'] ?? '', ['admin','operator']) ? $body['role'] : 'operator';
|
||||
|
||||
if (!$nama || !$email || !$pwd) {
|
||||
jsonResponse(['success' => false, 'message' => 'Nama, email, dan password wajib diisi'], 422);
|
||||
}
|
||||
if (strlen($pwd) < 6) {
|
||||
jsonResponse(['success' => false, 'message' => 'Password minimal 6 karakter'], 422);
|
||||
}
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
jsonResponse(['success' => false, 'message' => 'Format email tidak valid'], 422);
|
||||
}
|
||||
|
||||
// Cek duplikat email
|
||||
$check = $db->prepare("SELECT id FROM users WHERE email = ?");
|
||||
$check->execute([$email]);
|
||||
if ($check->fetch()) {
|
||||
jsonResponse(['success' => false, 'message' => 'Email sudah terdaftar'], 409);
|
||||
}
|
||||
|
||||
$hash = password_hash($pwd, PASSWORD_BCRYPT);
|
||||
$stmt = $db->prepare("INSERT INTO users (nama, email, password, role) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$nama, $email, $hash, $role]);
|
||||
$newId = $db->lastInsertId();
|
||||
$new = $db->query("SELECT id, nama, email, role, created_at FROM users WHERE id = $newId")->fetch();
|
||||
jsonResponse(['success' => true, 'message' => 'Pengguna berhasil ditambahkan', 'data' => $new], 201);
|
||||
}
|
||||
|
||||
// ── PUT (Edit user) ────────────────────────────────────────────
|
||||
if ($method === 'PUT') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$existing = $db->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$existing->execute([$id]);
|
||||
$user = $existing->fetch();
|
||||
if (!$user) jsonResponse(['success' => false, 'message' => 'User tidak ditemukan'], 404);
|
||||
|
||||
$nama = trim($body['nama'] ?? $user['nama']);
|
||||
$email = trim($body['email'] ?? $user['email']);
|
||||
$role = in_array($body['role'] ?? '', ['admin','operator']) ? $body['role'] : $user['role'];
|
||||
$pwd = $body['password'] ?? '';
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
jsonResponse(['success' => false, 'message' => 'Format email tidak valid'], 422);
|
||||
}
|
||||
|
||||
// Cek duplikat email (kecuali user ini sendiri)
|
||||
$checkEmail = $db->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
|
||||
$checkEmail->execute([$email, $id]);
|
||||
if ($checkEmail->fetch()) {
|
||||
jsonResponse(['success' => false, 'message' => 'Email sudah digunakan oleh user lain'], 409);
|
||||
}
|
||||
|
||||
if ($pwd) {
|
||||
if (strlen($pwd) < 6) jsonResponse(['success' => false, 'message' => 'Password minimal 6 karakter'], 422);
|
||||
$hash = password_hash($pwd, PASSWORD_BCRYPT);
|
||||
$stmt = $db->prepare("UPDATE users SET nama=?, email=?, role=?, password=? WHERE id=?");
|
||||
$stmt->execute([$nama, $email, $role, $hash, $id]);
|
||||
} else {
|
||||
$stmt = $db->prepare("UPDATE users SET nama=?, email=?, role=? WHERE id=?");
|
||||
$stmt->execute([$nama, $email, $role, $id]);
|
||||
}
|
||||
|
||||
$updated = $db->query("SELECT id, nama, email, role, created_at FROM users WHERE id = $id")->fetch();
|
||||
jsonResponse(['success' => true, 'message' => 'Data pengguna berhasil diperbarui', 'data' => $updated]);
|
||||
}
|
||||
|
||||
// ── DELETE ─────────────────────────────────────────────────────
|
||||
if ($method === 'DELETE') {
|
||||
if (!$id) jsonResponse(['success' => false, 'message' => 'ID wajib disertakan'], 400);
|
||||
|
||||
// Tidak boleh hapus diri sendiri
|
||||
if ($id == $sessionUserId) {
|
||||
jsonResponse(['success' => false, 'message' => 'Tidak bisa menghapus akun sendiri'], 403);
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->rowCount() === 0) jsonResponse(['success' => false, 'message' => 'User tidak ditemukan'], 404);
|
||||
jsonResponse(['success' => true, 'message' => 'Pengguna berhasil dihapus']);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => false, 'message' => 'Method tidak didukung'], 405);
|
||||
Reference in New Issue
Block a user