Initial commit — WebGIS UAS GeoMiskin

Sistem WebGIS analisis sebaran penduduk miskin dan jangkauan rumah ibadah.
Fitur: peta interaktif Leaflet.js, CRUD rumah ibadah & penduduk miskin,
auto-kategorisasi haversine, heatmap, export PDF/CSV, dashboard analitik,
manajemen pengguna (superadmin/admin), login sistem.

Teknologi: PHP, MySQL, Leaflet.js, OpenStreetMap, Nominatim Geocoding, mPDF.

Catatan: jalankan `composer install` di folder uas/ untuk install dependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Athallah Ghathfan Aqila
2026-06-11 13:31:33 +07:00
commit 6215b4367a
59 changed files with 6154 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
$user_id = (int)($_SESSION['user']['id'] ?? 0);
$old = $_POST['old_password'] ?? '';
$new = $_POST['new_password'] ?? '';
if (strlen($new) < 6) {
echo json_encode(['status' => 'error', 'message' => 'Password baru minimal 6 karakter']);
exit;
}
$st = mysqli_prepare($conn, "SELECT password FROM users WHERE id = ?");
mysqli_stmt_bind_param($st, 'i', $user_id);
mysqli_stmt_execute($st);
$row = mysqli_fetch_assoc(mysqli_stmt_get_result($st));
if (!$row || !password_verify($old, $row['password'])) {
echo json_encode(['status' => 'error', 'message' => 'Password lama tidak sesuai']);
exit;
}
$hash = password_hash($new, PASSWORD_DEFAULT);
$st2 = mysqli_prepare($conn, "UPDATE users SET password = ? WHERE id = ?");
mysqli_stmt_bind_param($st2, 'si', $hash, $user_id);
mysqli_stmt_execute($st2);
$_SESSION['user']['password'] = $hash;
echo json_encode(['status' => 'success']);
+25
View File
@@ -0,0 +1,25 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireSuperAdmin();
$type = $_POST['type'] ?? '';
$id = (int)($_POST['id'] ?? 0);
$whitelist = ['ibadah' => 'rumah_ibadah', 'penduduk' => 'penduduk_miskin'];
if (!isset($whitelist[$type]) || $id <= 0) {
echo json_encode(['status'=>'error','message'=>'Request tidak valid.']);
exit;
}
$table = $whitelist[$type];
if (mysqli_query($conn, "DELETE FROM `{$table}` WHERE id = $id")) {
if (mysqli_affected_rows($conn) === 0) {
echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan.']);
} else {
echo json_encode(['status'=>'success']);
}
} else {
echo json_encode(['status'=>'error','message'=>mysqli_error($conn)]);
}
+41
View File
@@ -0,0 +1,41 @@
<?php
include '../koneksi.php';
include '../auth.php';
requireLogin();
$rows = mysqli_fetch_all(mysqli_query($conn,
"SELECT p.*, i.nama AS ibadah_nama,
DATE_FORMAT(p.tanggal_bantuan,'%d/%m/%Y') AS tgl_fmt
FROM penduduk_miskin p
LEFT JOIN rumah_ibadah i ON i.id = p.rumah_ibadah_id
ORDER BY p.kk_nama"), MYSQLI_ASSOC);
$date = date('Y-m-d');
header('Content-Type: text/csv; charset=UTF-8');
header("Content-Disposition: attachment; filename=penduduk_miskin_{$date}.csv");
$out = fopen('php://output', 'w');
// UTF-8 BOM for Excel
fwrite($out, "\xEF\xBB\xBF");
fputcsv($out, ['No','Nama KK','NIK','Alamat','Jumlah Anggota','Rumah Ibadah Terdekat','Jarak (m)','Status Bantuan','Jenis Bantuan','Tanggal Bantuan','Latitude','Longitude']);
$i = 1;
foreach ($rows as $r) {
fputcsv($out, [
$i++,
$r['kk_nama'],
$r['nik'],
$r['alamat'],
$r['jumlah_anggota'],
$r['ibadah_nama'] ?: '-',
$r['jarak_meter'] ? round($r['jarak_meter']) : '-',
$r['status_bantuan'],
$r['jenis_bantuan'],
$r['tgl_fmt'] ?: '-',
$r['lat'],
$r['lng'],
]);
}
fclose($out);
+208
View File
@@ -0,0 +1,208 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
include '../koneksi.php';
include '../auth.php';
requireLogin();
// ── Data ─────────────────────────────────────────────────────────────────────
$allIbadah = mysqli_fetch_all(
mysqli_query($conn, "SELECT * FROM rumah_ibadah ORDER BY nama"),
MYSQLI_ASSOC
);
$allPenduduk = mysqli_fetch_all(
mysqli_query($conn,
"SELECT p.*, i.nama AS ibadah_nama
FROM penduduk_miskin p
LEFT JOIN rumah_ibadah i ON i.id = p.rumah_ibadah_id
ORDER BY p.kk_nama"),
MYSQLI_ASSOC
);
// Anggota count per penduduk
$anggotaCounts = [];
$res = mysqli_query($conn, "SELECT penduduk_id, COUNT(*) as cnt FROM anggota_keluarga GROUP BY penduduk_id");
while ($r = mysqli_fetch_assoc($res)) {
$anggotaCounts[$r['penduduk_id']] = (int)$r['cnt'];
}
// ── Statistik ─────────────────────────────────────────────────────────────────
$total = count($allPenduduk);
$sudah = count(array_filter($allPenduduk, fn($p) => $p['status_bantuan'] === 'sudah_dibantu'));
$belum = count(array_filter($allPenduduk, fn($p) => $p['status_bantuan'] === 'belum_dibantu'));
$menunggu = count(array_filter($allPenduduk, fn($p) => $p['status_bantuan'] === 'menunggu_verifikasi'));
$pctSudah = $total > 0 ? round($sudah / $total * 100, 1) : 0;
$bulan = ['','Januari','Februari','Maret','April','Mei','Juni',
'Juli','Agustus','September','Oktober','November','Desember'];
$tgl = date('j') . ' ' . $bulan[(int)date('n')] . ' ' . date('Y');
$cetakOleh = htmlspecialchars(currentUser()['nama'] ?? 'Admin');
// ── Baris tabel ibadah ────────────────────────────────────────────────────────
$rowsIbadah = '';
foreach ($allIbadah as $i => $ib) {
$binaan = count(array_filter($allPenduduk, fn($p) => $p['rumah_ibadah_id'] == $ib['id']));
$bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb';
$rowsIbadah .= "
<tr style='background:{$bg}'>
<td style='text-align:center'>" . ($i + 1) . "</td>
<td><b>" . htmlspecialchars($ib['nama']) . "</b></td>
<td style='text-align:center'>" . htmlspecialchars($ib['jenis']) . "</td>
<td style='text-align:center'>{$ib['radius']} m</td>
<td style='text-align:center'><b style='color:#1a7a3e'>{$binaan}</b></td>
</tr>";
}
if (!$rowsIbadah) {
$rowsIbadah = "<tr><td colspan='5' style='text-align:center;color:#999'>Belum ada data rumah ibadah.</td></tr>";
}
// ── Baris tabel penduduk ─────────────────────────────────────────────────────
$statusLabel = [
'belum_dibantu' => 'Belum Dibantu',
'sudah_dibantu' => 'Sudah Dibantu',
'menunggu_verifikasi'=> 'Menunggu Verifikasi',
];
$statusColor = [
'belum_dibantu' => '#c0392b',
'sudah_dibantu' => '#1a7a3e',
'menunggu_verifikasi'=> '#856404',
];
$rowsPenduduk = '';
foreach ($allPenduduk as $i => $p) {
$bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb';
$status = $p['status_bantuan'];
$slbl = $statusLabel[$status] ?? $status;
$scol = $statusColor[$status] ?? '#333';
$jml = $p['jumlah_anggota'] ?? ($anggotaCounts[$p['id']] ?? 1);
$jarak = $p['jarak_meter'] ? round($p['jarak_meter']) . ' m' : '-';
$tglB = $p['tanggal_bantuan']
? date('d/m/Y', strtotime($p['tanggal_bantuan'])) : '-';
$rowsPenduduk .= "
<tr style='background:{$bg}'>
<td style='text-align:center'>" . ($i + 1) . "</td>
<td><b>" . htmlspecialchars($p['kk_nama']) . "</b>" .
($p['nik'] ? "<br><span style='font-size:8px;color:#888'>{$p['nik']}</span>" : '') .
"</td>
<td style='font-size:9px'>" . htmlspecialchars($p['alamat'] ?? '-') . "</td>
<td style='text-align:center'>{$jml}</td>
<td style='font-size:9px'>" . htmlspecialchars($p['ibadah_nama'] ?? '-') . "</td>
<td style='text-align:center'>{$jarak}</td>
<td style='text-align:center;color:{$scol};font-weight:bold'>{$slbl}</td>
<td style='font-size:9px'>" . htmlspecialchars($p['jenis_bantuan'] ?? '-') . "</td>
<td style='text-align:center'>{$tglB}</td>
</tr>";
}
if (!$rowsPenduduk) {
$rowsPenduduk = "<tr><td colspan='9' style='text-align:center;color:#999'>Belum ada data penduduk miskin.</td></tr>";
}
// ── HTML Template ─────────────────────────────────────────────────────────────
$html = <<<HTML
<style>
body { font-family: sans-serif; font-size: 10.5px; color: #1a1a1a; }
h1 { font-size: 15px; text-align: center; color: #1e3a5f; margin-bottom: 2px; }
.sub { text-align: center; font-size: 9.5px; color: #555; margin-bottom: 14px; }
.section { font-size: 11px; font-weight: bold; color: #1e3a5f;
border-bottom: 2px solid #1e3a5f; padding-bottom: 3px; margin: 12px 0 7px; }
table { width: 100%; border-collapse: collapse; font-size: 9.5px; }
th { background: #1e3a5f; color: white; padding: 5px 4px; text-align: center; }
td { padding: 4px 5px; border-bottom: 1px solid #e5e7eb; vertical-align: middle; }
.sg { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
.sc { width: 25%; padding: 8px; text-align: center; border: 1px solid #d1d5db; border-radius: 4px; }
.sn { font-size: 20px; font-weight: bold; margin-bottom: 2px; }
.sl { font-size: 8.5px; color: #6b7280; }
.g { color: #1a7a3e; }
.r { color: #c0392b; }
.y { color: #856404; }
</style>
<h1>LAPORAN SEBARAN PENDUDUK MISKIN</h1>
<div class="sub">
Sistem WebGIS GeoMiskin &mdash; Universitas Tanjungpura &nbsp;|&nbsp;
Dicetak: {$tgl} &nbsp;|&nbsp;
Oleh: {$cetakOleh}
</div>
<div class="section">Ringkasan Statistik</div>
<table class="sg">
<tr>
<td class="sc">
<div class="sn">{$total}</div>
<div class="sl">Total KK Miskin</div>
</td>
<td class="sc">
<div class="sn g">{$sudah}</div>
<div class="sl">Sudah Dibantu ({$pctSudah}%)</div>
</td>
<td class="sc">
<div class="sn r">{$belum}</div>
<div class="sl">Belum Dibantu</div>
</td>
<td class="sc">
<div class="sn y">{$menunggu}</div>
<div class="sl">Menunggu Verifikasi</div>
</td>
</tr>
</table>
<div class="section">Data Rumah Ibadah</div>
<table>
<thead>
<tr>
<th width="5%">No</th>
<th width="35%">Nama Rumah Ibadah</th>
<th width="20%">Jenis</th>
<th width="15%">Radius</th>
<th width="25%">KK Binaan</th>
</tr>
</thead>
<tbody>{$rowsIbadah}</tbody>
</table>
<div class="section">Daftar Penduduk Miskin</div>
<table>
<thead>
<tr>
<th width="4%">No</th>
<th width="14%">Nama KK / NIK</th>
<th width="16%">Alamat</th>
<th width="5%">Jiwa</th>
<th width="14%">Rumah Ibadah</th>
<th width="8%">Jarak</th>
<th width="13%">Status</th>
<th width="14%">Jenis Bantuan</th>
<th width="12%">Tgl Bantuan</th>
</tr>
</thead>
<tbody>{$rowsPenduduk}</tbody>
</table>
HTML;
// ── Generate PDF ───────────────────────────────────────────────────────────────
try {
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4-L',
'margin_top' => 12,
'margin_bottom' => 12,
'margin_left' => 10,
'margin_right' => 10,
]);
$mpdf->SetTitle('Laporan Sebaran Penduduk Miskin');
$mpdf->SetAuthor('GeoMiskin WebGIS');
$mpdf->WriteHTML($html);
$fname = 'laporan_penduduk_miskin_' . date('Y-m-d') . '.pdf';
$mpdf->Output($fname, 'D');
} catch (\Mpdf\MpdfException $e) {
http_response_code(500);
echo '<pre style="font-family:monospace;padding:20px">
<b>Gagal membuat PDF:</b> ' . htmlspecialchars($e->getMessage()) . '
Pastikan extension GD aktif di XAMPP:
1. Buka C:\\xampp\\php\\php.ini
2. Cari ;extension=gd &rarr; hapus tanda titik koma
3. Restart Apache di XAMPP Control Panel
</pre>';
}
+40
View File
@@ -0,0 +1,40 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
// Rumah Ibadah
$ibadah = mysqli_fetch_all(
mysqli_query($conn, "SELECT * FROM rumah_ibadah ORDER BY nama"),
MYSQLI_ASSOC
);
// Penduduk miskin with anggota count
$penduduk_raw = mysqli_fetch_all(
mysqli_query($conn,
"SELECT p.*, COUNT(a.id) as jumlah_anggota_db
FROM penduduk_miskin p
LEFT JOIN anggota_keluarga a ON a.penduduk_id = p.id
GROUP BY p.id
ORDER BY p.kk_nama"),
MYSQLI_ASSOC
);
// Attach anggota keluarga to each penduduk
$penduduk = [];
foreach ($penduduk_raw as $p) {
$pid = (int)$p['id'];
$anggota = mysqli_fetch_all(
mysqli_query($conn, "SELECT * FROM anggota_keluarga WHERE penduduk_id = $pid ORDER BY id"),
MYSQLI_ASSOC
);
// Use jumlah_anggota from table if set, else count from anggota table
if (empty($p['jumlah_anggota'])) {
$p['jumlah_anggota'] = count($anggota) ?: 1;
}
$p['anggota'] = $anggota;
$penduduk[] = $p;
}
echo json_encode(['ibadah' => $ibadah, 'penduduk' => $penduduk]);
+29
View File
@@ -0,0 +1,29 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
$nama = mysqli_real_escape_string($conn, trim($_POST['nama'] ?? ''));
$jenis = $_POST['jenis'] ?? 'Masjid';
$kontak = mysqli_real_escape_string($conn, trim($_POST['kontak'] ?? ''));
$alamat = mysqli_real_escape_string($conn, trim($_POST['alamat'] ?? ''));
$lat = (float)($_POST['lat'] ?? 0);
$lng = (float)($_POST['lng'] ?? 0);
$radius = max(50, min(5000, (int)($_POST['radius'] ?? 300)));
$valid_jenis = ['Masjid','Musholla','Gereja Protestan','Gereja Katolik','Pura','Vihara','Klenteng','Lainnya'];
if (!$nama || !$alamat || !$lat || !$lng || !in_array($jenis, $valid_jenis)) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap atau tidak valid.']);
exit;
}
$jenis = mysqli_real_escape_string($conn, $jenis);
$q = "INSERT INTO rumah_ibadah (nama, jenis, kontak, alamat, lat, lng, radius)
VALUES ('$nama','$jenis','$kontak','$alamat',$lat,$lng,$radius)";
if (mysqli_query($conn, $q)) {
echo json_encode(['status' => 'success', 'id' => mysqli_insert_id($conn)]);
} else {
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
}
+92
View File
@@ -0,0 +1,92 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
$kk_nama = esc($conn, $_POST['kk_nama'] ?? '');
$nik = esc($conn, $_POST['nik'] ?? '');
$alamat = esc($conn, $_POST['alamat'] ?? '');
$kecamatan = esc($conn, $_POST['kecamatan'] ?? '');
$kelurahan = esc($conn, $_POST['kelurahan'] ?? '');
$lat = (float)($_POST['lat'] ?? 0);
$lng = (float)($_POST['lng'] ?? 0);
$jumlah = max(1, (int)($_POST['jumlah_anggota'] ?? 1));
$status_allowed = ['belum_dibantu','sudah_dibantu','menunggu_verifikasi'];
$status = in_array($_POST['status_bantuan'] ?? '', $status_allowed) ? $_POST['status_bantuan'] : 'belum_dibantu';
$jenis_bantuan = esc($conn, $_POST['jenis_bantuan'] ?? '');
$tanggal = $_POST['tanggal_bantuan'] ?? '';
$tanggal_sql = ($tanggal && preg_match('/^\d{4}-\d{2}-\d{2}$/', $tanggal)) ? "'$tanggal'" : 'NULL';
if (!$kk_nama || !$alamat || !$lat || !$lng) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap.']);
exit;
}
// --- Find nearest rumah ibadah ---
$ibadah_res = mysqli_query($conn, "SELECT id, lat, lng, radius FROM rumah_ibadah");
$nearest_id = null;
$nearest_dist = PHP_FLOAT_MAX;
while ($row = mysqli_fetch_assoc($ibadah_res)) {
$d = haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
if ($d <= (float)$row['radius'] && $d < $nearest_dist) {
$nearest_dist = $d;
$nearest_id = $row['id'];
}
}
// If no ibadah covers, find nearest anyway
if (!$nearest_id) {
mysqli_data_seek($ibadah_res, 0);
while ($row = mysqli_fetch_assoc($ibadah_res)) {
$d = haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
if ($d < $nearest_dist) { $nearest_dist = $d; $nearest_id = $row['id']; }
}
}
$jarak = $nearest_id ? round($nearest_dist, 2) : 'NULL';
$ibadah_id_sql = $nearest_id ? $nearest_id : 'NULL';
$jarak_sql = is_numeric($jarak) ? $jarak : 'NULL';
// --- Handle file upload ---
$bukti_file = '';
if (!empty($_FILES['bukti_file']['name']) && $_FILES['bukti_file']['error'] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($_FILES['bukti_file']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg','jpeg','png','webp','pdf','doc','docx'];
if (in_array($ext, $allowed) && $_FILES['bukti_file']['size'] <= 5 * 1024 * 1024) {
$dir = __DIR__ . '/../uploads/bukti/';
if (!is_dir($dir)) mkdir($dir, 0755, true);
$fname = 'bukti_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
if (move_uploaded_file($_FILES['bukti_file']['tmp_name'], $dir . $fname)) {
$bukti_file = $fname;
}
}
}
$bukti_sql = $bukti_file ? "'".esc($conn,$bukti_file)."'" : 'NULL';
// --- Insert penduduk ---
$q = "INSERT INTO penduduk_miskin (kk_nama,nik,alamat,kecamatan,kelurahan,lat,lng,jumlah_anggota,rumah_ibadah_id,jarak_meter,status_bantuan,jenis_bantuan,tanggal_bantuan,bukti_file)
VALUES ('$kk_nama','$nik','$alamat','$kecamatan','$kelurahan',$lat,$lng,$jumlah,$ibadah_id_sql,$jarak_sql,'$status','$jenis_bantuan',$tanggal_sql,$bukti_sql)";
if (!mysqli_query($conn, $q)) {
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
exit;
}
$pid = mysqli_insert_id($conn);
// --- Insert anggota keluarga ---
$anggota = json_decode($_POST['anggota'] ?? '[]', true) ?: [];
foreach ($anggota as $a) {
if (empty($a['nama'])) continue;
$an = esc($conn,$a['nama']); $ah = esc($conn,$a['hubungan'] ?? '');
$au = (int)($a['umur'] ?? 0); $ap = esc($conn,$a['pekerjaan'] ?? ''); $ak = esc($conn,$a['keterangan'] ?? '');
mysqli_query($conn, "INSERT INTO anggota_keluarga (penduduk_id,nama,hubungan,umur,pekerjaan,keterangan) VALUES ($pid,'$an','$ah',$au,'$ap','$ak')");
}
echo json_encode(['status' => 'success', 'id' => $pid, 'rumah_ibadah_id' => $nearest_id, 'jarak_meter' => $jarak]);
function haversine($lat1, $lon1, $lat2, $lon2) {
$R = 6371000;
$d1 = deg2rad($lat1 - $lat2); $d2 = deg2rad($lon1 - $lon2);
$a = sin($d1/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($d2/2)**2;
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
}
+30
View File
@@ -0,0 +1,30 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) { echo json_encode(['status'=>'error','message'=>'ID tidak valid']); exit; }
$nama = mysqli_real_escape_string($conn, trim($_POST['nama'] ?? ''));
$jenis = $_POST['jenis'] ?? 'Masjid';
$kontak = mysqli_real_escape_string($conn, trim($_POST['kontak'] ?? ''));
$alamat = mysqli_real_escape_string($conn, trim($_POST['alamat'] ?? ''));
$lat = (float)($_POST['lat'] ?? 0);
$lng = (float)($_POST['lng'] ?? 0);
$radius = max(50, min(5000, (int)($_POST['radius'] ?? 300)));
$valid_jenis = ['Masjid','Musholla','Gereja Protestan','Gereja Katolik','Pura','Vihara','Klenteng','Lainnya'];
if (!$nama || !$alamat || !$lat || !$lng || !in_array($jenis, $valid_jenis)) {
echo json_encode(['status'=>'error','message'=>'Data tidak valid']); exit;
}
$jenis = mysqli_real_escape_string($conn, $jenis);
$q = "UPDATE rumah_ibadah SET nama='$nama',jenis='$jenis',kontak='$kontak',alamat='$alamat',lat=$lat,lng=$lng,radius=$radius WHERE id=$id";
if (mysqli_query($conn, $q)) {
// Recalculate nearest ibadah for affected penduduk
echo json_encode(['status'=>'success']);
} else {
echo json_encode(['status'=>'error','message'=>mysqli_error($conn)]);
}
+65
View File
@@ -0,0 +1,65 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
function haversine($lat1,$lon1,$lat2,$lon2){$R=6371000;$d1=deg2rad($lat1-$lat2);$d2=deg2rad($lon1-$lon2);$a=sin($d1/2)**2+cos(deg2rad($lat1))*cos(deg2rad($lat2))*sin($d2/2)**2;return $R*2*atan2(sqrt($a),sqrt(1-$a));}
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) { echo json_encode(['status'=>'error','message'=>'ID tidak valid']); exit; }
$kk_nama = esc($conn,$_POST['kk_nama'] ?? '');
$nik = esc($conn,$_POST['nik'] ?? '');
$alamat = esc($conn,$_POST['alamat'] ?? '');
$kecamatan = esc($conn,$_POST['kecamatan'] ?? '');
$kelurahan = esc($conn,$_POST['kelurahan'] ?? '');
$lat = (float)($_POST['lat'] ?? 0);
$lng = (float)($_POST['lng'] ?? 0);
$jumlah = max(1,(int)($_POST['jumlah_anggota'] ?? 1));
$valid_st = ['belum_dibantu','sudah_dibantu','menunggu_verifikasi'];
$status = in_array($_POST['status_bantuan']??'',$valid_st) ? $_POST['status_bantuan'] : 'belum_dibantu';
$jenis_bantuan = esc($conn,$_POST['jenis_bantuan'] ?? '');
$tanggal = $_POST['tanggal_bantuan'] ?? '';
$tanggal_sql = ($tanggal && preg_match('/^\d{4}-\d{2}-\d{2}$/',$tanggal)) ? "'$tanggal'" : 'NULL';
if (!$kk_nama||!$alamat||!$lat||!$lng){echo json_encode(['status'=>'error','message'=>'Data tidak lengkap']);exit;}
// Find nearest ibadah
$ibadah_res = mysqli_query($conn,"SELECT id,lat,lng,radius FROM rumah_ibadah");
$nid=null;$ndist=PHP_FLOAT_MAX;
while($row=mysqli_fetch_assoc($ibadah_res)){
$d=haversine($lat,$lng,(float)$row['lat'],(float)$row['lng']);
if($d<=(float)$row['radius']&&$d<$ndist){$ndist=$d;$nid=$row['id'];}
}
if(!$nid){mysqli_data_seek($ibadah_res,0);while($row=mysqli_fetch_assoc($ibadah_res)){$d=haversine($lat,$lng,(float)$row['lat'],(float)$row['lng']);if($d<$ndist){$ndist=$d;$nid=$row['id'];}}}
$jarak=$nid?round($ndist,2):'NULL';$ibadah_sql=$nid?$nid:'NULL';$jarak_sql=is_numeric($jarak)?$jarak:'NULL';
// Handle file upload
$bukti_sql='bukti_file=bukti_file'; // keep existing
if(!empty($_FILES['bukti_file']['name'])&&$_FILES['bukti_file']['error']===UPLOAD_ERR_OK){
$ext=strtolower(pathinfo($_FILES['bukti_file']['name'],PATHINFO_EXTENSION));
$allowed=['jpg','jpeg','png','webp','pdf','doc','docx'];
if(in_array($ext,$allowed)&&$_FILES['bukti_file']['size']<=5*1024*1024){
$dir=__DIR__.'/../uploads/bukti/';
if(!is_dir($dir))mkdir($dir,0755,true);
$fname='bukti_'.time().'_'.bin2hex(random_bytes(4)).'.'.$ext;
if(move_uploaded_file($_FILES['bukti_file']['tmp_name'],$dir.$fname)){
$bukti_sql="bukti_file='".esc($conn,$fname)."'";
}
}
}
$q="UPDATE penduduk_miskin SET kk_nama='$kk_nama',nik='$nik',alamat='$alamat',kecamatan='$kecamatan',kelurahan='$kelurahan',lat=$lat,lng=$lng,jumlah_anggota=$jumlah,rumah_ibadah_id=$ibadah_sql,jarak_meter=$jarak_sql,status_bantuan='$status',jenis_bantuan='$jenis_bantuan',tanggal_bantuan=$tanggal_sql,$bukti_sql WHERE id=$id";
if(!mysqli_query($conn,$q)){echo json_encode(['status'=>'error','message'=>mysqli_error($conn)]);exit;}
// Update anggota
mysqli_query($conn,"DELETE FROM anggota_keluarga WHERE penduduk_id=$id");
$anggota=json_decode($_POST['anggota']??'[]',true)?:[];
foreach($anggota as $a){
if(empty($a['nama']))continue;
$an=esc($conn,$a['nama']);$ah=esc($conn,$a['hubungan']??'');$au=(int)($a['umur']??0);$ap=esc($conn,$a['pekerjaan']??'');$ak=esc($conn,$a['keterangan']??'');
mysqli_query($conn,"INSERT INTO anggota_keluarga (penduduk_id,nama,hubungan,umur,pekerjaan,keterangan) VALUES ($id,'$an','$ah',$au,'$ap','$ak')");
}
echo json_encode(['status'=>'success']);
+101
View File
@@ -0,0 +1,101 @@
<?php
header('Content-Type: application/json');
include '../koneksi.php';
include '../auth.php';
requireLogin();
$action = $_POST['action'] ?? $_GET['action'] ?? 'list';
// ── LIST ────────────────────────────────────────────────────────
if ($action === 'list') {
$rows = mysqli_fetch_all(
mysqli_query($conn, "SELECT id, username, nama, created_at FROM users ORDER BY nama"),
MYSQLI_ASSOC
);
echo json_encode(['status' => 'success', 'data' => $rows]);
exit;
}
// ── ADD ─────────────────────────────────────────────────────────
if ($action === 'add') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$nama = trim($_POST['nama'] ?? '');
if (!$username || !$nama || strlen($password) < 6) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap (password minimal 6 karakter)']);
exit;
}
$role = in_array($_POST['role'] ?? '', ['admin','superadmin']) ? $_POST['role'] : 'admin';
$hash = password_hash($password, PASSWORD_DEFAULT);
$st = mysqli_prepare($conn, "INSERT INTO users (username, password, nama, role) VALUES (?,?,?,?)");
mysqli_stmt_bind_param($st, 'ssss', $username, $hash, $nama, $role);
if (mysqli_stmt_execute($st)) {
echo json_encode(['status' => 'success', 'id' => mysqli_insert_id($conn)]);
} else {
$err = mysqli_error($conn);
$msg = strpos($err, 'Duplicate') !== false ? "Username '{$username}' sudah digunakan" : $err;
echo json_encode(['status' => 'error', 'message' => $msg]);
}
exit;
}
// ── UPDATE ──────────────────────────────────────────────────────
if ($action === 'update') {
$id = (int)($_POST['id'] ?? 0);
$nama = trim($_POST['nama'] ?? '');
$pass = $_POST['password'] ?? '';
$role = in_array($_POST['role'] ?? '', ['admin','superadmin']) ? $_POST['role'] : 'admin';
if ($id <= 0 || !$nama) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak valid']);
exit;
}
if (strlen($pass) >= 6) {
$hash = password_hash($pass, PASSWORD_DEFAULT);
$st = mysqli_prepare($conn, "UPDATE users SET nama=?, role=?, password=? WHERE id=?");
mysqli_stmt_bind_param($st, 'sssi', $nama, $role, $hash, $id);
} else {
$st = mysqli_prepare($conn, "UPDATE users SET nama=?, role=? WHERE id=?");
mysqli_stmt_bind_param($st, 'ssi', $nama, $role, $id);
}
if (mysqli_stmt_execute($st)) {
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
}
exit;
}
// ── DELETE ──────────────────────────────────────────────────────
if ($action === 'delete') {
$id = (int)($_POST['id'] ?? 0);
$my_id = (int)($_SESSION['user']['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
exit;
}
if ($id === $my_id) {
echo json_encode(['status' => 'error', 'message' => 'Tidak dapat menghapus akun sendiri']);
exit;
}
$st = mysqli_prepare($conn, "DELETE FROM users WHERE id = ?");
mysqli_stmt_bind_param($st, 'i', $id);
if (mysqli_stmt_execute($st)) {
if (mysqli_stmt_affected_rows($st) > 0) {
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => 'User tidak ditemukan']);
}
} else {
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
}
exit;
}
echo json_encode(['status' => 'error', 'message' => 'Action tidak dikenal']);