fix : dockerfile
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# Development files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Local XAMPP/PHP Server files
|
||||||
|
error_log
|
||||||
|
access_log
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment specific configuration
|
||||||
|
# (Note: we leave koneksi.php tracked for this template,
|
||||||
|
# but usually it should be ignored in a real production repo)
|
||||||
|
# php/koneksi.php
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
# ⚡ QUICK START - Mulai Cepat dalam 5 Menit
|
||||||
|
|
||||||
|
## 🎯 Langkah 1: Ekstrak & Tempatkan File
|
||||||
|
|
||||||
|
1. **Extract ZIP** ke folder web Anda:
|
||||||
|
- Windows: `C:\xampp\htdocs\pemetaan-kemiskinan`
|
||||||
|
- Linux: `/var/www/html/pemetaan-kemiskinan`
|
||||||
|
|
||||||
|
2. **Pastikan XAMPP/Web Server JALAN**
|
||||||
|
- Buka XAMPP Control Panel
|
||||||
|
- Klik **"Start"** untuk Apache
|
||||||
|
- Klik **"Start"** untuk MySQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Langkah 2: Setup Database (3 Langkah Mudah)
|
||||||
|
|
||||||
|
### Cara A: Menggunakan phpMyAdmin (PALING MUDAH)
|
||||||
|
|
||||||
|
1. **Buka browser** → ketik: `http://localhost/phpmyadmin`
|
||||||
|
2. **Klik menu "Import"** di bagian atas
|
||||||
|
3. **Klik "Choose File"** → pilih file `database.sql` dari folder pemetaan-kemiskinan
|
||||||
|
4. **Klik tombol "Import"** → SELESAI ✅
|
||||||
|
|
||||||
|
Tunggu 5-10 detik, database akan terbuat otomatis dengan semua tabel yang diperlukan.
|
||||||
|
|
||||||
|
### Cara B: Manual (Jika Cara A tidak berhasil)
|
||||||
|
|
||||||
|
1. Buka phpMyAdmin → `http://localhost/phpmyadmin`
|
||||||
|
2. Di sebelah kiri, cari **"Create new database"**
|
||||||
|
3. Ketik nama: `pemetaan_kemiskinan`
|
||||||
|
4. Klik **"Create"**
|
||||||
|
5. Pilih database yang baru dibuat
|
||||||
|
6. Klik tab **"SQL"**
|
||||||
|
7. Copy-paste semua kode dari file `database.sql`
|
||||||
|
8. Klik **"Go"**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Langkah 3: Verifikasi Koneksi
|
||||||
|
|
||||||
|
1. **Buka file**: `php/koneksi.php`
|
||||||
|
2. **Periksa konfigurasi** (biasanya tidak perlu diubah):
|
||||||
|
```php
|
||||||
|
$host = "localhost";
|
||||||
|
$username = "root";
|
||||||
|
$password = ""; // Kosongkan jika XAMPP default
|
||||||
|
$database = "pemetaan_kemiskinan";
|
||||||
|
```
|
||||||
|
3. **Jika ada password MySQL**, ubah baris:
|
||||||
|
```php
|
||||||
|
$password = "password_anda_di_sini";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Langkah 4: Buka Aplikasi
|
||||||
|
|
||||||
|
**Buka browser dan ketik:**
|
||||||
|
```
|
||||||
|
http://localhost/pemetaan-kemiskinan/
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **SELESAI! Aplikasi siap digunakan!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 Test Aplikasi
|
||||||
|
|
||||||
|
### Test Input Masyarakat Miskin:
|
||||||
|
|
||||||
|
1. Map akan muncul dengan area Pontianak
|
||||||
|
2. Klik tombol di sidebar → Tab "Masyarakat"
|
||||||
|
3. **Klik di peta** untuk set koordinat lokasi
|
||||||
|
4. Isi form dengan data:
|
||||||
|
```
|
||||||
|
Nama: Siti Nur Azizah
|
||||||
|
Tanggal Lahir: 1985-03-15
|
||||||
|
Jenis Kelamin: Perempuan
|
||||||
|
Alamat: Jl. Mahakam No. 25
|
||||||
|
Kelurahan: Sungai Jawi
|
||||||
|
Kecamatan: Pontianak Kota
|
||||||
|
No HP: 081234567890
|
||||||
|
Status BPJS: Ya
|
||||||
|
(Koordinat sudah otomatis dari klik peta)
|
||||||
|
```
|
||||||
|
5. Klik **"Simpan Data"** → Data berhasil disimpan ✅
|
||||||
|
6. Lihat marker merah di peta, dan nama muncul di sidebar list
|
||||||
|
|
||||||
|
### Test Input Rumah Ibadah:
|
||||||
|
|
||||||
|
1. Klik tab **"Rumah Ibadah"** di sidebar
|
||||||
|
2. **Klik di peta** untuk koordinat
|
||||||
|
3. Isi form:
|
||||||
|
```
|
||||||
|
Nama: Masjid Al-Hidayah
|
||||||
|
Jenis: Masjid
|
||||||
|
Alamat: Jl. Diponegoro No. 45
|
||||||
|
Nama Pemimpin: Ustaz Ahmad
|
||||||
|
```
|
||||||
|
4. Klik **"Simpan Data"** → Marker hijau muncul ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Interface Penjelasan
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ SIDEBAR (300px) │ MAP (Sisa) │
|
||||||
|
│ │ │
|
||||||
|
│ 📍 Pemetaan Kemiskinan │ Marker Merah │
|
||||||
|
│ [Masyarakat] [Ibadah] │ Masyarakat │
|
||||||
|
│ │ │
|
||||||
|
│ [Search Box] │ Marker Hijau │
|
||||||
|
│ [Filter Dropdown] │ Rumah Ibadah │
|
||||||
|
│ │ │
|
||||||
|
│ - Nama 1 │ │
|
||||||
|
│ - Nama 2 │ Klik di peta │
|
||||||
|
│ - Nama 3 │ = Set Koordinat
|
||||||
|
│ │ │
|
||||||
|
│ [Tombol Hapus] │ Klik Marker │
|
||||||
|
│ │ = Lihat Detail
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ FAQ
|
||||||
|
|
||||||
|
**Q: Koordinat tidak ketangkap saat klik peta?**
|
||||||
|
A: Pastikan modal form sudah terbuka (berwarna putih). Klik dulu field nama atau di peta, baru akan bisa set koordinat otomatis.
|
||||||
|
|
||||||
|
**Q: Data tidak tampil di peta?**
|
||||||
|
A: Cek browser console (F12), atau pastikan latitude longitude valid (antara -90 hingga 90 untuk lat, -180 hingga 180 untuk lng).
|
||||||
|
|
||||||
|
**Q: Database tidak terbuat?**
|
||||||
|
A: Cek di phpMyAdmin apakah database `pemetaan_kemiskinan` sudah ada. Jika belum, buat manual atau coba import ulang file `database.sql`.
|
||||||
|
|
||||||
|
**Q: Error "Access denied" saat jalankan?**
|
||||||
|
A: Password MySQL tidak cocok. Edit file `php/koneksi.php` dan ubah password sesuai konfigurasi MySQL Anda.
|
||||||
|
|
||||||
|
**Q: Peta tidak muncul?**
|
||||||
|
A: Periksa koneksi internet (butuh untuk CDN Leaflet & OpenStreetMap). Atau cek browser console untuk error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Struktur File Penting
|
||||||
|
|
||||||
|
```
|
||||||
|
pemetaan-kemiskinan/
|
||||||
|
├── index.html ← Buka file ini di browser
|
||||||
|
├── database.sql ← File untuk import database
|
||||||
|
├── config.php ← Konfigurasi umum
|
||||||
|
├── php/
|
||||||
|
│ ├── koneksi.php ← EDIT INI jika ada password MySQL
|
||||||
|
│ ├── simpan_masyarakat.php
|
||||||
|
│ ├── simpan_rumah_ibadah.php
|
||||||
|
│ ├── ambil_data_masyarakat.php
|
||||||
|
│ └── ...
|
||||||
|
├── js/
|
||||||
|
│ └── main.js ← Logic aplikasi
|
||||||
|
└── docs/
|
||||||
|
└── PANDUAN_PHPMY_ADMIN.md ← Dokumentasi detail
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Jika Ada Masalah
|
||||||
|
|
||||||
|
1. **Cek Browser Console** (Tekan F12):
|
||||||
|
- Lihat tab "Console"
|
||||||
|
- Ada error merah? Catat error message-nya
|
||||||
|
- Refresh halaman (Ctrl+F5)
|
||||||
|
|
||||||
|
2. **Cek phpMyAdmin**:
|
||||||
|
- Database ada?
|
||||||
|
- Tabel ada? (masyarakat_miskin, rumah_ibadah, dll)
|
||||||
|
- Ada data sample?
|
||||||
|
|
||||||
|
3. **Cek File koneksi.php**:
|
||||||
|
- Username & password MySQL cocok?
|
||||||
|
- Nama database benar? (`pemetaan_kemiskinan`)
|
||||||
|
|
||||||
|
4. **Restart Web Server**:
|
||||||
|
- Tutup XAMPP
|
||||||
|
- Buka kembali XAMPP
|
||||||
|
- Start Apache & MySQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**✅ Selesai! Nikmati aplikasi pemetaan kemiskinan Anda!**
|
||||||
|
|
||||||
|
*Untuk dokumentasi lengkap, baca file `README.md`*
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# WebGIS Poverty Mapping - Pontianak
|
||||||
|
|
||||||
|
Sistem Informasi Geografis (GIS) Terpadu untuk pemetaan kemiskinan, fasilitas tata kota, dan pelaporan masyarakat di Kota Pontianak. Dibangun dengan HTML, CSS, JavaScript (Leaflet.js), dan PHP (MySQL).
|
||||||
|
|
||||||
|
## 🚀 Fitur Utama
|
||||||
|
1. **Fasilitas & Tata Kota**: Pemetaan jalan raya (Polyline), batas area taman/perumahan (Polygon), dan titik lokasi SPBU (Marker).
|
||||||
|
2. **Sosial & Kemasyarakatan**: Pemetaan data warga miskin dengan detail bantuan, status BPJS, serta pemetaan lokasi rumah ibadah.
|
||||||
|
3. **Portal Publik (Read-Only Mode)**: Akses bagi warga untuk melihat data fasilitas terdekat tanpa kemampuan merubah atau menghapus data.
|
||||||
|
|
||||||
|
## 🛠️ Persiapan Lingkungan Lokal (Local Development)
|
||||||
|
|
||||||
|
### Syarat Sistem
|
||||||
|
- XAMPP (Apache & MySQL)
|
||||||
|
- PHP 7.4 atau lebih baru (Rekomendasi PHP 8+)
|
||||||
|
|
||||||
|
### Langkah Instalasi
|
||||||
|
1. Nyalakan modul **Apache** dan **MySQL** di XAMPP Control Panel Anda.
|
||||||
|
2. Buka `phpMyAdmin` (`http://localhost/phpmyadmin`).
|
||||||
|
3. Buat database baru bernama `pemetaan_kemiskinan`.
|
||||||
|
4. Import file `database.sql` yang ada di direktori utama ke dalam database tersebut. File ini sudah berisi skema tabel dan beberapa *dummy data* (titik awal).
|
||||||
|
5. Akses portal di `http://localhost/web_miskin/01` (sesuaikan port jika diperlukan).
|
||||||
|
|
||||||
|
## 📤 Git Push & Kolaborasi
|
||||||
|
|
||||||
|
Proyek ini telah dilengkapi dengan `.gitignore`.
|
||||||
|
Untuk mengunggah kode ke GitHub/GitLab:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit WebGIS"
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin https://github.com/username-anda/repo-anda.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Panduan Deployment (cPanel Hosting)
|
||||||
|
|
||||||
|
Untuk mendeploy proyek ini ke internet (contoh menggunakan Niagahoster, Hostinger, atau cPanel standar):
|
||||||
|
|
||||||
|
1. **Siapkan Database di Server**
|
||||||
|
- Masuk ke cPanel hosting Anda, buka menu **MySQL Databases**.
|
||||||
|
- Buat database baru (contoh: `u12345_pemetaan`).
|
||||||
|
- Buat user database baru dan hubungkan user tersebut ke database dengan semua hak akses (*All Privileges*).
|
||||||
|
- Buka **phpMyAdmin** dari cPanel, pilih database baru Anda, lalu menu **Import**.
|
||||||
|
- Unggah file `database.sql` dari komputer Anda.
|
||||||
|
|
||||||
|
2. **Upload File Web**
|
||||||
|
- Di cPanel, buka **File Manager** -> masuk ke direktori `public_html`.
|
||||||
|
- Jika Anda memiliki banyak project, buat folder baru (misal `webgis`). Jika ini aplikasi utama, biarkan di root `public_html`.
|
||||||
|
- *Zip* seluruh isi folder `01` dari PC Anda (jangan Zip folder `01`-nya, tapi isi di dalamnya: `index.html`, `dashboard.html`, folder `css`, `js`, `php`).
|
||||||
|
- Upload file *Zip* tersebut ke File Manager dan klik *Extract*.
|
||||||
|
|
||||||
|
3. **Konfigurasi Koneksi PHP**
|
||||||
|
- Cari dan edit file `php/koneksi.php` yang sudah di-*upload*.
|
||||||
|
- Sesuaikan bagian ini dengan kredensial dari cPanel Anda:
|
||||||
|
```php
|
||||||
|
$host = "localhost";
|
||||||
|
$username = "u12345_userdb"; // User dari cPanel
|
||||||
|
$password = "PasswordKuat123!"; // Password user DB
|
||||||
|
$database = "u12345_pemetaan"; // Nama DB dari cPanel
|
||||||
|
```
|
||||||
|
|
||||||
|
Selesai! Aplikasi WebGIS Anda sekarang sudah online.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Analisis Kode: Sistem Pemetaan Kemiskinan
|
||||||
|
|
||||||
|
Proyek ini adalah aplikasi web bernama **Sistem Pemetaan Kemiskinan** yang digunakan untuk memetakan lokasi masyarakat miskin dan rumah ibadah di suatu wilayah (secara bawaan diset ke daerah Pontianak). Aplikasi ini dibangun dengan menggunakan arsitektur *client-server* sederhana yang memisahkan sisi tampilan (frontend) dan sisi server (backend).
|
||||||
|
|
||||||
|
## Struktur Direktori dan File
|
||||||
|
|
||||||
|
Berikut adalah hasil analisis berdasarkan penataan folder dan file yang ada di dalam proyek ini:
|
||||||
|
|
||||||
|
### 1. Root Direktori (`/01/`)
|
||||||
|
Direktori utama ini menyimpan file-file inti, konfigurasi, dokumentasi, dan titik masuk aplikasi (*entry point*).
|
||||||
|
- **`index.html`**: File utama antarmuka pengguna. Memuat kerangka HTML, tata letak sidebar, kontainer peta, gaya desain CSS internal, dan kerangka *form modal* untuk menambahkan data. File ini juga mengintegrasikan pustaka eksternal **Leaflet.js** untuk merender peta interaktif.
|
||||||
|
- **`config.php`**: File konfigurasi utama di sisi server. Berisi definisi konstanta penting seperti konfigurasi database MySQL, pengaturan awal tampilan peta (*latitude*, *longitude*, *zoom level*), serta lingkungan aplikasi (*development*).
|
||||||
|
- **`database.sql`**: File skrip SQL yang memuat skema struktur database, berisi tabel `masyarakat_miskin`, `rumah_ibadah`, `bantuan_riwayat`, `bpjs_record`, dan tabel `users` untuk sistem login. Terdapat juga beberapa data sampel (*dummy*) untuk keperluan testing.
|
||||||
|
- **`README.md` & `QUICK_START.md`**: Dokumentasi proyek yang menjelaskan instruksi instalasi, penggunaan aplikasi, serta fitur-fiturnya.
|
||||||
|
- **`{php,js,css,icons,docs}`**: Folder kosong yang sepertinya terbuat karena ketidaksengajaan (*typo* pada *command-line* saat membuat *multiple directories* sekaligus).
|
||||||
|
|
||||||
|
### 2. Folder `js/` (Logika Frontend / Interaksi)
|
||||||
|
Folder ini menangani segala logika yang berjalan di sisi *browser* (klien).
|
||||||
|
- **`main.js`**: Skrip JavaScript tunggal yang mengontrol aplikasi. Bertanggung jawab atas:
|
||||||
|
- Menginisialisasi peta dari `index.html`.
|
||||||
|
- Mengambil data spasial dari backend menggunakan *Fetch API* secara asinkron (*AJAX*).
|
||||||
|
- Meletakkan *marker* (titik lokasi) untuk masyarakat miskin dan rumah ibadah di peta.
|
||||||
|
- Memproses pencarian (*search*) dan filter di bagian sidebar.
|
||||||
|
- Menangani aksi pengiriman form, tombol klik, serta memberikan *feedback* sukses/error kepada pengguna (*alert*).
|
||||||
|
|
||||||
|
### 3. Folder `php/` (Logika Backend / API)
|
||||||
|
Berisi sekumpulan skrip PHP yang berfungsi sebagai perantara antara antarmuka pengguna dan *database*. Endpoint ini menerima input dan mengembalikan format **JSON**.
|
||||||
|
- **`koneksi.php`**: Pusat koneksi database menggunakan `mysqli`. Menangani perizinan *CORS Headers* agar API bisa diakses dari frontend dan mengatur proteksi anti-XSS melalui fungsi *sanitasi input*.
|
||||||
|
- **Endpoint Pengambilan Data (`ambil_*.php`)**:
|
||||||
|
- `ambil_data_masyarakat.php` & `ambil_rumah_ibadah.php`: Merespons permintaan dengan daftar titik-titik lokasi dari tabel terkait di database.
|
||||||
|
- `ambil_detail.php`: Menampilkan data terperinci masyarakat miskin, mencakup data BPJS dan riwayat bantuan.
|
||||||
|
- **Endpoint Penyimpanan Data (`simpan_*.php`)**:
|
||||||
|
- `simpan_masyarakat.php`, `simpan_rumah_ibadah.php`: Mengamankan data yang masuk dari POST *Request* form, memvalidasi dan memasukkannya ke database. Terdapat file lain seperti `simpan_bantuan.php` dan `simpan_bpjs.php` untuk relasi tabel sekunder.
|
||||||
|
- **`hapus_data.php`**: Endpoint untuk menghapus data berdasarkan ID.
|
||||||
|
|
||||||
|
### 4. Folder `codeanalysis/`
|
||||||
|
- **`codeanalyisis.md`**: File dokumen *Markdown* tempat tersimpannya hasil analisis kode yang menguraikan arsitektur dan struktur proyek ini.
|
||||||
|
|
||||||
|
### 5. Folder `docs/`
|
||||||
|
- **`PANDUAN_PHPMY_ADMIN.md`**: Dokumen petunjuk teknis sekunder terkait pengelolaan basis data menggunakan *phpMyAdmin*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kesimpulan Arsitektur Sistem
|
||||||
|
Proyek ini mengadopsi pola perancangan **API-driven (Vanilla)**.
|
||||||
|
1. **Frontend** dibuat reaktif tanpa membebani server, di mana `index.html` dan `main.js` merender visual serta mengirimkan permintaan *asinkron*.
|
||||||
|
2. **Backend (PHP)** bertindak hanya sebagai API pengelola data yang merespons dalam format JSON (bukan sebagai perender HTML). Hal ini membuat kode lebih terisolasi, bersih (*clean code*), dan mudah dirawat ke depannya.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: config.php
|
||||||
|
// Konfigurasi Umum Aplikasi
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// DATABASE CONFIG
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_USER', 'root');
|
||||||
|
define('DB_PASS', ''); // Kosongkan jika XAMPP default, atau masukkan password
|
||||||
|
define('DB_NAME', 'pemetaan_kemiskinan');
|
||||||
|
|
||||||
|
// API CONFIG
|
||||||
|
define('API_URL', 'php/');
|
||||||
|
define('APP_NAME', 'Sistem Pemetaan Kemiskinan');
|
||||||
|
define('APP_VERSION', '1.0.0');
|
||||||
|
|
||||||
|
// MAP CONFIG
|
||||||
|
define('MAP_CENTER_LAT', -0.0612); // Pontianak
|
||||||
|
define('MAP_CENTER_LNG', 109.3449);
|
||||||
|
define('MAP_DEFAULT_ZOOM', 12);
|
||||||
|
define('MAP_MAX_ZOOM', 19);
|
||||||
|
|
||||||
|
// TIMEZONE
|
||||||
|
date_default_timezone_set('Asia/Jakarta');
|
||||||
|
|
||||||
|
// ERROR REPORTING
|
||||||
|
if (getenv('APP_ENV') === 'development') {
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
} else {
|
||||||
|
error_reporting(0);
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,865 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Sistem Pemetaan Kemiskinan</title>
|
||||||
|
|
||||||
|
<!-- LEAFLET -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
|
<!-- LEAFLET DRAW -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css"/>
|
||||||
|
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
|
||||||
|
|
||||||
|
<!-- LEAFLET GEOMETRY UTIL -->
|
||||||
|
<script src="https://unpkg.com/leaflet-geometryutil"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
font-family: 'Segoe UI', Tahoma, sans-serif;
|
||||||
|
background: #f1f3f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SIDEBAR */
|
||||||
|
#sidebar {
|
||||||
|
width: 320px;
|
||||||
|
background: linear-gradient(180deg, #1e3c72, #2a5298);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 2px 0 10px rgba(0,0,0,0.2);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-bottom: 2px solid rgba(255,255,255,0.3);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button.active {
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button:hover {
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#search, #filter {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
display: none;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.3s;
|
||||||
|
font-size: 13px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li:hover {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
transform: translateX(5px);
|
||||||
|
border-left-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li small {
|
||||||
|
opacity: 0.8;
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ACCORDION */
|
||||||
|
.accordion {
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
transition: 0.4s;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion.active, .accordion:hover {
|
||||||
|
background-color: rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAP */
|
||||||
|
#map {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MODAL */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
margin: 5% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
border-radius: 10px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #1e3c72;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
color: #1e3c72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group select, .form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #da190b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-ya {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tidak {
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-proses {
|
||||||
|
background: #ff9800;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background: #f9f9f9;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-left: 4px solid #1e3c72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section h3 {
|
||||||
|
color: #1e3c72;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ALERT */
|
||||||
|
.alert {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LOADING */
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #1e3c72;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- SIDEBAR -->
|
||||||
|
<div id="sidebar">
|
||||||
|
<a href="index.html" style="display:inline-block; margin-bottom:15px; color:#fff; text-decoration:none; font-size:12px; background:rgba(255,255,255,0.2); padding:5px 10px; border-radius:5px;">← Kembali ke Portal</a>
|
||||||
|
<h2>📍 Portal Pemetaan Terpadu</h2>
|
||||||
|
|
||||||
|
<!-- ACCORDION: FASILITAS KOTA -->
|
||||||
|
<button class="accordion">🏗️ Fasilitas Kota <span>▼</span></button>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="tab-buttons" style="flex-wrap: wrap;">
|
||||||
|
<button class="tab-button" data-tab="jalan" style="margin-bottom:5px;">Jalan</button>
|
||||||
|
<button class="tab-button" data-tab="polygon" style="margin-bottom:5px;">Area</button>
|
||||||
|
<button class="tab-button" data-tab="spbu" style="margin-bottom:5px;">SPBU</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;gap:5px;margin-bottom:10px;">
|
||||||
|
<button class="btn-primary" onclick="alert('Untuk menambahkan data Jalan, gunakan ikon menggambar Garis (Polyline) di pojok kiri peta.')" style="font-size:11px;padding:5px;background:#555;">+ Jalan</button>
|
||||||
|
<button class="btn-primary" onclick="alert('Untuk menambahkan Area, gunakan ikon menggambar Polygon di pojok kiri peta.')" style="font-size:11px;padding:5px;background:#555;">+ Area</button>
|
||||||
|
<button class="btn-primary" onclick="openModal('modal-form-spbu')" style="font-size:11px;padding:5px;">+ SPBU</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: JALAN -->
|
||||||
|
<div id="tab-jalan" class="list-container">
|
||||||
|
<ul id="list-jalan"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: POLYGON -->
|
||||||
|
<div id="tab-polygon" class="list-container">
|
||||||
|
<ul id="list-polygon"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: SPBU -->
|
||||||
|
<div id="tab-spbu" class="list-container">
|
||||||
|
<ul id="list-spbu"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ACCORDION: SOSIALISASI KEMASYARAKATAN -->
|
||||||
|
<button class="accordion">👥 Sosialisasi Kemasyarakatan <span>▼</span></button>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="tab-buttons">
|
||||||
|
<button class="tab-button" data-tab="masyarakat">Masyarakat</button>
|
||||||
|
<button class="tab-button" data-tab="ibadah">Rumah Ibadah</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;gap:5px;margin-bottom:10px;">
|
||||||
|
<button class="btn-primary" onclick="openModal('modal-form-masyarakat')" style="font-size:11px;padding:5px;">+ Masyarakat</button>
|
||||||
|
<button class="btn-primary" onclick="openModal('modal-form-ibadah')" style="font-size:11px;padding:5px;">+ Rumah Ibadah</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: MASYARAKAT MISKIN -->
|
||||||
|
<div id="tab-masyarakat" class="list-container">
|
||||||
|
<input type="text" id="search" placeholder="Cari nama/alamat...">
|
||||||
|
|
||||||
|
<select id="filter-kecamatan">
|
||||||
|
<option value="">- Pilih Kecamatan -</option>
|
||||||
|
<option value="Pontianak Kota">Pontianak Kota</option>
|
||||||
|
<option value="Pontianak Utara">Pontianak Utara</option>
|
||||||
|
<option value="Pontianak Barat">Pontianak Barat</option>
|
||||||
|
<option value="Pontianak Selatan">Pontianak Selatan</option>
|
||||||
|
<option value="Pontianak Tenggara">Pontianak Tenggara</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="filter-bpjs">
|
||||||
|
<option value="">- Status BPJS -</option>
|
||||||
|
<option value="Ya">BPJS Aktif</option>
|
||||||
|
<option value="Tidak">Tidak BPJS</option>
|
||||||
|
<option value="Proses">Proses</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<ul id="list-masyarakat"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: RUMAH IBADAH -->
|
||||||
|
<div id="tab-ibadah" class="list-container">
|
||||||
|
<select id="filter-jenis">
|
||||||
|
<option value="">- Semua Jenis -</option>
|
||||||
|
<option value="Masjid">Masjid</option>
|
||||||
|
<option value="Gereja">Gereja</option>
|
||||||
|
<option value="Vihara">Vihara</option>
|
||||||
|
<option value="Kelenteng">Kelenteng</option>
|
||||||
|
<option value="Lain">Lain-lain</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<ul id="list-ibadah"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ACCORDION: PORTAL PUBLIK -->
|
||||||
|
<button class="accordion" id="acc-publik">📢 Layanan Publik & Laporan <span>▼</span></button>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="tab-buttons">
|
||||||
|
<button class="tab-button" data-tab="pelaporan">Lapor Darurat</button>
|
||||||
|
<button class="tab-button" data-tab="info-fasilitas">Info Terdekat</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: PELAPORAN DARURAT -->
|
||||||
|
<div id="tab-pelaporan" class="list-container">
|
||||||
|
<div style="background:#fee2e2; padding:15px; border-radius:8px; margin-bottom:15px; border-left:4px solid #ef4444;">
|
||||||
|
<h3 style="color:#b91c1c; margin:0 0 5px 0; font-size:14px;"><i class="fa-solid fa-triangle-exclamation"></i> Darurat/Bencana</h3>
|
||||||
|
<p style="font-size:12px; color:#7f1d1d; margin:0 0 10px 0;">Laporkan segera jika terjadi kebakaran, banjir, darurat medis, atau jalan rusak parah.</p>
|
||||||
|
<button class="btn-primary" onclick="alert('Formulir Laporan Terkirim ke BPBD / Kepolisian Resort Kota Pontianak!')" style="width:100%; background:#ef4444; border:none; padding:10px;">🚨 Panggil Bantuan</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: INFO FASILITAS -->
|
||||||
|
<div id="tab-info-fasilitas" class="list-container">
|
||||||
|
<p style="font-size:12px; color:#cbf4c9; background:#166534; padding:10px; border-radius:5px;">Klik icon lokasi pada peta untuk melihat Detail Fasilitas, SPBU terdekat, maupun Rumah Ibadah di area sekitar Anda.</p>
|
||||||
|
<ul class="data-list" id="list-info-terdekat">
|
||||||
|
<li style="text-align:center; color:#94a3b8; padding:20px; font-style:italic;">Silakan berinteraksi dengan peta</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MAP -->
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: DETAIL MASYARAKAT -->
|
||||||
|
<div id="modal-detail" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Detail Masyarakat</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-detail')">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="modal-body"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: FORM INPUT MASYARAKAT -->
|
||||||
|
<div id="modal-form-masyarakat" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Input Masyarakat Miskin</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-form-masyarakat')">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="alert-form" class="alert"></div>
|
||||||
|
|
||||||
|
<form id="form-masyarakat">
|
||||||
|
<div style="
|
||||||
|
background:#e8f4ff;
|
||||||
|
padding:10px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
border-radius:5px;
|
||||||
|
font-size:12px;">
|
||||||
|
📍 Klik lokasi rumah warga pada peta untuk mengisi Latitude dan Longitude secara otomatis.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Lengkap *</label>
|
||||||
|
<input type="text" name="nama" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tanggal Lahir</label>
|
||||||
|
<input type="date" name="tanggal_lahir">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Jenis Kelamin</label>
|
||||||
|
<select name="jenis_kelamin">
|
||||||
|
<option value="Laki-laki">Laki-laki</option>
|
||||||
|
<option value="Perempuan">Perempuan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>NIK</label>
|
||||||
|
<input type="text" name="nik" placeholder="16 digit">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Alamat *</label>
|
||||||
|
<textarea name="alamat" required></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kelurahan</label>
|
||||||
|
<input type="text" name="kelurahan">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kecamatan</label>
|
||||||
|
<select name="kecamatan">
|
||||||
|
<option value="">Pilih Kecamatan</option>
|
||||||
|
<option value="Pontianak Kota">Pontianak Kota</option>
|
||||||
|
<option value="Pontianak Utara">Pontianak Utara</option>
|
||||||
|
<option value="Pontianak Barat">Pontianak Barat</option>
|
||||||
|
<option value="Pontianak Selatan">Pontianak Selatan</option>
|
||||||
|
<option value="Pontianak Tenggara">Pontianak Tenggara</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Latitude *</label>
|
||||||
|
<input type="number" name="latitude" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Longitude *</label>
|
||||||
|
<input type="number" name="longitude" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>No. HP</label>
|
||||||
|
<input type="text" name="no_hp" placeholder="Cth: 081234567890">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Status BPJS *</label>
|
||||||
|
<select name="status_bpjs" required>
|
||||||
|
<option value="Tidak">Tidak Memiliki</option>
|
||||||
|
<option value="Ya">Sudah Memiliki</option>
|
||||||
|
<option value="Proses">Proses Pendaftaran</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Pekerjaan</label>
|
||||||
|
<input type="text" name="pekerjaan" placeholder="Cth: Buruh, Petani, dll">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Pendapatan Bulanan (Rp)</label>
|
||||||
|
<input type="number" name="pendapatan_bulanan">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Jumlah Tanggungan</label>
|
||||||
|
<input type="number" name="jumlah_tanggungan" min="0">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Status Rumah</label>
|
||||||
|
<select name="status_rumah">
|
||||||
|
<option value="Menumpang">Menumpang</option>
|
||||||
|
<option value="Sewa">Sewa</option>
|
||||||
|
<option value="Milik Sendiri">Milik Sendiri</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit" class="btn-primary">Simpan Data</button>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeModal('modal-form-masyarakat')">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: FORM INPUT RUMAH IBADAH -->
|
||||||
|
<div id="modal-form-ibadah" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Input Rumah Ibadah</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-form-ibadah')">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="alert-form-ibadah" class="alert"></div>
|
||||||
|
|
||||||
|
<form id="form-ibadah">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Tempat Ibadah *</label>
|
||||||
|
<input type="text" name="nama" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Jenis Tempat Ibadah *</label>
|
||||||
|
<select name="jenis" required>
|
||||||
|
<option value="">Pilih Jenis</option>
|
||||||
|
<option value="Masjid">Masjid</option>
|
||||||
|
<option value="Gereja">Gereja</option>
|
||||||
|
<option value="Vihara">Vihara</option>
|
||||||
|
<option value="Kelenteng">Kelenteng</option>
|
||||||
|
<option value="Sinagog">Sinagog</option>
|
||||||
|
<option value="Lain">Lain-lain</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Alamat *</label>
|
||||||
|
<textarea name="alamat" required></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Latitude *</label>
|
||||||
|
<input type="number" name="latitude" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Longitude *</label>
|
||||||
|
<input type="number" name="longitude" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Pemimpin</label>
|
||||||
|
<input type="text" name="nama_pemimpin">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>No. HP Pemimpin</label>
|
||||||
|
<input type="text" name="no_hp_pemimpin">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kapasitas (orang)</label>
|
||||||
|
<input type="number" name="kapasitas" min="0">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tahun Didirikan</label>
|
||||||
|
<input type="number" name="tahun_didirikan" min="1900" max="2099">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Keterangan</label>
|
||||||
|
<textarea name="keterangan"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit" class="btn-primary">Simpan Data</button>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeModal('modal-form-ibadah')">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: FORM INPUT JALAN -->
|
||||||
|
<div id="modal-form-jalan" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Input Fasilitas Jalan</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-form-jalan')">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="alert-form-jalan" class="alert"></div>
|
||||||
|
<form id="form-jalan">
|
||||||
|
<div style="background:#e8f4ff; padding:10px; margin-bottom:10px; border-radius:5px; font-size:12px;">
|
||||||
|
📍 Gunakan tombol <strong>Polyline</strong> di peta untuk menggambar rute jalan, data GeoJSON akan otomatis terisi di bawah.
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Jalan *</label>
|
||||||
|
<input type="text" name="nama" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tipe Jalan</label>
|
||||||
|
<select name="tipe">
|
||||||
|
<option value="Jalan Utama">Jalan Utama</option>
|
||||||
|
<option value="Jalan Lingkungan">Jalan Lingkungan</option>
|
||||||
|
<option value="Jalan Tol">Jalan Tol</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Data GeoJSON *</label>
|
||||||
|
<textarea name="geojson" id="geojson-jalan" required readonly placeholder="Gambar garis di peta untuk mengisi ini"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Keterangan</label>
|
||||||
|
<textarea name="keterangan"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit" class="btn-primary">Simpan Data</button>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeModal('modal-form-jalan')">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: FORM INPUT POLYGON -->
|
||||||
|
<div id="modal-form-polygon" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Input Fasilitas Area (Polygon)</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-form-polygon')">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="alert-form-polygon" class="alert"></div>
|
||||||
|
<form id="form-polygon">
|
||||||
|
<div style="background:#e8f4ff; padding:10px; margin-bottom:10px; border-radius:5px; font-size:12px;">
|
||||||
|
📍 Gunakan tombol <strong>Polygon</strong> di peta untuk menggambar area, data GeoJSON akan otomatis terisi di bawah.
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Area *</label>
|
||||||
|
<input type="text" name="nama" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tipe Area</label>
|
||||||
|
<select name="tipe">
|
||||||
|
<option value="Taman">Taman Kota</option>
|
||||||
|
<option value="Pasar">Pasar / Pusat Perbelanjaan</option>
|
||||||
|
<option value="Perumahan">Kawasan Perumahan</option>
|
||||||
|
<option value="Lainnya">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Data GeoJSON *</label>
|
||||||
|
<textarea name="geojson" id="geojson-polygon" required readonly placeholder="Gambar polygon di peta untuk mengisi ini"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Keterangan</label>
|
||||||
|
<textarea name="keterangan"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit" class="btn-primary">Simpan Data</button>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeModal('modal-form-polygon')">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL: FORM INPUT SPBU -->
|
||||||
|
<div id="modal-form-spbu" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Input SPBU</h2>
|
||||||
|
<button class="close-btn" onclick="closeModal('modal-form-spbu')">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="alert-form-spbu" class="alert"></div>
|
||||||
|
<form id="form-spbu">
|
||||||
|
<div style="background:#e8f4ff; padding:10px; margin-bottom:10px; border-radius:5px; font-size:12px;">
|
||||||
|
📍 Klik lokasi di peta untuk mengisi Latitude dan Longitude secara otomatis.
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama SPBU *</label>
|
||||||
|
<input type="text" name="nama" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Latitude *</label>
|
||||||
|
<input type="number" name="latitude" id="lat-spbu" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Longitude *</label>
|
||||||
|
<input type="number" name="longitude" id="lng-spbu" step="0.00000001" required readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Alamat Lengkap</label>
|
||||||
|
<textarea name="alamat"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Buka 24 Jam?</label>
|
||||||
|
<select name="is_24_jam" required>
|
||||||
|
<option value="Tidak">Tidak</option>
|
||||||
|
<option value="Ya">Ya, 24 Jam</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit" class="btn-primary">Simpan Data</button>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeModal('modal-form-spbu')">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- DATABASE: pemetaan_kemiskinan
|
||||||
|
-- DIBUAT UNTUK: Sistem Pemetaan Kemiskinan
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- CREATE DATABASE
|
||||||
|
CREATE DATABASE IF NOT EXISTS `pemetaan_kemiskinan` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
USE `pemetaan_kemiskinan`;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 1: masyarakat_miskin
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `masyarakat_miskin` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`nama` varchar(100) NOT NULL,
|
||||||
|
`tanggal_lahir` date NOT NULL,
|
||||||
|
`jenis_kelamin` enum('Laki-laki','Perempuan') NOT NULL,
|
||||||
|
`nik` varchar(20),
|
||||||
|
`alamat` text NOT NULL,
|
||||||
|
`latitude` decimal(10,8) NOT NULL,
|
||||||
|
`longitude` decimal(11,8) NOT NULL,
|
||||||
|
`status_bpjs` enum('Ya','Tidak','Proses') DEFAULT 'Tidak',
|
||||||
|
`no_hp` varchar(15),
|
||||||
|
`pekerjaan` varchar(100),
|
||||||
|
`pendapatan_bulanan` int(11),
|
||||||
|
`jumlah_tanggungan` int(11),
|
||||||
|
`status_rumah` enum('Milik Sendiri','Sewa','Menumpang') DEFAULT 'Menumpang',
|
||||||
|
`kelurahan` varchar(100),
|
||||||
|
`kecamatan` varchar(100),
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX `idx_nama` (`nama`),
|
||||||
|
INDEX `idx_status_bpjs` (`status_bpjs`),
|
||||||
|
INDEX `idx_kecamatan` (`kecamatan`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 2: rumah_ibadah
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `rumah_ibadah` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`nama` varchar(100) NOT NULL,
|
||||||
|
`jenis` enum('Masjid','Gereja','Vihara','Kelenteng','Sinagog','Lain') NOT NULL,
|
||||||
|
`latitude` decimal(10,8) NOT NULL,
|
||||||
|
`longitude` decimal(11,8) NOT NULL,
|
||||||
|
`alamat` text NOT NULL,
|
||||||
|
`nama_pemimpin` varchar(100),
|
||||||
|
`no_hp_pemimpin` varchar(15),
|
||||||
|
`kapasitas` int(11),
|
||||||
|
`tahun_didirikan` year,
|
||||||
|
`keterangan` text,
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX `idx_nama` (`nama`),
|
||||||
|
INDEX `idx_jenis` (`jenis`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 3: bantuan_riwayat
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `bantuan_riwayat` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`id_masyarakat` int(11) NOT NULL,
|
||||||
|
`jenis_bantuan` varchar(100) NOT NULL,
|
||||||
|
`tanggal_bantuan` date NOT NULL,
|
||||||
|
`nilai` int(11) NOT NULL,
|
||||||
|
`satuan` varchar(50),
|
||||||
|
`keterangan` text,
|
||||||
|
`nama_pemberi` varchar(100),
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`id_masyarakat`) REFERENCES `masyarakat_miskin`(`id`) ON DELETE CASCADE,
|
||||||
|
INDEX `idx_id_masyarakat` (`id_masyarakat`),
|
||||||
|
INDEX `idx_tanggal` (`tanggal_bantuan`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 4: bpjs_record
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `bpjs_record` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`id_masyarakat` int(11) NOT NULL,
|
||||||
|
`no_peserta` varchar(20) NOT NULL UNIQUE,
|
||||||
|
`jenis_bpjs` enum('Kesehatan','Ketenagakerjaan','Jaminan Hari Tua') DEFAULT 'Kesehatan',
|
||||||
|
`tingkat` enum('Penerima Bantuan Iuran (PBI)','Pekerja Penerima Upah','Pekerja Bukan Penerima Upah','Peserta Mandiri') DEFAULT 'Penerima Bantuan Iuran (PBI)',
|
||||||
|
`tanggal_daftar` date NOT NULL,
|
||||||
|
`status_aktif` enum('Aktif','Nonaktif','Suspended') DEFAULT 'Aktif',
|
||||||
|
`faskes_tingkat_1` varchar(100),
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`id_masyarakat`) REFERENCES `masyarakat_miskin`(`id`) ON DELETE CASCADE,
|
||||||
|
INDEX `idx_id_masyarakat` (`id_masyarakat`),
|
||||||
|
INDEX `idx_no_peserta` (`no_peserta`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 5: users (untuk login admin)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`username` varchar(50) NOT NULL UNIQUE,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`nama_lengkap` varchar(100) NOT NULL,
|
||||||
|
`role` enum('Admin','Surveyor','Viewer') DEFAULT 'Surveyor',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Insert default admin user (password: admin123)
|
||||||
|
INSERT INTO `users` (`username`, `password`, `nama_lengkap`, `role`) VALUES
|
||||||
|
('admin', '5f4dcc3b5aa765d61d8327deb882cf99', 'Administrator', 'Admin');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 6: fasilitas_jalan
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `fasilitas_jalan` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`nama` varchar(150) NOT NULL,
|
||||||
|
`geojson` text NOT NULL,
|
||||||
|
`tipe` varchar(50),
|
||||||
|
`keterangan` text,
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 7: fasilitas_polygon
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `fasilitas_polygon` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`nama` varchar(150) NOT NULL,
|
||||||
|
`geojson` text NOT NULL,
|
||||||
|
`tipe` varchar(50),
|
||||||
|
`keterangan` text,
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- TABLE 8: fasilitas_spbu
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS `fasilitas_spbu` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`nama` varchar(150) NOT NULL,
|
||||||
|
`latitude` decimal(10,8) NOT NULL,
|
||||||
|
`longitude` decimal(11,8) NOT NULL,
|
||||||
|
`alamat` text,
|
||||||
|
`is_24_jam` enum('Ya','Tidak') DEFAULT 'Tidak',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- SAMPLE DATA (Opsional - untuk testing)
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- Sample Masyarakat Miskin
|
||||||
|
INSERT INTO `masyarakat_miskin` (`nama`, `tanggal_lahir`, `jenis_kelamin`, `nik`, `alamat`, `latitude`, `longitude`, `status_bpjs`, `no_hp`, `pekerjaan`, `pendapatan_bulanan`, `jumlah_tanggungan`, `status_rumah`, `kelurahan`, `kecamatan`) VALUES
|
||||||
|
('Siti Nur Azizah', '1985-03-15', 'Perempuan', '6101234567890123', 'Jl. Mahakam No. 25', -0.0612, 109.3445, 'Ya', '081234567890', 'Buruh Harian', 1500000, 4, 'Menumpang', 'Sungai Jawi', 'Pontianak Kota');
|
||||||
|
|
||||||
|
-- Sample Rumah Ibadah
|
||||||
|
INSERT INTO `rumah_ibadah` (`nama`, `jenis`, `latitude`, `longitude`, `alamat`, `nama_pemimpin`, `no_hp_pemimpin`, `kapasitas`, `tahun_didirikan`) VALUES
|
||||||
|
('Masjid Al-Hidayah', 'Masjid', -0.0615, 109.3450, 'Jl. Diponegoro No. 45', 'Ustaz Ahmad', '081234567890', 500, 2005),
|
||||||
|
('Gereja Hati Kudus', 'Gereja', -0.0610, 109.3440, 'Jl. Jend Sudirman No. 12', 'Pdt. Bonaventura', '081234567891', 300, 1995);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- SELESAI
|
||||||
|
-- ============================================
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# 📖 Panduan Lengkap: Membuat Database di phpMyAdmin
|
||||||
|
|
||||||
|
## STEP 1: Akses phpMyAdmin
|
||||||
|
1. Buka browser dan akses: `http://localhost/phpmyadmin`
|
||||||
|
- Username: `root`
|
||||||
|
- Password: *(kosongkan atau sesuai konfigurasi)*
|
||||||
|
2. Tekan tombol **Login**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 2: Buat Database Baru
|
||||||
|
|
||||||
|
### Cara Manual:
|
||||||
|
1. **Di halaman utama phpMyAdmin**, cari kolom **"Create new database"** di sebelah kiri
|
||||||
|
2. Isikan nama database: `pemetaan_kemiskinan`
|
||||||
|
3. Pilih **Collation**: `utf8mb4_general_ci`
|
||||||
|
4. Klik tombol **Create**
|
||||||
|
|
||||||
|
### Atau menggunakan Import SQL:
|
||||||
|
1. Di menu atas, pilih tab **"Import"**
|
||||||
|
2. Klik **"Choose File"** dan pilih file `database.sql`
|
||||||
|
3. Pastikan encoding: `utf-8`
|
||||||
|
4. Klik **Import** (database akan terbuat otomatis)
|
||||||
|
|
||||||
|
✅ Database berhasil dibuat
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 3: Verifikasi Tabel-Tabel
|
||||||
|
|
||||||
|
Setelah import atau membuat manual, klik pada database `pemetaan_kemiskinan` di sebelah kiri.
|
||||||
|
|
||||||
|
**Anda seharusnya melihat tabel-tabel berikut:**
|
||||||
|
|
||||||
|
```
|
||||||
|
✓ masyarakat_miskin
|
||||||
|
✓ rumah_ibadah
|
||||||
|
✓ bantuan_riwayat
|
||||||
|
✓ bpjs_record
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 4: Struktur Tabel (REFERENSI)
|
||||||
|
|
||||||
|
### Tabel: `masyarakat_miskin`
|
||||||
|
| Field | Type | Keterangan |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| id | INT (Primary Key) | ID unik |
|
||||||
|
| nama | VARCHAR(100) | Nama lengkap |
|
||||||
|
| tanggal_lahir | DATE | Tgl lahir (YYYY-MM-DD) |
|
||||||
|
| jenis_kelamin | ENUM('Laki-laki','Perempuan') | - |
|
||||||
|
| alamat | TEXT | Alamat rumah |
|
||||||
|
| latitude | DECIMAL(10,8) | Koordinat lat |
|
||||||
|
| longitude | DECIMAL(11,8) | Koordinat lng |
|
||||||
|
| status_bpjs | ENUM('Ya','Tidak','Proses') | Status BPJS |
|
||||||
|
| no_hp | VARCHAR(15) | Nomor HP |
|
||||||
|
| created_at | TIMESTAMP | Waktu input |
|
||||||
|
|
||||||
|
### Tabel: `rumah_ibadah`
|
||||||
|
| Field | Type | Keterangan |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| id | INT (Primary Key) | ID unik |
|
||||||
|
| nama | VARCHAR(100) | Nama tempat ibadah |
|
||||||
|
| jenis | ENUM('Masjid','Gereja','Vihara','Kelenteng','Lain') | Tipe |
|
||||||
|
| latitude | DECIMAL(10,8) | Koordinat lat |
|
||||||
|
| longitude | DECIMAL(11,8) | Koordinat lng |
|
||||||
|
| alamat | TEXT | Alamat |
|
||||||
|
| keterangan | TEXT | Info tambahan |
|
||||||
|
| created_at | TIMESTAMP | Waktu input |
|
||||||
|
|
||||||
|
### Tabel: `bantuan_riwayat`
|
||||||
|
| Field | Type | Keterangan |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| id | INT (Primary Key) | ID unik |
|
||||||
|
| id_masyarakat | INT | ID masyarakat (foreign key) |
|
||||||
|
| jenis_bantuan | VARCHAR(100) | Tipe bantuan |
|
||||||
|
| tanggal_bantuan | DATE | Tanggal pemberian |
|
||||||
|
| nilai | INT | Nominal (Rp) |
|
||||||
|
| keterangan | TEXT | Catatan |
|
||||||
|
|
||||||
|
### Tabel: `bpjs_record`
|
||||||
|
| Field | Type | Keterangan |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| id | INT (Primary Key) | ID unik |
|
||||||
|
| id_masyarakat | INT | ID masyarakat |
|
||||||
|
| no_peserta | VARCHAR(20) | Nomor peserta BPJS |
|
||||||
|
| tingkat | ENUM('Keluarga','Mandiri','Penerima Bantuan Iuran') | Tipe BPJS |
|
||||||
|
| tanggal_daftar | DATE | Tgl pendaftaran |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 5: Cek Koneksi Database (Opsional)
|
||||||
|
|
||||||
|
Jika ingin test koneksi manual:
|
||||||
|
|
||||||
|
1. Buat file `test_koneksi.php` di folder project
|
||||||
|
2. Isi dengan kode ini:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$conn = new mysqli("localhost", "root", "", "pemetaan_kemiskinan");
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Koneksi berhasil!";
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Akses: `http://localhost/pemetaan-kemiskinan/test_koneksi.php`
|
||||||
|
4. Jika muncul pesan hijau = Database siap digunakan ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TROUBLESHOOTING
|
||||||
|
|
||||||
|
### ❌ "Access denied for user 'root'@'localhost'"
|
||||||
|
- Buka file `config/koneksi.php`
|
||||||
|
- Ubah password sesuai konfigurasi MySQL Anda
|
||||||
|
- Jika menggunakan XAMPP default, kosongkan saja password
|
||||||
|
|
||||||
|
### ❌ "Database pemetaan_kemiskinan tidak ditemukan"
|
||||||
|
- Pastikan sudah run file `database.sql` melalui Import
|
||||||
|
- Atau buat manual: **Create new database** → nama: `pemetaan_kemiskinan`
|
||||||
|
|
||||||
|
### ❌ File upload tidak berfungsi
|
||||||
|
- Cek folder upload di `_uploads/` sudah ada & permission 755
|
||||||
|
- Atau buat manual: klik kanan folder → Properties → Permission → 755
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
✅ **Selesai! Database siap digunakan untuk website pemetaan kemiskinan**
|
||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>WebGIS Poverty Mapping</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary: #00f2fe;
|
||||||
|
--secondary: #4facfe;
|
||||||
|
--accent: #ff0844;
|
||||||
|
--glass-bg: rgba(15, 23, 42, 0.6);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: 'Outfit', sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bg-map {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: linear-gradient(135deg, rgba(15,23,42,0.9) 0%, rgba(15,23,42,0.6) 100%);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 2rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
animation: fadeInDown 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 900;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
background: linear-gradient(to right, var(--primary), var(--secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-shadow: 0 0 30px rgba(79, 172, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 100%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-15px) scale(1.02);
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 30px 60px -10px rgba(0, 242, 254, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-1 .icon-container { color: #10b981; text-shadow: 0 0 20px rgba(16,185,129,0.5); }
|
||||||
|
.card-2 .icon-container { color: #f43f5e; text-shadow: 0 0 20px rgba(244,63,94,0.5); }
|
||||||
|
.card-3 .icon-container { color: #3b82f6; text-shadow: 0 0 20px rgba(59,130,246,0.5); }
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #f8fafc;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-weight: 700;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #cbd5e1;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0 0 2rem 0;
|
||||||
|
font-weight: 300;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.8rem 2rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-green {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
box-shadow: 0 10px 20px -5px rgba(16,185,129,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pink {
|
||||||
|
background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%);
|
||||||
|
box-shadow: 0 10px 20px -5px rgba(244,63,94,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
box-shadow: 0 10px 20px -5px rgba(59,130,246,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from { opacity: 0; transform: translateY(-30px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-1 { animation: fadeInUp 0.8s ease-out 0.2s both; }
|
||||||
|
.card-2 { animation: fadeInUp 0.8s ease-out 0.4s both; }
|
||||||
|
.card-3 { animation: fadeInUp 0.8s ease-out 0.6s both; }
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translateY(40px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header h1 { font-size: 2.5rem; }
|
||||||
|
.card { width: 100%; max-width: 350px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Background Map -->
|
||||||
|
<div id="bg-map"></div>
|
||||||
|
<div class="overlay"></div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<h1>WebGIS Pontianak</h1>
|
||||||
|
<p>Pusat Integrasi Data Geospasial & Kemiskinan</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards-container">
|
||||||
|
<!-- Fasilitas & Tata Kota -->
|
||||||
|
<div class="card card-1">
|
||||||
|
<div class="icon-container"><i class="fa-solid fa-layer-group"></i></div>
|
||||||
|
<h2>Fasilitas & Tata Kota</h2>
|
||||||
|
<p>Manajemen pemetaan jalan, fasilitas publik, batas area, dan inventarisasi tata ruang kota Pontianak.</p>
|
||||||
|
<a href="dashboard.html" class="btn btn-green" onclick="localStorage.setItem('activeTab', 'fasilitas')">Masuk Sistem →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sosial & Kemasyarakatan -->
|
||||||
|
<div class="card card-2">
|
||||||
|
<div class="icon-container"><i class="fa-solid fa-users-viewfinder"></i></div>
|
||||||
|
<h2>Sosial Kemasyarakatan</h2>
|
||||||
|
<p>Sistem informasi geografis kawasan rentan, sebaran warga miskin, dan pemantauan rumah ibadah terpadu.</p>
|
||||||
|
<a href="dashboard.html" class="btn btn-pink" onclick="localStorage.setItem('activeTab', 'sosial')">Masuk Sistem →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Portal Publik -->
|
||||||
|
<div class="card card-3">
|
||||||
|
<div class="icon-container"><i class="fa-solid fa-tower-broadcast"></i></div>
|
||||||
|
<h2>Portal Publik</h2>
|
||||||
|
<p>Akses peta interaktif secara transparan untuk masyarakat umum dan layanan pelaporan darurat.</p>
|
||||||
|
<a href="dashboard.html" class="btn btn-blue" onclick="localStorage.setItem('activeTab', 'publik')">Akses Warga →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Initialize an interactive background map
|
||||||
|
const map = L.map('bg-map', {
|
||||||
|
zoomControl: false,
|
||||||
|
dragging: false,
|
||||||
|
scrollWheelZoom: false,
|
||||||
|
doubleClickZoom: false,
|
||||||
|
boxZoom: false,
|
||||||
|
keyboard: false
|
||||||
|
}).setView([-0.0227, 109.3333], 13); // Pontianak coordinates
|
||||||
|
|
||||||
|
// Dark theme map tiles for better integration with the dark overlay
|
||||||
|
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||||
|
attribution: '© OpenStreetMap contributors © CARTO'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Add a slow continuous pan effect
|
||||||
|
setInterval(() => {
|
||||||
|
map.panBy([1, 0.5], {animate: true, duration: 1});
|
||||||
|
}, 100);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+912
@@ -0,0 +1,912 @@
|
|||||||
|
// ========================================
|
||||||
|
// FILE: js/main.js
|
||||||
|
// Main JavaScript untuk Pemetaan Kemiskinan
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// ========== KONFIGURASI ==========
|
||||||
|
const MAP_CENTER = [-0.0612, 109.3449]; // Koordinat pusat peta (Pontianak)
|
||||||
|
const MAP_ZOOM = 12; // Tingkat perbesaran bawaan peta
|
||||||
|
const API_PATH = 'php/'; // Jalur relatif menuju berkas API PHP
|
||||||
|
|
||||||
|
let map;
|
||||||
|
const markers = {
|
||||||
|
masyarakat: [],
|
||||||
|
ibadah: [],
|
||||||
|
spbu: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const layers = {
|
||||||
|
jalan: L.layerGroup(),
|
||||||
|
polygon: L.layerGroup()
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_tab = 'masyarakat';
|
||||||
|
const all_data = {
|
||||||
|
masyarakat: [],
|
||||||
|
ibadah: [],
|
||||||
|
jalan: [],
|
||||||
|
polygon: [],
|
||||||
|
spbu: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let drawnItems;
|
||||||
|
|
||||||
|
// ========== ICON MARKER ==========
|
||||||
|
const iconMasyarakat = L.icon({
|
||||||
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||||
|
iconSize: [25, 41],
|
||||||
|
iconAnchor: [12, 41],
|
||||||
|
popupAnchor: [1, -34],
|
||||||
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||||
|
shadowSize: [41, 41]
|
||||||
|
});
|
||||||
|
|
||||||
|
const iconIbadah = L.icon({
|
||||||
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||||
|
iconSize: [25, 41],
|
||||||
|
iconAnchor: [12, 41],
|
||||||
|
popupAnchor: [1, -34],
|
||||||
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||||
|
shadowSize: [41, 41]
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== UTILITAS KEAMANAN & FORMAT ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengamankan string teks dari potensi serangan injeksi XSS.
|
||||||
|
* @param {string} str - String mentah yang akan dibersihkan.
|
||||||
|
* @returns {string} String yang aman untuk direndering ke DOM.
|
||||||
|
*/
|
||||||
|
function escapeHTML(str) {
|
||||||
|
if (str === null || str === undefined) return '-';
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Melakukan validasi dasar terhadap respons HTTP Fetch sebelum diproses menjadi JSON.
|
||||||
|
* @param {Response} response - Objek respons dari Fetch API.
|
||||||
|
* @returns {Promise} Mengembalikan Promise berupa objek JSON jika sukses.
|
||||||
|
*/
|
||||||
|
function handleFetchResponse(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Gagal memuat data dari server (Status: ${response.status})`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== INISIALISASI ==========
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initMap();
|
||||||
|
loadData();
|
||||||
|
attachEventListeners();
|
||||||
|
|
||||||
|
// Check initial active tab from landing page
|
||||||
|
const activeTab = localStorage.getItem('activeTab');
|
||||||
|
const accs = document.querySelectorAll('.accordion');
|
||||||
|
|
||||||
|
if (activeTab === 'fasilitas') {
|
||||||
|
if (accs.length > 0) {
|
||||||
|
accs[0].style.display = 'flex'; // Show Fasilitas
|
||||||
|
if (accs.length > 1) accs[1].style.display = 'none'; // Hide Sosial
|
||||||
|
if (accs.length > 2) accs[2].style.display = 'none'; // Hide Publik
|
||||||
|
|
||||||
|
accs[0].classList.add('active');
|
||||||
|
accs[0].nextElementSibling.style.display = 'block';
|
||||||
|
const firstTab = accs[0].nextElementSibling.querySelector('.tab-button');
|
||||||
|
if (firstTab) firstTab.click();
|
||||||
|
}
|
||||||
|
} else if (activeTab === 'sosial') {
|
||||||
|
if (accs.length > 1) {
|
||||||
|
if (accs.length > 0) accs[0].style.display = 'none'; // Hide Fasilitas
|
||||||
|
accs[1].style.display = 'flex'; // Show Sosial
|
||||||
|
if (accs.length > 2) accs[2].style.display = 'none'; // Hide Publik
|
||||||
|
|
||||||
|
accs[1].classList.add('active');
|
||||||
|
accs[1].nextElementSibling.style.display = 'block';
|
||||||
|
const firstTab = accs[1].nextElementSibling.querySelector('.tab-button');
|
||||||
|
if (firstTab) firstTab.click();
|
||||||
|
}
|
||||||
|
} else if (activeTab === 'publik') {
|
||||||
|
// Mode Publik: Sembunyikan semua tombol Add dan Form Input
|
||||||
|
document.querySelectorAll('.btn-primary').forEach(btn => {
|
||||||
|
if(btn.textContent.includes('+')) btn.style.display = 'none';
|
||||||
|
});
|
||||||
|
document.querySelector('.leaflet-draw').style.display = 'none'; // Sembunyikan toolbar Leaflet Draw
|
||||||
|
|
||||||
|
// Buka Layanan Publik (Accordion 3)
|
||||||
|
if (accs.length > 0) accs[0].style.display = 'none';
|
||||||
|
if (accs.length > 1) accs[1].style.display = 'none';
|
||||||
|
if (accs.length > 2) {
|
||||||
|
accs[2].style.display = 'flex';
|
||||||
|
accs[2].classList.add('active');
|
||||||
|
accs[2].nextElementSibling.style.display = 'block';
|
||||||
|
const firstTab = accs[2].nextElementSibling.querySelector('.tab-button');
|
||||||
|
if (firstTab) firstTab.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan badge Portal Publik di sidebar jika belum ada
|
||||||
|
if (!document.getElementById('badge-publik')) {
|
||||||
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
const badge = document.createElement('div');
|
||||||
|
badge.id = 'badge-publik';
|
||||||
|
badge.innerHTML = '<div style="background:#2563eb;color:white;padding:10px;text-align:center;border-radius:5px;margin-bottom:15px;font-weight:bold;">Mode Akses Publik</div>';
|
||||||
|
sidebar.insertBefore(badge, sidebar.children[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menginisialisasi objek peta Leaflet dan menambahkan event listener klik.
|
||||||
|
*/
|
||||||
|
function initMap() {
|
||||||
|
map = L.map('map').setView(MAP_CENTER, MAP_ZOOM);
|
||||||
|
|
||||||
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// LEAFLET DRAW
|
||||||
|
drawnItems = new L.FeatureGroup();
|
||||||
|
map.addLayer(drawnItems);
|
||||||
|
|
||||||
|
const drawControl = new L.Control.Draw({
|
||||||
|
edit: { featureGroup: drawnItems },
|
||||||
|
draw: {
|
||||||
|
polyline: true,
|
||||||
|
polygon: true,
|
||||||
|
circle: false,
|
||||||
|
marker: false,
|
||||||
|
circlemarker: false,
|
||||||
|
rectangle: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map.addControl(drawControl);
|
||||||
|
|
||||||
|
map.on(L.Draw.Event.CREATED, function (e) {
|
||||||
|
const type = e.layerType;
|
||||||
|
const layer = e.layer;
|
||||||
|
const geojson = JSON.stringify(layer.toGeoJSON());
|
||||||
|
|
||||||
|
if (type === 'polyline') {
|
||||||
|
openModal('modal-form-jalan');
|
||||||
|
setTimeout(() => { document.getElementById('geojson-jalan').value = geojson; }, 100);
|
||||||
|
} else if (type === 'polygon') {
|
||||||
|
openModal('modal-form-polygon');
|
||||||
|
setTimeout(() => { document.getElementById('geojson-polygon').value = geojson; }, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Klik peta untuk memunculkan pilihan tambah data
|
||||||
|
map.on('click', function(e) {
|
||||||
|
const lat = e.latlng.lat.toFixed(8);
|
||||||
|
const lng = e.latlng.lng.toFixed(8);
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="latitude"]').forEach(el => el.value = lat);
|
||||||
|
document.querySelectorAll('input[name="longitude"]').forEach(el => el.value = lng);
|
||||||
|
|
||||||
|
let buttonsHTML = '';
|
||||||
|
if (current_tab === 'masyarakat') {
|
||||||
|
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-masyarakat');" style="background:#4CAF50; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah Warga Miskin</button>`;
|
||||||
|
} else if (current_tab === 'ibadah') {
|
||||||
|
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-ibadah');" style="background:#2196F3; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah Rumah Ibadah</button>`;
|
||||||
|
} else if (current_tab === 'spbu') {
|
||||||
|
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-spbu');" style="background:#ff9800; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah SPBU</button>`;
|
||||||
|
} else {
|
||||||
|
buttonsHTML = `<small style="color:#f44336;">Silakan pilih tab yang sesuai (Warga, Ibadah, atau SPBU) di menu kiri terlebih dahulu.</small>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat konten popup pilihan
|
||||||
|
const popupContent = `
|
||||||
|
<div style="text-align: center; min-width: 160px; padding: 5px;">
|
||||||
|
<b style="font-size: 14px; color: #1e3c72; margin-bottom:5px; display:block;">Pilih Data untuk Ditambah</b>
|
||||||
|
<small style="color: #666; display:block; margin-bottom:10px;">${lat}, ${lng}</small>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
${buttonsHTML}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
L.popup()
|
||||||
|
.setLatLng(e.latlng)
|
||||||
|
.setContent(popupContent)
|
||||||
|
.openOn(map);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memicu proses memuat seluruh data dari API secara paralel.
|
||||||
|
*/
|
||||||
|
function loadData() {
|
||||||
|
loadMasyarakat();
|
||||||
|
loadRumahIbadah();
|
||||||
|
loadJalan();
|
||||||
|
loadPolygon();
|
||||||
|
loadSPBU();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadJalan() {
|
||||||
|
fetch(API_PATH + 'ambil_fasilitas_jalan.php').then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
all_data.jalan = result.data || [];
|
||||||
|
tampilkanDataJalan(all_data.jalan);
|
||||||
|
}
|
||||||
|
}).catch(e => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPolygon() {
|
||||||
|
fetch(API_PATH + 'ambil_fasilitas_polygon.php').then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
all_data.polygon = result.data || [];
|
||||||
|
tampilkanDataPolygon(all_data.polygon);
|
||||||
|
}
|
||||||
|
}).catch(e => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSPBU() {
|
||||||
|
fetch(API_PATH + 'ambil_fasilitas_spbu.php').then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
all_data.spbu = result.data || [];
|
||||||
|
tampilkanDataSPBU(all_data.spbu);
|
||||||
|
}
|
||||||
|
}).catch(e => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memuat data masyarakat miskin dari server.
|
||||||
|
*/
|
||||||
|
function loadMasyarakat() {
|
||||||
|
fetch(API_PATH + 'ambil_data_masyarakat.php')
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
all_data.masyarakat = result.data || [];
|
||||||
|
tampilkanDataMasyarakat(all_data.masyarakat);
|
||||||
|
} else {
|
||||||
|
console.error('Gagal memuat data masyarakat:', result.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error saat fetch data masyarakat:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memuat data fasilitas rumah ibadah dari server.
|
||||||
|
*/
|
||||||
|
function loadRumahIbadah() {
|
||||||
|
fetch(API_PATH + 'ambil_rumah_ibadah.php')
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
all_data.ibadah = result.data || [];
|
||||||
|
tampilkanDataIbadah(all_data.ibadah);
|
||||||
|
} else {
|
||||||
|
console.error('Gagal memuat data rumah ibadah:', result.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error saat fetch data rumah ibadah:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merender penanda peta dan elemen daftar sidebar untuk kategori masyarakat.
|
||||||
|
* @param {Array} data - Larik berisi data objek masyarakat.
|
||||||
|
*/
|
||||||
|
function tampilkanDataMasyarakat(data) {
|
||||||
|
markers.masyarakat.forEach(m => map.removeLayer(m));
|
||||||
|
markers.masyarakat = [];
|
||||||
|
|
||||||
|
const list = document.getElementById('list-masyarakat');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
const marker = L.marker(
|
||||||
|
[parseFloat(item.latitude), parseFloat(item.longitude)],
|
||||||
|
{ icon: iconMasyarakat }
|
||||||
|
).addTo(map);
|
||||||
|
|
||||||
|
const bpjs_class = item.status_bpjs === 'Ya' ? 'status-ya' : (item.status_bpjs === 'Proses' ? 'status-proses' : 'status-tidak');
|
||||||
|
|
||||||
|
// Penerapan escapeHTML untuk mencegah kerentanan XSS pada popup
|
||||||
|
marker.bindPopup(`
|
||||||
|
<div style="font-size:13px; min-width:250px;">
|
||||||
|
<b>${escapeHTML(item.nama)}</b><br>
|
||||||
|
<small style="opacity:0.7;">${escapeHTML(item.alamat)}</small><br>
|
||||||
|
Status BPJS: <span class="status-label ${bpjs_class}">${escapeHTML(item.status_bpjs)}</span><br>
|
||||||
|
<button onclick="showDetail('${escapeHTML(item.id)}')" style="width:100%; padding:8px; margin-top:10px; background:#1e3c72; color:white; border:none; border-radius:5px; cursor:pointer;">Lihat Detail</button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
markers.masyarakat.push(marker);
|
||||||
|
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `
|
||||||
|
<b>${escapeHTML(item.nama)}</b>
|
||||||
|
<small>${escapeHTML(item.kecamatan)} | BPJS: ${escapeHTML(item.status_bpjs)}</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
li.onclick = () => {
|
||||||
|
map.setView([item.latitude, item.longitude], 16);
|
||||||
|
marker.openPopup();
|
||||||
|
};
|
||||||
|
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merender penanda peta dan elemen daftar sidebar untuk kategori rumah ibadah.
|
||||||
|
* @param {Array} data - Larik berisi data objek rumah ibadah.
|
||||||
|
*/
|
||||||
|
function tampilkanDataIbadah(data) {
|
||||||
|
markers.ibadah.forEach(m => map.removeLayer(m));
|
||||||
|
markers.ibadah = [];
|
||||||
|
|
||||||
|
const list = document.getElementById('list-ibadah');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
const marker = L.marker(
|
||||||
|
[parseFloat(item.latitude), parseFloat(item.longitude)],
|
||||||
|
{ icon: iconIbadah }
|
||||||
|
).addTo(map);
|
||||||
|
|
||||||
|
const info_pemimpin = item.nama_pemimpin ? 'Pemimpin: ' + escapeHTML(item.nama_pemimpin) + '<br>' : '';
|
||||||
|
const info_alamat = item.alamat ? '<small>' + escapeHTML(item.alamat) + '</small>' : '';
|
||||||
|
|
||||||
|
marker.bindPopup(`
|
||||||
|
<div style="font-size:13px; min-width:250px;">
|
||||||
|
<b>${escapeHTML(item.nama)}</b><br>
|
||||||
|
<small>${escapeHTML(item.jenis)}</small><br>
|
||||||
|
${info_pemimpin}
|
||||||
|
${info_alamat}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
markers.ibadah.push(marker);
|
||||||
|
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `
|
||||||
|
<b>${escapeHTML(item.nama)}</b>
|
||||||
|
<small>${escapeHTML(item.jenis)}</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
li.onclick = () => {
|
||||||
|
map.setView([item.latitude, item.longitude], 16);
|
||||||
|
marker.openPopup();
|
||||||
|
};
|
||||||
|
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tampilkanDataJalan(data) {
|
||||||
|
if (layers.jalan) map.removeLayer(layers.jalan);
|
||||||
|
layers.jalan = L.layerGroup().addTo(map);
|
||||||
|
const list = document.getElementById('list-jalan');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
try {
|
||||||
|
const geojson = JSON.parse(item.geojson);
|
||||||
|
const layer = L.geoJSON(geojson, { style: { color: 'blue', weight: 4 } }).addTo(layers.jalan);
|
||||||
|
layer.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small><br><button onclick="hapusData('jalan', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||||||
|
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small>`;
|
||||||
|
li.onclick = () => { map.fitBounds(layer.getBounds()); layer.openPopup(); };
|
||||||
|
list.appendChild(li);
|
||||||
|
} catch(e) { console.error('Invalid GeoJSON', e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tampilkanDataPolygon(data) {
|
||||||
|
if (layers.polygon) map.removeLayer(layers.polygon);
|
||||||
|
layers.polygon = L.layerGroup().addTo(map);
|
||||||
|
const list = document.getElementById('list-polygon');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
try {
|
||||||
|
const geojson = JSON.parse(item.geojson);
|
||||||
|
const layer = L.geoJSON(geojson, { style: { color: 'purple', fillColor: 'purple', fillOpacity: 0.4 } }).addTo(layers.polygon);
|
||||||
|
layer.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small><br><button onclick="hapusData('polygon', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||||||
|
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small>`;
|
||||||
|
li.onclick = () => { map.fitBounds(layer.getBounds()); layer.openPopup(); };
|
||||||
|
list.appendChild(li);
|
||||||
|
} catch(e) { console.error('Invalid GeoJSON', e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tampilkanDataSPBU(data) {
|
||||||
|
markers.spbu.forEach(m => map.removeLayer(m));
|
||||||
|
markers.spbu = [];
|
||||||
|
const list = document.getElementById('list-spbu');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
const is24 = item.is_24_jam === 'Ya';
|
||||||
|
// Reuse iconIbadah for green (24h) and iconMasyarakat for red (not 24h)
|
||||||
|
const iconSPBU = is24 ? iconIbadah : iconMasyarakat;
|
||||||
|
const statusText = is24 ? '<span style="color:green;font-weight:bold;">Buka 24 Jam</span>' : '<span style="color:red;font-weight:bold;">Tidak 24 Jam</span>';
|
||||||
|
|
||||||
|
const marker = L.marker([parseFloat(item.latitude), parseFloat(item.longitude)], {
|
||||||
|
icon: iconSPBU,
|
||||||
|
draggable: true
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
marker.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.alamat)}</small><br>${statusText}<br><button onclick="hapusData('spbu', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||||||
|
|
||||||
|
marker.on('dragend', function(e) {
|
||||||
|
const newPos = marker.getLatLng();
|
||||||
|
if (confirm('Apakah Anda yakin ingin memperbarui lokasi SPBU ini?')) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append('id', item.id);
|
||||||
|
fd.append('latitude', newPos.lat.toFixed(8));
|
||||||
|
fd.append('longitude', newPos.lng.toFixed(8));
|
||||||
|
fetch(API_PATH + 'update_lokasi_spbu.php', { method: 'POST', body: fd })
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
alert('Lokasi berhasil diperbarui');
|
||||||
|
item.latitude = newPos.lat;
|
||||||
|
item.longitude = newPos.lng;
|
||||||
|
} else {
|
||||||
|
alert('Gagal: ' + res.message);
|
||||||
|
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
alert('Error: ' + err.message);
|
||||||
|
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
markers.spbu.push(marker);
|
||||||
|
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.alamat)}</small><br><small>${statusText}</small>`;
|
||||||
|
li.onclick = () => { map.setView([item.latitude, item.longitude], 16); marker.openPopup(); };
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memuat detail data spesifik masyarakat miskin termasuk riwayat bantuan dan status BPJS.
|
||||||
|
* @param {string|number} id - ID unik entitas masyarakat miskin.
|
||||||
|
*/
|
||||||
|
function showDetail(id) {
|
||||||
|
fetch(API_PATH + 'ambil_detail.php?id=' + encodeURIComponent(id))
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
const data = result.data;
|
||||||
|
const masyarakat = data.masyarakat;
|
||||||
|
const bantuan = data.bantuan || [];
|
||||||
|
const bpjs = data.bpjs;
|
||||||
|
|
||||||
|
const bpjs_class = masyarakat.status_bpjs === 'Ya' ? 'status-ya' : (masyarakat.status_bpjs === 'Proses' ? 'status-proses' : 'status-tidak');
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="info-section">
|
||||||
|
<h3>👤 Data Pribadi</h3>
|
||||||
|
<div class="info-row"><span class="info-label">Nama</span><span class="info-value">${escapeHTML(masyarakat.nama)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Tanggal Lahir</span><span class="info-value">${escapeHTML(masyarakat.tanggal_lahir)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Jenis Kelamin</span><span class="info-value">${escapeHTML(masyarakat.jenis_kelamin)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">NIK</span><span class="info-value">${escapeHTML(masyarakat.nik)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">No. HP</span><span class="info-value">${escapeHTML(masyarakat.no_hp)}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h3>🏠 Data Alamat</h3>
|
||||||
|
<div class="info-row"><span class="info-label">Alamat</span><span class="info-value">${escapeHTML(masyarakat.alamat)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Kelurahan</span><span class="info-value">${escapeHTML(masyarakat.kelurahan)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Kecamatan</span><span class="info-value">${escapeHTML(masyarakat.kecamatan)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Status Rumah</span><span class="info-value">${escapeHTML(masyarakat.status_rumah)}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h3>💼 Data Ekonomi</h3>
|
||||||
|
<div class="info-row"><span class="info-label">Pekerjaan</span><span class="info-value">${escapeHTML(masyarakat.pekerjaan)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Pendapatan Bulanan</span><span class="info-value">Rp. ${new Intl.NumberFormat('id-ID').format(masyarakat.pendapatan_bulanan || 0)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Jumlah Tanggungan</span><span class="info-value">${escapeHTML(masyarakat.jumlah_tanggungan)} orang</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h3>🏥 Status BPJS</h3>
|
||||||
|
<div class="info-row"><span class="info-label">Status BPJS</span><span class="info-value"><span class="status-label ${bpjs_class}">${escapeHTML(masyarakat.status_bpjs)}</span></span></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (bpjs) {
|
||||||
|
html += `
|
||||||
|
<div class="info-row"><span class="info-label">No. Peserta BPJS</span><span class="info-value">${escapeHTML(bpjs.no_peserta)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Jenis BPJS</span><span class="info-value">${escapeHTML(bpjs.jenis_bpjs)}</span></div>
|
||||||
|
<div class="info-row"><span class="info-label">Tingkat</span><span class="info-value">${escapeHTML(bpjs.tingkat)}</span></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`; // Memperbaiki string penutup tag div yang sebelumnya memicu syntax error
|
||||||
|
|
||||||
|
if (bantuan.length > 0) {
|
||||||
|
html += `<div class="info-section"><h3>💰 Riwayat Bantuan</h3>`;
|
||||||
|
bantuan.forEach(b => {
|
||||||
|
html += `
|
||||||
|
<div style="background:white; padding:8px; margin-bottom:8px; border-left:3px solid #ff9800; border-radius:3px; font-size:12px;">
|
||||||
|
<b>${escapeHTML(b.jenis_bantuan)}</b> (${escapeHTML(b.tanggal_bantuan)})<br>
|
||||||
|
Nilai: <strong>Rp. ${new Intl.NumberFormat('id-ID').format(b.nilai)}</strong> ${escapeHTML(b.satuan)}
|
||||||
|
${b.keterangan ? '<br><small>' + escapeHTML(b.keterangan) + '</small>' : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
html += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn-primary" onclick="editBantuan(${parseInt(id)})">➕ Tambah Bantuan</button>
|
||||||
|
<button class="btn-secondary" onclick="hapusData('masyarakat', ${parseInt(id)})">🗑️ Hapus</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modal_body = document.getElementById('modal-body');
|
||||||
|
if (modal_body) {
|
||||||
|
modal_body.innerHTML = html;
|
||||||
|
openModal('modal-detail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error saat fetch detail data:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menghubungkan fungsi penangan kejadian (event handler) ke elemen-elemen HTML DOM.
|
||||||
|
*/
|
||||||
|
function attachEventListeners() {
|
||||||
|
// Accordion Logic
|
||||||
|
document.querySelectorAll('.accordion').forEach(acc => {
|
||||||
|
acc.addEventListener('click', function() {
|
||||||
|
// Close other accordions
|
||||||
|
document.querySelectorAll('.accordion').forEach(otherAcc => {
|
||||||
|
if (otherAcc !== this) {
|
||||||
|
otherAcc.classList.remove('active');
|
||||||
|
if (otherAcc.nextElementSibling) {
|
||||||
|
otherAcc.nextElementSibling.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle this one
|
||||||
|
this.classList.toggle('active');
|
||||||
|
const panel = this.nextElementSibling;
|
||||||
|
if (panel.style.display === "block") {
|
||||||
|
panel.style.display = "none";
|
||||||
|
} else {
|
||||||
|
panel.style.display = "block";
|
||||||
|
// Optionally auto-click the first tab in this accordion
|
||||||
|
const firstTab = panel.querySelector('.tab-button');
|
||||||
|
if (firstTab && !firstTab.classList.contains('active')) {
|
||||||
|
firstTab.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.tab-button').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
const tab = this.dataset.tab;
|
||||||
|
switchTab(e, tab);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const search_input = document.getElementById('search');
|
||||||
|
const f_kecamatan = document.getElementById('filter-kecamatan');
|
||||||
|
const f_bpjs = document.getElementById('filter-bpjs');
|
||||||
|
const f_jenis = document.getElementById('filter-jenis');
|
||||||
|
|
||||||
|
if (search_input) search_input.addEventListener('input', filterMasyarakat);
|
||||||
|
if (f_kecamatan) f_kecamatan.addEventListener('change', filterMasyarakat);
|
||||||
|
if (f_bpjs) f_bpjs.addEventListener('change', filterMasyarakat);
|
||||||
|
if (f_jenis) f_jenis.addEventListener('change', filterIbadah);
|
||||||
|
|
||||||
|
const form_masyarakat = document.getElementById('form-masyarakat');
|
||||||
|
const form_ibadah = document.getElementById('form-ibadah');
|
||||||
|
const form_jalan = document.getElementById('form-jalan');
|
||||||
|
const form_polygon = document.getElementById('form-polygon');
|
||||||
|
const form_spbu = document.getElementById('form-spbu');
|
||||||
|
|
||||||
|
if (form_masyarakat) form_masyarakat.addEventListener('submit', function(e) { e.preventDefault(); submitFormMasyarakat(); });
|
||||||
|
if (form_ibadah) form_ibadah.addEventListener('submit', function(e) { e.preventDefault(); submitFormIbadah(); });
|
||||||
|
if (form_jalan) form_jalan.addEventListener('submit', function(e) { e.preventDefault(); submitFormJalan(); });
|
||||||
|
if (form_polygon) form_polygon.addEventListener('submit', function(e) { e.preventDefault(); submitFormPolygon(); });
|
||||||
|
if (form_spbu) form_spbu.addEventListener('submit', function(e) { e.preventDefault(); submitFormSPBU(); });
|
||||||
|
|
||||||
|
// Shortcut keyboard untuk membuka formulir penambahan data baru
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'n' || e.key === 'N') {
|
||||||
|
const active_modal = document.querySelector('.modal.active');
|
||||||
|
if (active_modal) return; // Cegah konflik jika modal lain sudah terbuka
|
||||||
|
|
||||||
|
if (current_tab === 'masyarakat') openModal('modal-form-masyarakat');
|
||||||
|
else if (current_tab === 'ibadah') openModal('modal-form-ibadah');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengubah status visibilitas tab kategori aktif (Masyarakat / Rumah Ibadah).
|
||||||
|
* @param {Event} e - Objek event klik dari DOM.
|
||||||
|
* @param {string} tab - Nama tab penanda tujuan ('masyarakat' / 'ibadah').
|
||||||
|
*/
|
||||||
|
function switchTab(e, tab) {
|
||||||
|
current_tab = tab;
|
||||||
|
|
||||||
|
document.querySelectorAll('.tab-button').forEach(btn => {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
});
|
||||||
|
if (e && e.target) {
|
||||||
|
e.target.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.list-container').forEach(container => {
|
||||||
|
container.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
const target_tab = document.getElementById('tab-' + tab);
|
||||||
|
if (target_tab) target_tab.classList.add('active');
|
||||||
|
|
||||||
|
updateMapVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMapVisibility() {
|
||||||
|
markers.masyarakat.forEach(m => map.removeLayer(m));
|
||||||
|
markers.ibadah.forEach(m => map.removeLayer(m));
|
||||||
|
markers.spbu.forEach(m => map.removeLayer(m));
|
||||||
|
if (layers.jalan) map.removeLayer(layers.jalan);
|
||||||
|
if (layers.polygon) map.removeLayer(layers.polygon);
|
||||||
|
|
||||||
|
if (current_tab === 'masyarakat' || current_tab === 'info-fasilitas') {
|
||||||
|
markers.masyarakat.forEach(m => map.addLayer(m));
|
||||||
|
}
|
||||||
|
if (current_tab === 'ibadah' || current_tab === 'info-fasilitas') {
|
||||||
|
markers.ibadah.forEach(m => map.addLayer(m));
|
||||||
|
}
|
||||||
|
if (current_tab === 'spbu' || current_tab === 'info-fasilitas') {
|
||||||
|
markers.spbu.forEach(m => map.addLayer(m));
|
||||||
|
}
|
||||||
|
if (current_tab === 'jalan' || current_tab === 'info-fasilitas') {
|
||||||
|
if (layers.jalan) map.addLayer(layers.jalan);
|
||||||
|
}
|
||||||
|
if (current_tab === 'polygon' || current_tab === 'info-fasilitas') {
|
||||||
|
if (layers.polygon) map.addLayer(layers.polygon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memfilter kumpulan data masyarakat berdasarkan kata kunci pencarian, kecamatan, dan status BPJS.
|
||||||
|
*/
|
||||||
|
function filterMasyarakat() {
|
||||||
|
const search_el = document.getElementById('search');
|
||||||
|
const kec_el = document.getElementById('filter-kecamatan');
|
||||||
|
const bpjs_el = document.getElementById('filter-bpjs');
|
||||||
|
|
||||||
|
const keyword = search_el ? search_el.value.toLowerCase() : '';
|
||||||
|
const kecamatan = kec_el ? kec_el.value : '';
|
||||||
|
const bpjs = bpjs_el ? bpjs_el.value : '';
|
||||||
|
|
||||||
|
const filtered = all_data.masyarakat.filter(item => {
|
||||||
|
const item_nama = item.nama ? item.nama.toLowerCase() : '';
|
||||||
|
const item_alamat = item.alamat ? item.alamat.toLowerCase() : '';
|
||||||
|
|
||||||
|
const match_keyword = item_nama.includes(keyword) || item_alamat.includes(keyword);
|
||||||
|
const match_kecamatan = !kecamatan || item.kecamatan === kecamatan;
|
||||||
|
const match_bpjs = !bpjs || item.status_bpjs === bpjs;
|
||||||
|
|
||||||
|
return match_keyword && match_kecamatan && match_bpjs;
|
||||||
|
});
|
||||||
|
|
||||||
|
tampilkanDataMasyarakat(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memfilter kumpulan data tempat ibadah berdasarkan jenis kategori tempat ibadah.
|
||||||
|
*/
|
||||||
|
function filterIbadah() {
|
||||||
|
const jenis_el = document.getElementById('filter-jenis');
|
||||||
|
const jenis = jenis_el ? jenis_el.value : '';
|
||||||
|
|
||||||
|
const filtered = all_data.ibadah.filter(item => {
|
||||||
|
return !jenis || item.jenis === jenis;
|
||||||
|
});
|
||||||
|
|
||||||
|
tampilkanDataIbadah(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengirimkan formulir data masyarakat baru ke berkas PHP pemroses menggunakan Fetch FormData.
|
||||||
|
*/
|
||||||
|
function submitFormMasyarakat() {
|
||||||
|
const form = document.getElementById('form-masyarakat');
|
||||||
|
if (!form) return;
|
||||||
|
const form_data = new FormData(form);
|
||||||
|
|
||||||
|
fetch(API_PATH + 'simpan_masyarakat.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: form_data
|
||||||
|
})
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
showAlert('alert-form', 'Data berhasil disimpan!', 'success');
|
||||||
|
form.reset();
|
||||||
|
setTimeout(() => {
|
||||||
|
closeModal('modal-form-masyarakat');
|
||||||
|
loadMasyarakat();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
showAlert('alert-form', result.message || 'Gagal menyimpan data.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
showAlert('alert-form', 'Terjadi kesalahan sistem: ' + error.message, 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengirimkan formulir data rumah ibadah baru ke berkas PHP pemroses menggunakan Fetch FormData.
|
||||||
|
*/
|
||||||
|
function submitFormIbadah() {
|
||||||
|
const form = document.getElementById('form-ibadah');
|
||||||
|
if (!form) return;
|
||||||
|
const form_data = new FormData(form);
|
||||||
|
|
||||||
|
fetch(API_PATH + 'simpan_rumah_ibadah.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: form_data
|
||||||
|
})
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
showAlert('alert-form-ibadah', 'Data berhasil disimpan!', 'success');
|
||||||
|
form.reset();
|
||||||
|
setTimeout(() => {
|
||||||
|
closeModal('modal-form-ibadah');
|
||||||
|
loadRumahIbadah();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
showAlert('alert-form-ibadah', result.message || 'Gagal menyimpan data.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
showAlert('alert-form-ibadah', 'Terjadi kesalahan sistem: ' + error.message, 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitFormJalan() {
|
||||||
|
const form = document.getElementById('form-jalan');
|
||||||
|
if (!form) return;
|
||||||
|
const form_data = new FormData(form);
|
||||||
|
fetch(API_PATH + 'simpan_fasilitas_jalan.php', { method: 'POST', body: form_data })
|
||||||
|
.then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
showAlert('alert-form-jalan', 'Berhasil disimpan', 'success'); form.reset(); drawnItems.clearLayers();
|
||||||
|
setTimeout(() => { closeModal('modal-form-jalan'); loadJalan(); }, 1000);
|
||||||
|
} else showAlert('alert-form-jalan', result.message, 'error');
|
||||||
|
}).catch(e => showAlert('alert-form-jalan', e.message, 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitFormPolygon() {
|
||||||
|
const form = document.getElementById('form-polygon');
|
||||||
|
if (!form) return;
|
||||||
|
const form_data = new FormData(form);
|
||||||
|
fetch(API_PATH + 'simpan_fasilitas_polygon.php', { method: 'POST', body: form_data })
|
||||||
|
.then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
showAlert('alert-form-polygon', 'Berhasil disimpan', 'success'); form.reset(); drawnItems.clearLayers();
|
||||||
|
setTimeout(() => { closeModal('modal-form-polygon'); loadPolygon(); }, 1000);
|
||||||
|
} else showAlert('alert-form-polygon', result.message, 'error');
|
||||||
|
}).catch(e => showAlert('alert-form-polygon', e.message, 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitFormSPBU() {
|
||||||
|
const form = document.getElementById('form-spbu');
|
||||||
|
if (!form) return;
|
||||||
|
const form_data = new FormData(form);
|
||||||
|
fetch(API_PATH + 'simpan_fasilitas_spbu.php', { method: 'POST', body: form_data })
|
||||||
|
.then(handleFetchResponse).then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
showAlert('alert-form-spbu', 'Berhasil disimpan', 'success'); form.reset();
|
||||||
|
setTimeout(() => { closeModal('modal-form-spbu'); loadSPBU(); }, 1000);
|
||||||
|
} else showAlert('alert-form-spbu', result.message, 'error');
|
||||||
|
}).catch(e => showAlert('alert-form-spbu', e.message, 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengirimkan instruksi penghapusan data spesifik (masyarakat/ibadah) ke sistem basis data backend.
|
||||||
|
* @param {string} tipe - Jenis kategori data yang akan dihapus ('masyarakat' / 'ibadah').
|
||||||
|
* @param {string|number} id - ID baris data target.
|
||||||
|
*/
|
||||||
|
function hapusData(tipe, id) {
|
||||||
|
if (!confirm('Apakah Anda yakin ingin menghapus data ini? Tindakan ini tidak dapat dibatalkan.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form_data = new FormData();
|
||||||
|
form_data.append('tipe', tipe);
|
||||||
|
form_data.append('id', id);
|
||||||
|
|
||||||
|
fetch(API_PATH + 'hapus_data.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: form_data
|
||||||
|
})
|
||||||
|
.then(handleFetchResponse)
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
alert('Data berhasil dihapus');
|
||||||
|
closeModal('modal-detail');
|
||||||
|
if (tipe === 'masyarakat') loadMasyarakat();
|
||||||
|
else if (tipe === 'ibadah') loadRumahIbadah();
|
||||||
|
else if (tipe === 'jalan') loadJalan();
|
||||||
|
else if (tipe === 'polygon') loadPolygon();
|
||||||
|
else if (tipe === 'spbu') loadSPBU();
|
||||||
|
} else {
|
||||||
|
alert('Gagal menghapus: ' + (result.message || 'Error tidak diketahui'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert('Terjadi kesalahan jaringan atau sistem: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editBantuan(id) {
|
||||||
|
alert('Fitur tambah bantuan dengan ID ' + id + ' akan ditambahkan segera');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
if (modal) modal.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
if (modal) modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(elementId, message, type) {
|
||||||
|
const alert_el = document.getElementById(elementId);
|
||||||
|
if (!alert_el) return;
|
||||||
|
|
||||||
|
alert_el.textContent = message;
|
||||||
|
alert_el.className = 'alert active alert-' + type;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alert_el.classList.remove('active');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menutup jendela modal otomatis jika pengguna mengklik area luar modal (overlay)
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target && e.target.classList.contains('modal')) {
|
||||||
|
e.target.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/ambil_data_masyarakat.php
|
||||||
|
// Mengambil data masyarakat miskin
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
|
||||||
|
// Kumpulkan filter dulu SEBELUM query dibuat
|
||||||
|
$filters = [];
|
||||||
|
$params = [];
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
if (!empty($_GET['kecamatan'])) {
|
||||||
|
$filters[] = "kecamatan = ?";
|
||||||
|
$params[] = $_GET['kecamatan'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['status_bpjs'])) {
|
||||||
|
$filters[] = "status_bpjs = ?";
|
||||||
|
$params[] = $_GET['status_bpjs'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$filters[] = "(nama LIKE ? OR alamat LIKE ?)";
|
||||||
|
$searchParam = "%" . $_GET['search'] . "%";
|
||||||
|
$params[] = $searchParam;
|
||||||
|
$params[] = $searchParam;
|
||||||
|
$types .= "ss";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Susun query dengan urutan benar: WHERE dulu, baru ORDER BY
|
||||||
|
$query = "SELECT * FROM masyarakat_miskin";
|
||||||
|
|
||||||
|
if (!empty($filters)) {
|
||||||
|
$query .= " WHERE " . implode(" AND ", $filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
if ($stmt === false) {
|
||||||
|
sendJSON('error', 'Query prepare gagal: ' . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
sendJSON('error', 'Query gagal dieksekusi: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendJSON('success', 'Data berhasil diambil', $data);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/ambil_detail.php
|
||||||
|
// Mengambil detail lengkap data masyarakat + bantuan + BPJS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
|
||||||
|
$id = intval($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id == 0) {
|
||||||
|
sendJSON('error', 'ID tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMBIL DATA MASYARAKAT
|
||||||
|
$query_masyarakat = "SELECT * FROM masyarakat_miskin WHERE id = $id";
|
||||||
|
$result_masyarakat = $conn->query($query_masyarakat);
|
||||||
|
|
||||||
|
if ($result_masyarakat->num_rows === 0) {
|
||||||
|
sendJSON('error', 'Data masyarakat tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$masyarakat = $result_masyarakat->fetch_assoc();
|
||||||
|
|
||||||
|
// AMBIL DATA BANTUAN
|
||||||
|
$query_bantuan = "SELECT * FROM bantuan_riwayat WHERE id_masyarakat = $id ORDER BY tanggal_bantuan DESC";
|
||||||
|
$result_bantuan = $conn->query($query_bantuan);
|
||||||
|
$bantuan_list = [];
|
||||||
|
while ($row = $result_bantuan->fetch_assoc()) {
|
||||||
|
$bantuan_list[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMBIL DATA BPJS
|
||||||
|
$query_bpjs = "SELECT * FROM bpjs_record WHERE id_masyarakat = $id";
|
||||||
|
$result_bpjs = $conn->query($query_bpjs);
|
||||||
|
$bpjs = null;
|
||||||
|
if ($result_bpjs->num_rows > 0) {
|
||||||
|
$bpjs = $result_bpjs->fetch_assoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'masyarakat' => $masyarakat,
|
||||||
|
'bantuan' => $bantuan_list,
|
||||||
|
'bpjs' => $bpjs
|
||||||
|
];
|
||||||
|
|
||||||
|
sendJSON('success', 'Detail data berhasil diambil', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
$query = "SELECT * FROM fasilitas_jalan ORDER BY nama ASC";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
if ($result) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
sendJSON('success', 'Data jalan berhasil diambil', $data);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal mengambil data: ' . $conn->error);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
$query = "SELECT * FROM fasilitas_polygon ORDER BY nama ASC";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
if ($result) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
sendJSON('success', 'Data polygon berhasil diambil', $data);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal mengambil data: ' . $conn->error);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
$query = "SELECT * FROM fasilitas_spbu ORDER BY nama ASC";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
if ($result) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
sendJSON('success', 'Data SPBU berhasil diambil', $data);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal mengambil data: ' . $conn->error);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/ambil_rumah_ibadah.php
|
||||||
|
// Mengambil data rumah ibadah
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
|
||||||
|
// Susun query dengan urutan benar: WHERE dulu, baru ORDER BY
|
||||||
|
$query = "SELECT * FROM rumah_ibadah";
|
||||||
|
$params = [];
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
if (!empty($_GET['jenis'])) {
|
||||||
|
$query .= " WHERE jenis = ?";
|
||||||
|
$params[] = $_GET['jenis'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
if ($stmt === false) {
|
||||||
|
sendJSON('error', 'Query prepare gagal: ' . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
sendJSON('error', 'Query gagal dieksekusi: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendJSON('success', 'Data berhasil diambil', $data);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/hapus_data.php
|
||||||
|
// Menghapus data masyarakat miskin atau rumah ibadah
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$tipe = sanitize($_POST['tipe'] ?? '', $conn); // 'masyarakat' atau 'rumah_ibadah'
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id == 0 || empty($tipe)) {
|
||||||
|
sendJSON('error', 'Data tidak lengkap');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tipe === 'masyarakat') {
|
||||||
|
$query = "DELETE FROM masyarakat_miskin WHERE id = $id";
|
||||||
|
} elseif ($tipe === 'ibadah' || $tipe === 'rumah_ibadah') { // To be safe
|
||||||
|
$query = "DELETE FROM rumah_ibadah WHERE id = $id";
|
||||||
|
} elseif ($tipe === 'jalan') {
|
||||||
|
$query = "DELETE FROM fasilitas_jalan WHERE id = $id";
|
||||||
|
} elseif ($tipe === 'polygon') {
|
||||||
|
$query = "DELETE FROM fasilitas_polygon WHERE id = $id";
|
||||||
|
} elseif ($tipe === 'spbu') {
|
||||||
|
$query = "DELETE FROM fasilitas_spbu WHERE id = $id";
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Tipe tidak valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conn->query($query) === TRUE) {
|
||||||
|
if ($conn->affected_rows > 0) {
|
||||||
|
sendJSON('success', 'Data berhasil dihapus');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Data tidak ditemukan');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menghapus: ' . $conn->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/koneksi.php
|
||||||
|
// Konfigurasi Koneksi Database
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// CORS HEADERS - Wajib ada agar request dari browser tidak diblok
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// Handle preflight request (OPTIONS)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baca dari Environment Variable (untuk deployment Coolify/server)
|
||||||
|
// Jika tidak ada, fallback ke konfigurasi XAMPP lokal
|
||||||
|
$host = getenv('DB_HOST') ?: "localhost";
|
||||||
|
$username = getenv('DB_USER') ?: "root";
|
||||||
|
$password = getenv('DB_PASSWORD') ?: "";
|
||||||
|
$database = getenv('DB_DATABASE') ?: "pemetaan_kemiskinan";
|
||||||
|
$port = getenv('DB_PORT') ?: 3306;
|
||||||
|
|
||||||
|
// KONEKSI MYSQLI
|
||||||
|
$conn = new mysqli($host, $username, $password, $database, (int)$port);
|
||||||
|
|
||||||
|
// CEK KONEKSI
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
sendJSON('error', 'Koneksi database gagal: ' . $conn->connect_error);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET CHARSET UTF-8
|
||||||
|
$conn->set_charset("utf8mb4");
|
||||||
|
|
||||||
|
// FUNCTION UNTUK ESCAPE INPUT
|
||||||
|
function sanitize($string, $conn) {
|
||||||
|
return $conn->real_escape_string(strip_tags(trim($string)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FUNCTION UNTUK RESPONSE JSON
|
||||||
|
function sendJSON($status, $message, $data = null) {
|
||||||
|
$response = [
|
||||||
|
'status' => $status,
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
if ($data !== null) {
|
||||||
|
$response['data'] = $data;
|
||||||
|
}
|
||||||
|
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/setup_db.php
|
||||||
|
// Skrip Otomatis Impor Database
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// Karena koneksi.php sudah memilih database, kita buat koneksi mentah dulu
|
||||||
|
// untuk memastikan database 'pemetaan_kemiskinan' dibuat jika belum ada.
|
||||||
|
$host = "localhost";
|
||||||
|
$user = "root";
|
||||||
|
$pass = "";
|
||||||
|
|
||||||
|
$conn_init = new mysqli($host, $user, $pass);
|
||||||
|
if ($conn_init->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn_init->connect_error . " (Pastikan MySQL di XAMPP sudah menyala!)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat database jika belum ada
|
||||||
|
$conn_init->query("CREATE DATABASE IF NOT EXISTS `pemetaan_kemiskinan` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci");
|
||||||
|
$conn_init->select_db("pemetaan_kemiskinan");
|
||||||
|
|
||||||
|
// Baca isi file database.sql
|
||||||
|
$sql_file = __DIR__ . '/../database.sql';
|
||||||
|
if (!file_exists($sql_file)) {
|
||||||
|
die("File database.sql tidak ditemukan di $sql_file");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_content = file_get_contents($sql_file);
|
||||||
|
|
||||||
|
// Eksekusi semua query sekaligus
|
||||||
|
if ($conn_init->multi_query($sql_content)) {
|
||||||
|
do {
|
||||||
|
// Buang hasil dari setiap statement yang dieksekusi agar bisa lanjut ke statement berikutnya
|
||||||
|
if ($res = $conn_init->store_result()) {
|
||||||
|
$res->free();
|
||||||
|
}
|
||||||
|
} while ($conn_init->more_results() && $conn_init->next_result());
|
||||||
|
|
||||||
|
echo "<div style='background:#dcfce3; color:#166534; padding:20px; border-radius:10px; font-family:sans-serif; text-align:center;'>";
|
||||||
|
echo "<h1>✅ Database Berhasil Diimpor Otomatis!</h1>";
|
||||||
|
echo "<p>Seluruh tabel dan titik contoh masyarakat miskin & rumah ibadah sudah dimasukkan ke MySQL.</p>";
|
||||||
|
echo "<a href='../index.html' style='display:inline-block; padding:10px 20px; background:#16a34a; color:white; text-decoration:none; border-radius:5px;'>Kembali ke Portal</a>";
|
||||||
|
echo "</div>";
|
||||||
|
} else {
|
||||||
|
echo "Gagal mengimpor database: " . $conn_init->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn_init->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/simpan_bantuan.php
|
||||||
|
// Menyimpan riwayat bantuan
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$id_masyarakat = intval($_POST['id_masyarakat'] ?? 0);
|
||||||
|
$jenis_bantuan = sanitize($_POST['jenis_bantuan'] ?? '', $conn);
|
||||||
|
$tanggal_bantuan = sanitize($_POST['tanggal_bantuan'] ?? '', $conn);
|
||||||
|
$nilai = intval($_POST['nilai'] ?? 0);
|
||||||
|
$satuan = sanitize($_POST['satuan'] ?? 'Rupiah', $conn);
|
||||||
|
$keterangan = sanitize($_POST['keterangan'] ?? '', $conn);
|
||||||
|
$nama_pemberi = sanitize($_POST['nama_pemberi'] ?? '', $conn);
|
||||||
|
|
||||||
|
// VALIDASI
|
||||||
|
if ($id_masyarakat == 0 || empty($jenis_bantuan) || empty($tanggal_bantuan)) {
|
||||||
|
sendJSON('error', 'Data wajib lengkap');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CEK MASYARAKAT EXIST
|
||||||
|
$check = $conn->query("SELECT id FROM masyarakat_miskin WHERE id = $id_masyarakat");
|
||||||
|
if ($check->num_rows == 0) {
|
||||||
|
sendJSON('error', 'ID Masyarakat tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUERY INSERT
|
||||||
|
$query = "INSERT INTO bantuan_riwayat
|
||||||
|
(id_masyarakat, jenis_bantuan, tanggal_bantuan, nilai, satuan, keterangan, nama_pemberi)
|
||||||
|
VALUES
|
||||||
|
($id_masyarakat, '$jenis_bantuan', '$tanggal_bantuan', $nilai, '$satuan', '$keterangan', '$nama_pemberi')";
|
||||||
|
|
||||||
|
if ($conn->query($query) === TRUE) {
|
||||||
|
sendJSON('success', 'Riwayat bantuan berhasil disimpan', ['id' => $conn->insert_id]);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan: ' . $conn->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/simpan_bpjs.php
|
||||||
|
// Menyimpan data BPJS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$id_masyarakat = intval($_POST['id_masyarakat'] ?? 0);
|
||||||
|
$no_peserta = sanitize($_POST['no_peserta'] ?? '', $conn);
|
||||||
|
$jenis_bpjs = sanitize($_POST['jenis_bpjs'] ?? 'Kesehatan', $conn);
|
||||||
|
$tingkat = sanitize($_POST['tingkat'] ?? 'Penerima Bantuan Iuran (PBI)', $conn);
|
||||||
|
$tanggal_daftar = sanitize($_POST['tanggal_daftar'] ?? '', $conn);
|
||||||
|
$status_aktif = sanitize($_POST['status_aktif'] ?? 'Aktif', $conn);
|
||||||
|
$faskes = sanitize($_POST['faskes_tingkat_1'] ?? '', $conn);
|
||||||
|
|
||||||
|
// VALIDASI
|
||||||
|
if ($id_masyarakat == 0 || empty($no_peserta) || empty($tanggal_daftar)) {
|
||||||
|
sendJSON('error', 'Data wajib lengkap: ID, No Peserta, Tanggal');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CEK MASYARAKAT EXIST
|
||||||
|
$check = $conn->query("SELECT id FROM masyarakat_miskin WHERE id = $id_masyarakat");
|
||||||
|
if ($check->num_rows == 0) {
|
||||||
|
sendJSON('error', 'ID Masyarakat tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CEK DUPLIKAT NO PESERTA
|
||||||
|
$cek_dup = $conn->query("SELECT id FROM bpjs_record WHERE no_peserta = '$no_peserta' AND id_masyarakat != $id_masyarakat");
|
||||||
|
if ($cek_dup->num_rows > 0) {
|
||||||
|
sendJSON('error', 'Nomor peserta sudah terdaftar untuk masyarakat lain');
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUERY INSERT atau UPDATE
|
||||||
|
$existing = $conn->query("SELECT id FROM bpjs_record WHERE id_masyarakat = $id_masyarakat");
|
||||||
|
|
||||||
|
if ($existing->num_rows > 0) {
|
||||||
|
// UPDATE
|
||||||
|
$query = "UPDATE bpjs_record SET
|
||||||
|
no_peserta = '$no_peserta',
|
||||||
|
jenis_bpjs = '$jenis_bpjs',
|
||||||
|
tingkat = '$tingkat',
|
||||||
|
tanggal_daftar = '$tanggal_daftar',
|
||||||
|
status_aktif = '$status_aktif',
|
||||||
|
faskes_tingkat_1 = '$faskes'
|
||||||
|
WHERE id_masyarakat = $id_masyarakat";
|
||||||
|
} else {
|
||||||
|
// INSERT
|
||||||
|
$query = "INSERT INTO bpjs_record
|
||||||
|
(id_masyarakat, no_peserta, jenis_bpjs, tingkat, tanggal_daftar, status_aktif, faskes_tingkat_1)
|
||||||
|
VALUES
|
||||||
|
($id_masyarakat, '$no_peserta', '$jenis_bpjs', '$tingkat', '$tanggal_daftar', '$status_aktif', '$faskes')";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conn->query($query) === TRUE) {
|
||||||
|
sendJSON('success', 'Data BPJS berhasil disimpan');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan: ' . $conn->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
sendJSON('error', 'Metode tidak diizinkan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = sanitize($_POST['nama'] ?? '', $conn);
|
||||||
|
$geojson = $_POST['geojson'] ?? ''; // Don't strip tags for JSON
|
||||||
|
$tipe = sanitize($_POST['tipe'] ?? '', $conn);
|
||||||
|
$keterangan = sanitize($_POST['keterangan'] ?? '', $conn);
|
||||||
|
|
||||||
|
if (empty($nama) || empty($geojson)) {
|
||||||
|
sendJSON('error', 'Nama dan Data GeoJSON wajib diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO fasilitas_jalan (nama, geojson, tipe, keterangan) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("ssss", $nama, $geojson, $tipe, $keterangan);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
sendJSON('success', 'Data jalan berhasil disimpan');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
sendJSON('error', 'Metode tidak diizinkan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = sanitize($_POST['nama'] ?? '', $conn);
|
||||||
|
$geojson = $_POST['geojson'] ?? ''; // Don't strip tags for JSON
|
||||||
|
$tipe = sanitize($_POST['tipe'] ?? '', $conn);
|
||||||
|
$keterangan = sanitize($_POST['keterangan'] ?? '', $conn);
|
||||||
|
|
||||||
|
if (empty($nama) || empty($geojson)) {
|
||||||
|
sendJSON('error', 'Nama dan Data GeoJSON wajib diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO fasilitas_polygon (nama, geojson, tipe, keterangan) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("ssss", $nama, $geojson, $tipe, $keterangan);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
sendJSON('success', 'Data polygon berhasil disimpan');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
sendJSON('error', 'Metode tidak diizinkan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = sanitize($_POST['nama'] ?? '', $conn);
|
||||||
|
$latitude = sanitize($_POST['latitude'] ?? '', $conn);
|
||||||
|
$longitude = sanitize($_POST['longitude'] ?? '', $conn);
|
||||||
|
$alamat = sanitize($_POST['alamat'] ?? '', $conn);
|
||||||
|
$is_24_jam = sanitize($_POST['is_24_jam'] ?? 'Tidak', $conn);
|
||||||
|
|
||||||
|
if (empty($nama) || empty($latitude) || empty($longitude)) {
|
||||||
|
sendJSON('error', 'Nama, Latitude, dan Longitude wajib diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO fasilitas_spbu (nama, latitude, longitude, alamat, is_24_jam) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("sddss", $nama, $latitude, $longitude, $alamat, $is_24_jam);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
sendJSON('success', 'Data SPBU berhasil disimpan');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/simpan_masyarakat.php
|
||||||
|
// Menyimpan data masyarakat miskin
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$nama = sanitize($_POST['nama'] ?? '', $conn);
|
||||||
|
$tanggal_lahir = sanitize($_POST['tanggal_lahir'] ?? '', $conn);
|
||||||
|
$jenis_kelamin = sanitize($_POST['jenis_kelamin'] ?? '', $conn);
|
||||||
|
$nik = sanitize($_POST['nik'] ?? '', $conn);
|
||||||
|
$alamat = sanitize($_POST['alamat'] ?? '', $conn);
|
||||||
|
$latitude = floatval($_POST['latitude'] ?? 0);
|
||||||
|
$longitude = floatval($_POST['longitude'] ?? 0);
|
||||||
|
$status_bpjs = sanitize($_POST['status_bpjs'] ?? 'Tidak', $conn);
|
||||||
|
$no_hp = sanitize($_POST['no_hp'] ?? '', $conn);
|
||||||
|
$pekerjaan = sanitize($_POST['pekerjaan'] ?? '', $conn);
|
||||||
|
$pendapatan = intval($_POST['pendapatan_bulanan'] ?? 0);
|
||||||
|
$tanggungan = intval($_POST['jumlah_tanggungan'] ?? 0);
|
||||||
|
$status_rumah = sanitize($_POST['status_rumah'] ?? 'Menumpang', $conn);
|
||||||
|
$kelurahan = sanitize($_POST['kelurahan'] ?? '', $conn);
|
||||||
|
$kecamatan = sanitize($_POST['kecamatan'] ?? '', $conn);
|
||||||
|
|
||||||
|
// VALIDASI INPUT
|
||||||
|
if (empty($nama)) {
|
||||||
|
sendJSON('error', 'Nama wajib diisi');
|
||||||
|
}
|
||||||
|
if (empty($alamat)) {
|
||||||
|
sendJSON('error', 'Alamat wajib diisi');
|
||||||
|
}
|
||||||
|
if ($latitude == 0 || $longitude == 0) {
|
||||||
|
sendJSON('error', 'Koordinat lokasi wajib diisi (klik peta untuk menentukan titik)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CEK DUPLIKAT NIK (jika NIK diisi)
|
||||||
|
if (!empty($nik)) {
|
||||||
|
$stmt_cek = $conn->prepare("SELECT id FROM masyarakat_miskin WHERE nik = ?");
|
||||||
|
$stmt_cek->bind_param("s", $nik);
|
||||||
|
$stmt_cek->execute();
|
||||||
|
$cek = $stmt_cek->get_result();
|
||||||
|
if ($cek && $cek->num_rows > 0) {
|
||||||
|
sendJSON('error', 'NIK sudah terdaftar di sistem');
|
||||||
|
}
|
||||||
|
$stmt_cek->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUERY INSERT
|
||||||
|
$query = "INSERT INTO masyarakat_miskin
|
||||||
|
(nama, tanggal_lahir, jenis_kelamin, nik, alamat, latitude, longitude,
|
||||||
|
status_bpjs, no_hp, pekerjaan, pendapatan_bulanan, jumlah_tanggungan,
|
||||||
|
status_rumah, kelurahan, kecamatan)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
if ($stmt === false) {
|
||||||
|
sendJSON('error', 'Gagal prepare statement: ' . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("sssssddsssiisss", $nama, $tanggal_lahir, $jenis_kelamin, $nik, $alamat, $latitude, $longitude, $status_bpjs, $no_hp, $pekerjaan, $pendapatan, $tanggungan, $status_rumah, $kelurahan, $kecamatan);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$id = $stmt->insert_id;
|
||||||
|
sendJSON('success', 'Data masyarakat berhasil disimpan', ['id' => $id]);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan data: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Method tidak diizinkan. Gunakan POST.');
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
// ========================================
|
||||||
|
// FILE: php/simpan_rumah_ibadah.php
|
||||||
|
// Menyimpan data rumah ibadah
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$nama = sanitize($_POST['nama'] ?? '', $conn);
|
||||||
|
$jenis = sanitize($_POST['jenis'] ?? '', $conn);
|
||||||
|
$alamat = sanitize($_POST['alamat'] ?? '', $conn);
|
||||||
|
$latitude = floatval($_POST['latitude'] ?? 0);
|
||||||
|
$longitude = floatval($_POST['longitude'] ?? 0);
|
||||||
|
$nama_pemimpin = sanitize($_POST['nama_pemimpin'] ?? '', $conn);
|
||||||
|
$no_hp_pemimpin = sanitize($_POST['no_hp_pemimpin'] ?? '', $conn);
|
||||||
|
$kapasitas = intval($_POST['kapasitas'] ?? 0);
|
||||||
|
$tahun = sanitize($_POST['tahun_didirikan'] ?? '', $conn);
|
||||||
|
$keterangan = sanitize($_POST['keterangan'] ?? '', $conn);
|
||||||
|
|
||||||
|
// VALIDASI
|
||||||
|
if (empty($nama)) {
|
||||||
|
sendJSON('error', 'Nama rumah ibadah wajib diisi');
|
||||||
|
}
|
||||||
|
if (empty($jenis)) {
|
||||||
|
sendJSON('error', 'Jenis rumah ibadah wajib diisi');
|
||||||
|
}
|
||||||
|
if (empty($alamat)) {
|
||||||
|
sendJSON('error', 'Alamat wajib diisi');
|
||||||
|
}
|
||||||
|
if ($latitude == 0 || $longitude == 0) {
|
||||||
|
sendJSON('error', 'Koordinat lokasi wajib diisi (klik peta untuk menentukan titik)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi nilai ENUM jenis
|
||||||
|
$jenis_valid = ['Masjid', 'Gereja', 'Vihara', 'Kelenteng', 'Sinagog', 'Lain'];
|
||||||
|
if (!in_array($jenis, $jenis_valid)) {
|
||||||
|
sendJSON('error', 'Jenis rumah ibadah tidak valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isi tahun_didirikan dengan NULL jika kosong (karena tipe kolom YEAR)
|
||||||
|
$tahun_sql = empty($tahun) ? null : $tahun;
|
||||||
|
|
||||||
|
// QUERY INSERT
|
||||||
|
$query = "INSERT INTO rumah_ibadah
|
||||||
|
(nama, jenis, latitude, longitude, alamat,
|
||||||
|
nama_pemimpin, no_hp_pemimpin, kapasitas, tahun_didirikan, keterangan)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
if ($stmt === false) {
|
||||||
|
sendJSON('error', 'Gagal prepare statement: ' . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ssddsssiss", $nama, $jenis, $latitude, $longitude, $alamat, $nama_pemimpin, $no_hp_pemimpin, $kapasitas, $tahun_sql, $keterangan);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$id = $stmt->insert_id;
|
||||||
|
sendJSON('success', 'Data rumah ibadah berhasil disimpan', ['id' => $id]);
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal menyimpan data: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Method tidak diizinkan. Gunakan POST.');
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
$tables = ['masyarakat_miskin', 'rumah_ibadah', 'fasilitas_jalan', 'fasilitas_polygon', 'fasilitas_spbu', 'bantuan_riwayat'];
|
||||||
|
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
$res = $conn->query("SELECT COUNT(*) FROM $table");
|
||||||
|
if ($res) {
|
||||||
|
echo "$table count: " . $res->fetch_row()[0] . "\n";
|
||||||
|
} else {
|
||||||
|
echo "$table error: " . $conn->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
include 'koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
sendJSON('error', 'Metode tidak diizinkan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$latitude = sanitize($_POST['latitude'] ?? '', $conn);
|
||||||
|
$longitude = sanitize($_POST['longitude'] ?? '', $conn);
|
||||||
|
|
||||||
|
if ($id === 0 || empty($latitude) || empty($longitude)) {
|
||||||
|
sendJSON('error', 'ID, Latitude, dan Longitude wajib diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE fasilitas_spbu SET latitude = ?, longitude = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("ddi", $latitude, $longitude, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
sendJSON('success', 'Lokasi SPBU berhasil diperbarui');
|
||||||
|
} else {
|
||||||
|
sendJSON('error', 'Gagal memperbarui lokasi: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user