commit 6215b4367a15034a7d9acad1c91a2c3f99d670e1 Author: Athallah Ghathfan Aqila Date: Thu Jun 11 13:31:33 2026 +0700 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 diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..952421e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(node -e \"const { chromium } = require\\('playwright'\\); chromium.launch\\(\\).then\\(b => { console.log\\('chromium ok'\\); b.close\\(\\); }\\).catch\\(e => console.log\\('err:', e.message\\)\\);\")" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a202b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Composer dependencies +uas/vendor/ +vendor/ + +# Uploaded files (user data - tidak perlu di-version) +/uploads/ +uas/uploads/bukti/ +uas/uploads/kemiskinan/ + +# PHP session / cache +*.log +*.cache +/tmp/ + +# XAMPP / OS files +Thumbs.db +.DS_Store +desktop.ini + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Temp test files +uas/test_*.php diff --git a/ambil_data.php b/ambil_data.php new file mode 100644 index 0000000..b502203 --- /dev/null +++ b/ambil_data.php @@ -0,0 +1,4 @@ +[], 'jiwa'=>0]; +foreach ($allKemiskinan as $kk) { + $kkCoverage[$kk['id']] = []; + foreach ($allMasjid as $m) { + $dist = haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']); + if ($dist <= (int)$m['radius_m']) { + $kkCoverage[$kk['id']][] = $m; + $masjidCoverage[$m['id']]['kk'][] = $kk; + $masjidCoverage[$m['id']]['jiwa'] += (int)$kk['jumlah_kk']; + } + } +} + +$totalKK = count($allKemiskinan); +$totalJiwa = array_sum(array_column($allKemiskinan, 'jumlah_kk')); +$countT = 0; $jiwaT = 0; +foreach ($allKemiskinan as $kk) { + if (count($kkCoverage[$kk['id']]) > 0) { $countT++; $jiwaT += (int)$kk['jumlah_kk']; } +} +$countTT = $totalKK - $countT; $jiwaTT = $totalJiwa - $jiwaT; +$pct = $totalKK > 0 ? number_format($countT / $totalKK * 100, 1) : '0.0'; +$pctTT = $totalKK > 0 ? number_format($countTT / $totalKK * 100, 1) : '0.0'; + +$bulan = ['','Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember']; +$tgl = date('j') . ' ' . $bulan[(int)date('n')] . ' ' . date('Y'); + +$rowsMasjid = ''; +foreach ($allMasjid as $i => $m) { + $cov = $masjidCoverage[$m['id']]; + $bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb'; + $rowsMasjid .= " + " . ($i+1) . " + {$m['nama_masjid']} + {$m['radius_m']} + " . count($cov['kk']) . " + {$cov['jiwa']} + "; +} +if (!$rowsMasjid) $rowsMasjid = "Belum ada data masjid."; + +$rowsKK = ''; +foreach ($allKemiskinan as $i => $kk) { + $covers = $kkCoverage[$kk['id']]; + $covered = count($covers) > 0; + $minDist = '-'; + if (count($allMasjid) > 0) { + $dists = array_map(fn($m) => haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']), $allMasjid); + $minDist = min($dists); + } + $statusTxt = $covered ? 'Terjangkau' : 'Tidak Terjangkau'; + $masjidNames = $covered ? implode(', ', array_column($covers, 'nama_masjid')) : '-'; + $bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb'; + $pkhTxt = $kk['pkh'] ? '' : '-'; + $bpntTxt = $kk['bpnt'] ? '' : '-'; + $bltTxt = $kk['blt'] ? '' : '-'; + $rowsKK .= " + " . ($i+1) . " + {$kk['nama_kk']} + {$kk['jumlah_kk']} + {$kk['alamat']} + {$pkhTxt} + {$bpntTxt} + {$bltTxt} + {$statusTxt} + {$masjidNames} + {$minDist} + "; +} +if (!$rowsKK) $rowsKK = "Belum ada data kemiskinan."; + +$html = << + body { font-family: sans-serif; font-size: 11px; color: #222; } + h1 { font-size: 15px; text-align: center; margin-bottom: 2px; color: #1e3a5f; } + .subtitle { text-align: center; font-size: 10px; color: #555; margin-bottom: 14px; } + .section { font-size: 12px; 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: 10px; } + th { background: #1e3a5f; color: white; padding: 5px; text-align: center; } + td { padding: 4px 5px; border-bottom: 1px solid #e5e7eb; vertical-align: top; } + .sg { width: 100%; border-collapse: collapse; margin-bottom: 4px; } + .sc { width: 25%; padding: 7px; text-align: center; border: 1px solid #ddd; } + .sn { font-size: 20px; font-weight: bold; } + .sl { font-size: 9px; color: #666; margin-top: 1px; } + .g { color: #1a7a3e; } .r { color: #c0392b; } + +

Laporan Analisis Kemiskinan & Jangkauan Masjid

+
Sistem WebGIS SIG B — Universitas Tanjungpura  |  Dicetak: {$tgl}
+
Ringkasan Statistik
+ + + + + + + +
{$totalKK}
Total KK Miskin
{$totalJiwa}
Total Jiwa
{$countT} ({$pct}%)
KK Terjangkau • {$jiwaT} jiwa
{$countTT} ({$pctTT}%)
KK Tidak Terjangkau • {$jiwaTT} jiwa
+
Statistik per Masjid
+ + + {$rowsMasjid} +
NoNama MasjidRadius (m)KK TerjangkauJiwa Terjangkau
+
Daftar KK Miskin & Status Jangkauan
+ + + + + + + {$rowsKK} +
NoNama KKJiwaAlamatPKHBPNTBLTStatusDijangkau MasjidJarak Terdekat (m)
+HTML; + +try { + $mpdf = new \Mpdf\Mpdf(['mode'=>'utf-8','format'=>'A4-L','margin_top'=>14,'margin_bottom'=>14,'margin_left'=>12,'margin_right'=>12]); + $mpdf->SetTitle('Laporan Kemiskinan & Jangkauan Masjid'); + $mpdf->WriteHTML($html); + $mpdf->Output('laporan_kemiskinan_' . date('Y-m-d') . '.pdf', 'D'); +} catch (\Mpdf\MpdfException $e) { + http_response_code(500); + echo '
Gagal membuat PDF: ' . htmlspecialchars($e->getMessage()) . '
'; +} diff --git a/get_all_data.php b/get_all_data.php new file mode 100644 index 0000000..10aa668 --- /dev/null +++ b/get_all_data.php @@ -0,0 +1,12 @@ + mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM spbu"), MYSQLI_ASSOC), + 'jalan' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM jalan"), MYSQLI_ASSOC), + 'persil' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM persil"), MYSQLI_ASSOC), + 'wilayah' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM wilayah"), MYSQLI_ASSOC), +]; + +header('Content-Type: application/json'); +echo json_encode($data); diff --git a/hapus.php b/hapus.php new file mode 100644 index 0000000..fc84f9b --- /dev/null +++ b/hapus.php @@ -0,0 +1,16 @@ + 'error', 'message' => 'Request tidak valid.']); + exit; +} + +$q = "DELETE FROM `{$type}` WHERE id = {$id}"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/hapus_spbu.php b/hapus_spbu.php new file mode 100644 index 0000000..a69508a --- /dev/null +++ b/hapus_spbu.php @@ -0,0 +1,6 @@ +'error']); exit; } +if (mysqli_query($conn, "DELETE FROM spbu WHERE id = $id")) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/index.php b/index.php new file mode 100644 index 0000000..0663deb --- /dev/null +++ b/index.php @@ -0,0 +1,212 @@ + + + + + + Portal WebGIS | Universitas Tanjungpura + + + + + + +
+
WebGIS SIG B
+

Portal WebGIS Terpadu

+

Pilih sistem informasi spasial yang ingin Anda akses. Jelajahi data geospasial Kota Pontianak dengan mudah dan interaktif.

+
+ + + + + + + diff --git a/koneksi.php b/koneksi.php new file mode 100644 index 0000000..3a90b58 --- /dev/null +++ b/koneksi.php @@ -0,0 +1,6 @@ + 'error', 'message' => 'Koneksi database gagal: ' . mysqli_connect_error()])); +} +mysqli_set_charset($conn, 'utf8mb4'); diff --git a/simpan_jalan.php b/simpan_jalan.php new file mode 100644 index 0000000..9e4d70c --- /dev/null +++ b/simpan_jalan.php @@ -0,0 +1,10 @@ +'error','message'=>'Data tidak lengkap']); exit; } +$q = "INSERT INTO jalan (nama_jalan, status_jalan, panjang_m, geojson) VALUES ('$n', '$s', $p, '$g')"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/simpan_kemiskinan.php b/simpan_kemiskinan.php new file mode 100644 index 0000000..4c3bfc1 --- /dev/null +++ b/simpan_kemiskinan.php @@ -0,0 +1,30 @@ +'error','message'=>'Data tidak lengkap']); exit; } + +$fotoSql = 'NULL'; +if (!empty($_FILES['foto']['name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) { + $ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION)); + $allowed = ['jpg', 'jpeg', 'png', 'webp']; + if (in_array($ext, $allowed) && $_FILES['foto']['size'] <= 5 * 1024 * 1024) { + $dir = __DIR__ . '/uploads/kemiskinan/'; + if (!is_dir($dir)) mkdir($dir, 0755, true); + $fname = 'kk_' . time() . '_' . rand(1000,9999) . '.' . $ext; + if (move_uploaded_file($_FILES['foto']['tmp_name'], $dir . $fname)) { + $fotoSql = "'" . mysqli_real_escape_string($conn, $fname) . "'"; + } + } +} + +$q = "INSERT INTO kemiskinan (nama_kk, jumlah_kk, alamat, lat, lng, pkh, bpnt, blt, foto) + VALUES ('$n', $j, '$a', $lt, $lg, $pkh, $bpnt, $blt, $fotoSql)"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/simpan_masjid.php b/simpan_masjid.php new file mode 100644 index 0000000..f99bcf5 --- /dev/null +++ b/simpan_masjid.php @@ -0,0 +1,12 @@ +'error','message'=>'Data tidak lengkap']); exit; } +$q = "INSERT INTO masjid (nama_masjid, pic, alamat, radius_m, lat, lng) VALUES ('$n', '$p', '$a', $r, $lt, $lg)"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/simpan_persil.php b/simpan_persil.php new file mode 100644 index 0000000..09f360e --- /dev/null +++ b/simpan_persil.php @@ -0,0 +1,10 @@ +'error','message'=>'Data tidak lengkap']); exit; } +$q = "INSERT INTO persil (nomor_persil, status_milik, luas_m2, geojson) VALUES ('$nomor', '$status', $luas, '$geojson')"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/simpan_spbu.php b/simpan_spbu.php new file mode 100644 index 0000000..74e7080 --- /dev/null +++ b/simpan_spbu.php @@ -0,0 +1,11 @@ +'error','message'=>'Data tidak lengkap']); exit; } +$q = "INSERT INTO spbu (nama, nomor, status_24jam, lat, lng) VALUES ('$n', '$no', '$s', $lt, $lg)"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/simpan_wilayah.php b/simpan_wilayah.php new file mode 100644 index 0000000..08f9a7e --- /dev/null +++ b/simpan_wilayah.php @@ -0,0 +1,9 @@ +'error','message'=>'Data tidak lengkap']); exit; } +$q = "INSERT INTO wilayah (nama, tipe, geojson) VALUES ('$nama', '$tipe', '$geojson')"; +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/tugas.php b/tugas.php new file mode 100644 index 0000000..8307576 --- /dev/null +++ b/tugas.php @@ -0,0 +1,744 @@ + + + + + + + WebGIS Tugas SIG B | Universitas Tanjungpura + + + + + + + + +
+ + WebGIS SIG B — Universitas Tanjungpura + Athallah Ghathfan Aqila | D1041231060 | Dr. Ir. Yus Sholva + + Portal + + +
+ +
+ + +
+
+ +
+ Klik peta untuk pilih lokasi + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + diff --git a/uas/SKPL.docx b/uas/SKPL.docx new file mode 100644 index 0000000..b966e7e Binary files /dev/null and b/uas/SKPL.docx differ diff --git a/uas/SKPL.md b/uas/SKPL.md new file mode 100644 index 0000000..db66cde --- /dev/null +++ b/uas/SKPL.md @@ -0,0 +1,586 @@ +# Spesifikasi Kebutuhan Perangkat Lunak + +# WebGIS Partisipasi Rumah Ibadah (Masjid) untuk Pengentasan Kemiskinan + +Dosen Pengampu: + +Dr. Ir. Yus Sholva, S.T., M.T., C.Pro. + +197410192003121002 + +Disusun oleh: + +| | | +|---|---| +| **Nama** | Athallah Ghathfan Aqila | +| **NIM** | D1041231060 | +| **Mata Kuliah** | Sistem Informasi Geografis B | + +**PROGRAM STUDI INFORMATIKA** + +**FAKULTAS TEKNIK** + +**UNIVERSITAS TANJUNGPURA** + +**2025/2026** + +--- + +## DAFTAR ISI + +| | | +|---|---| +| **PENDAHULUAN** | 3 | +| 1.1 Tujuan Dokumen | 3 | +| 1.2 Ruang Lingkup | 3 | +| 1.3 Definisi, Akronim, dan Singkatan | 5 | +| 1.4 Referensi | 7 | +| **DESKRIPSI SISTEM** | 8 | +| 2.1 Perspektif Produk | 8 | +| 2.2 Fungsi Utama Sistem | 8 | +| 2.3 Kelas Pengguna dan Karakteristik | 10 | +| 2.4 Batasan dan Kendala Sistem | 11 | +| **KEBUTUHAN FUNGSIONAL** | 12 | +| 3.1 Daftar Use Case | 12 | +| 3.2 Detail Use Case | 13 | +| **KEBUTUHAN NON-FUNGSIONAL** | 28 | +| 4.1 Spesifikasi Performa | 28 | +| 4.2 Spesifikasi Keamanan | 29 | +| 4.3 Spesifikasi Kemudahan Penggunaan | 30 | +| 4.4 Spesifikasi Reliabilitas | 30 | +| 4.5 Spesifikasi Portabilitas | 31 | +| **ANTARMUKA EKSTERNAL** | 32 | +| 5.1 Antarmuka Pengguna | 32 | +| 5.2 Antarmuka Perangkat Keras | 33 | +| 5.3 Antarmuka Perangkat Lunak | 34 | +| 5.4 Antarmuka Komunikasi | 35 | +| **DEFINISI FUNGSI DAN KEBUTUHAN PENGGUNA** | 36 | +| 6.1 Kebutuhan Pengguna | 36 | +| 6.2 Detail Fungsionalitas Spesifik | 37 | +| 6.3 Analisis Alur Kerja Pengentasan Kemiskinan | 42 | +| **KESIMPULAN** | 43 | +| **LAMPIRAN** | 44 | + +--- + +## PENDAHULUAN + +### 1.1 Tujuan Dokumen + +Dokumen Spesifikasi Kebutuhan Perangkat Lunak (SKPL) ini mendefinisikan secara menyeluruh kebutuhan fungsional dan non-fungsional dalam pengembangan sistem "WebGIS Partisipasi Rumah Ibadah (Masjid) untuk Pengentasan Kemiskinan". Dokumen ini disusun oleh Athallah Ghathfan Aqila (D1041231060) sebagai panduan bagi pihak-pihak berikut: + +- **Tim Pengembang (Mahasiswa):** Sebagai acuan teknis implementasi fitur analisis spasial *multilayer* (Point, Line, Polygon) menggunakan PHP Native, Leaflet.js, dan database MySQL. +- **Pemerintah/Dinas Sosial:** Sebagai instrumen verifikasi akurasi sistem dalam mengidentifikasi titik kemiskinan yang berada di luar jangkauan bantuan komunitas (*blind spot*) melalui visualisasi data. +- **Pengelola Rumah Ibadah (Masjid):** Sebagai panduan operasional untuk memetakan warga binaan dan menentukan radius jangkauan layanan sosial secara mandiri per masjid. +- **Tim Penguji:** Sebagai landasan pembuatan skenario pengujian teknis, khususnya pada akurasi perhitungan jarak menggunakan rumus *geodesic* dan perhitungan luas area tanah. + +### 1.2 Ruang Lingkup + +Sistem yang dikembangkan adalah platform WebGIS dinamis yang mengintegrasikan data infrastruktur perkotaan dengan data kependudukan miskin di Kota Pontianak. Sistem ini berfungsi sebagai mesin analisis spasial untuk menentukan distribusi bantuan sosial berbasis jarak. + +**Sistem ini mencakup:** + +- **Visualisasi Multilayer Interaktif:** Menampilkan peta dasar (*Base Map*) yang memuat berbagai jenis geometri: + - *Point*: Lokasi SPBU, lokasi Masjid, dan lokasi rumah penduduk miskin. + - *Line*: Jaringan jalan dengan fitur penghitung panjang otomatis (m). + - *Polygon*: Parsil atau kavling tanah dengan fitur penghitung luas otomatis (m²), serta Batas Wilayah Administratif (RT/RW/Kelurahan/Kecamatan). +- **Manajemen Radius Dinamis per Masjid (Buffer):** Fitur khusus pada setiap titik Masjid yang memungkinkan pengguna mengatur variabel "Radius Deteksi" (100m hingga 5000m) menggunakan *slider* secara *real-time*, disertai tombol *toggle* visibilitas radius per masjid secara individual. +- **Analisis Spasial Real-Time (Intersect Analysis):** Algoritma internal yang mengevaluasi jarak koordinat rumah penduduk miskin terhadap radius layanan Masjid menggunakan formula Haversine. +- **Analisis Batas Wilayah:** Algoritma *point-in-polygon* yang mengidentifikasi KK yang berada dalam setiap wilayah administratif, disertai statistik *breakdown* per wilayah. +- **Indikator Visual Otomatis:** Perubahan warna marker penduduk miskin secara otomatis: + - *Warna Merah*: Menandakan penduduk berada di dalam jangkauan masjid (Tercakup). + - *Warna Abu-abu*: Menandakan penduduk berada di luar jangkauan masjid (*Blind Spot*). +- **Filter dan Pencarian Data KK:** Fitur pencarian nama KK secara *real-time* dan filter berdasarkan jenis bantuan yang diterima (PKH, BPNT, BLT) yang mempengaruhi tampilan panel dan peta secara bersamaan. +- **Panel Statistik Real-Time:** Menampilkan ringkasan cakupan kemiskinan (total KK, jiwa, terjangkau, *blind spot*) beserta *breakdown* per masjid dan per wilayah administratif. +- **Visualisasi Heatmap:** Lapisan kepadatan titik kemiskinan menggunakan gradien warna (kuning–oranye–merah) untuk mengidentifikasi konsentrasi penduduk miskin. +- **Ekspor Data:** Menghasilkan laporan dalam format CSV (*client-side* via Blob API) dan PDF (*server-side* menggunakan mPDF). + +**Sistem ini TIDAK mencakup:** + +- Sistem manajemen transaksi keuangan atau penyaluran dana (E-Wallet/Payment Gateway). +- Pelacakan lokasi penduduk miskin secara *real-time* melalui pergerakan GPS (data koordinat bersifat statis berdasarkan lokasi rumah). +- Verifikasi dokumen legalitas tanah atau SPBU (sistem hanya mengelola representasi grafis spasial). +- Sistem autentikasi multi-pengguna; aplikasi dirancang untuk penggunaan lokal/intranet. + +### 1.3 Definisi, Akronim, dan Singkatan + +| **Istilah** | **Definisi** | +|---|---| +| **SKPL / SRS** | Spesifikasi Kebutuhan Perangkat Lunak — dokumen panduan teknis pengembangan sistem. | +| **WebGIS** | Sistem informasi berbasis web yang mengolah data bereferensi koordinat geografis. | +| **Radius Dinamis** | Jarak jangkauan variabel yang dapat disesuaikan oleh pengguna untuk memetakan wilayah pelayanan sosial tiap masjid secara individual. | +| **Blind Spot** | Area kemiskinan yang tidak tersentuh oleh radius layanan masjid manapun, sehingga memerlukan intervensi langsung pemerintah. | +| **Marker** | Simbol visual (ikon) pada peta yang mewakili objek Point di lapangan. | +| **AJAX / Fetch API** | Metode pertukaran data antara JavaScript dan PHP secara asinkron untuk pembaruan data tanpa *refresh* halaman. | +| **GeometryUtil** | Plugin tambahan Leaflet untuk melakukan perhitungan matematis spasial seperti luas area dan jarak *geodesic*. | +| **Haversine** | Formula matematika untuk menghitung jarak terpendek antara dua titik koordinat di permukaan bumi (jarak *geodesic*). | +| **Point-in-Polygon** | Algoritma *ray casting* untuk menentukan apakah sebuah titik koordinat berada di dalam area polygon. | +| **GeoJSON** | Format data berbasis JSON standar internasional untuk merepresentasikan objek geografis (titik, garis, poligon). | +| **KK** | Kepala Keluarga — unit data terkecil dalam pendataan kemiskinan. | +| **PKH** | Program Keluarga Harapan — program bantuan sosial bersyarat dari pemerintah. | +| **BPNT** | Bantuan Pangan Non-Tunai — program subsidi pangan dari pemerintah. | +| **BLT** | Bantuan Langsung Tunai — program bantuan uang tunai dari pemerintah. | +| **RT / RW** | Rukun Tetangga / Rukun Warga — unit administratif terkecil di Indonesia. | +| **Toggle** | Fitur tombol untuk mengaktifkan atau menonaktifkan suatu kondisi secara bergantian tanpa me-*refresh* halaman. | +| **Heatmap** | Visualisasi peta kepadatan menggunakan gradien warna yang menunjukkan intensitas distribusi data spasial. | +| **Layer Control** | Komponen antarmuka Leaflet untuk mengaktifkan/menonaktifkan lapisan data tertentu pada peta. | +| **mPDF** | Library PHP untuk menghasilkan dokumen PDF dari konten HTML di sisi server. | +| **Material Design** | Panduan desain antarmuka dari Google yang menekankan konsistensi visual, tipografi Roboto, dan ikon Material Symbols. | + +### 1.4 Referensi + +- **Standar Dokumentasi (IEEE Std 830-1998):** Mengikuti panduan IEEE *Recommended Practice for Software Requirements Specifications* guna memastikan struktur dokumen memenuhi standar profesional internasional dalam mendefinisikan kebutuhan perangkat lunak. +- **Dokumentasi Resmi Leaflet.js (v1.9.4):** Digunakan sebagai referensi utama dalam implementasi mesin pemetaan, manipulasi layer spasial, penanganan marker, dan integrasi *tile layer* dari OpenStreetMap secara asinkron. +- **Dokumentasi Leaflet.GeometryUtil (v0.10.3):** Menjadi rujukan teknis khusus untuk melakukan kalkulasi matematis tingkat lanjut pada peta, seperti perhitungan jarak *geodesic* (Haversine) antara titik masjid dan penduduk, serta penghitungan luas area (m²) pada fitur parsil tanah secara presisi. +- **Dokumentasi Leaflet.heat (v0.2.0):** Referensi implementasi lapisan heatmap kepadatan kemiskinan. +- **Dokumentasi PHP Native & MySQL:** Referensi teknis mengenai arsitektur *server-side* untuk pengolahan data relasional, manajemen koneksi basis data, dan pertukaran data dalam format JSON melalui Fetch API. +- **Dokumentasi mPDF (v8.3):** Referensi implementasi ekspor laporan kemiskinan dalam format PDF (*server-side*) dengan konten tabel terstruktur format A4 *Landscape*. +- **Standar Geospasial Indonesia (BIG):** Pedoman teknis pemetaan skala mikro dan aturan simbolisasi peta untuk lingkungan permukiman yang berlaku di Indonesia. +- **Kebijakan Strategis Pengentasan Kemiskinan Kota Pontianak:** Landasan operasional mengenai mekanisme partisipasi masyarakat dan peran rumah ibadah dalam penyaluran bantuan sosial bagi masyarakat berpenghasilan rendah di wilayah Pontianak. + +--- + +## DESKRIPSI SISTEM + +### 2.1 Perspektif Produk + +Sistem WebGIS Partisipasi Rumah Ibadah ini dikembangkan sebagai aplikasi *standalone* (mandiri) yang mengintegrasikan data infrastruktur perkotaan dengan data kependudukan sosial. Produk ini bekerja dengan menggabungkan basis data relasional MySQL sebagai tempat penyimpanan atribut geografis dan library Leaflet.js sebagai mesin visualisasi interaktif pada sisi klien (*client-side*). + +Berbeda dengan sistem pemetaan statis, produk ini memiliki perspektif dinamis di mana analisis spasial (perhitungan jarak dan jangkauan) dilakukan secara asinkron di sisi klien tanpa memerlukan *refresh* halaman. Sistem memproses data koordinat Masjid dan Penduduk Miskin untuk menghasilkan indikator visual otomatis. Integrasi fitur manajemen Jalan (Line), Tanah (Polygon), dan Batas Wilayah Administratif (Polygon) dalam satu *interface* memberikan perspektif wilayah yang lebih luas bagi pengambil keputusan. Panel statistik *real-time* yang disertai fitur filter, pencarian, dan ekspor data menjadikan sistem ini sebagai alat analisis kemiskinan yang komprehensif. + +### 2.2 Fungsi Utama Sistem + +Sistem ini menyediakan fungsionalitas utama yang mencakup manajemen data spasial dan analisis jangkauan sebagai berikut: + +| **No** | **Fungsi Utama** | **Deskripsi Singkat** | +|---|---|---| +| **F01** | Manajemen Data Masjid | Mengelola titik lokasi masjid beserta informasi Nama Masjid, Penanggung Jawab (PIC), dan Alamat. | +| **F02** | Pengaturan Radius Dinamis per Masjid | Fitur *slider* interaktif untuk menentukan/mengubah jangkauan pelayanan sosial tiap masjid (100m–5000m) secara *real-time*, disertai *toggle* visibilitas radius per masjid secara individual. | +| **F03** | Manajemen Data Penduduk Miskin | Mengelola data penduduk miskin mencakup Nama KK, Jumlah Anggota Keluarga, Alamat, jenis bantuan yang diterima (PKH/BPNT/BLT), dan foto KK. | +| **F04** | Manajemen Data SPBU | Mengelola titik lokasi SPBU beserta informasi nama, nomor, dan status operasional 24 jam. | +| **F05** | Manajemen Infrastruktur Jalan & Tanah | Menambah dan mengelola jaringan jalan (Line) disertai hitung panjang otomatis (m), serta parsil tanah (Polygon) disertai hitung luas otomatis (m²). | +| **F06** | Manajemen Batas Wilayah Administratif | Menggambar dan mengelola polygon batas wilayah (RT/RW/Kelurahan/Kecamatan) untuk analisis distribusi kemiskinan berbasis wilayah. | +| **F07** | Visualisasi Multilayer | Menampilkan peta dasar (OpenStreetMap) dengan fitur *Layer Control* untuk memfilter tampilan berbagai kategori data, disertai warna ikon berbeda per layer. | +| **F08** | Analisis Spasial Real-Time | Sistem secara otomatis menghitung jarak *geodesic* antara titik penduduk terhadap radius masjid aktif setiap kali data atau parameter berubah. | +| **F09** | Indikator Visual Marker | Mengubah warna marker penduduk: Merah (jika tercakup radius masjid aktif) dan Abu-abu (jika berada di luar semua radius/*blind spot*). | +| **F10** | Filter dan Pencarian KK | Fitur pencarian nama KK secara *real-time* dan filter berdasarkan jenis bantuan (PKH, BPNT, BLT) dengan hasil yang mempengaruhi panel dan peta secara bersamaan. | +| **F11** | Panel Statistik Real-Time | Menampilkan ringkasan cakupan kemiskinan (total KK, jiwa, terjangkau, *blind spot*) beserta *breakdown* per masjid dan per wilayah administratif. | +| **F12** | Heatmap Kemiskinan | Menampilkan lapisan kepadatan titik kemiskinan menggunakan gradien warna (kuning–oranye–merah) untuk mengidentifikasi konsentrasi kemiskinan secara visual. | +| **F13** | Ekspor CSV | Menghasilkan file CSV data kemiskinan (mencerminkan filter aktif) secara *client-side* dengan encoding UTF-8 BOM untuk kompatibilitas Microsoft Excel. | +| **F14** | Ekspor PDF | Menghasilkan laporan PDF A4 *Landscape* berisi statistik dan tabel KK lengkap menggunakan mPDF di sisi server. | +| **F15** | Informasi Detail (Popup) | Menampilkan jendela informasi detail dan alat kontrol modifikasi data saat sebuah objek di peta diklik (Edit, Hapus, Update Radius, Toggle). | + +### 2.3 Kelas Pengguna dan Karakteristik + +| **Kelas Pengguna** | **Karakteristik** | **Hak Akses** | +|---|---|---| +| **Admin / Operator (Dinsos/PIC)** | Pengelola sistem yang memiliki otoritas terhadap validitas data sosial dan infrastruktur. | Melakukan operasi CRUD penuh pada semua layer, mengelola batas wilayah, menentukan/mengupdate radius masjid, mengekspor laporan. | +| **Pengurus Masjid (PIC)** | Pengurus masjid yang bertanggung jawab atas data warga binaan di sekitar masjid. | Mengelola data masjid dan radius per masjid, menambah data penduduk, melihat statistik jangkauan. | +| **Pengunjung / Masyarakat** | Pengguna umum atau *stakeholder* yang memerlukan informasi sebaran kemiskinan. | Melihat peta sebaran, memantau transparansi jangkauan bantuan, dan mengidentifikasi area *blind spot* secara visual. | + +### 2.4 Batasan dan Kendala Sistem + +Dalam pengembangan dan implementasinya, sistem ini memiliki batasan teknis dan operasional sebagai berikut: + +- **Teknologi Pengembangan:** Wajib menggunakan *stack* teknologi PHP Native (tanpa framework) dan MySQL untuk *backend*, serta Vanilla JavaScript dengan library Leaflet.js untuk mesin pemetaan spasial. +- **Konektivitas Global:** Sistem memerlukan koneksi internet aktif untuk memuat ubin peta (*tiles*) dari server OpenStreetMap (OSM). +- **Akurasi Data Spasial:** Ketepatan analisis radius sepenuhnya bergantung pada akurasi input koordinat *Latitude* dan *Longitude*. Kesalahan input koordinat akan menyebabkan kesalahan klasifikasi warna marker. +- **Geocoding Manual:** Sistem tidak menyediakan fitur konversi otomatis dari alamat teks ke koordinat (Geocoding); koordinat harus ditentukan secara manual melalui *plotting* pada peta. +- **Integritas Data Kemiskinan:** Sistem hanya bertugas sebagai alat visualisasi dan analisis; validasi status ekonomi penduduk dilakukan oleh otoritas terkait sebelum diinput ke sistem. +- **Lingkungan Operasional:** Aplikasi dioptimasi untuk berjalan pada server lokal menggunakan XAMPP/Laragon atau *shared hosting* dengan dukungan PHP minimal versi 7.4 dan ekstensi MySQLi aktif. +- **Keamanan Spasial:** Perlindungan data koordinat terbatas pada level aplikasi; sistem dirancang untuk lingkungan intranet. Implementasi HTTPS dan autentikasi disarankan sebelum *deployment* ke jaringan publik. +- **Kapasitas Data:** Sistem diuji dan dioptimasi untuk mengelola hingga 500 KK dan 20 Masjid secara bersamaan tanpa penurunan performa yang signifikan. + +--- + +## KEBUTUHAN FUNGSIONAL + +### 3.1 Daftar Use Case + +| **UC ID** | **Nama Use Case** | **Aktor** | **Prioritas** | +|---|---|---|---| +| UC-01 | Mengelola Data Infrastruktur Spasial | Admin | Must Have | +| UC-02 | Mengelola Data SPBU | Admin | Must Have | +| UC-03 | Mengelola Data Masjid dan Radius | Admin / PIC Masjid | Must Have | +| UC-04 | Mengelola Data Penduduk Miskin | Admin / PIC Masjid | Must Have | +| UC-05 | Mengelola Batas Wilayah Administratif | Admin | Must Have | +| UC-06 | Visualisasi Peta Multilayer | Semua Pengguna | Must Have | +| UC-07 | Analisis Intersect & Warna Otomatis | Sistem | Must Have | +| UC-08 | Modifikasi Radius Dinamis per Masjid | Admin / PIC Masjid | Must Have | +| UC-09 | Filter dan Pencarian Data Kemiskinan | Admin / PIC Masjid | Should Have | +| UC-10 | Ekspor Data Laporan | Admin | Should Have | + +### 3.2 Detail Use Case + +**UC-01 Mengelola Data Infrastruktur Spasial** + +| | | +|---|---| +| **Deskripsi** | Admin menambahkan data Line (Jalan) dan Polygon (Parsil Tanah) untuk memberikan konteks tata ruang pada peta kemiskinan. | +| **Aktor** | Admin | +| **Precondition** | Admin telah masuk ke sistem dan memilih mode (Jalan atau Tanah) di toolbar. | +| **Main Flow** | 1. Admin memilih mode "Jalan" atau "Tanah" dari toolbar. 2. Admin melakukan klik berkali-kali pada peta untuk membentuk jalur (Line) atau bidang (Polygon). 3. Sistem secara otomatis menghitung panjang jalan (m) atau luas tanah (m²) secara *real-time*. 4. Admin mengisi atribut terkait: Nama Jalan dan Status Jalan (Nasional/Provinsi/Kabupaten), atau Nomor Persil dan Status Milik (SHM/HGB/HGU/HP). 5. Admin menekan tombol "SIMPAN". 6. Sistem mengonversi koordinat ke format GeoJSON dan menyimpannya ke database. | +| **Alternative Flow** | 3a. Jika Polygon belum membentuk minimal 3 titik (segitiga), tombol simpan tidak dapat diklik. | +| **Postcondition** | Infrastruktur baru tergambar di peta dan data tersimpan di database (tabel *jalan* atau *persil*). | +| **Prioritas** | Must Have | + +**UC-02 Mengelola Data SPBU** + +| | | +|---|---| +| **Deskripsi** | Admin menambahkan dan mengelola titik lokasi SPBU beserta informasi operasional. | +| **Aktor** | Admin | +| **Precondition** | Admin telah memilih mode "SPBU" di toolbar. | +| **Main Flow** | 1. Admin memilih mode "SPBU" dari toolbar. 2. Admin melakukan klik (*plotting*) pada koordinat lokasi SPBU di peta. 3. *Popup* formulir input muncul. 4. Admin mengisi Nama SPBU, Nomor SPBU, dan Status 24 Jam (Ya/Tidak). 5. Admin menekan tombol "SIMPAN". 6. Sistem menyimpan data SPBU ke database. | +| **Alternative Flow** | — | +| **Postcondition** | Marker SPBU muncul di peta dengan ikon berbeda berdasarkan status 24 jam: biru (SPBU 24 jam) dan oranye (SPBU biasa). | +| **Prioritas** | Must Have | + +**UC-03 Mengelola Data Masjid dan Radius** + +| | | +|---|---| +| **Deskripsi** | Admin atau PIC menambahkan titik lokasi Masjid pada peta beserta informasi identitas dan radius layanan *default*. | +| **Aktor** | Admin / PIC Masjid | +| **Precondition** | Admin telah memilih mode "Masjid & Radius" di toolbar. | +| **Main Flow** | 1. Admin memilih menu "Masjid & Radius" dari toolbar. 2. Admin melakukan *plotting* satu titik (*marker*) pada peta. 3. Sistem menggambar lingkaran (*circle*) sementara dengan radius *default* 500m. 4. Admin mengisi Nama Masjid, PIC, dan Alamat Masjid. 5. Admin menggeser *slider* radius (100m–5000m) untuk menyesuaikan besaran lingkaran secara *real-time*. 6. Admin menekan tombol "SIMPAN MASJID". 7. Sistem menyimpan data titik koordinat dan nilai radius ke MySQL. | +| **Alternative Flow** | — | +| **Postcondition** | Titik Masjid muncul di peta lengkap dengan area lingkaran (*buffer*) hijau yang transparan dan tombol *toggle* radius. | +| **Prioritas** | Must Have | + +**UC-04 Mengelola Data Penduduk Miskin** + +| | | +|---|---| +| **Deskripsi** | Admin menambahkan data warga yang tergolong miskin ke dalam sistem untuk dianalisis posisinya terhadap jangkauan masjid. | +| **Aktor** | Admin / PIC Masjid | +| **Precondition** | Admin telah memilih mode "Kemiskinan" di toolbar. | +| **Main Flow** | 1. Admin memilih menu "Kemiskinan" dari toolbar. 2. Admin melakukan klik (*plotting*) pada koordinat rumah warga di peta. 3. *Popup* formulir input muncul. 4. Admin mengisi Nama Kepala Keluarga, Jumlah Anggota (Jiwa), dan Alamat Lengkap. 5. Admin mencentang jenis bantuan yang diterima (PKH, BPNT, BLT). 6. Opsional: Admin mengunggah foto KK (format JPG/PNG/WebP, maks. 5 MB). 7. Admin menekan tombol "SIMPAN". 8. Sistem menyimpan koordinat dan data demografi ke MySQL. | +| **Alternative Flow** | 6a. Jika foto tidak diunggah, data tetap tersimpan tanpa foto. 6b. Jika format atau ukuran foto tidak valid, sistem menampilkan pesan error dan menolak file. | +| **Postcondition** | Titik Penduduk muncul di peta. Warnanya bergantung pada hasil analisis radius (UC-07). | +| **Prioritas** | Must Have | + +**UC-05 Mengelola Batas Wilayah Administratif** + +| | | +|---|---| +| **Deskripsi** | Admin menggambar polygon batas wilayah administratif (RT/RW/Kelurahan/Kecamatan) untuk analisis distribusi kemiskinan per wilayah. | +| **Aktor** | Admin | +| **Precondition** | Admin telah memilih mode "Batas Wilayah" di toolbar. | +| **Main Flow** | 1. Admin memilih mode "Batas Wilayah" dari toolbar. 2. Admin melakukan klik berkali-kali pada peta untuk membentuk polygon batas wilayah. 3. Sistem secara otomatis menampilkan *preview* polygon yang sedang digambar. 4. Setelah selesai, Admin mengisi Nama Wilayah dan Tipe Wilayah (RT/RW/Kelurahan/Kecamatan). 5. Admin menekan tombol "SIMPAN WILAYAH". 6. Sistem mengonversi koordinat ke format GeoJSON dan menyimpan ke tabel *wilayah* di database. | +| **Alternative Flow** | 3a. Jika Polygon belum membentuk minimal 3 titik, formulir simpan tidak ditampilkan. | +| **Postcondition** | Polygon batas wilayah tergambar di peta dengan warna berbeda per tipe wilayah. Klik pada polygon menampilkan statistik KK di dalam wilayah tersebut di panel. | +| **Prioritas** | Must Have | + +**UC-06 Visualisasi Peta Multilayer** + +| | | +|---|---| +| **Deskripsi** | Pengguna melihat representasi visual dari seluruh data geografis dengan kontrol visibilitas (*Layer Control*). | +| **Aktor** | Semua Pengguna | +| **Precondition** | Halaman WebGIS dimuat dan browser memiliki koneksi internet untuk menarik *tiles* dari server OSM. | +| **Main Flow** | 1. Pengguna membuka halaman WebGIS. 2. Sistem memuat library Leaflet.js dan ubin peta dasar dari OpenStreetMap. 3. Sistem menggunakan Fetch API untuk memuat semua data dari PHP (`get_all_data.php`). 4. Sistem mengelompokkan data ke dalam L.layerGroup (SPBU 24 Jam, SPBU Biasa, Jalan, Persil, Kemiskinan, Masjid, Wilayah). 5. Sistem menjalankan analisis *coverage* otomatis dan merender semua marker dengan warna yang sesuai. 6. Pengguna menggunakan *Layer Control* di pojok kanan atas untuk mengaktifkan/menonaktifkan layer tertentu. | +| **Alternative Flow** | 3a. Jika database kosong, peta hanya menampilkan basis jalan (OSM) tanpa marker atau polygon. | +| **Postcondition** | Pengguna dapat menganalisis data spesifik (misal: hanya melihat Masjid dan Penduduk) dengan mudah melalui kontrol layer. | +| **Prioritas** | Must Have | + +**UC-07 Analisis Intersect & Warna Otomatis (Core Feature)** + +| | | +|---|---| +| **Deskripsi** | Sistem melakukan kalkulasi jarak secara otomatis di latar belakang untuk menentukan status jangkauan bantuan setiap KK. | +| **Aktor** | Sistem | +| **Precondition** | Data Masjid dan Data Penduduk Miskin berhasil dimuat dari database. | +| **Main Flow** | 1. Sistem melakukan iterasi pada seluruh titik Penduduk Miskin. 2. Untuk setiap penduduk, sistem menghitung jarak (*distanceTo*) ke seluruh titik Masjid yang status radius-nya aktif (*toggle* ON). 3. Jika jarak penduduk ke salah satu masjid aktif ≤ radius masjid tersebut, sistem mengklasifikasikan sebagai "Tercakup". 4. Sistem menerapkan ikon Merah untuk penduduk Tercakup dan ikon Abu-abu untuk penduduk *Blind Spot*. 5. Sistem memperbarui panel statistik (total KK terjangkau, total jiwa terjangkau, *breakdown* per masjid). 6. Sistem merender marker ke peta. | +| **Alternative Flow** | 2a. Jika radius masjid dimatikan (*toggle off*), masjid tersebut tidak ikut dalam perhitungan analisis. | +| **Postcondition** | Peta menampilkan dikotomi warna yang jelas antara penduduk yang sudah dijangkau masjid dan yang menjadi target bantuan pemerintah. Panel statistik diperbarui secara *real-time*. | +| **Prioritas** | Must Have | + +**UC-08 Modifikasi Radius Dinamis per Masjid** + +| | | +|---|---| +| **Deskripsi** | PIC Masjid mengubah radius jangkauan layanan atau menonaktifkan/mengaktifkan radius pada masjid tertentu secara individual. | +| **Aktor** | Admin / PIC Masjid | +| **Precondition** | Marker Masjid sudah dirender di peta. | +| **Main Flow** | **Alur A — Ubah Radius:** 1. Pengguna mengklik marker Masjid di peta. 2. *Popup* muncul menampilkan informasi masjid, *slider* radius, dan tombol *toggle*. 3. Pengguna menggeser *slider*; sistem secara instan mengubah ukuran lingkaran di peta. 4. Pengguna menekan tombol "UPDATE RADIUS". 5. Sistem mengirim ID Masjid dan nilai radius baru via Fetch API ke PHP. 6. Analisis *coverage* dijalankan ulang secara otomatis. **Alur B — Toggle Radius:** 1. Pengguna mengklik tombol "Aktif/Nonaktif" pada *popup* masjid. 2. Lingkaran radius masjid tersebut ditampilkan atau disembunyikan. 3. Analisis *coverage* dijalankan ulang tanpa menutup *popup*. | +| **Alternative Flow** | — | +| **Postcondition** | Radius masjid di database diperbarui (Alur A). Status cakupan warna penduduk di sekitarnya berubah mengikuti perubahan radius atau status *toggle* (Alur A & B). | +| **Prioritas** | Must Have | + +**UC-09 Filter dan Pencarian Data Kemiskinan** + +| | | +|---|---| +| **Deskripsi** | Pengguna melakukan pencarian nama KK atau memfilter data berdasarkan jenis bantuan yang diterima (PKH/BPNT/BLT) melalui panel statistik. | +| **Aktor** | Admin / PIC Masjid | +| **Precondition** | Data kemiskinan telah dimuat dan panel statistik ditampilkan. | +| **Main Flow** | 1. Pengguna mengetik nama KK pada kolom pencarian di panel. 2. Sistem secara *real-time* (per karakter yang diketik) memfilter daftar KK yang sesuai. 3. Marker KK yang tidak sesuai filter disembunyikan dari peta. 4. Panel menampilkan daftar hasil filter beserta status jangkauan masing-masing KK. 5. Opsional: Pengguna menekan *chip* filter bantuan (PKH/BPNT/BLT) untuk menyaring lebih lanjut. Filter bantuan dapat dikombinasikan dengan pencarian nama. 6. Pengguna menekan tombol "Hapus Filter" untuk kembali ke tampilan lengkap. | +| **Alternative Flow** | 2a. Jika tidak ada KK yang cocok dengan kata kunci, panel menampilkan pesan "Tidak ada data ditemukan". | +| **Postcondition** | Panel dan peta menampilkan hanya data KK yang sesuai filter yang diterapkan. | +| **Prioritas** | Should Have | + +**UC-10 Ekspor Data Laporan** + +| | | +|---|---| +| **Deskripsi** | Admin mengekspor data kemiskinan dan statistik *coverage* dalam format CSV atau PDF untuk keperluan pelaporan. | +| **Aktor** | Admin | +| **Precondition** | Data kemiskinan telah dimuat dan analisis *coverage* telah dijalankan. | +| **Main Flow** | **Ekspor CSV:** 1. Admin menekan tombol "Export CSV" di panel. 2. Sistem menghasilkan file CSV menggunakan Blob API di sisi klien. 3. File berisi kolom: No, Nama KK, Jumlah Jiwa, Alamat, PKH, BPNT, BLT, Status Jangkauan, Nama Masjid yang Menjangkau, Jarak Terdekat (m), Foto. 4. Browser mengunduh file secara otomatis dengan encoding UTF-8 BOM. **Ekspor PDF:** 1. Admin menekan tombol "Export PDF" di panel. 2. Sistem mengirim *request* ke server PHP (`cetak.php`). 3. Server menghasilkan PDF A4 *Landscape* menggunakan mPDF berisi: judul laporan, tanggal cetak, tabel ringkasan statistik, tabel *breakdown* per masjid, dan tabel daftar KK lengkap. 4. Browser mengunduh file PDF secara otomatis. | +| **Alternative Flow** | 1a. (CSV) Jika filter aktif, CSV hanya berisi data KK yang sesuai filter. | +| **Postcondition** | File laporan berhasil diunduh oleh pengguna. | +| **Prioritas** | Should Have | + +--- + +## KEBUTUHAN NON-FUNGSIONAL + +### 4.1 Spesifikasi Performa (Performance) + +Mengatur tingkat kecepatan dan efisiensi sistem dalam memproses data spasial serta interaksi pengguna secara dinamis. + +| **ID** | **Deskripsi Kebutuhan** | +|---|---| +| **P-01** | **Kecepatan Muat:** Seluruh komponen peta dasar dan aset data (SPBU, Jalan, Tanah, Masjid, Penduduk, Wilayah) harus muncul sepenuhnya dalam waktu kurang dari 3 detik pada koneksi LAN. | +| **P-02** | **Efisiensi Analisis:** Kalkulasi perubahan warna marker penduduk (Merah/Abu-abu) saat *slider* radius digeser harus diproses secara *asynchronous* di sisi klien tanpa memerlukan *refresh* halaman, dengan respons < 500 ms. | +| **P-03** | **Optimalisasi Query:** Pengambilan data koordinat melalui `get_all_data.php` menggunakan format JSON agar tidak membebani memori browser, meskipun jumlah titik penduduk mencapai ratusan. | +| **P-04** | **Responsivitas Filter:** Fungsi pencarian dan filter KK harus memberikan respons visual < 200 ms per karakter yang diketik. | +| **P-05** | **Waktu Generate PDF:** Server dapat menghasilkan PDF untuk ≤ 200 KK dalam waktu < 5 detik. | +| **P-06** | **Kapasitas Data:** Sistem mampu mengelola ≥ 500 KK dan ≥ 20 Masjid secara bersamaan tanpa penurunan performa yang signifikan. | + +### 4.2 Spesifikasi Keamanan (Security) + +Melindungi integritas basis data sistem dan menjaga kerahasiaan informasi kependudukan warga. + +| **ID** | **Deskripsi Kebutuhan** | +|---|---| +| **S-01** | **Pencegahan SQL Injection:** Seluruh proses input data pada file PHP menggunakan fungsi `mysqli_real_escape_string()` dan validasi tipe data sebelum masuk query SQL, guna mencegah manipulasi *database*. | +| **S-02** | **Privasi Data:** Sistem tidak menampilkan data sensitif seperti NIK pada peta publik; hanya Nama KK dan status cakupan bantuan yang ditampilkan di *popup* peta. | +| **S-03** | **Validasi Spasial:** Sistem menolak data input jika format *Latitude* dan *Longitude* tidak sesuai standar koordinat geografis (angka desimal) untuk menjaga validitas visual peta. | +| **S-04** | **Validasi Upload File:** Sistem memvalidasi tipe file foto di sisi server (hanya JPG, JPEG, PNG, WebP yang diterima) dan membatasi ukuran file maksimal 5 MB untuk mencegah *file upload exploit*. | +| **S-05** | **Whitelist Operasi Hapus:** Operasi DELETE data menggunakan *whitelist* nama tabel yang diizinkan pada `hapus.php` untuk mencegah penghapusan tabel sembarang (*arbitrary table deletion*). | +| **S-06** | **Validasi Enum:** Nilai yang memiliki pilihan terbatas (status jalan, tipe wilayah, status SPBU, status milik persil) divalidasi terhadap daftar nilai yang diizinkan sebelum masuk ke database. | + +### 4.3 Spesifikasi Kemudahan Penggunaan (Usability) + +Menjamin sistem dapat dioperasikan secara intuitif oleh pengurus masjid maupun admin instansi pemerintah. + +| **ID** | **Deskripsi Kebutuhan** | +|---|---| +| **U-01** | **Legenda Peta:** Sistem menyediakan legenda visual yang jelas untuk membedakan arti warna marker: Merah (Penduduk Tercakup Masjid) dan Abu-abu (Penduduk *Blind Spot*). | +| **U-02** | **Interaksi Intuitif:** Pengguna dapat menentukan lokasi objek (Masjid/Rumah/SPBU) secara akurat cukup dengan melakukan klik langsung pada posisi di peta untuk mendapatkan koordinat otomatis. | +| **U-03** | **Kontrol Navigasi:** Tersedianya alat navigasi standar Leaflet (*Zoom In/Out*) serta fitur *Layer Control* untuk memfilter visibilitas data infrastruktur dan data sosial secara terpisah. | +| **U-04** | **Diferensiasi Warna Toolbar:** Setiap tombol layer aktif memiliki warna yang berbeda (SPBU: oranye, Jalan: biru, Persil: hijau teal, Wilayah: indigo, Kemiskinan: ungu, Masjid: hijau) untuk identifikasi cepat mode aktif. | +| **U-05** | **Panel Statistik Interaktif:** Panel menampilkan ringkasan statistik secara *real-time* dan dapat menampilkan detail per masjid atau per wilayah melalui navigasi internal panel tanpa *reload* halaman. | +| **U-06** | **Ikon Material Design:** Sistem menggunakan Google Material Symbols sebagai ikon antarmuka yang konsisten dan familiar, menggantikan seluruh penggunaan emoji. | + +### 4.4 Spesifikasi Reliabilitas (Reliability) + +Menjamin tingkat keakuratan hasil analisis jangkauan sosial dan ketersediaan data secara konsisten. + +| **ID** | **Deskripsi Kebutuhan** | +|---|---| +| **AV-01** | **Akurasi Spasial:** Perhitungan jangkauan menggunakan algoritma *geodesic* (Leaflet `distanceTo`) untuk memastikan akurasi klasifikasi pada penduduk yang berada di perbatasan radius. | +| **AV-02** | **Ketersediaan Data:** Sistem menjamin data koordinat yang diinput tersimpan secara permanen pada database MySQL dan dapat dipanggil secara konsisten setiap kali halaman dimuat ulang. | +| **AV-03** | **Anti-Double Count:** Sistem menjamin KK yang dijangkau oleh lebih dari satu masjid tetap dihitung sebagai 1 KK terjangkau (tidak ada *double counting* dalam statistik total coverage). | +| **AV-04** | **Konsistensi State Toggle:** Status *toggle* radius per masjid dipertahankan selama sesi browser aktif menggunakan objek *state* `radiusVisibleState{}` tanpa perlu *refresh* halaman. | + +### 4.5 Spesifikasi Portabilitas (Portability) + +Memastikan sistem dapat berjalan di berbagai lingkungan operasional dan perangkat tanpa kendala teknis. + +| **ID** | **Deskripsi Kebutuhan** | +|---|---| +| **C-01** | **Kompatibilitas Browser:** Antarmuka WebGIS harus berfungsi penuh pada browser modern utama: Google Chrome, Mozilla Firefox, dan Microsoft Edge versi stabil terbaru. | +| **C-02** | **Akses Lintas Perangkat:** Tata letak peta dan form input responsif sehingga tetap nyaman digunakan pada Desktop, Laptop, maupun Tablet. | +| **C-03** | **Kemudahan Deployment:** Kode program terstruktur sehingga aplikasi mudah dipindahkan dari server lokal (XAMPP/Laragon) ke server *hosting* daring tanpa perubahan kode yang masif. | +| **C-04** | **Versi PHP:** Aplikasi kompatibel dengan PHP versi 7.4 ke atas dengan ekstensi MySQLi dan GD Library (untuk pemrosesan upload foto) aktif. | + +--- + +## ANTARMUKA EKSTERNAL + +### 5.1 Antarmuka Pengguna + +Antarmuka pengguna sistem dirancang dengan prinsip Material Design untuk mempermudah visualisasi data spasial dengan kriteria desain responsif dan interaktif agar dapat dioperasikan pada berbagai resolusi layar. + +| **Halaman** | **Deskripsi** | **Referensi** | +|---|---|---| +| **Halaman Peta Utama** | Area kerja utama yang menampilkan peta interaktif (Leaflet), toolbar navigasi (SPBU, Jalan, Tanah, Wilayah, Kemiskinan, Masjid), dan *Layer Control* untuk filter visibilitas. | Desain WebGIS Utama | +| **Form Input SPBU** | *Popup* formulir untuk menginput Nama SPBU, Nomor, dan Status 24 Jam. | Form Data SPBU | +| **Form Input Infrastruktur** | *Popup* formulir untuk menginput data Jalan (Line) dan Tanah (Polygon) yang muncul setelah geometri selesai digambar. | Form Data Spasial | +| **Form Input Masjid** | Panel input untuk Nama Masjid, PIC, Alamat, dan *slider* radius deteksi (100m–5000m) yang mengubah ukuran lingkaran secara *real-time*. | Form Data Masjid | +| **Form Input Penduduk Miskin** | Panel input untuk Nama KK, Alamat, Jumlah Jiwa, *checkbox* bantuan (PKH/BPNT/BLT), dan *upload* foto KK. | Form Data Miskin | +| **Form Input Batas Wilayah** | Panel input untuk Nama Wilayah dan Tipe (RT/RW/Kelurahan/Kecamatan) yang muncul setelah polygon selesai digambar. | Form Data Wilayah | +| **Panel Statistik Real-Time** | Panel di sisi peta yang menampilkan ringkasan total KK/jiwa/terjangkau/*blind spot*, *breakdown* per masjid, *breakdown* per wilayah, area filter/pencarian, dan tombol Export CSV/PDF. | Panel Analisis | +| **Popup Informasi & Update** | Jendela kecil yang muncul saat marker diklik, menampilkan detail data, *slider* radius dan tombol *toggle* (untuk masjid), tombol Edit, dan tombol Hapus. | Detail Popup & Update | + +### 5.2 Antarmuka Perangkat Keras (Hardware Interface) + +Sistem membutuhkan spesifikasi perangkat keras minimal agar analisis spasial dan pemrosesan data koordinat berjalan optimal. + +| **Komponen** | **Spesifikasi Minimal** | +|---|---| +| **Perangkat Input** | Mendukung mouse, keyboard, dan layar sentuh untuk navigasi peta di perangkat tablet/mobile. | +| **Perangkat Output** | Monitor/Layar dengan resolusi minimal 1024×768 untuk tampilan analisis spasial yang jernih. | +| **Koneksi Internet** | Bandwidth minimal 2 Mbps untuk proses *streaming* map tiles secara asinkron dari server OpenStreetMap. | +| **Penyimpanan Server** | Server web dengan kapasitas minimal 500 MB untuk menyimpan skrip PHP, basis data MySQL, dan file foto KK yang diunggah. | + +### 5.3 Antarmuka Perangkat Lunak (Software Interface) + +Sistem berinteraksi dengan beberapa komponen perangkat lunak eksternal dan library untuk menjalankan fungsi GIS. + +| **Layanan / Sistem** | **Tujuan Integrasi** | **Versi / Protokol** | +|---|---|---| +| **OpenStreetMap (OSM)** | Penyedia *Base Map* (Peta Dasar) yang dipanggil secara dinamis melalui *Tile Layer*. | HTTP/HTTPS | +| **Leaflet.js** | Library JavaScript utama untuk pengolahan layer, marker, dan perhitungan jarak *geodesic* di sisi klien. | v1.9.4 | +| **Leaflet.GeometryUtil** | Plugin untuk kalkulasi luas area (m²) parsil tanah dan panjang jalan secara akurat. | v0.10.3 | +| **Leaflet.heat** | Plugin untuk visualisasi heatmap kepadatan titik kemiskinan. | v0.2.0 | +| **Leaflet Color Markers** | Plugin CDN untuk ikon marker berwarna yang membedakan status jangkauan (merah/abu-abu) dan tipe SPBU. | via CDN | +| **Google Material Symbols** | *Icon font* (variabel) untuk ikon antarmuka pada toolbar dan tombol-tombol UI. | via Google Fonts CDN | +| **Google Fonts (Roboto)** | *Font* teks antarmuka dengan gaya Material Design. | via Google Fonts CDN | +| **MySQL Database** | Tempat penyimpanan data koordinat seluruh objek (Point, Line, Polygon) dan data demografi penduduk. | v5.7+ / SQL Query | +| **PHP Native** | Bahasa pemrograman *server-side* untuk logika bisnis, API *endpoint*, dan pemrosesan file upload. | v7.4+ | +| **mPDF** | Library PHP untuk menghasilkan dokumen PDF laporan kemiskinan dari konten HTML. | v8.3 | +| **Web Browser** | *Runtime environment* untuk eksekusi JavaScript/HTML5 dan rendering peta Leaflet. | Chrome / Firefox / Edge | +| **XAMPP / Laragon** | Lingkungan server lokal (Apache + MySQL + PHP) untuk pengembangan dan demonstrasi. | XAMPP 8.x | + +### 5.4 Antarmuka Komunikasi (Communication Interface) + +Protokol komunikasi yang digunakan untuk menjamin pertukaran data antara klien dan server berjalan lancar. + +| **Jalur Komunikasi** | **Deskripsi Mekanisme** | +|---|---| +| **Client–Server** | Komunikasi antara browser pengguna dan server web menggunakan protokol HTTP (lokal) atau HTTPS (produksi). | +| **Data Exchange (JSON)** | Pengiriman data dari MySQL ke peta dilakukan melalui skrip PHP yang menghasilkan format JSON untuk diproses oleh JavaScript (`get_all_data.php`). | +| **Database Connectivity** | Skrip PHP berkomunikasi dengan database MySQL secara internal menggunakan ekstensi MySQLi (`mysqli_query`, `mysqli_fetch_all`). | +| **Asynchronous Fetch API** | Operasi simpan, update, dan hapus data menggunakan JavaScript Fetch API (metode POST) untuk komunikasi asinkron tanpa *reload* halaman. | +| **Asynchronous Map Tiles** | Permintaan ubin peta (*map tiles*) dilakukan secara asinkron oleh Leaflet sehingga peta tetap lancar saat digeser (*panning*). | +| **File Upload (Multipart)** | Upload foto KK menggunakan `multipart/form-data` melalui PHP `$_FILES`, disimpan ke folder `uploads/kemiskinan/` di server. | + +--- + +## DEFINISI FUNGSI DAN KEBUTUHAN PENGGUNA + +### 6.1 Kebutuhan Pengguna + +| **ID** | **Peran Pengguna** | **Kebutuhan / Goal** | **Fungsi Sistem Terkait** | +|---|---|---|---| +| **UR-01** | Pengurus Masjid | Menentukan wilayah tanggung jawab sosial di sekitar masjid secara presisi. | Pengaturan Radius Dinamis per Masjid | +| **UR-02** | Pengurus Masjid | Memetakan posisi jamaah/warga miskin agar bantuan tepat sasaran. | Manajemen Data & *Plotting* Penduduk | +| **UR-03** | Pengurus Masjid | Menonaktifkan radius masjid tertentu sementara agar tidak masuk analisis. | *Toggle* Radius per Masjid | +| **UR-04** | Pemerintah/Dinsos | Mengetahui wilayah mana yang penduduk miskinnya belum tersentuh bantuan masjid (*blind spot*). | Klasifikasi Warna Marker Otomatis | +| **UR-05** | Pemerintah/Dinsos | Mencari data KK berdasarkan nama atau jenis bantuan untuk keperluan verifikasi lapangan. | Filter dan Pencarian KK | +| **UR-06** | Pemerintah/Dinsos | Mengunduh laporan kemiskinan untuk keperluan rapat atau pengajuan anggaran. | Ekspor CSV dan PDF | +| **UR-07** | Pemerintah/Dinsos | Mengetahui sebaran kemiskinan per wilayah administratif (RT/RW) untuk alokasi bantuan yang adil. | Analisis Batas Wilayah & Panel Statistik | +| **UR-08** | Masyarakat Umum | Melihat transparansi sebaran rumah ibadah dan kontribusinya terhadap pengentasan kemiskinan di lingkungan. | Visualisasi Peta Multilayer Interaktif | + +### 6.2 Detail Fungsionalitas Spesifik + +#### 6.2.1 Fungsi Analisis Radius Jangkauan + +- **Input:** Koordinat Masjid (*Lat_m*, *Long_m*), nilai Radius (*R*) dalam meter, dan status *toggle* radius per masjid. +- **Proses:** Sistem menggunakan library Leaflet untuk menggambar lingkaran transparan di sekeliling titik masjid. Secara *back-end* (di sisi klien), sistem membandingkan jarak setiap Penduduk (P) terhadap pusat Masjid (M) menggunakan formula Haversine (`marker.distanceTo()`). Hanya masjid yang status *toggle* radius-nya aktif (ON) yang diikutsertakan dalam perhitungan. +- **Output:** Penentuan status penduduk berdasarkan kondisi: + - Jika Jarak (P, M) ≤ R untuk setidaknya satu masjid aktif, maka penduduk masuk kategori "Terjangkau". + - Jika Jarak (P, M) > R untuk semua masjid aktif, maka penduduk masuk kategori "*Blind Spot*". +- **Anti-Double Count:** KK yang dijangkau lebih dari satu masjid tetap dihitung 1 KK dalam statistik total menggunakan struktur `kkCoverage[kkId]` yang berbasis ID unik KK. + +#### 6.2.2 Fungsi Analisis Point-in-Polygon (Batas Wilayah) + +- **Input:** Koordinat KK (titik) dan koordinat *vertex* polygon Batas Wilayah dalam format GeoJSON. +- **Proses:** Sistem menggunakan algoritma *ray casting* (`pointInPolygon()`) untuk menentukan apakah sebuah titik KK berada di dalam polygon wilayah. Algoritma memancarkan sebuah *ray* (sinar) horizontal dari titik ke arah tak terhingga dan menghitung jumlah perpotongan dengan sisi-sisi polygon. Jumlah ganjil berarti titik berada di dalam polygon. +- **Output:** Daftar KK per wilayah beserta statistik: jumlah total KK, jumlah jiwa, KK terjangkau masjid, dan KK *blind spot* dalam wilayah tersebut. Ditampilkan di panel saat wilayah diklik. + +#### 6.2.3 Fungsi Klasifikasi Warna Otomatis (Marker Coloring) + +Sistem mengubah atribut visual secara dinamis berdasarkan hasil analisis radius tanpa intervensi manual tambahan: + +- **Marker Merah:** Menunjukkan penduduk miskin yang berada dalam jangkauan minimal satu masjid aktif. Fungsi ini membantu pihak masjid memvalidasi daftar penerima bantuan internal mereka. +- **Marker Abu-abu:** Menunjukkan penduduk miskin yang berada di luar jangkauan semua masjid aktif (*Blind Spot*). Fungsi ini memberikan sinyal darurat bagi pemerintah untuk segera memberikan intervensi bantuan di titik tersebut. + +#### 6.2.4 Fungsi Informasi Interaktif (Tooltip & Popup) + +Sistem menampilkan informasi detail saat pengguna mengklik objek di peta: + +- **Informasi Masjid:** Nama Masjid, Nama PIC/Pengurus, Alamat, *slider* radius, tombol *toggle* radius (Aktif/Nonaktif), tombol Edit, tombol Hapus, dan jumlah KK yang dijangkau masjid ini. +- **Informasi Penduduk:** Profil KK (nama, jumlah jiwa, alamat), jenis bantuan (PKH/BPNT/BLT), foto KK (jika ada), status jangkauan (Terjangkau/*Blind Spot*), dan nama masjid yang menjangkau beserta jaraknya dalam meter. +- **Informasi SPBU:** Nama SPBU, Nomor, Status 24 Jam, tombol Edit, tombol Hapus. +- **Informasi Infrastruktur:** Panjang jalan otomatis (m), luas parsil tanah (m²), status kepemilikan/jalan, tombol Edit dan Hapus. +- **Informasi Batas Wilayah:** Nama wilayah, tipe (RT/RW/Kelurahan/Kecamatan), dan tombol untuk melihat detail KK per wilayah di panel statistik. + +#### 6.2.5 Fungsi Filter dan Pencarian + +- **Pencarian Nama KK:** Input teks *real-time* yang memfilter daftar KK berdasarkan kecocokan parsial nama (case-insensitive). Hasil filter ditampilkan di panel dan marker yang tidak cocok disembunyikan dari peta. +- **Filter Bantuan (PKH/BPNT/BLT):** Tombol *chip* yang dapat diklik untuk memfilter KK berdasarkan jenis bantuan yang diterima. Filter dapat dikombinasikan (multi-filter) dan digabungkan dengan pencarian nama. +- **Reset Filter:** Tombol "Hapus Filter" mengembalikan tampilan ke kondisi semula (semua KK ditampilkan di panel dan peta). + +#### 6.2.6 Fungsi Ekspor Data + +**Ekspor CSV:** + +- Menggunakan JavaScript Blob API (*client-side*) tanpa memerlukan *request* tambahan ke server. +- Kolom: No, Nama KK, Jumlah Jiwa, Alamat, PKH, BPNT, BLT, Status Jangkauan, Dijangkau Masjid (nama-nama masjid), Jarak Terdekat (m), Nama File Foto. +- Encoding UTF-8 BOM untuk kompatibilitas Microsoft Excel. +- Mencerminkan filter yang sedang aktif saat tombol diklik. + +**Ekspor PDF:** + +- Menggunakan library mPDF v8.3 di sisi server (`cetak.php`). +- Format kertas A4 *Landscape*. +- Konten laporan: judul dan tanggal cetak, tabel ringkasan statistik, tabel *breakdown* per masjid (nama, radius, KK terjangkau, jiwa terjangkau), dan tabel daftar KK lengkap. +- Data *coverage* (terjangkau/*blind spot*) dihitung ulang di server menggunakan formula Haversine dalam PHP untuk konsistensi dengan tampilan di peta. + +### 6.3 Analisis Alur Kerja Pengentasan Kemiskinan + +Secara operasional, sistem ini mendukung kolaborasi strategis dalam urutan berikut: + +| **Tahap** | **Aktivitas** | **Pelaksana** | +|---|---|---| +| **Inisialisasi Data** | Admin memasukkan data infrastruktur (SPBU, Jalan, Persil), menggambar polygon batas wilayah administratif, memasukkan koordinat masjid, serta menginput data penduduk miskin ke dalam database. | Admin Sistem | +| **Penentuan Komitmen** | Pengurus masjid menyesuaikan *slider* radius untuk menentukan berapa meter wilayah yang sanggup mereka "kawal" secara finansial (misal: 500m). Masjid yang belum siap dapat dinonaktifkan radiusnya sementara menggunakan *toggle*. | Pengurus Masjid | +| **Identifikasi Celah** | Pemerintah membuka peta, mengaktifkan layer kemiskinan, dan mencari posisi Marker Abu-abu (*blind spot*). Fitur filter dapat digunakan untuk memprioritaskan KK yang tidak menerima bantuan apapun (bukan PKH, BPNT, maupun BLT). | Pemerintah / Dinsos | +| **Analisis Per Wilayah** | Admin mengklik polygon batas wilayah untuk melihat distribusi KK terjangkau vs. *blind spot* per RT/RW, guna mengidentifikasi wilayah yang paling membutuhkan intervensi. | Pemerintah / Dinsos | +| **Pelaporan** | Admin mengekspor data ke CSV atau PDF untuk keperluan rapat koordinasi, pengajuan anggaran, atau dokumentasi program pengentasan kemiskinan. | Admin Sistem | +| **Intervensi Target** | Pemerintah mengirimkan bantuan khusus ke area Marker Abu-abu, sementara Marker Merah dikelola secara mandiri oleh masjid lokal berdasarkan radius yang telah ditetapkan. | Pemerintah / Dinsos | + +--- + +## KESIMPULAN + +Sebagai penutup, dokumen SKPL ini disusun untuk memberikan panduan komprehensif dalam pengembangan sistem **WebGIS Partisipasi Masjid untuk Pengentasan Kemiskinan**. Melalui integrasi antara basis data MySQL, logika PHP Native, dan visualisasi Leaflet.js, sistem ini mampu mengubah data kemiskinan yang kompleks menjadi informasi spasial yang mudah dipahami. + +Fitur-fitur inti yang diimplementasikan — meliputi analisis radius Haversine per masjid, *toggle* radius individual, batas wilayah administratif dengan analisis *point-in-polygon*, filter dan pencarian data KK, panel statistik *real-time*, visualisasi *heatmap*, serta ekspor CSV dan PDF — memungkinkan kolaborasi antara pemerintah dan rumah ibadah dalam menyalurkan bantuan menjadi lebih transparan, terukur, dan tepat sasaran. Antarmuka berbasis Material Design dengan Google Material Symbols menjamin kemudahan penggunaan bagi berbagai kalangan pengguna, mulai dari petugas pemerintah hingga pengurus masjid. + +--- + +## LAMPIRAN + +**Gambar 1** WebGIS Partisipasi Masjid untuk Pengentasan Kemiskinan + +*(Screenshot tampilan utama peta dengan semua layer aktif)* + +**Gambar 2** Input Masjid dan Radius + +*(Screenshot form input masjid dengan slider radius real-time)* + +**Gambar 3** Input Data Penduduk Miskin + +*(Screenshot form input KK dengan checkbox bantuan PKH/BPNT/BLT dan upload foto)* + +**Gambar 4** Fitur Batas Wilayah dan Panel Statistik Per Wilayah + +*(Screenshot polygon batas wilayah dan panel breakdown statistik per wilayah)* + +**Gambar 5** Fitur Filter dan Pencarian Data KK + +*(Screenshot panel filter dengan chip bantuan dan hasil pencarian real-time)* + +**Gambar 6** Use Case Diagram + +*(Diagram UML Use Case sistem WebGIS)* + +**Gambar 7** ERD Diagram + +*(Entity Relationship Diagram database: tabel spbu, masjid, kemiskinan, jalan, persil, wilayah)* diff --git a/uas/SKPL_v2.docx b/uas/SKPL_v2.docx new file mode 100644 index 0000000..f93bc74 Binary files /dev/null and b/uas/SKPL_v2.docx differ diff --git a/uas/ambil_data.php b/uas/ambil_data.php new file mode 100644 index 0000000..eaf6f5e --- /dev/null +++ b/uas/ambil_data.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/uas/api/change_password.php b/uas/api/change_password.php new file mode 100644 index 0000000..7d96d54 --- /dev/null +++ b/uas/api/change_password.php @@ -0,0 +1,33 @@ + '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']); diff --git a/uas/api/delete.php b/uas/api/delete.php new file mode 100644 index 0000000..5baf911 --- /dev/null +++ b/uas/api/delete.php @@ -0,0 +1,25 @@ + '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)]); +} diff --git a/uas/api/export_csv.php b/uas/api/export_csv.php new file mode 100644 index 0000000..23761a8 --- /dev/null +++ b/uas/api/export_csv.php @@ -0,0 +1,41 @@ + $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 .= " + + " . ($i + 1) . " + " . htmlspecialchars($ib['nama']) . " + " . htmlspecialchars($ib['jenis']) . " + {$ib['radius']} m + {$binaan} + "; +} +if (!$rowsIbadah) { + $rowsIbadah = "Belum ada data rumah ibadah."; +} + +// ── 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 .= " + + " . ($i + 1) . " + " . htmlspecialchars($p['kk_nama']) . "" . + ($p['nik'] ? "
{$p['nik']}" : '') . + " + " . htmlspecialchars($p['alamat'] ?? '-') . " + {$jml} + " . htmlspecialchars($p['ibadah_nama'] ?? '-') . " + {$jarak} + {$slbl} + " . htmlspecialchars($p['jenis_bantuan'] ?? '-') . " + {$tglB} + "; +} +if (!$rowsPenduduk) { + $rowsPenduduk = "Belum ada data penduduk miskin."; +} + +// ── HTML Template ───────────────────────────────────────────────────────────── +$html = << + 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; } + + +

LAPORAN SEBARAN PENDUDUK MISKIN

+
+ Sistem WebGIS GeoMiskin — Universitas Tanjungpura  |  + Dicetak: {$tgl}  |  + Oleh: {$cetakOleh} +
+ +
Ringkasan Statistik
+ + + + + + + +
+
{$total}
+
Total KK Miskin
+
+
{$sudah}
+
Sudah Dibantu ({$pctSudah}%)
+
+
{$belum}
+
Belum Dibantu
+
+
{$menunggu}
+
Menunggu Verifikasi
+
+ +
Data Rumah Ibadah
+ + + + + + + + + + + {$rowsIbadah} +
NoNama Rumah IbadahJenisRadiusKK Binaan
+ +
Daftar Penduduk Miskin
+ + + + + + + + + + + + + + + {$rowsPenduduk} +
NoNama KK / NIKAlamatJiwaRumah IbadahJarakStatusJenis BantuanTgl Bantuan
+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 '
+Gagal membuat PDF: ' . htmlspecialchars($e->getMessage()) . '
+
+Pastikan extension GD aktif di XAMPP:
+  1. Buka C:\\xampp\\php\\php.ini
+  2. Cari ;extension=gd → hapus tanda titik koma
+  3. Restart Apache di XAMPP Control Panel
+
'; +} diff --git a/uas/api/get_data.php b/uas/api/get_data.php new file mode 100644 index 0000000..ca492c5 --- /dev/null +++ b/uas/api/get_data.php @@ -0,0 +1,40 @@ + $ibadah, 'penduduk' => $penduduk]); diff --git a/uas/api/save_ibadah.php b/uas/api/save_ibadah.php new file mode 100644 index 0000000..2cce20d --- /dev/null +++ b/uas/api/save_ibadah.php @@ -0,0 +1,29 @@ + '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)]); +} diff --git a/uas/api/save_penduduk.php b/uas/api/save_penduduk.php new file mode 100644 index 0000000..e104898 --- /dev/null +++ b/uas/api/save_penduduk.php @@ -0,0 +1,92 @@ + '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)); +} diff --git a/uas/api/update_ibadah.php b/uas/api/update_ibadah.php new file mode 100644 index 0000000..2421e03 --- /dev/null +++ b/uas/api/update_ibadah.php @@ -0,0 +1,30 @@ +'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)]); +} diff --git a/uas/api/update_penduduk.php b/uas/api/update_penduduk.php new file mode 100644 index 0000000..4f90496 --- /dev/null +++ b/uas/api/update_penduduk.php @@ -0,0 +1,65 @@ +'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']); diff --git a/uas/api/users.php b/uas/api/users.php new file mode 100644 index 0000000..8abe7a5 --- /dev/null +++ b/uas/api/users.php @@ -0,0 +1,101 @@ + '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']); diff --git a/uas/auth.php b/uas/auth.php new file mode 100644 index 0000000..88a769b --- /dev/null +++ b/uas/auth.php @@ -0,0 +1,35 @@ + 'error', 'message' => 'Sesi habis, silakan login kembali.']); + exit; + } + header('Location: /webgus_lama/uas/login.php'); + exit; + } +} + +function requireSuperAdmin() { + requireLogin(); + if ($_SESSION['user']['role'] !== 'superadmin') { + http_response_code(403); + echo json_encode(['status' => 'error', 'message' => 'Akses ditolak. Hanya superadmin.']); + exit; + } +} + +function isSuperAdmin() { + return ($_SESSION['user']['role'] ?? '') === 'superadmin'; +} + +function currentUser() { + return $_SESSION['user'] ?? null; +} + +function esc($conn, $v) { + return mysqli_real_escape_string($conn, trim((string)$v)); +} diff --git a/uas/composer.json b/uas/composer.json new file mode 100644 index 0000000..85c292f --- /dev/null +++ b/uas/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "mpdf/mpdf": "^8.3" + } +} diff --git a/uas/composer.lock b/uas/composer.lock new file mode 100644 index 0000000..8d9f157 --- /dev/null +++ b/uas/composer.lock @@ -0,0 +1,478 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6acbc9f2692b6bca49a9658332298068", + "packages": [ + { + "name": "mpdf/mpdf", + "version": "v8.3.1", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "2a454ec334109911fdb323a284c19dbf3f049810" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/2a454ec334109911fdb323a284c19dbf3f049810", + "reference": "2a454ec334109911fdb323a284c19dbf3f049810", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "mpdf/psr-http-message-shim": "^1.0 || ^2.0", + "mpdf/psr-log-aware-trait": "^2.0 || ^3.0", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^1.3.0", + "mpdf/qrcode": "^1.1.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "~2.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-imagick": "Needed if developing the Mpdf library", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "support": { + "docs": "https://mpdf.github.io", + "issues": "https://github.com/mpdf/mpdf/issues", + "source": "https://github.com/mpdf/mpdf" + }, + "funding": [ + { + "url": "https://www.paypal.me/mpdf", + "type": "custom" + } + ], + "time": "2026-03-11T10:58:44+00:00" + }, + { + "name": "mpdf/psr-http-message-shim", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-http-message-shim.git", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f", + "shasum": "" + }, + "require": { + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrHttpMessageShim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + }, + { + "name": "Nigel Cunningham", + "email": "nigel.cunningham@technocrat.com.au" + } + ], + "description": "Shim to allow support of different psr/message versions.", + "support": { + "issues": "https://github.com/mpdf/psr-http-message-shim/issues", + "source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1" + }, + "time": "2023-10-02T14:34:03+00:00" + }, + { + "name": "mpdf/psr-log-aware-trait", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-log-aware-trait.git", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78", + "shasum": "" + }, + "require": { + "psr/log": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrLogAwareTrait\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + } + ], + "description": "Trait to allow support of different psr/log versions.", + "support": { + "issues": "https://github.com/mpdf/psr-log-aware-trait/issues", + "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0" + }, + "time": "2023-05-03T06:19:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "setasign/fpdi", + "version": "v2.6.7", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "388c51e69982a3fc16698710b763e8107a49f510" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/388c51e69982a3fc16698710b763e8107a49f510", + "reference": "388c51e69982a3fc16698710b763e8107a49f510", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": ">=7.2 <=8.5.99999" + }, + "conflict": { + "setasign/tfpdf": "<1.31" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.52", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.8" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "support": { + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.6.7" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", + "type": "tidelift" + } + ], + "time": "2026-05-13T10:16:22+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/uas/dashboard.php b/uas/dashboard.php new file mode 100644 index 0000000..326070e --- /dev/null +++ b/uas/dashboard.php @@ -0,0 +1,723 @@ + + + + + + +Dashboard — GeoMiskin + + + + + + + +
+ + +
+
+ +
+
GeoMiskin Dashboard
+
Ringkasan data sebaran penduduk miskin
+
+
+ + Buka Peta + + Pengguna + +
+
+
+
+
+
+ Keluar +
+ + +
+ + + + +
+
+
+
+
Total KK Miskin
+
+
+
+
+
Sudah Dibantu
+
+
+
+
+
Belum Dibantu
+
+
+
+
+
Menunggu Verifikasi
+
+
+
+
+
Rumah Ibadah
+
+
+
+
+
Total Jiwa
+
+
+ + +
+
+

Progress Bantuan

+
0%
+
+
+
+
0
Belum Dibantu
+
0
Sudah Dibantu
+
0
Menunggu Verifikasi
+
+
+ + +
+
+
Distribusi Status Bantuan
+
+
+
+
KK Binaan per Rumah Ibadah
+
+
+
+
Distribusi Jenis Rumah Ibadah
+
+
+
+
Jumlah Anggota per KK
+
+
+
+ + + + + +
+ +
+
+

Rumah Ibadah & KK Binaan

+
+ + + +
#NamaJenisKK Binaan
+
+ + +
+
+

Rekap Bantuan per Jenis Ibadah

+
+ + + +
JenisTotal KKDibantu%
+
+
+ + +
+
+

Daftar Penduduk Miskin

+
+ + +
+
+ + + + + + + + + + + + + + + + + +
#Nama KKNIKKecamatanAlamatAnggotaRumah IbadahJarakStatus

Memuat data...
+ +
+ +
+ +
+ + + + diff --git a/uas/db_webgis.sql b/uas/db_webgis.sql new file mode 100644 index 0000000..d09ca0d --- /dev/null +++ b/uas/db_webgis.sql @@ -0,0 +1,107 @@ +-- ================================================================ +-- WebGIS Sebaran Penduduk Miskin +-- Database: db_webgis +-- ================================================================ + +CREATE DATABASE IF NOT EXISTS db_webgis CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE db_webgis; + +-- ---------------------------------------------------------------- +-- Users +-- ---------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + nama VARCHAR(100), + role ENUM('admin','operator') DEFAULT 'operator', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ---------------------------------------------------------------- +-- Rumah Ibadah +-- ---------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS rumah_ibadah ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(255) NOT NULL, + jenis ENUM('Masjid','Musholla','Gereja Protestan','Gereja Katolik','Pura','Vihara','Klenteng','Lainnya') DEFAULT 'Masjid', + kontak VARCHAR(100), + alamat TEXT, + lat DECIMAL(10,7) NOT NULL, + lng DECIMAL(10,7) NOT NULL, + radius INT DEFAULT 300, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- ---------------------------------------------------------------- +-- Penduduk Miskin +-- ---------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS penduduk_miskin ( + id INT AUTO_INCREMENT PRIMARY KEY, + kk_nama VARCHAR(255) NOT NULL, + nik VARCHAR(20), + alamat TEXT, + lat DECIMAL(10,7) NOT NULL, + lng DECIMAL(10,7) NOT NULL, + jumlah_anggota INT DEFAULT 1, + rumah_ibadah_id INT, + jarak_meter DECIMAL(10,2), + status_bantuan ENUM('belum_dibantu','sudah_dibantu','menunggu_verifikasi') DEFAULT 'belum_dibantu', + jenis_bantuan VARCHAR(255), + tanggal_bantuan DATE, + bukti_file VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL +); + +-- ---------------------------------------------------------------- +-- Anggota Keluarga +-- ---------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS anggota_keluarga ( + id INT AUTO_INCREMENT PRIMARY KEY, + penduduk_id INT NOT NULL, + nama VARCHAR(255) NOT NULL, + hubungan VARCHAR(50), + umur INT, + pekerjaan VARCHAR(100), + keterangan TEXT, + FOREIGN KEY (penduduk_id) REFERENCES penduduk_miskin(id) ON DELETE CASCADE +); + +-- ---------------------------------------------------------------- +-- Default admin (password: admin123) +-- Ganti hash ini dengan php -r "echo password_hash('admin123', PASSWORD_DEFAULT);" +-- ---------------------------------------------------------------- +INSERT IGNORE INTO users (username, password, nama, role) VALUES +('admin', '$2y$10$TKh8H1.PfGLmAdaQXDLZQO3L4LYKE/VNRN3hJ3oZpYb7U0u0EWxLe', 'Administrator', 'admin'), +('operator', '$2y$10$TKh8H1.PfGLmAdaQXDLZQO3L4LYKE/VNRN3hJ3oZpYb7U0u0EWxLe', 'Operator Lapangan', 'operator'); + +-- ---------------------------------------------------------------- +-- Sample data (Pontianak area) +-- ---------------------------------------------------------------- +INSERT IGNORE INTO rumah_ibadah (id, nama, jenis, kontak, alamat, lat, lng, radius) VALUES +(1, 'Masjid Raya Mujahidin', 'Masjid', '0561-123456', 'Jl. Ahmad Yani, Pontianak', -0.0262, 109.3429, 500), +(2, 'Gereja Katedral Santo Yosef', 'Gereja Katolik', '0561-234567', 'Jl. Pattimura, Pontianak', -0.0312, 109.3301, 400), +(3, 'Vihara Bodhisattva Karaniya Metta', 'Vihara', '0561-345678', 'Jl. Tanjungpura, Pontianak', -0.0355, 109.3344, 350), +(4, 'Pura Agung Nusantara', 'Pura', '0561-456789', 'Jl. Gajah Mada, Pontianak', -0.0409, 109.3221, 400), +(5, 'Musholla Al-Ikhlas', 'Musholla', NULL, 'Jl. Kom Yos Sudarso, Pontianak', -0.0195, 109.3553, 250); + +INSERT IGNORE INTO penduduk_miskin (id, kk_nama, nik, alamat, lat, lng, rumah_ibadah_id, jarak_meter, status_bantuan, jenis_bantuan, tanggal_bantuan) VALUES +(1, 'Ahmad Subandi', '6171011234560001', 'Gg. Melati No.3, Pontianak Selatan', -0.0290, 109.3410, 1, 312.5, 'sudah_dibantu', 'PKH', '2024-03-01'), +(2, 'Siti Rahayu', '6171021234560002', 'Jl. Budi Utomo Gg.5, Pontianak', -0.0278, 109.3450, 1, 245.0, 'belum_dibantu', NULL, NULL), +(3, 'Budi Santoso', '6171031234560003', 'Jl. Gusti Hamzah, Pontianak Utara', -0.0320, 109.3290, 2, 198.3, 'menunggu_verifikasi', NULL, NULL), +(4, 'Maria Yuliana', '6171041234560004', 'Jl. Purnama Gg.2, Pontianak', -0.0350, 109.3320, 3, 267.1, 'sudah_dibantu', 'BPNT + PKH', '2024-01-15'), +(5, 'Wati Sari', '6171051234560005', 'Jl. Kom Yos Sudarso, Pontianak Timur', -0.0188, 109.3561, 5, 89.4, 'belum_dibantu', NULL, NULL); + +INSERT IGNORE INTO anggota_keluarga (penduduk_id, nama, hubungan, umur, pekerjaan) VALUES +(1, 'Ahmad Subandi', 'Kepala Keluarga', 45, 'Buruh Harian'), +(1, 'Dewi Subandi', 'Istri', 40, 'Ibu Rumah Tangga'), +(1, 'Rizki Subandi', 'Anak', 18, 'Pelajar'), +(2, 'Siti Rahayu', 'Kepala Keluarga', 38, 'Pedagang Kecil'), +(2, 'Putri Rahayu', 'Anak', 12, 'Pelajar'), +(3, 'Budi Santoso', 'Kepala Keluarga', 52, 'Tidak Bekerja'), +(3, 'Sari Santoso', 'Istri', 48, 'Buruh Cuci'), +(4, 'Maria Yuliana', 'Kepala Keluarga', 41, 'Pedagang'), +(5, 'Wati Sari', 'Kepala Keluarga', 33, 'Buruh'); diff --git a/uas/export_pdf.php b/uas/export_pdf.php new file mode 100644 index 0000000..5d32d3a --- /dev/null +++ b/uas/export_pdf.php @@ -0,0 +1,208 @@ + [masjid, ...] +$masjidCoverage = []; // masjidId -> ['kk'=>[], 'jiwa'=>0] +foreach ($allMasjid as $m) { + $masjidCoverage[$m['id']] = ['kk' => [], 'jiwa' => 0]; +} +foreach ($allKemiskinan as $kk) { + $kkCoverage[$kk['id']] = []; + foreach ($allMasjid as $m) { + $dist = haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']); + if ($dist <= (int)$m['radius_m']) { + $kkCoverage[$kk['id']][] = $m; + $masjidCoverage[$m['id']]['kk'][] = $kk; + $masjidCoverage[$m['id']]['jiwa'] += (int)$kk['jumlah_kk']; + } + } +} + +// ── Statistik ringkasan ─────────────────────────────────────────────────── +$totalKK = count($allKemiskinan); +$totalJiwa = array_sum(array_column($allKemiskinan, 'jumlah_kk')); +$countT = 0; $jiwaT = 0; +foreach ($allKemiskinan as $kk) { + if (count($kkCoverage[$kk['id']]) > 0) { $countT++; $jiwaT += (int)$kk['jumlah_kk']; } +} +$countTT = $totalKK - $countT; +$jiwaTT = $totalJiwa - $jiwaT; +$pct = $totalKK > 0 ? number_format(($countT / $totalKK) * 100, 1) : '0.0'; +$pctTT = $totalKK > 0 ? number_format((($countTT) / $totalKK) * 100, 1) : '0.0'; + +// ── Tanggal dalam format Indonesia ─────────────────────────────────────── +$bulan = ['','Januari','Februari','Maret','April','Mei','Juni', + 'Juli','Agustus','September','Oktober','November','Desember']; +$tgl = date('j') . ' ' . $bulan[(int)date('n')] . ' ' . date('Y'); + +// ── Baris tabel masjid ──────────────────────────────────────────────────── +$rowsMasjid = ''; +foreach ($allMasjid as $i => $m) { + $cov = $masjidCoverage[$m['id']]; + $bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb'; + $rowsMasjid .= " + + " . ($i+1) . " + {$m['nama_masjid']} + {$m['radius_m']} + " . count($cov['kk']) . " + {$cov['jiwa']} + "; +} +if (!$rowsMasjid) $rowsMasjid = "Belum ada data masjid."; + +// ── Baris tabel KK miskin ───────────────────────────────────────────────── +$rowsKK = ''; +foreach ($allKemiskinan as $i => $kk) { + $covers = $kkCoverage[$kk['id']]; + $covered = count($covers) > 0; + + // Jarak ke masjid terdekat + $minDist = '-'; + if (count($allMasjid) > 0) { + $dists = array_map(fn($m) => haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']), $allMasjid); + $minDist = min($dists); + } + + $statusTxt = $covered + ? 'Terjangkau' + : 'Tidak Terjangkau'; + $masjidNames = $covered ? implode(', ', array_column($covers, 'nama_masjid')) : '-'; + $bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb'; + + $pkhTxt = $kk['pkh'] ? '' : '-'; + $bpntTxt = $kk['bpnt'] ? '' : '-'; + $bltTxt = $kk['blt'] ? '' : '-'; + + $rowsKK .= " + + " . ($i+1) . " + {$kk['nama_kk']} + {$kk['jumlah_kk']} + {$kk['alamat']} + {$pkhTxt} + {$bpntTxt} + {$bltTxt} + {$statusTxt} + {$masjidNames} + {$minDist} + "; +} +if (!$rowsKK) $rowsKK = "Belum ada data kemiskinan."; + +// ── Template HTML untuk mPDF ────────────────────────────────────────────── +$html = << + body { font-family: sans-serif; font-size: 11px; color: #222; } + h1 { font-size: 16px; text-align: center; margin-bottom: 2px; color: #1a3a5c; } + .subtitle { text-align: center; font-size: 10px; color: #555; margin-bottom: 16px; } + .section { font-size: 12px; font-weight: bold; color: #1a3a5c; + border-bottom: 2px solid #1a3a5c; padding-bottom: 3px; margin: 14px 0 8px; } + table { width: 100%; border-collapse: collapse; font-size: 10px; } + th { background: #1a3a5c; color: white; padding: 6px 5px; text-align: center; } + td { padding: 5px; border-bottom: 1px solid #e0e0e0; vertical-align: top; } + .stat-grid { width: 100%; border-collapse: collapse; margin-bottom: 4px; } + .stat-cell { width: 25%; padding: 8px; text-align: center; border: 1px solid #ddd; } + .stat-big { font-size: 22px; font-weight: bold; } + .stat-lbl { font-size: 9px; color: #666; margin-top: 2px; } + .green { color: #1a7a3e; } + .red { color: #c0392b; } + + +

Laporan Analisis Kemiskinan & Jangkauan Masjid

+
Tanggal Cetak: {$tgl}  |  Sumber Data: Sistem WebGIS
+ +
Ringkasan Statistik
+ + + + + + + +
+
{$totalKK}
+
Total KK Miskin
+
+
{$totalJiwa}
+
Total Jiwa
+
+
{$countT} ({$pct}%)
+
KK Terjangkau • {$jiwaT} jiwa
+
+
{$countTT} ({$pctTT}%)
+
KK Tidak Terjangkau • {$jiwaTT} jiwa
+
+ +
Statistik per Masjid
+ + + + + + + + + + + {$rowsMasjid} +
NoNama MasjidRadius (m)KK TerjangkauJiwa Terjangkau
+ +
Daftar KK Miskin & Status Jangkauan
+ + + + + + + + + + + + + + + + {$rowsKK} +
NoNama KKJiwaAlamatPKHBPNTBLTStatusDijangkau MasjidJarak Terdekat (m)
+HTML; + +// ── Generate PDF ────────────────────────────────────────────────────────── +try { + $mpdf = new \Mpdf\Mpdf([ + 'mode' => 'utf-8', + 'format' => 'A4-L', // landscape untuk tabel lebar + 'margin_top' => 14, + 'margin_bottom' => 14, + 'margin_left' => 12, + 'margin_right' => 12, + ]); + $mpdf->SetTitle('Laporan Analisis Kemiskinan & Jangkauan Masjid'); + $mpdf->WriteHTML($html); + $fname = 'laporan_kemiskinan_' . date('Y-m-d') . '.pdf'; + $mpdf->Output($fname, 'D'); // 'D' = force download +} catch (\Mpdf\MpdfException $e) { + http_response_code(500); + echo '
Gagal membuat PDF: ' . htmlspecialchars($e->getMessage()) . "\n\n"
+       . "Pastikan extension PHP GD aktif di XAMPP:\n"
+       . "  1. Buka C:\\xampp\\php\\php.ini\n"
+       . "  2. Cari ;extension=gd dan hapus tanda titik koma\n"
+       . "  3. Restart Apache di XAMPP Control Panel
"; +} diff --git a/uas/get_all_data.php b/uas/get_all_data.php new file mode 100644 index 0000000..6d05be9 --- /dev/null +++ b/uas/get_all_data.php @@ -0,0 +1,15 @@ + mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM spbu"), MYSQLI_ASSOC), + 'jalan' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM jalan"), MYSQLI_ASSOC), + 'persil' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM persil"), MYSQLI_ASSOC), + 'kemiskinan'=> mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM kemiskinan"),MYSQLI_ASSOC), + 'masjid' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM masjid"), MYSQLI_ASSOC), + 'wilayah' => mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM wilayah"), MYSQLI_ASSOC), +]; + +header('Content-Type: application/json'); +echo json_encode($data); +?> \ No newline at end of file diff --git a/uas/get_all_spasial.php b/uas/get_all_spasial.php new file mode 100644 index 0000000..e69de29 diff --git a/uas/hapus.php b/uas/hapus.php new file mode 100644 index 0000000..66f7170 --- /dev/null +++ b/uas/hapus.php @@ -0,0 +1,18 @@ + 'error', 'message' => 'Request tidak valid.']); + exit; +} + +$q = "DELETE FROM `{$type}` WHERE id = {$id}"; +if (mysqli_query($conn, $q)) { + echo json_encode(['status' => 'success']); +} else { + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} diff --git a/uas/hapus_spbu.php b/uas/hapus_spbu.php new file mode 100644 index 0000000..6630e62 --- /dev/null +++ b/uas/hapus_spbu.php @@ -0,0 +1,14 @@ + 'success']); + } else { + echo json_encode(['status' => 'error']); + } +} +?> \ No newline at end of file diff --git a/uas/index.php b/uas/index.php new file mode 100644 index 0000000..d1907aa --- /dev/null +++ b/uas/index.php @@ -0,0 +1,877 @@ + + + + + + +GeoMiskin — WebGIS Sebaran Penduduk Miskin + + + + + + + + + + + +
+ +
Klik di peta untuk memilih lokasi — tekan Esc untuk batal
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/uas/koneksi.php b/uas/koneksi.php new file mode 100644 index 0000000..a6f68d0 --- /dev/null +++ b/uas/koneksi.php @@ -0,0 +1,10 @@ + 'error', 'message' => 'Koneksi DB gagal: ' . mysqli_connect_error()])); +} +mysqli_set_charset($conn, 'utf8mb4'); diff --git a/uas/login.php b/uas/login.php new file mode 100644 index 0000000..dc5a5da --- /dev/null +++ b/uas/login.php @@ -0,0 +1,254 @@ +true,'nama'=>$row['nama']] : ['ok'=>false]); + exit; +} + +// ── AJAX: reset password ──────────────────────────────────────── +if (($_POST['action'] ?? '') === 'reset_password') { + $u = trim($_POST['username'] ?? ''); + $pass = $_POST['new_password'] ?? ''; + if (strlen($pass) < 6) { echo json_encode(['ok'=>false,'msg'=>'Password minimal 6 karakter']); exit; } + $hash = password_hash($pass, PASSWORD_DEFAULT); + $st = mysqli_prepare($conn, "UPDATE users SET password=? WHERE username=?"); + mysqli_stmt_bind_param($st, 'ss', $hash, $u); + mysqli_stmt_execute($st); + echo json_encode(mysqli_stmt_affected_rows($st) > 0 + ? ['ok'=>true] + : ['ok'=>false,'msg'=>'Username tidak ditemukan']); + exit; +} + +// ── Login biasa ───────────────────────────────────────────────── +$error = ''; +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = trim($_POST['username'] ?? ''); + $password = $_POST['password'] ?? ''; + $stmt = mysqli_prepare($conn, "SELECT id, username, password, nama, role FROM users WHERE username = ?"); + mysqli_stmt_bind_param($stmt, 's', $username); + mysqli_stmt_execute($stmt); + $user = mysqli_fetch_assoc(mysqli_stmt_get_result($stmt)); + if ($user && password_verify($password, $user['password'])) { + $_SESSION['user'] = $user; + header('Location: index.php'); + exit; + } + $error = 'Username atau password salah.'; +} +?> + + + + + +Login — GeoMiskin + + + + +
+ + +
+ + + +
+ + +
+ + +
+
+ + +
+
+ + +
+ + +
+ +

Default: admin / admin123

+
+ + + Kembali ke Portal + +
+
+ + +
+ +
+ + + + diff --git a/uas/logout.php b/uas/logout.php new file mode 100644 index 0000000..37bc5ab --- /dev/null +++ b/uas/logout.php @@ -0,0 +1,5 @@ + 'success']); +?> \ No newline at end of file diff --git a/uas/simpan_kemiskinan.php b/uas/simpan_kemiskinan.php new file mode 100644 index 0000000..023a12f --- /dev/null +++ b/uas/simpan_kemiskinan.php @@ -0,0 +1,34 @@ + 'success']); +} else { + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} diff --git a/uas/simpan_masjid.php b/uas/simpan_masjid.php new file mode 100644 index 0000000..a40c7ac --- /dev/null +++ b/uas/simpan_masjid.php @@ -0,0 +1,6 @@ + 'success']); +?> \ No newline at end of file diff --git a/uas/simpan_persil.php b/uas/simpan_persil.php new file mode 100644 index 0000000..ed678e0 --- /dev/null +++ b/uas/simpan_persil.php @@ -0,0 +1,20 @@ + 'success']); +} else { + // Jika gagal, tampilkan error MySQL-nya biar ketahuan apa yang salah + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} +?> \ No newline at end of file diff --git a/uas/simpan_spbu.php b/uas/simpan_spbu.php new file mode 100644 index 0000000..4da5359 --- /dev/null +++ b/uas/simpan_spbu.php @@ -0,0 +1,14 @@ + 'success']); +// Tambahkan baris ini di file simpan_*.php kamu +$query = "INSERT INTO ... "; // query kamu +if (mysqli_query($conn, $query)) { + echo json_encode(['status' => 'success']); +} else { + // Ini akan menampilkan pesan error asli dari MySQL + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} +?> \ No newline at end of file diff --git a/uas/simpan_wilayah.php b/uas/simpan_wilayah.php new file mode 100644 index 0000000..5ae5f0c --- /dev/null +++ b/uas/simpan_wilayah.php @@ -0,0 +1,17 @@ + 'error', 'message' => 'Data tidak lengkap']); exit; +} + +$q = "INSERT INTO wilayah (nama, tipe, geojson) + VALUES ('$nama', '$tipe', '" . mysqli_real_escape_string($conn, $geojson) . "')"; + +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/uas/test_db.php b/uas/test_db.php new file mode 100644 index 0000000..fafa767 --- /dev/null +++ b/uas/test_db.php @@ -0,0 +1,13 @@ + 'error']); exit; } + +function esc($conn, $v) { return mysqli_real_escape_string($conn, trim($v)); } + +switch ($type) { + case 'spbu': + $nama = esc($conn, $_POST['nama'] ?? ''); + $nomor = esc($conn, $_POST['nomor'] ?? ''); + $status = in_array($_POST['status'] ?? '', ['Ya', 'Tidak']) ? $_POST['status'] : 'Tidak'; + $q = "UPDATE spbu SET nama='$nama', nomor='$nomor', status_24jam='$status' WHERE id=$id"; + break; + + case 'kemiskinan': + $nama_kk = esc($conn, $_POST['nama_kk'] ?? ''); + $jumlah_kk = max(1, (int)($_POST['jumlah_kk'] ?? 1)); + $alamat = esc($conn, $_POST['alamat'] ?? ''); + $pkh = isset($_POST['pkh']) && $_POST['pkh'] == '1' ? 1 : 0; + $bpnt = isset($_POST['bpnt']) && $_POST['bpnt'] == '1' ? 1 : 0; + $blt = isset($_POST['blt']) && $_POST['blt'] == '1' ? 1 : 0; + + $fotoSet = ''; + if (!empty($_FILES['foto']['name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) { + $ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION)); + $allowed = ['jpg', 'jpeg', 'png', 'webp']; + if (in_array($ext, $allowed) && $_FILES['foto']['size'] <= 5 * 1024 * 1024) { + $dir = __DIR__ . '/uploads/kemiskinan/'; + if (!is_dir($dir)) mkdir($dir, 0755, true); + $fname = 'kk_' . $id . '_' . time() . '.' . $ext; + if (move_uploaded_file($_FILES['foto']['tmp_name'], $dir . $fname)) { + $fotoSet = ", foto='" . mysqli_real_escape_string($conn, $fname) . "'"; + } + } + } + + $q = "UPDATE kemiskinan SET nama_kk='$nama_kk', jumlah_kk=$jumlah_kk, alamat='$alamat', + pkh=$pkh, bpnt=$bpnt, blt=$blt{$fotoSet} WHERE id=$id"; + break; + + case 'masjid': + $nama = esc($conn, $_POST['nama_masjid'] ?? ''); + $pic = esc($conn, $_POST['pic'] ?? ''); + $alamat = esc($conn, $_POST['alamat'] ?? ''); + $q = "UPDATE masjid SET nama_masjid='$nama', pic='$pic', alamat='$alamat' WHERE id=$id"; + break; + + case 'jalan': + $nama = esc($conn, $_POST['nama_jalan'] ?? ''); + $status = in_array($_POST['status_jalan'] ?? '', ['Nasional', 'Provinsi', 'Kabupaten']) + ? $_POST['status_jalan'] : 'Kabupaten'; + $q = "UPDATE jalan SET nama_jalan='$nama', status_jalan='$status' WHERE id=$id"; + break; + + case 'persil': + $nomor = esc($conn, $_POST['nomor_persil'] ?? ''); + $status = in_array($_POST['status_milik'] ?? '', ['SHM', 'HGB', 'HGU', 'HP']) + ? $_POST['status_milik'] : 'SHM'; + $q = "UPDATE persil SET nomor_persil='$nomor', status_milik='$status' WHERE id=$id"; + break; + + case 'wilayah': + $nama = esc($conn, $_POST['nama'] ?? ''); + $tipe = in_array($_POST['tipe'] ?? '', ['RT','RW','Kelurahan','Kecamatan']) + ? $_POST['tipe'] : 'RT'; + $q = "UPDATE wilayah SET nama='$nama', tipe='$tipe' WHERE id=$id"; + break; + + default: + echo json_encode(['status' => 'error', 'message' => 'Tipe tidak dikenal.']); exit; +} + +if (mysqli_query($conn, $q)) { + echo json_encode(['status' => 'success']); +} else { + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} diff --git a/uas/update_radius_masjid.php b/uas/update_radius_masjid.php new file mode 100644 index 0000000..f54a64e --- /dev/null +++ b/uas/update_radius_masjid.php @@ -0,0 +1,16 @@ + 'success']); +} else { + echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); +} +?> \ No newline at end of file diff --git a/uas/users.php b/uas/users.php new file mode 100644 index 0000000..77596f0 --- /dev/null +++ b/uas/users.php @@ -0,0 +1,378 @@ + + + + + + +Manajemen Pengguna — GeoMiskin + + + + + +
+ +
+
+ Kembali +
+
Manajemen Pengguna
+
Login sebagai: ()
+
+
+ + + +
+ + +
Hanya superadmin yang dapat mengelola pengguna. Role kamu:
+ + +
+ + + + + + + + + + + + +
PenggunaRoleBergabungAksi
+ Memuat data... +
+
+
+ +
+ + + +
+ +
+ + + + + diff --git a/update_data.php b/update_data.php new file mode 100644 index 0000000..0608cc4 --- /dev/null +++ b/update_data.php @@ -0,0 +1,91 @@ + 'error', 'message' => 'ID tidak valid']); exit; } + +function esc($conn, $v) { return mysqli_real_escape_string($conn, trim((string)$v)); } + +switch ($type) { + case 'spbu': + $nama = esc($conn, $_POST['nama'] ?? ''); + $nomor = esc($conn, $_POST['nomor'] ?? ''); + $status = in_array($_POST['status'] ?? '', ['Ya','Tidak']) ? $_POST['status'] : 'Tidak'; + $lat = (float)($_POST['lat'] ?? 0); + $lng = (float)($_POST['lng'] ?? 0); + $q = "UPDATE spbu SET nama='$nama', nomor='$nomor', status_24jam='$status', lat=$lat, lng=$lng WHERE id=$id"; + break; + + case 'kemiskinan': + $nama_kk = esc($conn, $_POST['nama_kk'] ?? ''); + $jumlah_kk = max(1, (int)($_POST['jumlah_kk'] ?? 1)); + $alamat = esc($conn, $_POST['alamat'] ?? ''); + $lat = (float)($_POST['lat'] ?? 0); + $lng = (float)($_POST['lng'] ?? 0); + $pkh = (isset($_POST['pkh']) && $_POST['pkh'] == '1') ? 1 : 0; + $bpnt = (isset($_POST['bpnt']) && $_POST['bpnt'] == '1') ? 1 : 0; + $blt = (isset($_POST['blt']) && $_POST['blt'] == '1') ? 1 : 0; + + $fotoSet = ''; + if (!empty($_FILES['foto']['name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) { + $ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION)); + $allowed = ['jpg','jpeg','png','webp']; + if (in_array($ext, $allowed) && $_FILES['foto']['size'] <= 5*1024*1024) { + $dir = __DIR__ . '/uploads/kemiskinan/'; + if (!is_dir($dir)) mkdir($dir, 0755, true); + $fname = 'kk_' . $id . '_' . time() . '.' . $ext; + if (move_uploaded_file($_FILES['foto']['tmp_name'], $dir.$fname)) { + $fotoSet = ", foto='" . esc($conn, $fname) . "'"; + } + } + } + $q = "UPDATE kemiskinan SET nama_kk='$nama_kk', jumlah_kk=$jumlah_kk, alamat='$alamat', + lat=$lat, lng=$lng, pkh=$pkh, bpnt=$bpnt, blt=$blt{$fotoSet} WHERE id=$id"; + break; + + case 'masjid': + $nama = esc($conn, $_POST['nama_masjid'] ?? ''); + $pic = esc($conn, $_POST['pic'] ?? ''); + $alamat = esc($conn, $_POST['alamat'] ?? ''); + $radius = max(50, min(5000, (int)($_POST['radius_m'] ?? 300))); + $lat = (float)($_POST['lat'] ?? 0); + $lng = (float)($_POST['lng'] ?? 0); + $q = "UPDATE masjid SET nama_masjid='$nama', pic='$pic', alamat='$alamat', + radius_m=$radius, lat=$lat, lng=$lng WHERE id=$id"; + break; + + case 'jalan': + $nama = esc($conn, $_POST['nama_jalan'] ?? ''); + $status = in_array($_POST['status_jalan']??'', ['Nasional','Provinsi','Kabupaten']) ? $_POST['status_jalan'] : 'Kabupaten'; + $panjang = (float)($_POST['panjang'] ?? 0); + $geojson = esc($conn, $_POST['geojson'] ?? ''); + $q = "UPDATE jalan SET nama_jalan='$nama', status_jalan='$status', panjang_m=$panjang" . + ($geojson ? ", geojson='$geojson'" : '') . " WHERE id=$id"; + break; + + case 'persil': + $nomor = esc($conn, $_POST['nomor_persil'] ?? ''); + $status = in_array($_POST['status_milik']??'', ['SHM','HGB','HGU','HP']) ? $_POST['status_milik'] : 'SHM'; + $luas = (float)($_POST['luas'] ?? 0); + $geojson = esc($conn, $_POST['geojson'] ?? ''); + $q = "UPDATE persil SET nomor_persil='$nomor', status_milik='$status', luas_m2=$luas" . + ($geojson ? ", geojson='$geojson'" : '') . " WHERE id=$id"; + break; + + case 'wilayah': + $nama = esc($conn, $_POST['nama'] ?? ''); + $tipe = in_array($_POST['tipe']??'', ['RT','RW','Kelurahan','Kecamatan']) ? $_POST['tipe'] : 'RT'; + $geojson = esc($conn, $_POST['geojson'] ?? ''); + $q = "UPDATE wilayah SET nama='$nama', tipe='$tipe'" . + ($geojson ? ", geojson='$geojson'" : '') . " WHERE id=$id"; + break; + + default: + echo json_encode(['status' => 'error', 'message' => 'Tipe tidak dikenal.']); exit; +} + +if (mysqli_query($conn, $q)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]); diff --git a/update_radius_masjid.php b/update_radius_masjid.php new file mode 100644 index 0000000..4cd8d9e --- /dev/null +++ b/update_radius_masjid.php @@ -0,0 +1,12 @@ +'error','message'=>'ID tidak valid']); exit; } + +$query = "UPDATE masjid SET radius_m = $radius_baru WHERE id = $id"; +if (mysqli_query($conn, $query)) echo json_encode(['status' => 'success']); +else echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);