fitur kelola bantuan melalui rumah ibadah

This commit is contained in:
Farha
2026-04-20 11:54:58 +07:00
parent 6408b0447a
commit bae0569701
13 changed files with 2487 additions and 33 deletions
+385
View File
@@ -0,0 +1,385 @@
# 📍 PANDUAN FITUR GIS PONTIANAK
## Setup Awal
### 1. Inisialisasi Database
```bash
# Buka di browser:
http://localhost/web-gis/create_tables.php
```
**Respons berhasil:**
```json
{
"status": "success",
"message": "Tabel berhasil dibuat atau sudah ada"
}
```
Ini akan membuat 3 tabel baru:
- `rumah_ibadah` - Lokasi tempat ibadah
- `penduduk_miskin` - Lokasi keluarga kurang mampu
- `jalan_rusak` - Laporan jalan rusak
---
## Fitur 1: RUMAH IBADAH (Tempat Ibadah)
### Cara Menggunakan:
**Step 1:** Buka GIS Interface
```
http://localhost/web-gis/index.html
```
**Step 2:** Klik tombol "Rumah Ibadah" di toolbar
- Ikon: 🏛️ (bangunan dengan tanda plus)
- Di toolbar utama sebelah atas map
**Step 3:** Klik di mana saja di peta
- Status akan berubah: "Klik Peta untuk menambah lokasi..."
- Modal dialog akan muncul
**Step 4:** Isi form:
```
┌─────────────────────────────────────┐
│ INPUT RUMAH IBADAH │
├─────────────────────────────────────┤
│ Nama : [Masjid Al-Nur] │
│ Alamat : [Auto-fill] │ ← Otomatis dari koordinat
│ PIC (Nama) : [Pengurus] │
│ Radius Pencarian: [1000] meter │ ← Geser slider
│ Koordinat : -0.1234, 109.3456 │
│ │
│ [SIMPAN] [BATAL] │
└─────────────────────────────────────┘
```
**Field Penjelasan:**
- **Nama:** Nama tempat ibadah (Masjid/Gereja/dll)
- **Alamat:** Otomatis terisi pakai Nominatim (Reverse Geocoding)
- Tunggu pesan "Mencari alamat..." hingga selesai
- **PIC:** Nama Pengurus/Koordinator
- **Radius:** Jarak pencarian untuk keluarga miskin sekitar (default 1000m)
**Step 5:** Klik "SIMPAN"
- Marker teal + lingkaran akan muncul di peta
- Data tersimpan di database
### Interaksi Lanjutan:
**Edit:**
1. Klik marker di peta
2. Popup muncul → Klik "Edit"
3. Modal akan terbuka dengan data lama
4. Ubah dan klik "Simpan"
**Hapus:**
1. Klik marker
2. Popup → Klik "Hapus"
3. Konfirmasi → Data terhapus
**Cari:**
- Gunakan Registry Drawer (kanan peta)
- Filter: Input Type = "Rumah Ibadah"
- Cari nama atau ketik untuk filter
---
## Fitur 2: PENDUDUK MISKIN (Keluarga Kurang Mampu)
### Cara Menggunakan:
**Step 1:** Klik tombol "Penduduk Miskin" di toolbar
- Ikon: 👤 (orang dengan tanda plus)
**Step 2:** Klik di peta untuk lokasi keluarga miskin
**Step 3:** Isi form:
```
┌─────────────────────────────────────────────┐
│ INPUT PENDUDUK MISKIN │
├─────────────────────────────────────────────┤
│ Nama Ketua KK : [Nama Kepala Keluarga]│
│ Jumlah Anggota : [5] │
│ Rumah Ibadah Terdekat: [OTOMATIS] │
│ Nama : Masjid Al-Nur │ ← Sistem mencari
│ Jarak : 234 meter │ otomatis
│ Koordinat : -0.1234, 109.3456 │
│ │
│ [SIMPAN] [BATAL] │
└─────────────────────────────────────────────┘
```
**Field Penjelasan:**
- **Nama Ketua KK:** Nama kepala keluarga yang bertanggung jawab
- **Jumlah Anggota:** Total orang dalam keluarga termasuk ketua
- **Rumah Ibadah Terdekat:** Sistem otomatis menemukan tempat ibadah terdekat
- Nama dan jarak ditampilkan (berdasarkan radius yang Anda set di rumah ibadah)
- TIDAK perlu isi manual
**Step 4:** Klik "SIMPAN"
- Marker merah akan muncul
- Data tersimpan beserta referensi rumah ibadah terdekat
### Fitur Khusus: Pencarian Miskin Sekitar Rumah Ibadah
**Gunakan tombol di popup Rumah Ibadah:**
1. Klik marker Rumah Ibadah di peta
2. Di popup klik: **"Cari Miskin"**
3. Semua keluarga miskin dalam radius akan ter-zoom
4. Map akan fit ke cluster keluarga miskin
---
## Fitur 3: CHOROPLETH KECAMATAN (Peta Populasi)
### Cara Menggunakan:
**Step 1:** Klik tombol "Choropleth" di toolbar
- Ikon: 🗺️ (peta)
**Step 2:** Peta akan berubah
- Menampilkan 6 kecamatan Pontianak
- Warna berbeda sesuai populasi
**Gradasi Warna:**
```
🔴 Merah Tua = Populasi > 120,000 jiwa
🟥 Merah Terang = Populasi > 100,000 jiwa
🟨 Merah Muda = Populasi > 80,000 jiwa
🟧 Merah Pucat = Populasi ≤ 80,000 jiwa
```
**Step 3:** Klik salah satu kecamatan
- Panel di kanan bawah muncul:
```
┌──────────────────────────────────┐
│ Choropleth Kecamatan Pontianak │
├──────────────────────────────────┤
│ PONTIANAK BARAT │
│ 148,800 jiwa │
│ │
│ Kepadatan: 2,500 jiwa/km² │
└──────────────────────────────────┘
```
**Data Populasi (BPS 2025):**
| # | Kecamatan | Populasi | Warna |
| --- | ------------------ | -------- | ----- |
| 1 | Pontianak Barat | 148,800 | 🔴 |
| 2 | Pontianak Selatan | 133,600 | 🔴 |
| 3 | Pontianak Utara | 129,200 | 🔴 |
| 4 | Pontianak Timur | 121,500 | 🔴 |
| 5 | Pontianak Kota | 98,500 | 🟥 |
| 6 | Pontianak Tenggara | 52,900 | 🟧 |
**Fitur Interaksi:**
- **Hover:** Kecamatan akan highlight
- **Klik:** Info populasi muncul di panel
- **Keluar Mode:** Klik tombol lain atau click tombol Choropleth lagi
---
## Layer Control (Kontrol Visibilitas)
### Tombol Layer (atas kiri map):
Klik ikon **☰** untuk membuka layer control.
```
✓ Choropleth Kecamatan
✓ Rumah Ibadah
✓ Penduduk Miskin
✓ Jalan Rusak
✓ Lokasi SPBU
... (layer lainnya)
```
**Toggle on/off:**
- Centang untuk tampilkan layer
- Uncheck untuk sembunyikan
**Catatan:** Saat aktif mode Choropleth, layer lain otomatis disembunyikan untuk fokus pada peta kecamatan.
---
## Registry / Pencarian
### Buka Registry Drawer:
Klik tombol **📋** di toolbar atau panel kanan.
### Cara Mencari:
**1. Filter by Type (Dropdown atas):**
```
Input Type:
▼ Semua
□ Point
□ Polyline
□ Polygon
□ Rumah Ibadah ← Pilih ini
□ Penduduk Miskin ← atau ini
□ Jalan Rusak
```
**2. Filter by Registry (Dropdown bawah):**
```
Registry Type:
▼ Semua
□ Jalan Nasional
□ Parkir
□ Rumah Ibadah
□ Penduduk Miskin
```
**3. Cari (Kolom input):**
```
Cari nama: [Masjid] 🔍
Hasil:
┌─────────────────────────────┐
│ ✓ Masjid Al-Nur │
│ Rumah Ibadah │
│ -0.1234, 109.3456 │
├─────────────────────────────┤
│ ✓ Masjid Besar Pontianak │
│ Rumah Ibadah │
│ -0.1245, 109.3467 │
└─────────────────────────────┘
```
**Klik hasil → Map akan:**
- Zoom ke lokasi
- Buka popup
- Highlight marker
---
## Troubleshooting
### ❌ Tombol tidak muncul
**Solusi:**
- Refresh browser (Ctrl+F5)
- Clear cache
- Cek console (F12) untuk error
### ❌ Alamat tidak terisi (Reverse Geocode gagal)
**Penyebab:** Tidak ada koneksi internet atau Nominatim API timeout
**Solusi:**
- Pastikan internet terhubung
- Coba refresh modal
- Manual isi alamat
### ❌ Rumah Ibadah tidak ditemukan saat input miskin
**Penyebab:** Belum ada data rumah ibadah, atau lokasi terlalu jauh
**Solusi:**
- Tambahkan rumah ibadah terlebih dahulu di mode Rumah Ibadah
- Pastikan radius rumah ibadah cukup besar
### ❌ Choropleth tidak tampil
**Penyebab:** File GeoJSON tidak ditemukan
**Solusi:**
- Cek file ada di: `data/pontianak-kecamatan.geojson`
- Cek console untuk error message
### ❌ Database error
**Solusi:**
- Jalankan ulang `create_tables.php`
- Cek koneksi database di `db_config.php`
---
## Tips & Trik
### 💡 Shortcut
- **Double-click** marker untuk langsung edit
- **Scroll zoom** di map untuk zoom in/out
- **Drag** untuk geser peta
- **Ctrl+A** di search untuk select all results
### 📊 Export Data
Semua data tersimpan di database MySQL. Untuk export:
```sql
SELECT * FROM rumah_ibadah;
SELECT * FROM penduduk_miskin;
```
### 🎨 Customize Warna
Edit di `index.html` function:
- `getPopulationFill()` - Ubah warna choropleth
- `createRumahIbadahIcon()` - Ubah marker rumah ibadah
- `createPendudukMiskinIcon()` - Ubah marker miskin
---
## File Penting
```
web-gis/
├── index.html ← Main GIS Interface
├── create_tables.php ← Init database
├── db_config.php ← Database config
├── api/
│ ├── save_rumah_ibadah.php
│ ├── get_rumah_ibadah.php
│ ├── delete_rumah_ibadah.php
│ ├── save_penduduk_miskin.php
│ ├── get_penduduk_miskin.php
│ └── delete_penduduk_miskin.php
└── data/
├── pontianak-kecamatan.geojson ← Choropleth data
└── Export_Output_2.json ← Road network data
```
---
**Versi:** 2025-04-20
**Status:** Production Ready ✅
+46 -7
View File
@@ -1,8 +1,9 @@
<?php
// Script untuk membuat tabel jalan_rusak jika belum ada
// Script untuk membuat tabel utama jika belum ada
include 'db_config.php';
$sql = "CREATE TABLE IF NOT EXISTS jalan_rusak (
$statements = [
"CREATE TABLE IF NOT EXISTS jalan_rusak (
id INT AUTO_INCREMENT PRIMARY KEY,
latitude DECIMAL(10, 8) NOT NULL,
longitude DECIMAL(11, 8) NOT NULL,
@@ -12,13 +13,51 @@ $sql = "CREATE TABLE IF NOT EXISTS jalan_rusak (
gambar VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_coords (latitude, longitude)
)";
)",
"CREATE TABLE IF NOT EXISTS rumah_ibadah (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(255) NOT NULL,
alamat TEXT NOT NULL,
pic VARCHAR(255) NOT NULL,
latitude DECIMAL(10, 8) NOT NULL,
longitude DECIMAL(11, 8) NOT NULL,
radius_m INT NOT NULL DEFAULT 1000,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_rumah_ibadah_coords (latitude, longitude)
)",
"CREATE TABLE IF NOT EXISTS penduduk_miskin (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_ketua_kk VARCHAR(255) NOT NULL,
jumlah_anggota INT NOT NULL,
latitude DECIMAL(10, 8) NOT NULL,
longitude DECIMAL(11, 8) NOT NULL,
rumah_ibadah_id INT DEFAULT NULL,
rumah_ibadah_nama VARCHAR(255) DEFAULT NULL,
jarak_ke_rumah_ibadah_m DECIMAL(10, 2) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_penduduk_coords (latitude, longitude),
INDEX idx_penduduk_rumah_ibadah (rumah_ibadah_id)
)"
];
if ($conn->query($sql) === TRUE) {
echo json_encode(['status' => 'success', 'message' => 'Tabel jalan_rusak berhasil dibuat atau sudah ada']);
} else {
echo json_encode(['status' => 'error', 'message' => $conn->error]);
foreach ($statements as $sql) {
if ($conn->query($sql) !== TRUE) {
echo json_encode(['status' => 'error', 'message' => $conn->error]);
$conn->close();
exit;
}
}
$alterStatements = [
"ALTER TABLE penduduk_miskin ADD COLUMN bantuan_status VARCHAR(20) NOT NULL DEFAULT 'belum' AFTER jarak_ke_rumah_ibadah_m",
"ALTER TABLE penduduk_miskin ADD COLUMN bantuan_catatan TEXT DEFAULT NULL AFTER bantuan_status",
"ALTER TABLE penduduk_miskin ADD COLUMN bantuan_updated_at TIMESTAMP NULL DEFAULT NULL AFTER bantuan_catatan"
];
foreach ($alterStatements as $alterSql) {
$conn->query($alterSql);
}
echo json_encode(['status' => 'success', 'message' => 'Tabel berhasil dibuat atau sudah ada']);
$conn->close();
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
$stmt = $conn->prepare("DELETE FROM penduduk_miskin WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin dihapus']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
$stmt = $conn->prepare("DELETE FROM rumah_ibadah WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah dihapus']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+34
View File
@@ -0,0 +1,34 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
$sql = "SELECT id, nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m, bantuan_status, bantuan_catatan, bantuan_updated_at, created_at
FROM penduduk_miskin
ORDER BY created_at DESC";
$result = $conn->query($sql);
$data = [];
if (!$result) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
$conn->close();
exit;
}
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['jumlah_anggota'] = (int)$row['jumlah_anggota'];
$row['rumah_ibadah_id'] = $row['rumah_ibadah_id'] !== null ? (int)$row['rumah_ibadah_id'] : null;
$row['jarak_ke_rumah_ibadah_m'] = $row['jarak_ke_rumah_ibadah_m'] !== null ? (float)$row['jarak_ke_rumah_ibadah_m'] : null;
$row['bantuan_status'] = $row['bantuan_status'] ?: 'belum';
$row['bantuan_catatan'] = $row['bantuan_catatan'] ?? '';
$data[] = $row;
}
}
echo json_encode($data);
$conn->close();
?>
+30
View File
@@ -0,0 +1,30 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
$sql = "SELECT id, nama, alamat, pic, latitude, longitude, radius_m, created_at
FROM rumah_ibadah
ORDER BY created_at DESC";
$result = $conn->query($sql);
$data = [];
if (!$result) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
$conn->close();
exit;
}
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['radius_m'] = (int)$row['radius_m'];
$data[] = $row;
}
}
echo json_encode($data);
$conn->close();
?>
+1381 -26
View File
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000;
$toRadians = function ($value) {
return $value * M_PI / 180;
};
$deltaLat = $toRadians($lat2 - $lat1);
$deltaLng = $toRadians($lng2 - $lng1);
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
}
function findNearestRumahIbadah($conn, $lat, $lng) {
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
if (!$result || $result->num_rows === 0) {
return null;
}
$nearest = null;
while ($row = $result->fetch_assoc()) {
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
if ($nearest === null || $distance < $nearest['distance']) {
$nearest = [
'id' => (int)$row['id'],
'nama' => $row['nama'],
'distance' => $distance
];
}
}
return $nearest;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
if ($namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
if (!$nearest) {
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
$conn->close();
exit;
}
$jarak = $nearest['distance'];
$rumahIbadahId = $nearest['id'];
$rumahIbadahNama = $nearest['nama'];
$stmt = $conn->prepare("INSERT INTO penduduk_miskin (nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param("siddisd", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Penduduk miskin berhasil disimpan']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nama = trim($_POST['nama'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$pic = trim($_POST['pic'] ?? '');
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$radius = $_POST['radius_m'] ?? 1000;
if ($nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$radius = max(50, (int)$radius);
$stmt = $conn->prepare("INSERT INTO rumah_ibadah (nama, alamat, pic, latitude, longitude, radius_m) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->bind_param("sssddi", $nama, $alamat, $pic, $lat, $lng, $radius);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil disimpan']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+278
View File
@@ -0,0 +1,278 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test GIS Features</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #f1f5f9; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
.test-section { background: white; margin-bottom: 20px; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
h2 { color: #1e293b; margin-bottom: 15px; border-bottom: 2px solid #3b82f6; padding-bottom: 10px; }
.test-item { margin: 15px 0; padding: 15px; background: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 4px; }
.status { font-weight: bold; padding: 5px 10px; border-radius: 4px; display: inline-block; margin-top: 10px; }
.success { background: #dcfce7; color: #166534; }
.error { background: #fee2e2; color: #991b1b; }
.info { background: #dbeafe; color: #1e40af; }
button { background: #3b82f6; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: bold; margin: 5px; }
button:hover { background: #2563eb; }
.response { background: #1e293b; color: #10b981; padding: 15px; border-radius: 4px; font-family: monospace; font-size: 12px; margin-top: 10px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; }
input { padding: 8px; margin: 5px; border: 1px solid #cbd5e1; border-radius: 4px; width: 100%; max-width: 400px; }
</style>
</head>
<body>
<div class="container">
<h1 style="margin-bottom: 30px; color: #1e293b;">🔍 GIS Features Debug & Test</h1>
<!-- Test 1: Database -->
<div class="test-section">
<h2>📊 Test 1: Database Connection</h2>
<div class="test-item">
<button onclick="testDatabase()">Test Database Connection</button>
<div id="dbResult"></div>
</div>
</div>
<!-- Test 2: PHP Endpoints -->
<div class="test-section">
<h2>🔗 Test 2: Fetch PHP Endpoints</h2>
<div class="test-item">
<h4>Load Rumah Ibadah Data</h4>
<button onclick="testLoadRumahIbadah()">GET /get_rumah_ibadah.php</button>
<div id="rumahIbadahResult"></div>
</div>
<div class="test-item">
<h4>Load Penduduk Miskin Data</h4>
<button onclick="testLoadPendudukMiskin()">GET /get_penduduk_miskin.php</button>
<div id="pendudukMiskinResult"></div>
</div>
<div class="test-item">
<h4>Load Jalan Rusak Data</h4>
<button onclick="testLoadJalanRusak()">GET /get_jalan_rusak.php</button>
<div id="jalanRusakResult"></div>
</div>
</div>
<!-- Test 3: Save Operations -->
<div class="test-section">
<h2>💾 Test 3: Save Operations</h2>
<div class="test-item">
<h4>Test Save Rumah Ibadah</h4>
<input type="text" id="namaRumah" placeholder="Nama Rumah Ibadah" value="Test Masjid">
<input type="text" id="alamatRumah" placeholder="Alamat" value="Jl. Test No. 1">
<input type="text" id="picRumah" placeholder="Nama PIC" value="Test PIC">
<input type="number" id="latRumah" placeholder="Latitude" value="-0.012345" step="0.000001">
<input type="number" id="lngRumah" placeholder="Longitude" value="109.345678" step="0.000001">
<input type="number" id="radiusRumah" placeholder="Radius (m)" value="1000">
<button onclick="testSaveRumahIbadah()">Save Test Rumah Ibadah</button>
<div id="saveRumahResult"></div>
</div>
</div>
<!-- Test 4: DOM Elements -->
<div class="test-section">
<h2>🎨 Test 4: DOM Elements Check</h2>
<div class="test-item">
<button onclick="testDOMElements()">Check HTML Elements</button>
<div id="domResult"></div>
</div>
</div>
<!-- Test 5: Console Monitoring -->
<div class="test-section">
<h2>📝 Test 5: Event Listener Test</h2>
<div class="test-item">
<h4>Click Buttons and Check Console</h4>
<p>Tombol di bawah akan log ke console jika event listener berfungsi:</p>
<button id="testBtnRumahIbadah" onclick="console.log('✓ Rumah Ibadah button clicked')">Test: Rumah Ibadah</button>
<button id="testBtnPendudukMiskin" onclick="console.log('✓ Penduduk Miskin button clicked')">Test: Penduduk Miskin</button>
<button id="testBtnChoropleth" onclick="console.log('✓ Choropleth button clicked')">Test: Choropleth</button>
<p style="margin-top: 10px; color: #666; font-size: 12px;">✓ F12 → Console untuk lihat hasil</p>
</div>
</div>
<!-- Summary -->
<div class="test-section" style="background: #f0fdf4; border: 2px solid #22c55e;">
<h2 style="border-bottom-color: #22c55e;">✅ Summary</h2>
<div id="summary">Jalankan semua test di atas untuk diagnosis lengkap.</div>
</div>
</div>
<script>
let testResults = {};
function setResult(id, title, status, message, response = '') {
const resultDiv = document.getElementById(id);
let html = `<div class="status ${status}">${status.toUpperCase()}</div>`;
html += `<div style="margin-top: 10px;"><strong>${title}</strong></div>`;
html += `<div style="margin-top: 5px; color: #666;">${message}</div>`;
if (response) {
html += `<div class="response">${response}</div>`;
}
resultDiv.innerHTML = html;
testResults[id] = status;
updateSummary();
}
function updateSummary() {
const summary = document.getElementById('summary');
const results = Object.values(testResults);
const successCount = results.filter(r => r === 'success').length;
const errorCount = results.filter(r => r === 'error').length;
let html = `<p><strong>Results:</strong> ✅ ${successCount} passed | ❌ ${errorCount} failed | Total: ${results.length}</p>`;
if (errorCount > 0) {
html += `<p style="color: #991b1b; margin-top: 10px;">⚠️ Ada ${errorCount} test yang gagal. Cek detail di atas.</p>`;
} else if (successCount === results.length && results.length > 0) {
html += `<p style="color: #166534; margin-top: 10px;">✅ Semua test berhasil! Fitur seharusnya berfungsi.</p>`;
}
summary.innerHTML = html;
}
async function testDatabase() {
try {
const response = await fetch('create_tables.php', { method: 'GET' });
const text = await response.text();
if (text.includes('success')) {
setResult('dbResult', 'Database & Tables', 'success', 'Database connection OK & tables exist', text.substring(0, 500));
} else {
setResult('dbResult', 'Database & Tables', 'error', 'Response tidak mengindikasikan success', text.substring(0, 500));
}
} catch (error) {
setResult('dbResult', 'Database & Tables', 'error', error.message);
}
}
async function testLoadRumahIbadah() {
try {
const response = await fetch('get_rumah_ibadah.php');
const text = await response.text();
const data = JSON.parse(text);
setResult('rumahIbadahResult', 'Load Rumah Ibadah', 'success',
`Loaded ${data.length} records`, JSON.stringify(data, null, 2).substring(0, 1000));
} catch (error) {
setResult('rumahIbadahResult', 'Load Rumah Ibadah', 'error', error.message);
}
}
async function testLoadPendudukMiskin() {
try {
const response = await fetch('get_penduduk_miskin.php');
const text = await response.text();
const data = JSON.parse(text);
setResult('pendudukMiskinResult', 'Load Penduduk Miskin', 'success',
`Loaded ${data.length} records`, JSON.stringify(data, null, 2).substring(0, 1000));
} catch (error) {
setResult('pendudukMiskinResult', 'Load Penduduk Miskin', 'error', error.message);
}
}
async function testLoadJalanRusak() {
try {
const response = await fetch('get_jalan_rusak.php');
const text = await response.text();
const data = JSON.parse(text);
setResult('jalanRusakResult', 'Load Jalan Rusak', 'success',
`Loaded ${data.length} records`, JSON.stringify(data, null, 2).substring(0, 1000));
} catch (error) {
setResult('jalanRusakResult', 'Load Jalan Rusak', 'error', error.message);
}
}
async function testSaveRumahIbadah() {
try {
const formData = new FormData();
formData.append('nama', document.getElementById('namaRumah').value);
formData.append('alamat', document.getElementById('alamatRumah').value);
formData.append('pic', document.getElementById('picRumah').value);
formData.append('latitude', document.getElementById('latRumah').value);
formData.append('longitude', document.getElementById('lngRumah').value);
formData.append('radius_m', document.getElementById('radiusRumah').value);
const response = await fetch('save_rumah_ibadah.php', {
method: 'POST',
body: formData
});
const text = await response.text();
const data = JSON.parse(text);
if (data.status === 'success') {
setResult('saveRumahResult', 'Save Rumah Ibadah', 'success',
`Data berhasil disimpan. ID: ${data.id}`, JSON.stringify(data, null, 2));
} else {
setResult('saveRumahResult', 'Save Rumah Ibadah', 'error',
data.message, JSON.stringify(data, null, 2));
}
} catch (error) {
setResult('saveRumahResult', 'Save Rumah Ibadah', 'error', error.message);
}
}
async function testDOMElements() {
const elements = [
'modeRumahIbadahBtn',
'modePendudukMiskinBtn',
'modeChoroplethBtn',
'rumahIbadahModal',
'pendudukMiskinModal',
'choroplethPanel',
'map',
'registryDrawer'
];
let html = '<div style="margin: 10px 0;"><strong>Checking HTML Elements in index.html:</strong></div>';
try {
const response = await fetch('index.html', { cache: 'no-store' });
if (!response.ok) {
throw new Error(`Gagal memuat index.html (HTTP ${response.status})`);
}
const source = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(source, 'text/html');
let allFound = true;
elements.forEach(id => {
const elem = doc.getElementById(id);
const status = elem ? '✅' : '❌';
const color = elem ? '#166534' : '#991b1b';
html += `<div style="color: ${color}; margin: 5px 0;">${status} #${id}</div>`;
if (!elem) allFound = false;
});
if (allFound) {
setResult('domResult', 'DOM Elements', 'success', 'Semua elements ditemukan di index.html.', html);
} else {
setResult('domResult', 'DOM Elements', 'error', 'Ada elements yang tidak ditemukan di index.html.', html);
}
} catch (error) {
setResult('domResult', 'DOM Elements', 'error', `Gagal validasi DOM index.html: ${error.message}`);
}
}
// Auto check on load
window.addEventListener('load', () => {
console.log('✅ Test page loaded');
});
</script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Metode tidak diizinkan']);
$conn->close();
exit;
}
$id = (int)($_POST['id'] ?? 0);
$status = strtolower(trim($_POST['bantuan_status'] ?? 'belum'));
$catatan = trim($_POST['bantuan_catatan'] ?? '');
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
if (!in_array($status, ['belum', 'proses', 'sudah'], true)) {
$status = 'belum';
}
$stmt = $conn->prepare("UPDATE penduduk_miskin SET bantuan_status = ?, bantuan_catatan = ?, bantuan_updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->bind_param("ssi", $status, $catatan, $id);
if ($stmt->execute()) {
$updatedAtResult = $conn->query("SELECT bantuan_updated_at FROM penduduk_miskin WHERE id = " . (int)$id . " LIMIT 1");
$updatedAt = null;
if ($updatedAtResult && $updatedAtResult->num_rows > 0) {
$row = $updatedAtResult->fetch_assoc();
$updatedAt = $row['bantuan_updated_at'] ?? null;
}
echo json_encode([
'status' => 'success',
'message' => 'Status bantuan berhasil diperbarui',
'bantuan_updated_at' => $updatedAt
]);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
$conn->close();
?>
+82
View File
@@ -0,0 +1,82 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000;
$toRadians = function ($value) {
return $value * M_PI / 180;
};
$deltaLat = $toRadians($lat2 - $lat1);
$deltaLng = $toRadians($lng2 - $lng1);
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
}
function findNearestRumahIbadah($conn, $lat, $lng) {
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
if (!$result || $result->num_rows === 0) {
return null;
}
$nearest = null;
while ($row = $result->fetch_assoc()) {
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
if ($nearest === null || $distance < $nearest['distance']) {
$nearest = [
'id' => (int)$row['id'],
'nama' => $row['nama'],
'distance' => $distance
];
}
}
return $nearest;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
if ($id <= 0 || $namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
if (!$nearest) {
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
$conn->close();
exit;
}
$jarak = $nearest['distance'];
$rumahIbadahId = $nearest['id'];
$rumahIbadahNama = $nearest['nama'];
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_ketua_kk = ?, jumlah_anggota = ?, latitude = ?, longitude = ?, rumah_ibadah_id = ?, rumah_ibadah_nama = ?, jarak_ke_rumah_ibadah_m = ? WHERE id = ?");
$stmt->bind_param("siddisdi", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak, $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin berhasil diperbarui']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
header('Content-Type: application/json');
include 'db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
$nama = trim($_POST['nama'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$pic = trim($_POST['pic'] ?? '');
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$radius = $_POST['radius_m'] ?? 1000;
if ($id <= 0 || $nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$radius = max(50, (int)$radius);
$stmt = $conn->prepare("UPDATE rumah_ibadah SET nama = ?, alamat = ?, pic = ?, latitude = ?, longitude = ?, radius_m = ? WHERE id = ?");
$stmt->bind_param("sssddii", $nama, $alamat, $pic, $lat, $lng, $radius, $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil diperbarui']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>