add files and folders
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
RUN docker-php-ext-install mysqli pdo pdo_mysql \
|
||||
&& a2enmod rewrite
|
||||
|
||||
COPY . /var/www/html/
|
||||
|
||||
RUN mkdir -p /var/www/html/uploads/bukti \
|
||||
&& mkdir -p /var/www/html/uploads/foto_laporan \
|
||||
&& mkdir -p /var/www/html/uploads/foto_profil \
|
||||
&& chown -R www-data:www-data /var/www/html/uploads \
|
||||
&& chown -R www-data:www-data /var/www/html
|
||||
@@ -0,0 +1,185 @@
|
||||
# WebGIS Pengentasan Kemiskinan v2.0
|
||||
### Berbasis Masyarakat & Rumah Ibadah
|
||||
|
||||
> Pengembangan dari sistem webgis-spbu menjadi sistem manajemen sosial yang lengkap, real-time, dan berbasis relasi database.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Fitur Lengkap
|
||||
|
||||
| Fitur | Status |
|
||||
|---|---|
|
||||
| Peta Leaflet interaktif + MarkerCluster | ✅ |
|
||||
| Layer: penduduk, keluarga, rumah ibadah, laporan, jalan | ✅ |
|
||||
| Warna marker berdasar status ekonomi (merah/kuning/hijau) | ✅ |
|
||||
| CRUD penduduk lengkap (NIK, umur otomatis, kesehatan, dll) | ✅ |
|
||||
| Sistem keluarga (KK → anggota) | ✅ |
|
||||
| Validasi logika pendidikan vs umur | ✅ |
|
||||
| Laporan masyarakat (siapapun bisa lapor, tanpa login) | ✅ |
|
||||
| Manajemen bantuan + histori per penduduk | ✅ |
|
||||
| Deteksi otomatis warga belum pernah dibantu | ✅ |
|
||||
| Prioritas bantuan (KRITIS = miskin + sakit parah) | ✅ |
|
||||
| Multi-role: admin, pengurus, pimpinan, masyarakat | ✅ |
|
||||
| Chat/diskusi antar pengurus per rumah ibadah | ✅ |
|
||||
| Notifikasi real-time (polling) | ✅ |
|
||||
| Audit log semua aktivitas | ✅ |
|
||||
| Dashboard monitoring statistik | ✅ |
|
||||
| Data dummy siap pakai (Pontianak) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Struktur Direktori
|
||||
|
||||
```
|
||||
webgis-kemiskinan/
|
||||
├── index.html ← Peta WebGIS utama (publik)
|
||||
├── admin/
|
||||
│ ├── login.php ← Halaman login
|
||||
│ ├── logout.php
|
||||
│ ├── init_admin.php ← Inisialisasi user (jalankan sekali!)
|
||||
│ ├── _layout.php ← Layout admin (header + nav)
|
||||
│ ├── index.php ← Dashboard
|
||||
│ ├── penduduk.php ← CRUD penduduk
|
||||
│ ├── bantuan.php ← Catat penyaluran bantuan
|
||||
│ ├── histori_bantuan.php ← Riwayat bantuan
|
||||
│ ├── belum_dibantu.php ← Warga belum tersentuh bantuan
|
||||
│ ├── laporan.php ← Manajemen laporan masyarakat
|
||||
│ └── log.php ← Audit log (admin only)
|
||||
├── php/
|
||||
│ ├── koneksi.php ← Config DB + helper functions
|
||||
│ ├── middleware/
|
||||
│ │ └── auth.php ← Autentikasi & otorisasi
|
||||
│ └── api/
|
||||
│ ├── auth.php ← Login/logout API
|
||||
│ ├── penduduk.php ← CRUD penduduk (RESTful)
|
||||
│ ├── keluarga.php ← CRUD keluarga
|
||||
│ ├── bantuan.php ← Bantuan + histori + stats
|
||||
│ ├── laporan.php ← Laporan masyarakat
|
||||
│ ├── rumah_ibadah.php ← CRUD rumah ibadah
|
||||
│ └── pesan.php ← Chat + notifikasi
|
||||
├── sql/
|
||||
│ └── schema.sql ← Schema + data dummy
|
||||
└── uploads/
|
||||
├── bukti/ ← Bukti penyaluran bantuan
|
||||
└── foto_laporan/ ← Foto dari laporan masyarakat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Instalasi (XAMPP)
|
||||
|
||||
### 1. Salin Folder
|
||||
```
|
||||
C:\xampp\htdocs\webgis-kemiskinan\
|
||||
```
|
||||
|
||||
### 2. Buat Database
|
||||
- Buka **phpMyAdmin** → `http://localhost/phpmyadmin`
|
||||
- Buat database baru: `webgis_kemiskinan`
|
||||
- Import: `sql/schema.sql`
|
||||
|
||||
### 3. Konfigurasi DB
|
||||
Edit `php/koneksi.php`:
|
||||
```php
|
||||
define('DB_NAME', 'webgis_kemiskinan');
|
||||
define('DB_USER', 'root');
|
||||
define('DB_PASS', ''); // sesuaikan password MySQL Anda
|
||||
```
|
||||
|
||||
### 4. Inisialisasi User
|
||||
Buka sekali di browser:
|
||||
```
|
||||
http://localhost/webgis-kemiskinan/admin/init_admin.php
|
||||
```
|
||||
> **Hapus file ini setelah dijalankan!**
|
||||
|
||||
### 5. Akses Sistem
|
||||
| URL | Keterangan |
|
||||
|---|---|
|
||||
| `http://localhost/webgis-kemiskinan/` | Peta WebGIS (publik) |
|
||||
| `http://localhost/webgis-kemiskinan/admin/login.php` | Panel Admin |
|
||||
|
||||
---
|
||||
|
||||
## 👤 Akun Demo
|
||||
|
||||
| Username | Password | Role |
|
||||
|---|---|---|
|
||||
| `admin` | `password` | Administrator |
|
||||
| `pengurus1` | `password` | Pengurus Masjid Al-Falah |
|
||||
| `pimpinan1` | `password` | Pimpinan Daerah |
|
||||
| `warga1` | `password` | Masyarakat |
|
||||
|
||||
> Hash yang digunakan adalah hash Laravel default untuk string `"password"`.
|
||||
> Untuk produksi, ganti dengan: `password_hash('PasswordBaru', PASSWORD_BCRYPT)`
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Tabel Database
|
||||
|
||||
| Tabel | Fungsi |
|
||||
|---|---|
|
||||
| `roles` | Definisi role sistem |
|
||||
| `users` | Akun pengguna |
|
||||
| `rumah_ibadah` | Data rumah ibadah + radius |
|
||||
| `keluarga` | Data KK |
|
||||
| `penduduk` | Data individu (NIK sebagai PK) |
|
||||
| `riwayat_pelatihan` | Pelatihan kerja per penduduk |
|
||||
| `jenis_bantuan` | Master jenis bantuan |
|
||||
| `histori_bantuan` | Riwayat penyaluran bantuan |
|
||||
| `laporan` | Laporan kondisi dari masyarakat |
|
||||
| `pesan` | Chat antar pengurus |
|
||||
| `notifikasi` | Notifikasi per user |
|
||||
| `log_aktivitas` | Audit trail semua perubahan data |
|
||||
| `jalan_lines` | Layer jalan (GeoJSON) |
|
||||
| `parsil_tanah` | Layer parsil tanah (GeoJSON) |
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Logika Khusus
|
||||
|
||||
### Validasi Pendidikan vs Umur
|
||||
- Umur 7–12 tahun → wajib masih sekolah (SD)
|
||||
- Umur 13–15 tahun → wajib masih sekolah (SMP)
|
||||
- Umur 16–18 tahun → wajib masih sekolah (SMA/SMK)
|
||||
- Umur >18 tahun → bebas (bisa tidak sekolah / ikut pelatihan)
|
||||
|
||||
### Prioritas Bantuan (otomatis)
|
||||
| Kondisi | Prioritas |
|
||||
|---|---|
|
||||
| Miskin + Sakit Parah/Disabilitas | 🔴 KRITIS |
|
||||
| Miskin + Belum pernah dibantu | 🟠 TINGGI |
|
||||
| Miskin (umum) | 🟡 SEDANG |
|
||||
| Rentan + Sakit | 🟡 SEDANG |
|
||||
| Lainnya | 🟢 RENDAH |
|
||||
|
||||
### Umur Otomatis
|
||||
Kolom `umur` di tabel `penduduk` adalah **GENERATED COLUMN** — dihitung langsung dari `tanggal_lahir` setiap kali data dibaca. Tidak perlu update manual.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Keamanan
|
||||
- PDO dengan prepared statements (anti SQL injection)
|
||||
- `password_hash()` / `password_verify()` untuk password
|
||||
- Session dengan `session_regenerate_id()` saat login
|
||||
- Validasi input di sisi server untuk semua endpoint
|
||||
- Otorisasi per-role di setiap API endpoint
|
||||
- Upload file: validasi ekstensi + ukuran + nama acak
|
||||
|
||||
---
|
||||
|
||||
## 📱 Cara Buat Laporan (Masyarakat)
|
||||
1. Buka `http://localhost/webgis-kemiskinan/`
|
||||
2. Klik tombol **"Buat Laporan"** di pojok kanan atas
|
||||
3. Isi deskripsi kondisi (nama pelapor opsional)
|
||||
4. Klik peta untuk memilih lokasi otomatis
|
||||
5. Kirim → pengurus akan mendapat notifikasi otomatis
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Pengembangan Lanjutan (Saran)
|
||||
- [ ] Integrasi SMS/WhatsApp notifikasi
|
||||
- [ ] Export PDF laporan distribusi bantuan
|
||||
- [ ] Login via Google / warga menggunakan NIK + tanggal lahir
|
||||
- [ ] Mobile app (React Native / Flutter)
|
||||
- [ ] Sinkronisasi data dengan Dukcapil
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
require_once __DIR__ . '/../php/middleware/auth.php';
|
||||
|
||||
function render_header(string $title): void {
|
||||
$user = $_SESSION['user_nama'] ?? 'Tamu';
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
$unread = 0;
|
||||
global $pdo;
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
try {
|
||||
$stmtN = $pdo->prepare("SELECT COUNT(*) FROM notifikasi WHERE user_id=:u AND dibaca=0");
|
||||
$stmtN->execute([':u' => $_SESSION['user_id']]);
|
||||
$unread = (int)$stmtN->fetchColumn();
|
||||
} catch(Exception $e){}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title><?= h($title) ?> — WebGIS Kemiskinan</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--bg:#0f172a;--sur:#1e293b;--sur2:#334155;--acc:#3b82f6;--green:#22c55e;--red:#ef4444;--yellow:#f59e0b;--txt:#f1f5f9;--txt2:#94a3b8;--brd:#334155;--rad:12px}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--txt);min-height:100vh}
|
||||
#topbar{background:var(--sur);border-bottom:1px solid var(--brd);padding:0 20px;height:54px;display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:100}
|
||||
#topbar .logo{font-weight:900;font-size:15px} #topbar .logo span{color:var(--acc)}
|
||||
#topbar .spacer{flex:1}
|
||||
#topbar .user{font-size:12px;color:var(--txt2);font-weight:700}
|
||||
#topbar a.tbtn,#topbar button.tbtn{background:var(--sur2);border:1px solid var(--brd);color:var(--txt);padding:6px 12px;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;cursor:pointer;transition:.2s;display:inline-flex;align-items:center;gap:5px}
|
||||
#topbar a.tbtn:hover,#topbar button.tbtn:hover{background:var(--acc);border-color:var(--acc);color:#fff}
|
||||
#wrap{display:flex;min-height:calc(100vh - 54px)}
|
||||
#nav{width:220px;background:var(--sur);border-right:1px solid var(--brd);padding:16px 0;flex-shrink:0}
|
||||
#nav a{display:flex;align-items:center;gap:10px;padding:10px 20px;color:var(--txt2);text-decoration:none;font-size:13px;font-weight:700;transition:.2s;border-left:3px solid transparent}
|
||||
#nav a:hover,#nav a.active{color:var(--txt);background:rgba(59,130,246,.1);border-left-color:var(--acc)}
|
||||
#nav .nav-section{padding:12px 20px 4px;font-size:10px;font-weight:900;color:var(--brd);text-transform:uppercase;letter-spacing:.5px}
|
||||
#content{flex:1;padding:24px;overflow-x:auto}
|
||||
.page-title{font-size:20px;font-weight:900;margin-bottom:20px;display:flex;align-items:center;gap:10px}
|
||||
.card{background:var(--sur);border:1px solid var(--brd);border-radius:var(--rad);padding:20px;margin-bottom:16px}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px}
|
||||
.stat{background:var(--sur);border:1px solid var(--brd);border-radius:var(--rad);padding:16px;text-align:center}
|
||||
.stat .val{font-size:28px;font-weight:900} .stat .lbl{font-size:11px;color:var(--txt2);font-weight:700;margin-top:2px}
|
||||
.stat.red .val{color:var(--red)} .stat.yellow .val{color:var(--yellow)} .stat.green .val{color:var(--green)} .stat.blue .val{color:var(--acc)}
|
||||
table{width:100%;border-collapse:collapse;font-size:13px}
|
||||
th{background:var(--sur2);padding:10px 12px;text-align:left;font-size:11px;font-weight:900;color:var(--txt2);text-transform:uppercase}
|
||||
td{padding:10px 12px;border-bottom:1px solid var(--brd)}
|
||||
tr:hover td{background:rgba(255,255,255,.02)}
|
||||
.tag{display:inline-block;padding:2px 8px;border-radius:99px;font-size:11px;font-weight:800}
|
||||
.tag.miskin{background:rgba(239,68,68,.15);color:var(--red)}
|
||||
.tag.rentan{background:rgba(245,158,11,.15);color:var(--yellow)}
|
||||
.tag.mampu{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.tag.pending{background:rgba(245,158,11,.15);color:var(--yellow)}
|
||||
.tag.diproses{background:rgba(59,130,246,.15);color:var(--acc)}
|
||||
.tag.selesai{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.tag.darurat{background:rgba(239,68,68,.2);color:var(--red)}
|
||||
.btn{padding:8px 16px;border-radius:8px;border:none;cursor:pointer;font-weight:700;font-size:12px;display:inline-flex;align-items:center;gap:5px;transition:.2s;text-decoration:none}
|
||||
.btn-primary{background:var(--acc);color:#fff} .btn-primary:hover{background:#2563eb}
|
||||
.btn-success{background:var(--green);color:#fff}
|
||||
.btn-danger{background:var(--red);color:#fff}
|
||||
.btn-secondary{background:var(--sur2);color:var(--txt);border:1px solid var(--brd)}
|
||||
.btn-sm{padding:4px 10px;font-size:11px}
|
||||
input,select,textarea{background:var(--sur2);border:1px solid var(--brd);color:var(--txt);padding:8px 12px;border-radius:8px;font-size:13px;width:100%}
|
||||
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--acc)}
|
||||
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||
.form-group{display:flex;flex-direction:column;gap:5px}
|
||||
.form-group.full{grid-column:1/-1}
|
||||
label{font-size:11px;font-weight:700;color:var(--txt2);text-transform:uppercase}
|
||||
.notice{padding:10px 14px;border-radius:8px;border:1px solid;font-size:13px;margin-bottom:12px}
|
||||
.actions{display:flex;gap:6px;flex-wrap:wrap}
|
||||
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||
@media(max-width:768px){#nav{display:none}#content{padding:16px}.form-grid{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<div class="logo">🗺️ <span>WebGIS</span> Kemiskinan</div>
|
||||
<div class="spacer"></div>
|
||||
<span class="user">👤 <?= h($user) ?> <span style="color:var(--acc)">(<?= h($role) ?>)</span></span>
|
||||
<a class="tbtn" href="../index.html"><i class="fa-solid fa-map"></i> Buka Peta</a>
|
||||
<a class="tbtn" href="logout.php"><i class="fa-solid fa-right-from-bracket"></i> Logout</a>
|
||||
</div>
|
||||
<div id="wrap">
|
||||
<div id="nav">
|
||||
<div class="nav-section">Menu Utama</div>
|
||||
<a href="index.php" <?= basename($_SERVER['PHP_SELF'])==='index.php'?'class="active"':'' ?>><i class="fa-solid fa-gauge" style="width:16px"></i> Dashboard</a>
|
||||
|
||||
<?php if (in_array($role, ['admin','pengurus'])): ?>
|
||||
<div class="nav-section">Data</div>
|
||||
<a href="penduduk.php" <?= basename($_SERVER['PHP_SELF'])==='penduduk.php'?'class="active"':'' ?>><i class="fa-solid fa-users" style="width:16px"></i> Penduduk</a>
|
||||
<a href="keluarga.php" <?= basename($_SERVER['PHP_SELF'])==='keluarga.php'?'class="active"':'' ?>><i class="fa-solid fa-house-chimney-user" style="width:16px"></i> Keluarga (KK)</a>
|
||||
<a href="rumah_ibadah.php" <?= basename($_SERVER['PHP_SELF'])==='rumah_ibadah.php'?'class="active"':'' ?>><i class="fa-solid fa-mosque" style="width:16px"></i> Rumah Ibadah</a>
|
||||
|
||||
<div class="nav-section">Bantuan</div>
|
||||
<a href="bantuan.php" <?= basename($_SERVER['PHP_SELF'])==='bantuan.php'?'class="active"':'' ?>><i class="fa-solid fa-gift" style="width:16px"></i> Catat Bantuan</a>
|
||||
<a href="histori_bantuan.php" <?= basename($_SERVER['PHP_SELF'])==='histori_bantuan.php'?'class="active"':'' ?>><i class="fa-solid fa-clock-rotate-left" style="width:16px"></i> Histori Bantuan</a>
|
||||
<a href="belum_dibantu.php" <?= basename($_SERVER['PHP_SELF'])==='belum_dibantu.php'?'class="active"':'' ?>><i class="fa-solid fa-triangle-exclamation" style="width:16px;color:var(--red)"></i> Belum Dibantu</a>
|
||||
|
||||
<div class="nav-section">Laporan</div>
|
||||
<a href="laporan.php" <?= basename($_SERVER['PHP_SELF'])==='laporan.php'?'class="active"':'' ?>><i class="fa-solid fa-flag" style="width:16px"></i> Laporan Masuk</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<div class="nav-section">Sistem</div>
|
||||
<a href="users.php" <?= basename($_SERVER['PHP_SELF'])==='users.php'?'class="active"':'' ?>><i class="fa-solid fa-user-shield" style="width:16px"></i> Manajemen User</a>
|
||||
<a href="log.php" <?= basename($_SERVER['PHP_SELF'])==='log.php'?'class="active"':'' ?>><i class="fa-solid fa-scroll" style="width:16px"></i> Log Aktivitas</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="content">
|
||||
<?php
|
||||
}
|
||||
|
||||
function render_footer(): void {
|
||||
echo '</div></div></body></html>';
|
||||
}
|
||||
|
||||
function require_login(array $roles = []): void {
|
||||
requireLogin($roles);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus']);
|
||||
|
||||
$msg = ''; $err = '';
|
||||
$nikPre = $_GET['nik'] ?? '';
|
||||
$namaPre = $_GET['nama'] ?? '';
|
||||
|
||||
if (isset($_POST['save'])) {
|
||||
$nik = trim($_POST['nik'] ?? '');
|
||||
$idJb = (int)($_POST['id_jenis_bantuan'] ?? 0);
|
||||
$tgl = $_POST['tanggal'] ?? date('Y-m-d');
|
||||
if (!preg_match('/^\d{16}$/', $nik)) $err = 'NIK tidak valid.';
|
||||
elseif (!$idJb) $err = 'Jenis bantuan wajib dipilih.';
|
||||
else {
|
||||
try {
|
||||
// Cek penduduk ada
|
||||
$pddk = $pdo->prepare("SELECT nik,id_keluarga FROM penduduk WHERE nik=:nik AND status_hidup='hidup'");
|
||||
$pddk->execute([':nik'=>$nik]);
|
||||
$pddk = $pddk->fetch();
|
||||
if (!$pddk) throw new Exception("Penduduk dengan NIK $nik tidak ditemukan / meninggal.");
|
||||
|
||||
$bukti = null;
|
||||
if (!empty($_FILES['bukti']['name'])) {
|
||||
$bukti = uploadFile($_FILES['bukti'], UPLOAD_BUKTI);
|
||||
}
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO histori_bantuan (nik,id_jenis_bantuan,id_keluarga,rumah_ibadah_id,tanggal,jumlah,nilai_rupiah,status_penyaluran,catatan,bukti_file,disalurkan_oleh)
|
||||
VALUES(:nik,:jb,:kk,:ri,:tgl,:jml,:rp,:status,:cat,:bukti,:user)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nik'=>$nik, ':jb'=>$idJb, ':kk'=>$pddk['id_keluarga'],
|
||||
':ri'=>$_SESSION['rumah_ibadah_id'] ?: null,
|
||||
':tgl'=>$tgl, ':jml'=>$_POST['jumlah']??'' ?: null,
|
||||
':rp'=>$_POST['nilai_rupiah']??'' ?: null,
|
||||
':status'=>$_POST['status_penyaluran']??'disalurkan',
|
||||
':cat'=>$_POST['catatan']??'' ?: null,
|
||||
':bukti'=>$bukti, ':user'=>$_SESSION['user_id'],
|
||||
]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'CREATE', 'histori_bantuan', (string)$pdo->lastInsertId());
|
||||
$msg = "Bantuan untuk NIK $nik berhasil dicatat.";
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$jenisList = $pdo->query("SELECT * FROM jenis_bantuan WHERE aktif=1 ORDER BY kategori,nama")->fetchAll();
|
||||
|
||||
render_header('Catat Bantuan');
|
||||
?>
|
||||
<div class="page-title">🎁 Catat Penyaluran Bantuan</div>
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
|
||||
<div>
|
||||
<div class="card">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="form-grid">
|
||||
<div class="form-group full">
|
||||
<label>NIK Penerima *</label>
|
||||
<input name="nik" maxlength="16" value="<?= h($nikPre) ?>" placeholder="16 digit NIK" required onblur="cekPenduduk(this.value)">
|
||||
</div>
|
||||
<div class="form-group full" id="info-pddk" style="background:#334155;border-radius:8px;padding:10px;font-size:12px;display:<?= $namaPre?'block':'none' ?>">
|
||||
<?= $namaPre ? '👤 '.$namaPre : '' ?>
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Jenis Bantuan *</label>
|
||||
<select name="id_jenis_bantuan" required>
|
||||
<option value="">— Pilih —</option>
|
||||
<?php foreach ($jenisList as $j): ?>
|
||||
<option value="<?= $j['id'] ?>"><?= h($j['nama']) ?> (<?= h($j['kategori']) ?>)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tanggal *</label>
|
||||
<input type="date" name="tanggal" required value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select name="status_penyaluran">
|
||||
<option value="disalurkan">Disalurkan</option>
|
||||
<option value="dijadwalkan">Dijadwalkan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Jumlah / Keterangan</label>
|
||||
<input name="jumlah" placeholder="mis: 1 paket sembako">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nilai (Rp)</label>
|
||||
<input type="number" name="nilai_rupiah" placeholder="0">
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Catatan</label>
|
||||
<textarea name="catatan" rows="2" placeholder="Opsional"></textarea>
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Bukti Foto/Dokumen</label>
|
||||
<input type="file" name="bukti" accept=".jpg,.jpeg,.png,.webp,.pdf">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="save" value="1" class="btn btn-success" style="margin-top:14px;width:100%">✅ Simpan Bantuan</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<strong>📋 Bantuan Terakhir Disalurkan</strong>
|
||||
<table style="margin-top:12px">
|
||||
<thead><tr><th>Penerima</th><th>Bantuan</th><th>Tanggal</th></tr></thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$recent = $pdo->query("
|
||||
SELECT p.nama_lengkap, jb.nama AS nama_bantuan, hb.tanggal
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik=hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id=hb.id_jenis_bantuan
|
||||
WHERE hb.status_penyaluran='disalurkan'
|
||||
ORDER BY hb.created_at DESC LIMIT 10
|
||||
")->fetchAll();
|
||||
foreach ($recent as $r): ?>
|
||||
<tr>
|
||||
<td><?= h($r['nama_lengkap']) ?></td>
|
||||
<td><?= h($r['nama_bantuan']) ?></td>
|
||||
<td style="font-size:11px"><?= date('d/m/Y', strtotime($r['tanggal'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function cekPenduduk(nik) {
|
||||
if (!/^\d{16}$/.test(nik)) return;
|
||||
const res = await fetch('../php/api/penduduk.php?action=detail&nik='+nik);
|
||||
const json = await res.json();
|
||||
const info = document.getElementById('info-pddk');
|
||||
if (json.success) {
|
||||
const p = json.data;
|
||||
info.style.display = 'block';
|
||||
info.innerHTML = `👤 <strong>${p.nama_lengkap}</strong> | ${p.umur}th |
|
||||
<span style="color:${p.status_ekonomi==='miskin'?'#ef4444':'#f59e0b'}">${p.status_ekonomi}</span> |
|
||||
Sudah dibantu: ${json.histori.length}x`;
|
||||
} else {
|
||||
info.style.display = 'block';
|
||||
info.innerHTML = '<span style="color:#ef4444">❌ NIK tidak ditemukan</span>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$rows = [];
|
||||
try {
|
||||
$rows = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.umur, p.pekerjaan, p.penghasilan, p.alamat,
|
||||
k.no_kk, k.nama_kk, ri.nama AS nama_ri, ri.jenis AS jenis_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id=p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
WHERE p.status_hidup='hidup'
|
||||
AND p.status_ekonomi IN ('miskin','rentan')
|
||||
AND NOT EXISTS(
|
||||
SELECT 1 FROM histori_bantuan hb
|
||||
WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan'
|
||||
)
|
||||
ORDER BY
|
||||
CASE p.status_ekonomi WHEN 'miskin' THEN 1 ELSE 2 END,
|
||||
CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 WHEN 'disabilitas' THEN 2 WHEN 'sakit_ringan' THEN 3 ELSE 4 END,
|
||||
p.nama_lengkap
|
||||
")->fetchAll();
|
||||
} catch(Exception $e) {}
|
||||
|
||||
render_header('Belum Pernah Dibantu');
|
||||
?>
|
||||
<div class="page-title">⚠️ Warga Belum Pernah Menerima Bantuan</div>
|
||||
|
||||
<div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);border-radius:12px;padding:14px 18px;margin-bottom:16px;font-size:13px">
|
||||
Ditemukan <strong style="color:#ef4444"><?= count($rows) ?> jiwa</strong> dengan status ekonomi miskin/rentan yang belum pernah menerima bantuan apapun. Prioritaskan yang kondisi kesehatannya kritis.
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Nama</th><th>NIK</th><th>Status</th><th>Kesehatan</th><th>Umur</th><th>Pekerjaan</th><th>Rumah Ibadah</th>
|
||||
<?php if (in_array($_SESSION['role'], ['admin','pengurus'])): ?><th>Aksi</th><?php endif; ?>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r):
|
||||
$kritis = $r['status_ekonomi']==='miskin' && in_array($r['kondisi_kesehatan'],['sakit_parah','disabilitas']);
|
||||
?>
|
||||
<tr <?= $kritis ? 'style="background:rgba(239,68,68,.05)"' : '' ?>>
|
||||
<td>
|
||||
<?= $kritis ? '🔥 ' : '' ?><strong><?= h($r['nama_lengkap']) ?></strong>
|
||||
<?= $r['status_ekonomi']==='miskin' && $r['kondisi_kesehatan']==='sakit_parah' ? '<span class="tag darurat" style="font-size:9px;margin-left:4px">KRITIS</span>' : '' ?>
|
||||
</td>
|
||||
<td style="font-size:11px;color:#94a3b8"><?= h($r['nik']) ?></td>
|
||||
<td><span class="tag <?= $r['status_ekonomi'] ?>"><?= h($r['status_ekonomi']) ?></span></td>
|
||||
<td><?= h(str_replace('_',' ',$r['kondisi_kesehatan'])) ?></td>
|
||||
<td><?= $r['umur'] ?> th</td>
|
||||
<td><?= h($r['pekerjaan'] ?: '-') ?></td>
|
||||
<td><?= h($r['nama_ri'] ?: '-') ?></td>
|
||||
<?php if (in_array($_SESSION['role'], ['admin','pengurus'])): ?>
|
||||
<td>
|
||||
<a href="bantuan.php?nik=<?= h($r['nik']) ?>&nama=<?= urlencode($r['nama_lengkap']) ?>" class="btn btn-success btn-sm">
|
||||
🎁 Beri Bantuan
|
||||
</a>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="8" style="text-align:center;padding:24px;color:#22c55e">🎉 Semua warga miskin/rentan sudah pernah dibantu!</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lupa Password — WebGIS Pengentasan Kemiskinan</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:#0f172a;color:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:16px}
|
||||
.card{background:#1e293b;border:1px solid #334155;border-radius:20px;padding:40px 36px;width:100%;max-width:400px;box-shadow:0 25px 60px rgba(0,0,0,.5)}
|
||||
.logo{text-align:center;margin-bottom:28px}
|
||||
.logo .icon{font-size:40px;margin-bottom:8px}
|
||||
.logo h1{font-size:20px;font-weight:900}
|
||||
.logo p{font-size:12px;color:#94a3b8;margin-top:4px}
|
||||
.form-group{margin-bottom:16px}
|
||||
label{display:block;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;margin-bottom:6px}
|
||||
input{width:100%;background:#0f172a;border:1px solid #334155;color:#f1f5f9;padding:11px 14px;border-radius:10px;font-size:14px}
|
||||
input:focus{outline:none;border-color:#3b82f6}
|
||||
.btn{width:100%;background:#3b82f6;color:#fff;border:none;padding:12px;border-radius:10px;font-size:14px;font-weight:800;cursor:pointer;transition:.2s}
|
||||
.btn:hover{background:#2563eb}
|
||||
.error{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#ef4444;padding:10px 14px;border-radius:8px;font-size:13px;margin-bottom:16px;}
|
||||
.success{background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.3);color:#22c55e;padding:10px 14px;border-radius:8px;font-size:13px;margin-bottom:16px;}
|
||||
.back{text-align:center;margin-top:16px}
|
||||
.back a{color:#3b82f6;font-size:13px;text-decoration:none}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = sanitizeStr($_POST['username'] ?? '');
|
||||
$email = sanitizeStr($_POST['email'] ?? '');
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
|
||||
if (!$username || !$email || !$new_password) {
|
||||
$error = 'Semua kolom wajib diisi.';
|
||||
} else {
|
||||
// Cek kecocokan username dan email
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = :u AND email = :e LIMIT 1");
|
||||
$stmt->execute([':u' => $username, ':e' => $email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user) {
|
||||
$hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
$update = $pdo->prepare("UPDATE users SET password_hash = :p WHERE id = :id");
|
||||
$update->execute([':p' => $hash, ':id' => $user['id']]);
|
||||
$success = 'Password berhasil direset! Silakan login dengan password baru Anda.';
|
||||
} else {
|
||||
$error = 'Username dan Email tidak cocok atau tidak ditemukan.';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="logo">
|
||||
<div class="icon">🔑</div>
|
||||
<h1>Reset Password</h1>
|
||||
<p>Masukkan Username & Email Terdaftar</p>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?= h($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="success"><?= h($success) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" name="username" placeholder="Cth: admin" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email Terdaftar</label>
|
||||
<input type="email" name="email" placeholder="Cth: admin@webgis.local" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password Baru</label>
|
||||
<input type="password" name="new_password" placeholder="Masukkan password baru" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">🔄 Reset Password</button>
|
||||
</form>
|
||||
|
||||
<div class="back"><a href="login.php">← Kembali ke Halaman Login</a></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$rows = [];
|
||||
try {
|
||||
$rows = $pdo->query("
|
||||
SELECT hb.id, hb.nik, p.nama_lengkap, hb.tanggal,
|
||||
jb.nama AS nama_bantuan, jb.kategori,
|
||||
hb.jumlah, hb.nilai_rupiah, hb.status_penyaluran,
|
||||
ri.nama AS nama_ri, u.nama AS disalurkan_oleh
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik=hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id=hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=hb.rumah_ibadah_id
|
||||
LEFT JOIN users u ON u.id=hb.disalurkan_oleh
|
||||
ORDER BY hb.tanggal DESC
|
||||
LIMIT 500
|
||||
")->fetchAll();
|
||||
} catch(Exception $e) {}
|
||||
|
||||
render_header('Histori Bantuan');
|
||||
?>
|
||||
<div class="page-title">📜 Histori Penyaluran Bantuan</div>
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>#</th><th>Nama Penerima</th><th>Jenis Bantuan</th><th>Jumlah</th>
|
||||
<th>Tanggal</th><th>Rumah Ibadah</th><th>Disalurkan Oleh</th><th>Status</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td><?= $r['id'] ?></td>
|
||||
<td><?= h($r['nama_lengkap']) ?><br><span style="font-size:10px;color:#94a3b8"><?= h($r['nik']) ?></span></td>
|
||||
<td><?= h($r['nama_bantuan']) ?> <span style="font-size:10px;color:#94a3b8">(<?= h($r['kategori']) ?>)</span></td>
|
||||
<td><?= h($r['jumlah'] ?? '-') ?><?= $r['nilai_rupiah'] ? '<br><span style="font-size:10px;color:#22c55e">Rp '.number_format($r['nilai_rupiah'],0,',','.').'</span>' : '' ?></td>
|
||||
<td><?= date('d/m/Y', strtotime($r['tanggal'])) ?></td>
|
||||
<td><?= h($r['nama_ri'] ?? '-') ?></td>
|
||||
<td><?= h($r['disalurkan_oleh'] ?? '-') ?></td>
|
||||
<td><span class="tag <?= $r['status_penyaluran']==='disalurkan'?'selesai':($r['status_penyaluran']==='dijadwalkan'?'pending':'darurat') ?>"><?= h($r['status_penyaluran']) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?><tr><td colspan="8" style="text-align:center;padding:24px;color:#94a3b8">Belum ada data</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$stats = [];
|
||||
try {
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='miskin' AND status_hidup='hidup') AS miskin,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='rentan' AND status_hidup='hidup') AS rentan,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup') AS total_pddk,
|
||||
(SELECT COUNT(*) FROM keluarga) AS total_kk,
|
||||
(SELECT COUNT(*) FROM rumah_ibadah) AS total_ri,
|
||||
(SELECT COUNT(*) FROM laporan WHERE status='pending') AS laporan_pending,
|
||||
(SELECT COUNT(*) FROM laporan WHERE tingkat_urgensi='darurat' AND status NOT IN ('selesai','ditolak')) AS laporan_darurat,
|
||||
(SELECT COUNT(*) FROM histori_bantuan WHERE MONTH(tanggal)=MONTH(CURDATE()) AND YEAR(tanggal)=YEAR(CURDATE()) AND status_penyaluran='disalurkan') AS bantuan_bulan,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi IN ('miskin','rentan')
|
||||
AND NOT EXISTS(SELECT 1 FROM histori_bantuan hb WHERE hb.nik=penduduk.nik AND hb.status_penyaluran='disalurkan')
|
||||
) AS belum_dibantu
|
||||
")->fetch();
|
||||
} catch(Exception $e) {}
|
||||
|
||||
// Laporan terbaru
|
||||
$laporan_terbaru = [];
|
||||
try {
|
||||
$laporan_terbaru = $pdo->query("
|
||||
SELECT l.id, l.tingkat_urgensi, l.kategori, l.status, l.created_at,
|
||||
l.nama_pelapor, p.nama_lengkap AS nama_terdampak
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik = l.nik_terdampak
|
||||
ORDER BY
|
||||
CASE l.tingkat_urgensi WHEN 'darurat' THEN 1 WHEN 'tinggi' THEN 2 ELSE 3 END,
|
||||
l.created_at DESC
|
||||
LIMIT 8
|
||||
")->fetchAll();
|
||||
} catch(Exception $e) {}
|
||||
|
||||
// Prioritas belum dibantu
|
||||
$prioritas = [];
|
||||
try {
|
||||
$prioritas = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.alamat, k.no_kk, ri.nama AS nama_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id=p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
WHERE p.status_hidup='hidup'
|
||||
AND p.status_ekonomi='miskin'
|
||||
AND p.kondisi_kesehatan IN ('sakit_parah','disabilitas')
|
||||
AND NOT EXISTS(SELECT 1 FROM histori_bantuan hb WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan')
|
||||
ORDER BY CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 ELSE 2 END
|
||||
LIMIT 5
|
||||
")->fetchAll();
|
||||
} catch(Exception $e) {}
|
||||
|
||||
render_header('Dashboard');
|
||||
?>
|
||||
|
||||
<div class="page-title">📊 Dashboard Monitoring</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="stats-row">
|
||||
<div class="stat red"><div class="val"><?= $stats['miskin'] ?? 0 ?></div><div class="lbl">Warga Miskin</div></div>
|
||||
<div class="stat yellow"><div class="val"><?= $stats['rentan'] ?? 0 ?></div><div class="lbl">Warga Rentan</div></div>
|
||||
<div class="stat blue"><div class="val"><?= $stats['total_kk'] ?? 0 ?></div><div class="lbl">Total KK</div></div>
|
||||
<div class="stat green"><div class="val"><?= $stats['bantuan_bulan'] ?? 0 ?></div><div class="lbl">Bantuan Bulan Ini</div></div>
|
||||
<div class="stat red"><div class="val"><?= $stats['laporan_pending'] ?? 0 ?></div><div class="lbl">Laporan Pending</div></div>
|
||||
<div class="stat red"><div class="val"><?= $stats['belum_dibantu'] ?? 0 ?></div><div class="lbl">Belum Pernah Dibantu</div></div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($stats['laporan_darurat']) && $stats['laporan_darurat'] > 0): ?>
|
||||
<div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);border-radius:12px;padding:14px 18px;margin-bottom:16px;display:flex;align-items:center;gap:12px">
|
||||
<span style="font-size:24px">🚨</span>
|
||||
<div>
|
||||
<div style="font-weight:900;color:#ef4444"><?= $stats['laporan_darurat'] ?> LAPORAN DARURAT belum ditangani!</div>
|
||||
<div style="font-size:12px;color:#94a3b8;margin-top:2px">Segera tindak lanjuti laporan kondisi darurat dari masyarakat.</div>
|
||||
</div>
|
||||
<a href="laporan.php?filter=darurat" class="btn btn-danger" style="margin-left:auto">Lihat →</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
|
||||
|
||||
<!-- Laporan terbaru -->
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
|
||||
<strong>📋 Laporan Terbaru</strong>
|
||||
<a href="laporan.php" class="btn btn-secondary btn-sm">Semua →</a>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>Pelapor/Terdampak</th><th>Urgensi</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($laporan_terbaru as $l): ?>
|
||||
<tr>
|
||||
<td><?= h($l['nama_terdampak'] ?? $l['nama_pelapor'] ?? 'Anonim') ?></td>
|
||||
<td><span class="tag <?= $l['tingkat_urgensi'] === 'darurat' ? 'darurat' : 'pending' ?>"><?= h($l['tingkat_urgensi']) ?></span></td>
|
||||
<td><span class="tag <?= $l['status'] ?>"><?= h($l['status']) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($laporan_terbaru)): ?><tr><td colspan="3" style="text-align:center;color:#94a3b8">Belum ada laporan</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Prioritas kritis -->
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
|
||||
<strong>🔥 Prioritas KRITIS (Belum Dibantu)</strong>
|
||||
<a href="belum_dibantu.php" class="btn btn-danger btn-sm">Semua →</a>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>Nama</th><th>Status</th><th>Kesehatan</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($prioritas as $p): ?>
|
||||
<tr>
|
||||
<td><a href="penduduk.php?detail=<?= h($p['nik']) ?>" style="color:var(--acc)"><?= h($p['nama_lengkap']) ?></a></td>
|
||||
<td><span class="tag miskin"><?= h($p['status_ekonomi']) ?></span></td>
|
||||
<td style="color:#ef4444"><?= h($p['kondisi_kesehatan']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($prioritas)): ?><tr><td colspan="3" style="text-align:center;color:#94a3b8">🎉 Tidak ada kasus kritis saat ini</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// Script inisialisasi — jalankan SEKALI untuk membuat akun admin & demo
|
||||
// URL: http://localhost/webgis-kemiskinan/admin/init_admin.php
|
||||
// HAPUS file ini setelah dipakai di produksi!
|
||||
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
|
||||
$users = [
|
||||
['admin', 'Administrator', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 1, null],
|
||||
['pengurus1', 'H. Ahmad Fauzi (Pengurus)', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 2, 1],
|
||||
['pimpinan1', 'Camat Pontianak Selatan', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 3, null],
|
||||
['warga1', 'Budi Santoso', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 4, null],
|
||||
];
|
||||
|
||||
$created = 0;
|
||||
foreach ($users as [$uname, $nama, $hash, $role, $ri]) {
|
||||
$cek = $pdo->prepare("SELECT id FROM users WHERE username=:u");
|
||||
$cek->execute([':u'=>$uname]);
|
||||
if (!$cek->fetch()) {
|
||||
$pdo->prepare("INSERT INTO users (role_id,nama,username,password_hash,rumah_ibadah_id) VALUES(:r,:n,:u,:p,:ri)")
|
||||
->execute([':r'=>$role,':n'=>$nama,':u'=>$uname,':p'=>$hash,':ri'=>$ri]);
|
||||
$created++;
|
||||
echo "✅ User '$uname' dibuat.<br>";
|
||||
} else {
|
||||
echo "ℹ️ User '$uname' sudah ada — dilewati.<br>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<br><strong>$created user baru dibuat.</strong><br>";
|
||||
echo "<br>Password semua akun demo: <code>password</code><br>";
|
||||
echo "<br><strong>Segera hapus file ini setelah digunakan!</strong><br>";
|
||||
echo "<br><a href='login.php'>→ Ke halaman login</a>";
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$role = $_SESSION['role'];
|
||||
$canEdit = in_array($role, ['admin','pengurus']);
|
||||
$msg = ''; $err = '';
|
||||
|
||||
// DELETE
|
||||
if ($canEdit && isset($_GET['delete'])) {
|
||||
$id = (int)$_GET['delete'];
|
||||
try {
|
||||
$pdo->prepare("DELETE FROM keluarga WHERE id=:id")->execute([':id'=>$id]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'DELETE', 'keluarga', (string)$id);
|
||||
$msg = "Data keluarga #$id dihapus.";
|
||||
} catch(Exception $e) { $err = 'Gagal hapus: '.$e->getMessage(); }
|
||||
}
|
||||
|
||||
// SAVE
|
||||
if ($canEdit && isset($_POST['save'])) {
|
||||
$nokk = trim($_POST['no_kk'] ?? '');
|
||||
$nama = trim($_POST['nama_kk'] ?? '');
|
||||
$idEdit = (int)($_POST['id_edit'] ?? 0);
|
||||
if (!preg_match('/^\d{16}$/', $nokk)) $err = 'No KK harus 16 digit.';
|
||||
elseif (strlen($nama) < 3) $err = 'Nama KK terlalu pendek.';
|
||||
else {
|
||||
try {
|
||||
$data = [
|
||||
':nokk' => $nokk, ':nama' => $nama,
|
||||
':almt' => $_POST['alamat']??'' ?: null,
|
||||
':kel' => $_POST['kelurahan']??'' ?: null,
|
||||
':kec' => $_POST['kecamatan']??'' ?: null,
|
||||
':kota' => $_POST['kota']??'' ?: null,
|
||||
':lat' => $_POST['latitude']??'' ?: null,
|
||||
':lng' => $_POST['longitude']??'' ?: null,
|
||||
':se' => $_POST['status_ekonomi']??'rentan',
|
||||
':ri' => $_POST['rumah_ibadah_id']??'' ?: null,
|
||||
];
|
||||
if ($idEdit) {
|
||||
$data[':id'] = $idEdit;
|
||||
$pdo->prepare("UPDATE keluarga SET no_kk=:nokk,nama_kk=:nama,alamat=:almt,kelurahan=:kel,
|
||||
kecamatan=:kec,kota=:kota,latitude=:lat,longitude=:lng,status_ekonomi=:se,
|
||||
rumah_ibadah_id=:ri WHERE id=:id")->execute($data);
|
||||
$msg = "Data KK $nama diperbarui.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'UPDATE', 'keluarga', (string)$idEdit);
|
||||
} else {
|
||||
$pdo->prepare("INSERT INTO keluarga (no_kk,nama_kk,alamat,kelurahan,kecamatan,kota,latitude,longitude,status_ekonomi,rumah_ibadah_id)
|
||||
VALUES(:nokk,:nama,:almt,:kel,:kec,:kota,:lat,:lng,:se,:ri)")->execute($data);
|
||||
$msg = "Keluarga $nama berhasil ditambahkan.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'CREATE', 'keluarga', (string)$pdo->lastInsertId());
|
||||
}
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$s = $pdo->prepare("SELECT * FROM keluarga WHERE id=:id");
|
||||
$s->execute([':id'=>(int)$_GET['edit']]);
|
||||
$edit = $s->fetch() ?: null;
|
||||
}
|
||||
|
||||
$search = trim($_GET['s'] ?? '');
|
||||
$where = []; $params = [];
|
||||
if ($search) { $where[] = '(k.nama_kk LIKE :q OR k.no_kk LIKE :q2)'; $params[':q']='%'.$search.'%'; $params[':q2']='%'.$search.'%'; }
|
||||
$whereStr = $where ? 'WHERE '.implode(' AND ',$where) : '';
|
||||
$rows = $pdo->prepare("
|
||||
SELECT k.*, ri.nama AS nama_ri,
|
||||
(SELECT COUNT(*) FROM penduduk p WHERE p.id_keluarga=k.id) AS jml_anggota
|
||||
FROM keluarga k
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
$whereStr ORDER BY k.nama_kk LIMIT 300
|
||||
");
|
||||
$rows->execute($params);
|
||||
$rows = $rows->fetchAll();
|
||||
$riList = $pdo->query("SELECT id,nama,jenis FROM rumah_ibadah ORDER BY nama")->fetchAll();
|
||||
|
||||
render_header('Keluarga (KK)');
|
||||
?>
|
||||
<div class="page-title">🏠 Data Keluarga (Kartu Keluarga)</div>
|
||||
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<div style="display:grid;grid-template-columns:<?= $canEdit ? '1fr 1.5fr' : '1fr' ?>;gap:16px">
|
||||
<?php if ($canEdit): ?>
|
||||
<div class="card">
|
||||
<strong><?= $edit ? '✏️ Edit KK' : '➕ Tambah KK' ?></strong><br><br>
|
||||
<form method="POST">
|
||||
<?php if ($edit): ?><input type="hidden" name="id_edit" value="<?= $edit['id'] ?>"><?php endif; ?>
|
||||
<div class="form-grid">
|
||||
<div class="form-group full"><label>No KK (16 digit) *</label>
|
||||
<input name="no_kk" maxlength="16" required value="<?= h($edit['no_kk'] ?? '') ?>"></div>
|
||||
<div class="form-group full"><label>Nama Kepala Keluarga *</label>
|
||||
<input name="nama_kk" required value="<?= h($edit['nama_kk'] ?? '') ?>"></div>
|
||||
<div class="form-group full"><label>Alamat</label>
|
||||
<textarea name="alamat" rows="2"><?= h($edit['alamat'] ?? '') ?></textarea></div>
|
||||
<div class="form-group"><label>Kelurahan</label>
|
||||
<input name="kelurahan" value="<?= h($edit['kelurahan'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Kecamatan</label>
|
||||
<input name="kecamatan" value="<?= h($edit['kecamatan'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Status Ekonomi</label>
|
||||
<select name="status_ekonomi">
|
||||
<?php foreach (['miskin','rentan','mampu'] as $s): ?>
|
||||
<option value="<?= $s ?>" <?= ($edit['status_ekonomi'] ?? 'rentan')===$s?'selected':'' ?>><?= ucfirst($s) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select></div>
|
||||
<div class="form-group"><label>Rumah Ibadah Terdekat</label>
|
||||
<select name="rumah_ibadah_id">
|
||||
<option value="">— Tidak ada —</option>
|
||||
<?php foreach ($riList as $ri): ?>
|
||||
<option value="<?= $ri['id'] ?>" <?= ($edit['rumah_ibadah_id'] ?? '')==$ri['id']?'selected':'' ?>><?= h($ri['nama']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select></div>
|
||||
<div class="form-group"><label>Latitude</label>
|
||||
<input name="latitude" type="number" step="any" value="<?= h($edit['latitude'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Longitude</label>
|
||||
<input name="longitude" type="number" step="any" value="<?= h($edit['longitude'] ?? '') ?>"></div>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" name="save" class="btn btn-primary"><?= $edit ? '💾 Update' : '➕ Simpan' ?></button>
|
||||
<?php if ($edit): ?><a href="keluarga.php" class="btn btn-secondary">Batal</a><?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div>
|
||||
<div style="display:flex;gap:8px;margin-bottom:12px">
|
||||
<form method="GET" style="display:flex;gap:8px;flex:1">
|
||||
<input name="s" placeholder="Cari nama / No KK…" value="<?= h($search) ?>" style="flex:1">
|
||||
<button class="btn btn-primary">🔍</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr><th>No KK</th><th>Nama KK</th><th>Status</th><th>Anggota</th><th>Rumah Ibadah</th><?= $canEdit?'<th>Aksi</th>':'' ?></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td style="font-size:11px"><?= h($r['no_kk']) ?></td>
|
||||
<td><strong><?= h($r['nama_kk']) ?></strong><br><span style="font-size:11px;color:#94a3b8"><?= h($r['kecamatan']??'') ?></span></td>
|
||||
<td><span class="tag <?= $r['status_ekonomi'] ?>"><?= h($r['status_ekonomi']) ?></span></td>
|
||||
<td><?= $r['jml_anggota'] ?> jiwa</td>
|
||||
<td style="font-size:12px"><?= h($r['nama_ri'] ?? '-') ?></td>
|
||||
<?php if ($canEdit): ?>
|
||||
<td class="actions">
|
||||
<a href="?edit=<?= $r['id'] ?>" class="btn btn-secondary btn-sm">✏️</a>
|
||||
<a href="?delete=<?= $r['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('Hapus KK ini?')">🗑️</a>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?><tr><td colspan="6" style="text-align:center;padding:24px;color:#94a3b8">Belum ada data</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$currentUser = ['id' => (int)$_SESSION['user_id'], 'role' => $_SESSION['role']];
|
||||
$filter = $_GET['filter'] ?? 'semua';
|
||||
$msg = ''; $err = '';
|
||||
|
||||
// Update status laporan
|
||||
if (isset($_POST['update_status']) && in_array($currentUser['role'], ['admin','pengurus'])) {
|
||||
$id = (int)($_POST['lap_id'] ?? 0);
|
||||
$status = $_POST['status'] ?? '';
|
||||
$catatan = trim($_POST['catatan'] ?? '');
|
||||
$valid = ['diverifikasi','diproses','selesai','ditolak'];
|
||||
if ($id > 0 && in_array($status, $valid)) {
|
||||
try {
|
||||
$sets = ['status=:s','updated_at=NOW()'];
|
||||
$params = [':s'=>$status,':id'=>$id];
|
||||
if ($status === 'diverifikasi') { $sets[] = 'diverifikasi_oleh=:v'; $params[':v'] = $currentUser['id']; }
|
||||
if ($status === 'diproses') { $sets[] = 'ditangani_oleh=:t'; $params[':t'] = $currentUser['id']; }
|
||||
if ($status === 'selesai') { $sets[] = 'tanggal_selesai=NOW()'; }
|
||||
if ($catatan) { $sets[] = 'catatan_verifikasi=:c'; $params[':c'] = $catatan; }
|
||||
$pdo->prepare("UPDATE laporan SET ".implode(',',$sets)." WHERE id=:id")->execute($params);
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE', 'laporan', (string)$id, null, ['status'=>$status]);
|
||||
$msg = "Status laporan #$id diperbarui ke '$status'.";
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil laporan
|
||||
$where = [];
|
||||
$params = [];
|
||||
if ($filter === 'pending') { $where[] = "status='pending'"; }
|
||||
if ($filter === 'darurat') { $where[] = "tingkat_urgensi IN ('darurat','tinggi')"; }
|
||||
if ($filter === 'diproses') { $where[] = "status IN ('diverifikasi','diproses')"; }
|
||||
if ($filter === 'selesai') { $where[] = "status='selesai'"; }
|
||||
$whereStr = $where ? 'WHERE '.implode(' AND ',$where) : '';
|
||||
|
||||
$rows = [];
|
||||
try {
|
||||
$rows = $pdo->query("
|
||||
SELECT l.*, p.nama_lengkap AS nama_terdampak, u.nama AS nama_verifikator
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik=l.nik_terdampak
|
||||
LEFT JOIN users u ON u.id=l.diverifikasi_oleh
|
||||
$whereStr
|
||||
ORDER BY CASE l.tingkat_urgensi WHEN 'darurat' THEN 1 WHEN 'tinggi' THEN 2 ELSE 3 END,
|
||||
CASE l.status WHEN 'pending' THEN 1 WHEN 'diverifikasi' THEN 2 ELSE 3 END,
|
||||
l.created_at DESC
|
||||
")->fetchAll();
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
|
||||
render_header('Manajemen Laporan');
|
||||
?>
|
||||
|
||||
<div class="page-title">🚩 Laporan Masyarakat</div>
|
||||
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap">
|
||||
<?php foreach (['semua'=>'Semua','pending'=>'Pending','darurat'=>'🔴 Darurat','diproses'=>'Diproses','selesai'=>'Selesai'] as $k=>$v): ?>
|
||||
<a href="?filter=<?= $k ?>" class="btn <?= $filter===$k?'btn-primary':'btn-secondary' ?>"><?= $v ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>Pelapor</th><th>Terdampak</th><th>Kategori</th>
|
||||
<th>Urgensi</th><th>Status</th><th>Tanggal</th><th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td><?= $r['id'] ?></td>
|
||||
<td><?= h($r['nama_pelapor'] ?: 'Anonim') ?></td>
|
||||
<td><?= h($r['nama_terdampak'] ?? ($r['nik_terdampak'] ?? '-')) ?></td>
|
||||
<td style="font-size:11px"><?= h(str_replace('_',' ',$r['kategori'])) ?></td>
|
||||
<td><span class="tag <?= $r['tingkat_urgensi']==='darurat'?'darurat':'pending' ?>"><?= h($r['tingkat_urgensi']) ?></span></td>
|
||||
<td><span class="tag <?= $r['status'] ?>"><?= h($r['status']) ?></span></td>
|
||||
<td style="font-size:11px"><?= date('d/m/y H:i', strtotime($r['created_at'])) ?></td>
|
||||
<td>
|
||||
<?php if (in_array($currentUser['role'], ['admin','pengurus'])): ?>
|
||||
<button class="btn btn-secondary btn-sm" onclick="showUpdate(<?= $r['id'] ?>, '<?= h($r['status']) ?>', '<?= addslashes($r['catatan_verifikasi'] ?? '') ?>')">
|
||||
Update
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button class="btn btn-secondary btn-sm" onclick="showDetail(<?= htmlspecialchars(json_encode($r), ENT_QUOTES) ?>)">Detail</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="8" style="text-align:center;padding:30px;color:#94a3b8">Tidak ada laporan</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal Update Status -->
|
||||
<div id="modal-update" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:999;align-items:center;justify-content:center">
|
||||
<div style="background:#1e293b;border-radius:16px;padding:24px;width:100%;max-width:440px;border:1px solid #334155">
|
||||
<h3 style="margin-bottom:16px">Update Status Laporan #<span id="upd-id"></span></h3>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="lap_id" id="upd-lap-id">
|
||||
<div class="form-group" style="margin-bottom:12px">
|
||||
<label>Status Baru</label>
|
||||
<select name="status" id="upd-status">
|
||||
<option value="diverifikasi">Diverifikasi</option>
|
||||
<option value="diproses">Diproses</option>
|
||||
<option value="selesai">Selesai</option>
|
||||
<option value="ditolak">Ditolak</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px">
|
||||
<label>Catatan</label>
|
||||
<textarea name="catatan" id="upd-catatan" rows="3" placeholder="Catatan verifikasi/tindakan..."></textarea>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end">
|
||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('modal-update').style.display='none'">Batal</button>
|
||||
<button type="submit" name="update_status" value="1" class="btn btn-primary">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Detail -->
|
||||
<div id="modal-detail" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:999;align-items:center;justify-content:center">
|
||||
<div style="background:#1e293b;border-radius:16px;padding:24px;width:100%;max-width:520px;border:1px solid #334155;max-height:80vh;overflow-y:auto">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
||||
<h3>Detail Laporan</h3>
|
||||
<button class="btn btn-secondary btn-sm" onclick="document.getElementById('modal-detail').style.display='none'">✕</button>
|
||||
</div>
|
||||
<div id="detail-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showUpdate(id, status, catatan) {
|
||||
document.getElementById('upd-id').textContent = id;
|
||||
document.getElementById('upd-lap-id').value = id;
|
||||
document.getElementById('upd-status').value = status;
|
||||
document.getElementById('upd-catatan').value = catatan;
|
||||
const m = document.getElementById('modal-update');
|
||||
m.style.display = 'flex';
|
||||
}
|
||||
function showDetail(data) {
|
||||
const m = document.getElementById('modal-detail');
|
||||
document.getElementById('detail-content').innerHTML = `
|
||||
<div style="font-size:13px;display:grid;gap:8px">
|
||||
<div><strong>Pelapor:</strong> ${data.nama_pelapor || 'Anonim'}</div>
|
||||
<div><strong>Terdampak:</strong> ${data.nik_terdampak || '-'}</div>
|
||||
<div><strong>Kategori:</strong> ${data.kategori.replace(/_/g,' ')}</div>
|
||||
<div><strong>Urgensi:</strong> ${data.tingkat_urgensi}</div>
|
||||
<div><strong>Alamat:</strong> ${data.alamat || '-'}</div>
|
||||
<div style="background:#334155;border-radius:8px;padding:10px;margin-top:4px"><strong>Deskripsi:</strong><br>${data.deskripsi}</div>
|
||||
${data.catatan_verifikasi ? `<div><strong>Catatan:</strong> ${data.catatan_verifikasi}</div>` : ''}
|
||||
${data.foto ? `<div><strong>Foto:</strong> <a href="../uploads/foto_laporan/${data.foto}" target="_blank" style="color:#3b82f6">Lihat Foto</a></div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
m.style.display = 'flex';
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin']);
|
||||
|
||||
$rows = $pdo->query("
|
||||
SELECT l.*, u.nama AS nama_user
|
||||
FROM log_aktivitas l
|
||||
LEFT JOIN users u ON u.id=l.user_id
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT 200
|
||||
")->fetchAll();
|
||||
|
||||
render_header('Log Aktivitas');
|
||||
?>
|
||||
<div class="page-title">📜 Log Aktivitas Sistem</div>
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr><th>Waktu</th><th>User</th><th>Aksi</th><th>Tabel</th><th>Record</th><th>IP</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td style="font-size:11px"><?= date('d/m/y H:i:s', strtotime($r['created_at'])) ?></td>
|
||||
<td><?= h($r['nama_user'] ?? 'Sistem') ?></td>
|
||||
<td><span class="tag <?= $r['aksi']==='DELETE'?'darurat':($r['aksi']==='CREATE'?'selesai':'pending') ?>"><?= h($r['aksi']) ?></span></td>
|
||||
<td style="font-size:11px"><?= h($r['tabel'] ?? '-') ?></td>
|
||||
<td style="font-size:11px"><?= h($r['record_id'] ?? '-') ?></td>
|
||||
<td style="font-size:10px;color:#94a3b8"><?= h($r['ip_address'] ?? '-') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Login — WebGIS Pengentasan Kemiskinan</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:#0f172a;color:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:16px}
|
||||
.card{background:#1e293b;border:1px solid #334155;border-radius:20px;padding:40px 36px;width:100%;max-width:400px;box-shadow:0 25px 60px rgba(0,0,0,.5)}
|
||||
.logo{text-align:center;margin-bottom:28px}
|
||||
.logo .icon{font-size:40px;margin-bottom:8px}
|
||||
.logo h1{font-size:20px;font-weight:900}
|
||||
.logo p{font-size:12px;color:#94a3b8;margin-top:4px}
|
||||
.form-group{margin-bottom:16px}
|
||||
label{display:block;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;margin-bottom:6px}
|
||||
input{width:100%;background:#0f172a;border:1px solid #334155;color:#f1f5f9;padding:11px 14px;border-radius:10px;font-size:14px}
|
||||
input:focus{outline:none;border-color:#3b82f6}
|
||||
.btn{width:100%;background:#3b82f6;color:#fff;border:none;padding:12px;border-radius:10px;font-size:14px;font-weight:800;cursor:pointer;transition:.2s}
|
||||
.btn:hover{background:#2563eb}
|
||||
.error{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#ef4444;padding:10px 14px;border-radius:8px;font-size:13px;margin-bottom:16px;display:none}
|
||||
.roles{margin-top:20px;background:#0f172a;border-radius:10px;padding:14px}
|
||||
.roles h4{font-size:11px;font-weight:900;color:#94a3b8;margin-bottom:10px;text-transform:uppercase}
|
||||
.role-row{display:flex;justify-content:space-between;font-size:12px;margin-bottom:6px;padding:6px 0;border-bottom:1px solid #1e293b}
|
||||
.role-row:last-child{border:none;margin:0}
|
||||
.role-badge{background:#334155;padding:2px 8px;border-radius:99px;font-size:10px;font-weight:700}
|
||||
.back{text-align:center;margin-top:16px}
|
||||
.back a{color:#3b82f6;font-size:13px;text-decoration:none}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
require_once __DIR__ . '/../php/middleware/auth.php';
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$result = doLogin($pdo, $_POST['username'] ?? '', $_POST['password'] ?? '');
|
||||
if ($result['success']) {
|
||||
$redirect = [
|
||||
'admin' => 'index.php',
|
||||
'pengurus' => 'index.php',
|
||||
'pimpinan' => 'index.php',
|
||||
'masyarakat' => '../index.html',
|
||||
];
|
||||
header('Location: ' . ($redirect[$result['role']] ?? 'index.php'));
|
||||
exit;
|
||||
}
|
||||
$error = $result['message'];
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="logo">
|
||||
<div class="icon">🗺️</div>
|
||||
<h1>WebGIS Kemiskinan</h1>
|
||||
<p>Berbasis Masyarakat & Rumah Ibadah</p>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error" style="display:block"><?= h($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" name="username" placeholder="Masukkan username" required autofocus value="<?= h($_POST['username'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" placeholder="Masukkan password" required>
|
||||
</div>
|
||||
<div class="form-group" style="text-align:right; margin-top:-8px; margin-bottom:16px;">
|
||||
<a href="forgot.php" style="color:#3b82f6; font-size:11px; text-decoration:none; font-weight:700;">Lupa Password?</a>
|
||||
</div>
|
||||
<button type="submit" class="btn">🔐 Masuk</button>
|
||||
</form>
|
||||
|
||||
<div class="roles">
|
||||
<h4>Akun Demo (password: password)</h4>
|
||||
<div class="role-row"><span>admin</span><span class="role-badge">Administrator</span></div>
|
||||
<div class="role-row"><span>pengurus1</span><span class="role-badge">Pengurus Masjid</span></div>
|
||||
<div class="role-row"><span>pimpinan1</span><span class="role-badge">Pimpinan Daerah</span></div>
|
||||
<div class="role-row"><span>warga1</span><span class="role-badge">Masyarakat</span></div>
|
||||
</div>
|
||||
|
||||
<div class="back"><a href="../../index.html">← Kembali ke Landing Page</a></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
require_once __DIR__ . '/../php/middleware/auth.php';
|
||||
doLogout($pdo);
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$role = $_SESSION['role'];
|
||||
$canEdit = in_array($role, ['admin','pengurus']);
|
||||
|
||||
$msg = ''; $err = '';
|
||||
|
||||
// DELETE
|
||||
if ($canEdit && isset($_GET['delete'])) {
|
||||
$nik = $_GET['delete'];
|
||||
try {
|
||||
$pdo->prepare("DELETE FROM penduduk WHERE nik=:nik")->execute([':nik'=>$nik]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'DELETE', 'penduduk', $nik);
|
||||
$msg = "Penduduk NIK $nik dihapus.";
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
|
||||
// SAVE
|
||||
if ($canEdit && isset($_POST['save'])) {
|
||||
$nik = trim($_POST['nik'] ?? '');
|
||||
$nama = trim($_POST['nama_lengkap'] ?? '');
|
||||
$tgl = $_POST['tanggal_lahir'] ?? '';
|
||||
$nikEdit = $_POST['nik_edit'] ?? '';
|
||||
|
||||
if (!preg_match('/^\d{16}$/', $nik)) $err = 'NIK harus 16 digit.';
|
||||
elseif (strlen($nama) < 3) $err = 'Nama terlalu pendek.';
|
||||
else {
|
||||
try {
|
||||
$data = [
|
||||
':kk'=>$_POST['id_keluarga']?:(null),
|
||||
':nama'=>$nama, ':jk'=>$_POST['jenis_kelamin']??'L',
|
||||
':tgl'=>$tgl, ':sk'=>$_POST['status_keluarga']??'anggota_lain',
|
||||
':sp'=>$_POST['status_perkawinan']??'belum_kawin',
|
||||
':sh'=>$_POST['status_hidup']??'hidup',
|
||||
':ptk'=>$_POST['pendidikan_terakhir']??'tidak_sekolah',
|
||||
':spd'=>$_POST['status_pendidikan']??'tidak_sekolah',
|
||||
':pkr'=>$_POST['pekerjaan']??'' ?: null,
|
||||
':pgn'=>(float)($_POST['penghasilan']??0),
|
||||
':se'=>$_POST['status_ekonomi']??'rentan',
|
||||
':kkh'=>$_POST['kondisi_kesehatan']??'sehat',
|
||||
':ckkh'=>$_POST['catatan_kesehatan']??'' ?: null,
|
||||
':tlp'=>$_POST['no_telp']??'' ?: null,
|
||||
':almt'=>$_POST['alamat']??'' ?: null,
|
||||
':lat'=>$_POST['latitude']??'' ?: null,
|
||||
':lng'=>$_POST['longitude']??'' ?: null,
|
||||
];
|
||||
if ($nikEdit) {
|
||||
$data[':nik'] = $nikEdit;
|
||||
$pdo->prepare("UPDATE penduduk SET id_keluarga=:kk,nama_lengkap=:nama,jenis_kelamin=:jk,tanggal_lahir=:tgl,
|
||||
status_keluarga=:sk,status_perkawinan=:sp,status_hidup=:sh,pendidikan_terakhir=:ptk,status_pendidikan=:spd,
|
||||
pekerjaan=:pkr,penghasilan=:pgn,status_ekonomi=:se,kondisi_kesehatan=:kkh,catatan_kesehatan=:ckkh,
|
||||
no_telp=:tlp,alamat=:almt,latitude=:lat,longitude=:lng WHERE nik=:nik")->execute($data);
|
||||
$msg = "Data penduduk $nama diperbarui.";
|
||||
} else {
|
||||
$data[':nik'] = $nik;
|
||||
$pdo->prepare("INSERT INTO penduduk (nik,id_keluarga,nama_lengkap,jenis_kelamin,tanggal_lahir,
|
||||
status_keluarga,status_perkawinan,status_hidup,pendidikan_terakhir,status_pendidikan,
|
||||
pekerjaan,penghasilan,status_ekonomi,kondisi_kesehatan,catatan_kesehatan,no_telp,alamat,latitude,longitude)
|
||||
VALUES(:nik,:kk,:nama,:jk,:tgl,:sk,:sp,:sh,:ptk,:spd,:pkr,:pgn,:se,:kkh,:ckkh,:tlp,:almt,:lat,:lng)")->execute($data);
|
||||
$msg = "Penduduk $nama berhasil ditambahkan.";
|
||||
}
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], $nikEdit?'UPDATE':'CREATE', 'penduduk', $nik);
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM penduduk WHERE nik=:nik");
|
||||
$stmt->execute([':nik'=>$_GET['edit']]);
|
||||
$edit = $stmt->fetch() ?: null;
|
||||
}
|
||||
|
||||
// List penduduk
|
||||
$search = trim($_GET['s'] ?? '');
|
||||
$se = $_GET['se'] ?? '';
|
||||
$where = []; $params = [];
|
||||
if ($search) { $where[] = '(p.nama_lengkap LIKE :q OR p.nik LIKE :q2)'; $params[':q']='%'.$search.'%'; $params[':q2']='%'.$search.'%'; }
|
||||
if ($se) { $where[] = 'p.status_ekonomi=:se'; $params[':se']=$se; }
|
||||
$whereStr = $where ? 'WHERE '.implode(' AND ',$where) : '';
|
||||
|
||||
$rows = $pdo->prepare("
|
||||
SELECT p.nik,p.nama_lengkap,p.umur,p.jenis_kelamin,p.status_ekonomi,p.kondisi_kesehatan,
|
||||
p.pekerjaan,p.status_hidup,k.no_kk,ri.nama AS nama_ri,
|
||||
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan') AS jml_bantuan
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id=p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
$whereStr
|
||||
ORDER BY CASE p.status_ekonomi WHEN 'miskin' THEN 1 WHEN 'rentan' THEN 2 ELSE 3 END, p.nama_lengkap
|
||||
LIMIT 300
|
||||
");
|
||||
$rows->execute($params);
|
||||
$rows = $rows->fetchAll();
|
||||
|
||||
// Keluarga untuk dropdown
|
||||
$kkList = $pdo->query("SELECT id, no_kk, nama_kk FROM keluarga ORDER BY nama_kk")->fetchAll();
|
||||
|
||||
render_header('Manajemen Penduduk');
|
||||
?>
|
||||
<div class="page-title">👥 Manajemen Penduduk</div>
|
||||
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<?php if ($canEdit): ?>
|
||||
<!-- FORM -->
|
||||
<div class="card">
|
||||
<strong><?= $edit ? '✏️ Edit: '.h($edit['nama_lengkap']) : '➕ Tambah Penduduk' ?></strong>
|
||||
<form method="POST" style="margin-top:14px">
|
||||
<input type="hidden" name="nik_edit" value="<?= h($edit['nik']??'') ?>">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>NIK *</label>
|
||||
<input name="nik" maxlength="16" required value="<?= h($edit['nik']??'') ?>" <?= $edit?'readonly':'' ?>>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keluarga (KK)</label>
|
||||
<select name="id_keluarga">
|
||||
<option value="">— Pilih KK —</option>
|
||||
<?php foreach ($kkList as $kk): ?>
|
||||
<option value="<?= $kk['id'] ?>" <?= ($edit['id_keluarga']??'')==$kk['id']?'selected':'' ?>><?= h($kk['no_kk'].' — '.$kk['nama_kk']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Nama Lengkap *</label>
|
||||
<input name="nama_lengkap" required value="<?= h($edit['nama_lengkap']??'') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Jenis Kelamin</label>
|
||||
<select name="jenis_kelamin">
|
||||
<option value="L" <?= ($edit['jenis_kelamin']??'L')==='L'?'selected':'' ?>>Laki-laki</option>
|
||||
<option value="P" <?= ($edit['jenis_kelamin']??'')==='P'?'selected':'' ?>>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tanggal Lahir *</label>
|
||||
<input type="date" name="tanggal_lahir" required value="<?= h($edit['tanggal_lahir']??'') ?>" onchange="hitungUmur(this)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Keluarga</label>
|
||||
<select name="status_keluarga">
|
||||
<?php foreach (['kepala_keluarga'=>'Kepala Keluarga','istri'=>'Istri','anak'=>'Anak','menantu'=>'Menantu','orang_tua'=>'Orang Tua','anggota_lain'=>'Anggota Lain'] as $v=>$l): ?>
|
||||
<option value="<?= $v ?>" <?= ($edit['status_keluarga']??'')===$v?'selected':'' ?>><?= $l ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Perkawinan</label>
|
||||
<select name="status_perkawinan">
|
||||
<?php foreach (['belum_kawin'=>'Belum Kawin','kawin'=>'Kawin','cerai_hidup'=>'Cerai Hidup','cerai_mati'=>'Cerai Mati'] as $v=>$l): ?>
|
||||
<option value="<?= $v ?>" <?= ($edit['status_perkawinan']??'')===$v?'selected':'' ?>><?= $l ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Hidup</label>
|
||||
<select name="status_hidup">
|
||||
<option value="hidup" <?= ($edit['status_hidup']??'hidup')==='hidup'?'selected':'' ?>>Hidup</option>
|
||||
<option value="meninggal" <?= ($edit['status_hidup']??'')==='meninggal'?'selected':'' ?>>Meninggal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pendidikan Terakhir</label>
|
||||
<select name="pendidikan_terakhir" onchange="cekPendidikan()">
|
||||
<?php foreach (['tidak_sekolah','SD','SMP','SMA','SMK','D3','S1','S2','S3'] as $v): ?>
|
||||
<option value="<?= $v ?>" <?= ($edit['pendidikan_terakhir']??'')===$v?'selected':'' ?>><?= $v ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Pendidikan</label>
|
||||
<select name="status_pendidikan" id="status_pendidikan" onchange="cekPendidikan()">
|
||||
<option value="sekolah" <?= ($edit['status_pendidikan']??'')==='sekolah'?'selected':'' ?>>Masih Sekolah</option>
|
||||
<option value="tidak_sekolah" <?= ($edit['status_pendidikan']??'')==='tidak_sekolah'?'selected':'' ?>>Tidak Sekolah</option>
|
||||
<option value="lulus" <?= ($edit['status_pendidikan']??'')==='lulus'?'selected':'' ?>>Lulus</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Ekonomi</label>
|
||||
<select name="status_ekonomi">
|
||||
<option value="miskin" <?= ($edit['status_ekonomi']??'')==='miskin'?'selected':'' ?>>Miskin</option>
|
||||
<option value="rentan" <?= ($edit['status_ekonomi']??'rentan')==='rentan'?'selected':'' ?>>Rentan</option>
|
||||
<option value="mampu" <?= ($edit['status_ekonomi']??'')==='mampu'?'selected':'' ?>>Mampu</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Kondisi Kesehatan</label>
|
||||
<select name="kondisi_kesehatan">
|
||||
<?php foreach (['sehat'=>'Sehat','sakit_ringan'=>'Sakit Ringan','sakit_parah'=>'Sakit Parah','disabilitas'=>'Disabilitas'] as $v=>$l): ?>
|
||||
<option value="<?= $v ?>" <?= ($edit['kondisi_kesehatan']??'sehat')===$v?'selected':'' ?>><?= $l ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pekerjaan</label>
|
||||
<input name="pekerjaan" value="<?= h($edit['pekerjaan']??'') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Penghasilan / Bulan (Rp)</label>
|
||||
<input type="number" name="penghasilan" value="<?= h($edit['penghasilan']??0) ?>">
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Alamat</label>
|
||||
<input name="alamat" value="<?= h($edit['alamat']??'') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Latitude</label>
|
||||
<input name="latitude" value="<?= h($edit['latitude']??'') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Longitude</label>
|
||||
<input name="longitude" value="<?= h($edit['longitude']??'') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div id="pendidikan-warn" style="display:none;background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;font-size:12px;color:#f59e0b;margin-top:10px"></div>
|
||||
<div style="display:flex;gap:8px;margin-top:14px">
|
||||
<button type="submit" name="save" value="1" class="btn btn-primary">💾 <?= $edit ? 'Update' : 'Simpan' ?></button>
|
||||
<?php if ($edit): ?><a href="penduduk.php" class="btn btn-secondary">Batal</a><?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- TABEL -->
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<div style="padding:14px 16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;border-bottom:1px solid var(--brd)">
|
||||
<form method="GET" style="display:flex;gap:8px;flex:1;min-width:200px">
|
||||
<input name="s" placeholder="Cari nama / NIK..." value="<?= h($search) ?>" style="flex:1">
|
||||
<select name="se" style="width:120px">
|
||||
<option value="">Semua Status</option>
|
||||
<option value="miskin" <?= $se==='miskin'?'selected':'' ?>>Miskin</option>
|
||||
<option value="rentan" <?= $se==='rentan'?'selected':'' ?>>Rentan</option>
|
||||
<option value="mampu" <?= $se==='mampu'?'selected':'' ?>>Mampu</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-secondary">Cari</button>
|
||||
<a href="penduduk.php" class="btn btn-secondary">Reset</a>
|
||||
</form>
|
||||
<span style="font-size:12px;color:#94a3b8"><?= count($rows) ?> data</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>NIK</th><th>Nama</th><th>Umur/JK</th><th>Status Ekonomi</th><th>Kesehatan</th><th>Bantuan</th><th>Rumah Ibadah</th><?php if($canEdit): ?><th>Aksi</th><?php endif; ?></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td style="font-size:11px"><?= h($r['nik']) ?></td>
|
||||
<td><strong><?= h($r['nama_lengkap']) ?></strong><?= $r['status_hidup']==='meninggal'?'<br><span style="font-size:10px;color:#ef4444">†meninggal</span>':'' ?></td>
|
||||
<td><?= $r['umur'] ?>th / <?= $r['jenis_kelamin']==='L'?'♂':'♀' ?></td>
|
||||
<td><span class="tag <?= $r['status_ekonomi'] ?>"><?= h($r['status_ekonomi']) ?></span></td>
|
||||
<td style="font-size:11px"><?= h(str_replace('_',' ',$r['kondisi_kesehatan'])) ?></td>
|
||||
<td><?= $r['jml_bantuan'] > 0 ? "<span style='color:#22c55e'>✅ {$r['jml_bantuan']}x</span>" : "<span style='color:#ef4444'>⚠️ Belum</span>" ?></td>
|
||||
<td style="font-size:11px"><?= h($r['nama_ri'] ?? '-') ?></td>
|
||||
<?php if($canEdit): ?>
|
||||
<td class="actions">
|
||||
<a href="penduduk.php?edit=<?= h($r['nik']) ?>" class="btn btn-secondary btn-sm">Edit</a>
|
||||
<a href="bantuan.php?nik=<?= h($r['nik']) ?>&nama=<?= urlencode($r['nama_lengkap']) ?>" class="btn btn-success btn-sm">Bantu</a>
|
||||
<?php if ($_SESSION['role']==='admin'): ?>
|
||||
<a href="penduduk.php?delete=<?= h($r['nik']) ?>" class="btn btn-danger btn-sm" onclick="return confirm('Hapus <?= h(addslashes($r['nama_lengkap'])) ?>?')">Hapus</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?><tr><td colspan="8" style="text-align:center;padding:24px;color:#94a3b8">Tidak ada data</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function hitungUmur(el) {
|
||||
const tgl = el.value;
|
||||
if (!tgl) return;
|
||||
const umur = Math.floor((new Date() - new Date(tgl)) / (365.25*24*3600*1000));
|
||||
cekPendidikanUmur(umur);
|
||||
}
|
||||
function cekPendidikan() {
|
||||
const tgl = document.querySelector('[name=tanggal_lahir]').value;
|
||||
if (!tgl) return;
|
||||
const umur = Math.floor((new Date() - new Date(tgl)) / (365.25*24*3600*1000));
|
||||
cekPendidikanUmur(umur);
|
||||
}
|
||||
function cekPendidikanUmur(umur) {
|
||||
const status = document.getElementById('status_pendidikan').value;
|
||||
const warn = document.getElementById('pendidikan-warn');
|
||||
let msg = '';
|
||||
if (umur>=7&&umur<=12&&status!=='sekolah') msg = `⚠️ Anak usia ${umur} tahun seharusnya masih sekolah SD.`;
|
||||
if (umur>=13&&umur<=15&&status!=='sekolah') msg = `⚠️ Anak usia ${umur} tahun seharusnya masih sekolah SMP.`;
|
||||
if (umur>=16&&umur<=18&&status!=='sekolah') msg = `⚠️ Anak usia ${umur} tahun seharusnya masih sekolah SMA/SMK.`;
|
||||
warn.style.display = msg ? 'block' : 'none';
|
||||
warn.textContent = msg;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin','pengurus','pimpinan']);
|
||||
|
||||
$role = $_SESSION['role'];
|
||||
$canEdit = in_array($role, ['admin','pengurus']);
|
||||
$msg = ''; $err = '';
|
||||
|
||||
// DELETE
|
||||
if ($canEdit && isset($_GET['delete'])) {
|
||||
$id = (int)$_GET['delete'];
|
||||
try {
|
||||
$pdo->prepare("DELETE FROM rumah_ibadah WHERE id=:id")->execute([':id'=>$id]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'DELETE', 'rumah_ibadah', (string)$id);
|
||||
$msg = "Rumah ibadah #$id dihapus.";
|
||||
} catch(Exception $e) { $err = 'Gagal hapus: '.$e->getMessage(); }
|
||||
}
|
||||
|
||||
// SAVE
|
||||
if ($canEdit && isset($_POST['save'])) {
|
||||
$nama = trim($_POST['nama'] ?? '');
|
||||
$lat = $_POST['latitude'] ?? '';
|
||||
$lng = $_POST['longitude'] ?? '';
|
||||
$idEdit = (int)($_POST['id_edit'] ?? 0);
|
||||
if (strlen($nama) < 3) $err = 'Nama terlalu pendek.';
|
||||
elseif (!is_numeric($lat) || !is_numeric($lng)) $err = 'Latitude/Longitude tidak valid.';
|
||||
else {
|
||||
try {
|
||||
$data = [
|
||||
':nama' => $nama,
|
||||
':jenis' => $_POST['jenis']??'Lainnya',
|
||||
':kontak'=> $_POST['kontak']??'' ?: null,
|
||||
':rad' => (float)($_POST['radius_meter']??300),
|
||||
':lat' => (float)$lat, ':lng' => (float)$lng,
|
||||
':almt' => $_POST['alamat']??'' ?: null,
|
||||
':kel' => $_POST['kelurahan']??'' ?: null,
|
||||
':kec' => $_POST['kecamatan']??'' ?: null,
|
||||
':kota' => $_POST['kota']??'' ?: null,
|
||||
':desk' => $_POST['deskripsi']??'' ?: null,
|
||||
];
|
||||
if ($idEdit) {
|
||||
$data[':id'] = $idEdit;
|
||||
$pdo->prepare("UPDATE rumah_ibadah SET nama=:nama,jenis=:jenis,kontak=:kontak,radius_meter=:rad,
|
||||
latitude=:lat,longitude=:lng,alamat=:almt,kelurahan=:kel,kecamatan=:kec,kota=:kota,deskripsi=:desk
|
||||
WHERE id=:id")->execute($data);
|
||||
$msg = "$nama diperbarui.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'UPDATE', 'rumah_ibadah', (string)$idEdit);
|
||||
} else {
|
||||
$pdo->prepare("INSERT INTO rumah_ibadah (nama,jenis,kontak,radius_meter,latitude,longitude,alamat,kelurahan,kecamatan,kota,deskripsi)
|
||||
VALUES(:nama,:jenis,:kontak,:rad,:lat,:lng,:almt,:kel,:kec,:kota,:desk)")->execute($data);
|
||||
$msg = "$nama berhasil ditambahkan.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'CREATE', 'rumah_ibadah', (string)$pdo->lastInsertId());
|
||||
}
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$s = $pdo->prepare("SELECT * FROM rumah_ibadah WHERE id=:id");
|
||||
$s->execute([':id'=>(int)$_GET['edit']]);
|
||||
$edit = $s->fetch() ?: null;
|
||||
}
|
||||
|
||||
$rows = $pdo->query("
|
||||
SELECT ri.*,
|
||||
(SELECT COUNT(*) FROM keluarga k WHERE k.rumah_ibadah_id=ri.id) AS jml_kk
|
||||
FROM rumah_ibadah ri ORDER BY ri.nama
|
||||
")->fetchAll();
|
||||
|
||||
$jenisOpts = ['Masjid','Musholla','Gereja Protestan','Gereja Katolik','Pura','Vihara','Klenteng','Lainnya'];
|
||||
|
||||
render_header('Rumah Ibadah');
|
||||
?>
|
||||
<div class="page-title">🕌 Data Rumah Ibadah</div>
|
||||
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<div style="display:grid;grid-template-columns:<?= $canEdit ? '1fr 1.5fr' : '1fr' ?>;gap:16px">
|
||||
<?php if ($canEdit): ?>
|
||||
<div class="card">
|
||||
<strong><?= $edit ? '✏️ Edit' : '➕ Tambah Rumah Ibadah' ?></strong><br><br>
|
||||
<form method="POST">
|
||||
<?php if ($edit): ?><input type="hidden" name="id_edit" value="<?= $edit['id'] ?>"><?php endif; ?>
|
||||
<div class="form-grid">
|
||||
<div class="form-group full"><label>Nama *</label>
|
||||
<input name="nama" required value="<?= h($edit['nama'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Jenis</label>
|
||||
<select name="jenis">
|
||||
<?php foreach ($jenisOpts as $j): ?>
|
||||
<option value="<?= $j ?>" <?= ($edit['jenis']??'Lainnya')===$j?'selected':'' ?>><?= $j ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select></div>
|
||||
<div class="form-group"><label>Kontak / No Telp</label>
|
||||
<input name="kontak" value="<?= h($edit['kontak'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Radius Layanan (meter)</label>
|
||||
<input name="radius_meter" type="number" min="50" max="5000" value="<?= $edit['radius_meter'] ?? 300 ?>"></div>
|
||||
<div class="form-group"><label>Latitude *</label>
|
||||
<input name="latitude" type="number" step="any" required value="<?= $edit['latitude'] ?? '' ?>"></div>
|
||||
<div class="form-group"><label>Longitude *</label>
|
||||
<input name="longitude" type="number" step="any" required value="<?= $edit['longitude'] ?? '' ?>"></div>
|
||||
<div class="form-group full"><label>Alamat</label>
|
||||
<textarea name="alamat" rows="2"><?= h($edit['alamat'] ?? '') ?></textarea></div>
|
||||
<div class="form-group"><label>Kelurahan</label>
|
||||
<input name="kelurahan" value="<?= h($edit['kelurahan'] ?? '') ?>"></div>
|
||||
<div class="form-group"><label>Kecamatan</label>
|
||||
<input name="kecamatan" value="<?= h($edit['kecamatan'] ?? '') ?>"></div>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" name="save" class="btn btn-primary"><?= $edit ? '💾 Update' : '➕ Simpan' ?></button>
|
||||
<?php if ($edit): ?><a href="rumah_ibadah.php" class="btn btn-secondary">Batal</a><?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr><th>Nama</th><th>Jenis</th><th>Alamat</th><th>Radius</th><th>KK</th><?= $canEdit?'<th>Aksi</th>':'' ?></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td><strong><?= h($r['nama']) ?></strong><br><span style="font-size:10px;color:#94a3b8"><?= h($r['kontak']??'') ?></span></td>
|
||||
<td style="font-size:12px"><?= h($r['jenis']) ?></td>
|
||||
<td style="font-size:11px"><?= h($r['kelurahan']??'') ?><?= $r['kecamatan'] ? ', '.$r['kecamatan'] : '' ?></td>
|
||||
<td><?= $r['radius_meter'] ?>m</td>
|
||||
<td><?= $r['jml_kk'] ?> KK</td>
|
||||
<?php if ($canEdit): ?>
|
||||
<td class="actions">
|
||||
<a href="?edit=<?= $r['id'] ?>" class="btn btn-secondary btn-sm">✏️</a>
|
||||
<a href="?delete=<?= $r['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('Hapus rumah ibadah ini?')">🗑️</a>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows)): ?><tr><td colspan="6" style="text-align:center;padding:24px;color:#94a3b8">Belum ada data</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login(['admin']);
|
||||
|
||||
$msg = ''; $err = '';
|
||||
|
||||
// TOGGLE AKTIF
|
||||
if (isset($_GET['toggle'])) {
|
||||
$id = (int)$_GET['toggle'];
|
||||
if ($id !== (int)$_SESSION['user_id']) {
|
||||
try {
|
||||
$pdo->prepare("UPDATE users SET aktif = 1 - aktif WHERE id=:id")->execute([':id'=>$id]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'UPDATE', 'users', (string)$id);
|
||||
$msg = "Status user #$id diperbarui.";
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
} else { $err = 'Tidak dapat mengubah status akun sendiri.'; }
|
||||
}
|
||||
|
||||
// RESET PASSWORD
|
||||
if (isset($_POST['reset_pw'])) {
|
||||
$id = (int)($_POST['user_id'] ?? 0);
|
||||
$pw = $_POST['new_password'] ?? '';
|
||||
if (strlen($pw) < 6) $err = 'Password minimal 6 karakter.';
|
||||
else {
|
||||
$hash = password_hash($pw, PASSWORD_BCRYPT);
|
||||
$pdo->prepare("UPDATE users SET password_hash=:h WHERE id=:id")->execute([':h'=>$hash,':id'=>$id]);
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'UPDATE', 'users', (string)$id, null, ['action'=>'reset_password']);
|
||||
$msg = "Password user #$id berhasil direset.";
|
||||
}
|
||||
}
|
||||
|
||||
// SAVE USER BARU
|
||||
if (isset($_POST['save'])) {
|
||||
$uname = trim($_POST['username'] ?? '');
|
||||
$nama = trim($_POST['nama'] ?? '');
|
||||
$pw = $_POST['password'] ?? '';
|
||||
$idEdit = (int)($_POST['id_edit'] ?? 0);
|
||||
if (strlen($uname) < 3) $err = 'Username minimal 3 karakter.';
|
||||
elseif (strlen($nama) < 3) $err = 'Nama minimal 3 karakter.';
|
||||
elseif (!$idEdit && strlen($pw) < 6) $err = 'Password minimal 6 karakter.';
|
||||
else {
|
||||
try {
|
||||
if ($idEdit) {
|
||||
$pdo->prepare("UPDATE users SET nama=:nama,role_id=:rid,email=:em,no_telp=:tlp,rumah_ibadah_id=:ri WHERE id=:id")
|
||||
->execute([':nama'=>$nama,':rid'=>(int)$_POST['role_id'],':em'=>$_POST['email']??'' ?: null,
|
||||
':tlp'=>$_POST['no_telp']??'' ?: null,':ri'=>$_POST['rumah_ibadah_id']??'' ?: null,':id'=>$idEdit]);
|
||||
$msg = "User $nama diperbarui.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'UPDATE', 'users', (string)$idEdit);
|
||||
} else {
|
||||
$hash = password_hash($pw, PASSWORD_BCRYPT);
|
||||
$pdo->prepare("INSERT INTO users (role_id,nama,username,email,password_hash,no_telp,rumah_ibadah_id)
|
||||
VALUES(:rid,:nama,:uname,:em,:hash,:tlp,:ri)")
|
||||
->execute([':rid'=>(int)$_POST['role_id'],':nama'=>$nama,':uname'=>$uname,
|
||||
':em'=>$_POST['email']??'' ?: null,':hash'=>$hash,
|
||||
':tlp'=>$_POST['no_telp']??'' ?: null,':ri'=>$_POST['rumah_ibadah_id']??'' ?: null]);
|
||||
$msg = "User $uname berhasil dibuat.";
|
||||
logAktivitas($pdo, (int)$_SESSION['user_id'], 'CREATE', 'users', (string)$pdo->lastInsertId());
|
||||
}
|
||||
} catch(Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$s = $pdo->prepare("SELECT * FROM users WHERE id=:id");
|
||||
$s->execute([':id'=>(int)$_GET['edit']]);
|
||||
$edit = $s->fetch() ?: null;
|
||||
}
|
||||
|
||||
$rows = $pdo->query("
|
||||
SELECT u.*, r.nama AS role_nama, ri.nama AS nama_ri
|
||||
FROM users u
|
||||
JOIN roles r ON r.id=u.role_id
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id
|
||||
ORDER BY u.role_id, u.nama
|
||||
")->fetchAll();
|
||||
|
||||
$roles = $pdo->query("SELECT * FROM roles ORDER BY id")->fetchAll();
|
||||
$riList = $pdo->query("SELECT id,nama FROM rumah_ibadah ORDER BY nama")->fetchAll();
|
||||
|
||||
render_header('Manajemen User');
|
||||
?>
|
||||
<div class="page-title">👥 Manajemen User</div>
|
||||
|
||||
<?php if ($msg): ?><div class="notice" style="border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1.8fr;gap:16px">
|
||||
<div>
|
||||
<div class="card">
|
||||
<strong><?= $edit ? '✏️ Edit User' : '➕ Tambah User' ?></strong><br><br>
|
||||
<form method="POST">
|
||||
<?php if ($edit): ?><input type="hidden" name="id_edit" value="<?= $edit['id'] ?>"><?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Username <?= $edit ? '' : '*' ?></label>
|
||||
<input name="username" <?= $edit ? 'disabled value="'.h($edit['username'] ?? '').'"' : 'required' ?>></div>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Nama Lengkap *</label>
|
||||
<input name="nama" required value="<?= h($edit['nama'] ?? '') ?>"></div>
|
||||
<?php if (!$edit): ?>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Password * (min 6 karakter)</label>
|
||||
<input name="password" type="password" required minlength="6"></div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Role</label>
|
||||
<select name="role_id">
|
||||
<?php foreach ($roles as $r): ?>
|
||||
<option value="<?= $r['id'] ?>" <?= ($edit['role_id']??4)==$r['id']?'selected':'' ?>><?= h($r['label']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select></div>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Email</label>
|
||||
<input name="email" type="email" value="<?= h($edit['email'] ?? '') ?>"></div>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>No Telp</label>
|
||||
<input name="no_telp" value="<?= h($edit['no_telp'] ?? '') ?>"></div>
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Rumah Ibadah (untuk Pengurus)</label>
|
||||
<select name="rumah_ibadah_id">
|
||||
<option value="">— Tidak ada —</option>
|
||||
<?php foreach ($riList as $ri): ?>
|
||||
<option value="<?= $ri['id'] ?>" <?= ($edit['rumah_ibadah_id']??'')==$ri['id']?'selected':'' ?>><?= h($ri['nama']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select></div>
|
||||
<br>
|
||||
<button type="submit" name="save" class="btn btn-primary"><?= $edit ? '💾 Update' : '➕ Buat' ?></button>
|
||||
<?php if ($edit): ?><a href="users.php" class="btn btn-secondary">Batal</a><?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ($edit): ?>
|
||||
<div class="card" style="margin-top:0">
|
||||
<strong>🔑 Reset Password</strong><br><br>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="user_id" value="<?= $edit['id'] ?>">
|
||||
<div class="form-group" style="margin-bottom:10px"><label>Password Baru *</label>
|
||||
<input name="new_password" type="password" required minlength="6" placeholder="min 6 karakter"></div>
|
||||
<button type="submit" name="reset_pw" class="btn btn-danger">🔑 Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<table>
|
||||
<thead><tr><th>Nama</th><th>Username</th><th>Role</th><th>Rumah Ibadah</th><th>Status</th><th>Aksi</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr <?= !$r['aktif'] ? 'style="opacity:.5"' : '' ?>>
|
||||
<td><strong><?= h($r['nama']) ?></strong><br><span style="font-size:10px;color:#94a3b8"><?= h($r['email']??'') ?></span></td>
|
||||
<td style="font-size:12px"><?= h($r['username']) ?></td>
|
||||
<td><span class="tag <?= $r['role_nama']==='admin'?'darurat':($r['role_nama']==='pengurus'?'diproses':'pending') ?>"><?= h($r['role_nama']) ?></span></td>
|
||||
<td style="font-size:11px"><?= h($r['nama_ri'] ?? '-') ?></td>
|
||||
<td><?= $r['aktif'] ? '<span class="tag selesai">Aktif</span>' : '<span class="tag darurat">Nonaktif</span>' ?></td>
|
||||
<td class="actions">
|
||||
<a href="?edit=<?= $r['id'] ?>" class="btn btn-secondary btn-sm">✏️</a>
|
||||
<?php if ($r['id'] != $_SESSION['user_id']): ?>
|
||||
<a href="?toggle=<?= $r['id'] ?>" class="btn <?= $r['aktif']?'btn-danger':'btn-success' ?> btn-sm" onclick="return confirm('Ubah status user ini?')">
|
||||
<?= $r['aktif'] ? '🚫' : '✅' ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Autentikasi
|
||||
// POST ?action=login
|
||||
// POST ?action=logout
|
||||
// GET ?action=me
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
if ($method === 'POST' && $action === 'login') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$username = sanitizeStr($body['username'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
|
||||
if (!$username || !$password) {
|
||||
jsonResponse(false, 'Username dan password wajib diisi.', [], 400);
|
||||
}
|
||||
|
||||
$result = doLogin($pdo, $username, $password);
|
||||
if (!$result['success']) {
|
||||
jsonResponse(false, $result['message'], [], 401);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Login berhasil.', [
|
||||
'role' => $result['role'],
|
||||
'user_id' => $_SESSION['user_id'],
|
||||
'nama' => $_SESSION['user_nama'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $action === 'logout') {
|
||||
doLogout($pdo);
|
||||
jsonResponse(true, 'Logout berhasil.');
|
||||
}
|
||||
|
||||
if ($method === 'GET' && $action === 'me') {
|
||||
$user = getApiUser($pdo);
|
||||
if (!$user) jsonResponse(false, 'Belum login.', [], 401);
|
||||
$detail = $pdo->prepare("SELECT u.id, u.nama, u.username, u.email, r.nama AS role, u.rumah_ibadah_id, ri.nama AS nama_ri FROM users u JOIN roles r ON r.id=u.role_id LEFT JOIN rumah_ibadah ri ON ri.id=u.rumah_ibadah_id WHERE u.id=:id");
|
||||
$detail->execute([':id' => $user['id']]);
|
||||
jsonResponse(true, 'OK', ['data' => $detail->fetch()]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Bantuan & Histori Bantuan
|
||||
// GET ?action=jenis|histori&nik=...
|
||||
// POST action=salurkan
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'jenis') {
|
||||
$rows = $pdo->query("SELECT * FROM jenis_bantuan WHERE aktif=1 ORDER BY kategori, nama")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
|
||||
if ($action === 'histori') {
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
|
||||
if ($nik && validateNIK($nik)) {
|
||||
// Histori per penduduk
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT hb.*, jb.nama AS nama_bantuan, jb.kategori, jb.satuan,
|
||||
ri.nama AS nama_rumah_ibadah, u.nama AS disalurkan_oleh_nama
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
LEFT JOIN users u ON u.id = hb.disalurkan_oleh
|
||||
WHERE hb.nik = :nik
|
||||
ORDER BY hb.tanggal DESC
|
||||
");
|
||||
$stmt->execute([':nik' => $nik]);
|
||||
$data = $stmt->fetchAll();
|
||||
|
||||
// Ringkasan
|
||||
$summary = [
|
||||
'total_bantuan' => count($data),
|
||||
'kategori' => [],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$summary['kategori'][$row['kategori']] = ($summary['kategori'][$row['kategori']] ?? 0) + 1;
|
||||
}
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $data, 'summary' => $summary]);
|
||||
}
|
||||
|
||||
// List semua histori (admin/pengurus)
|
||||
if (!canViewAll($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($_GET['bulan'])) { $where[] = 'MONTH(hb.tanggal) = :bln'; $params[':bln'] = (int)$_GET['bulan']; }
|
||||
if (!empty($_GET['tahun'])) { $where[] = 'YEAR(hb.tanggal) = :thn'; $params[':thn'] = (int)$_GET['tahun']; }
|
||||
if (!empty($_GET['kategori'])) { $where[] = 'jb.kategori = :kat'; $params[':kat'] = $_GET['kategori']; }
|
||||
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT hb.id, hb.nik, p.nama_lengkap, hb.tanggal,
|
||||
jb.nama AS nama_bantuan, jb.kategori,
|
||||
hb.jumlah, hb.nilai_rupiah, hb.status_penyaluran,
|
||||
ri.nama AS nama_ri
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik = hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
$whereStr
|
||||
ORDER BY hb.tanggal DESC
|
||||
LIMIT 1000
|
||||
");
|
||||
$stmt->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'belum_dibantu') {
|
||||
// Warga miskin yang BELUM pernah menerima bantuan
|
||||
$rows = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.pekerjaan, p.alamat, p.latitude, p.longitude,
|
||||
k.nama_kk, k.no_kk, ri.nama AS nama_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
WHERE p.status_hidup = 'hidup'
|
||||
AND p.status_ekonomi IN ('miskin', 'rentan')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM histori_bantuan hb
|
||||
WHERE hb.nik = p.nik AND hb.status_penyaluran = 'disalurkan'
|
||||
)
|
||||
ORDER BY
|
||||
CASE p.status_ekonomi WHEN 'miskin' THEN 1 ELSE 2 END,
|
||||
CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 WHEN 'disabilitas' THEN 2 ELSE 3 END
|
||||
")->fetchAll();
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
if ($action === 'dashboard_stats') {
|
||||
// Statistik untuk dashboard
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='miskin' AND status_hidup='hidup') AS total_miskin,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='rentan' AND status_hidup='hidup') AS total_rentan,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_ekonomi='mampu' AND status_hidup='hidup') AS total_mampu,
|
||||
(SELECT COUNT(*) FROM keluarga) AS total_keluarga,
|
||||
(SELECT COUNT(*) FROM rumah_ibadah) AS total_rumah_ibadah,
|
||||
(SELECT COUNT(*) FROM laporan WHERE status='pending') AS laporan_pending,
|
||||
(SELECT COUNT(*) FROM laporan WHERE status='diproses') AS laporan_diproses,
|
||||
(SELECT COUNT(*) FROM histori_bantuan WHERE MONTH(tanggal)=MONTH(CURDATE()) AND status_penyaluran='disalurkan') AS bantuan_bulan_ini,
|
||||
(SELECT COUNT(*) FROM penduduk WHERE status_hidup='hidup' AND status_ekonomi IN ('miskin','rentan')
|
||||
AND NOT EXISTS (SELECT 1 FROM histori_bantuan hb WHERE hb.nik=penduduk.nik AND hb.status_penyaluran='disalurkan')
|
||||
) AS belum_pernah_dibantu
|
||||
")->fetch();
|
||||
|
||||
// Bantuan per kategori bulan ini
|
||||
$per_kategori = $pdo->query("
|
||||
SELECT jb.kategori, COUNT(*) AS jumlah
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
WHERE MONTH(hb.tanggal) = MONTH(CURDATE())
|
||||
AND hb.status_penyaluran = 'disalurkan'
|
||||
GROUP BY jb.kategori
|
||||
")->fetchAll();
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $stats, 'per_kategori' => $per_kategori]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST: salurkan bantuan
|
||||
// ============================================================
|
||||
if ($method === 'POST' && $action === 'salurkan') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$nik = sanitizeStr($body['nik'] ?? '');
|
||||
$idJenis = (int)($body['id_jenis_bantuan'] ?? 0);
|
||||
$tanggal = sanitizeStr($body['tanggal'] ?? date('Y-m-d'));
|
||||
$jumlah = sanitizeStr($body['jumlah'] ?? '');
|
||||
$status = sanitizeStr($body['status_penyaluran'] ?? 'dijadwalkan');
|
||||
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
if ($idJenis <= 0) jsonResponse(false, 'Jenis bantuan harus dipilih.', [], 400);
|
||||
if (!strtotime($tanggal)) jsonResponse(false, 'Format tanggal tidak valid.', [], 400);
|
||||
|
||||
// Cek penduduk ada
|
||||
$cek = $pdo->prepare("SELECT nik, id_keluarga FROM penduduk WHERE nik = :nik AND status_hidup='hidup'");
|
||||
$cek->execute([':nik' => $nik]);
|
||||
$pddk = $cek->fetch();
|
||||
if (!$pddk) jsonResponse(false, 'Penduduk tidak ditemukan atau sudah meninggal.', [], 404);
|
||||
|
||||
// Cek jenis bantuan ada
|
||||
$cekJb = $pdo->prepare("SELECT id FROM jenis_bantuan WHERE id = :id AND aktif=1");
|
||||
$cekJb->execute([':id' => $idJenis]);
|
||||
if (!$cekJb->fetch()) jsonResponse(false, 'Jenis bantuan tidak ditemukan.', [], 404);
|
||||
|
||||
$buktiNama = null;
|
||||
if (!empty($_FILES['bukti']['name'])) {
|
||||
try { $buktiNama = uploadFile($_FILES['bukti'], UPLOAD_BUKTI); }
|
||||
catch (RuntimeException $e) { jsonResponse(false, $e->getMessage(), [], 400); }
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO histori_bantuan
|
||||
(nik, id_jenis_bantuan, id_keluarga, rumah_ibadah_id, tanggal,
|
||||
jumlah, nilai_rupiah, status_penyaluran, catatan, bukti_file, disalurkan_oleh)
|
||||
VALUES
|
||||
(:nik,:jb,:kk,:ri,:tgl,:jml,:rp,:status,:cat,:bukti,:user)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nik' => $nik,
|
||||
':jb' => $idJenis,
|
||||
':kk' => $pddk['id_keluarga'],
|
||||
':ri' => $currentUser['rumah_ibadah_id'],
|
||||
':tgl' => $tanggal,
|
||||
':jml' => $jumlah ?: null,
|
||||
':rp' => !empty($body['nilai_rupiah']) ? (float)$body['nilai_rupiah'] : null,
|
||||
':status' => in_array($status, ['dijadwalkan','disalurkan','dibatalkan']) ? $status : 'dijadwalkan',
|
||||
':cat' => sanitizeStr($body['catatan'] ?? '') ?: null,
|
||||
':bukti' => $buktiNama,
|
||||
':user' => $currentUser['id'],
|
||||
]);
|
||||
|
||||
$hbId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id'], 'CREATE', 'histori_bantuan', (string)$hbId, null, $body);
|
||||
|
||||
// Notifikasi ke pimpinan
|
||||
$pimpinan = $pdo->query("SELECT id FROM users WHERE role_id=3")->fetchAll();
|
||||
foreach ($pimpinan as $pm) {
|
||||
$pdo->prepare("INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id) VALUES (:u,:j,:i,'bantuan',:r)")
|
||||
->execute([':u' => $pm['id'], ':j' => 'Bantuan Disalurkan', ':i' => "Bantuan untuk NIK $nik telah dicatat.", ':r' => $hbId]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Bantuan berhasil dicatat.', ['id' => $hbId], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: update status penyaluran
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$status = sanitizeStr($body['status_penyaluran'] ?? '');
|
||||
|
||||
if (!in_array($status, ['dijadwalkan','disalurkan','dibatalkan'])) {
|
||||
jsonResponse(false, 'Status tidak valid.', [], 400);
|
||||
}
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM histori_bantuan WHERE id = :id");
|
||||
$old->execute([':id' => $id]);
|
||||
if (!$old->fetch()) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$pdo->prepare("UPDATE histori_bantuan SET status_penyaluran=:s WHERE id=:id")
|
||||
->execute([':s' => $status, ':id' => $id]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE', 'histori_bantuan', (string)$id);
|
||||
jsonResponse(true, 'Status penyaluran diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Keluarga
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($_GET['status_ekonomi'])) {
|
||||
$where[] = 'k.status_ekonomi = :se';
|
||||
$params[':se'] = $_GET['status_ekonomi'];
|
||||
}
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$rows = $pdo->prepare("
|
||||
SELECT k.*,
|
||||
ri.nama AS nama_ri, ri.jenis AS jenis_ri,
|
||||
COUNT(p.nik) AS jumlah_anggota_db,
|
||||
SUM(CASE WHEN p.kondisi_kesehatan IN ('sakit_parah','disabilitas') THEN 1 ELSE 0 END) AS anggota_sakit
|
||||
FROM keluarga k
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
LEFT JOIN penduduk p ON p.id_keluarga = k.id AND p.status_hidup='hidup'
|
||||
$whereStr
|
||||
GROUP BY k.id
|
||||
ORDER BY CASE k.status_ekonomi WHEN 'miskin' THEN 1 WHEN 'rentan' THEN 2 ELSE 3 END ASC
|
||||
");
|
||||
$rows->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $rows->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("SELECT k.*, ri.nama AS nama_ri FROM keluarga k LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id WHERE k.id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$kk = $stmt->fetch();
|
||||
if (!$kk) jsonResponse(false, 'Keluarga tidak ditemukan.', [], 404);
|
||||
|
||||
$anggota = $pdo->prepare("SELECT * FROM penduduk WHERE id_keluarga=:id ORDER BY CASE status_keluarga WHEN 'kepala_keluarga' THEN 1 ELSE 2 END, tanggal_lahir");
|
||||
$anggota->execute([':id' => $id]);
|
||||
|
||||
$histori = $pdo->prepare("
|
||||
SELECT hb.tanggal, jb.nama AS bantuan, hb.jumlah, hb.status_penyaluran, p.nama_lengkap
|
||||
FROM histori_bantuan hb
|
||||
JOIN penduduk p ON p.nik=hb.nik
|
||||
JOIN jenis_bantuan jb ON jb.id=hb.id_jenis_bantuan
|
||||
WHERE hb.id_keluarga=:id
|
||||
ORDER BY hb.tanggal DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$histori->execute([':id' => $id]);
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $kk, 'anggota' => $anggota->fetchAll(), 'histori' => $histori->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT k.id, k.no_kk, k.nama_kk, k.status_ekonomi, k.latitude, k.longitude,
|
||||
COUNT(p.nik) AS jml_anggota, ri.nama AS nama_ri
|
||||
FROM keluarga k
|
||||
LEFT JOIN penduduk p ON p.id_keluarga=k.id AND p.status_hidup='hidup'
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id=k.rumah_ibadah_id
|
||||
WHERE k.latitude IS NOT NULL AND k.longitude IS NOT NULL
|
||||
GROUP BY k.id
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['longitude'],(float)$r['latitude']]],
|
||||
'properties' => array_merge($r, [
|
||||
'color' => match($r['status_ekonomi']){'miskin'=>'#ef4444','rentan'=>'#f59e0b',default=>'#22c55e'},
|
||||
'layer' => 'keluarga'
|
||||
])
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type'=>'FeatureCollection','features'=>$features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$noKk = sanitizeStr($body['no_kk'] ?? '');
|
||||
$namaKk = sanitizeStr($body['nama_kk'] ?? '');
|
||||
if (strlen($noKk) !== 16) jsonResponse(false, 'No KK harus 16 digit.', [], 400);
|
||||
if (strlen($namaKk) < 3) jsonResponse(false, 'Nama KK terlalu pendek.', [], 400);
|
||||
|
||||
$cek = $pdo->prepare("SELECT id FROM keluarga WHERE no_kk=:nkk");
|
||||
$cek->execute([':nkk' => $noKk]);
|
||||
if ($cek->fetch()) jsonResponse(false, 'No KK sudah terdaftar.', [], 409);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO keluarga (no_kk, nama_kk, alamat, kelurahan, kecamatan, kota, latitude, longitude, status_ekonomi, rumah_ibadah_id)
|
||||
VALUES (:nkk,:nkk_nama,:almt,:kel,:kec,:kota,:lat,:lng,:se,:ri)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nkk' => $noKk,
|
||||
':nkk_nama' => $namaKk,
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
':kota' => sanitizeStr($body['kota'] ?? '') ?: null,
|
||||
':lat' => !empty($body['latitude']) ? (float)$body['latitude'] : null,
|
||||
':lng' => !empty($body['longitude']) ? (float)$body['longitude'] : null,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':ri' => !empty($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : null,
|
||||
]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'CREATE', 'keluarga', (string)$newId, null, $body);
|
||||
jsonResponse(true, 'Keluarga berhasil ditambahkan.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
if ($id <= 0) jsonResponse(false, 'ID tidak valid.', [], 400);
|
||||
|
||||
if ($action === 'update_koordinat') {
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat tidak valid.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE keluarga SET latitude=:lat, longitude=:lng WHERE id=:id")
|
||||
->execute([':lat'=>$lat, ':lng'=>$lng, ':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'keluarga', (string)$id, null, ['lat'=>$lat,'lng'=>$lng]);
|
||||
jsonResponse(true, 'Koordinat diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
$pdo->prepare("
|
||||
UPDATE keluarga SET
|
||||
nama_kk=:nkk, alamat=:almt, kelurahan=:kel, kecamatan=:kec,
|
||||
kota=:kota, latitude=:lat, longitude=:lng,
|
||||
status_ekonomi=:se, rumah_ibadah_id=:ri
|
||||
WHERE id=:id
|
||||
")->execute([
|
||||
':nkk' => sanitizeStr($body['nama_kk'] ?? ''),
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
':kota' => sanitizeStr($body['kota'] ?? '') ?: null,
|
||||
':lat' => !empty($body['latitude']) ? (float)$body['latitude'] : null,
|
||||
':lng' => !empty($body['longitude']) ? (float)$body['longitude'] : null,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':ri' => !empty($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : null,
|
||||
':id' => $id,
|
||||
]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'keluarga', (string)$id);
|
||||
jsonResponse(true, 'Keluarga diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin yang bisa menghapus keluarga.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
// Cek apakah masih ada anggota
|
||||
$jml = (int)$pdo->prepare("SELECT COUNT(*) FROM penduduk WHERE id_keluarga=:id")->execute([':id'=>$id]);
|
||||
if ($jml > 0) jsonResponse(false, 'Tidak bisa dihapus: masih ada anggota keluarga.', [], 409);
|
||||
$pdo->prepare("DELETE FROM keluarga WHERE id=:id")->execute([':id' => $id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'DELETE', 'keluarga', (string)$id);
|
||||
jsonResponse(true, 'Keluarga dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Laporan Masyarakat
|
||||
// GET ?action=list|detail&id=...
|
||||
// POST action=create (multipart untuk foto)
|
||||
// PUT action=update_status&id=...
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET: list laporan
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status'])) {
|
||||
$where[] = 'l.status = :status';
|
||||
$params[':status'] = $_GET['status'];
|
||||
}
|
||||
if (!empty($_GET['urgensi'])) {
|
||||
$where[] = 'l.tingkat_urgensi = :urgensi';
|
||||
$params[':urgensi'] = $_GET['urgensi'];
|
||||
}
|
||||
|
||||
$whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$rows = $pdo->prepare("
|
||||
SELECT l.*,
|
||||
p.nama_lengkap AS nama_terdampak,
|
||||
u.nama AS nama_verifikator
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik = l.nik_terdampak
|
||||
LEFT JOIN users u ON u.id = l.diverifikasi_oleh
|
||||
$whereStr
|
||||
ORDER BY
|
||||
CASE l.tingkat_urgensi WHEN 'darurat' THEN 1 WHEN 'tinggi' THEN 2 WHEN 'sedang' THEN 3 ELSE 4 END ASC,
|
||||
CASE l.status WHEN 'pending' THEN 1 WHEN 'diverifikasi' THEN 2 ELSE 3 END ASC,
|
||||
l.created_at DESC
|
||||
LIMIT 500
|
||||
");
|
||||
$rows->execute($params);
|
||||
jsonResponse(true, 'OK', ['data' => $rows->fetchAll(), 'total' => $rows->rowCount()]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT l.*, p.nama_lengkap AS nama_terdampak, p.status_ekonomi,
|
||||
p.kondisi_kesehatan, p.alamat AS alamat_penduduk
|
||||
FROM laporan l
|
||||
LEFT JOIN penduduk p ON p.nik = l.nik_terdampak
|
||||
WHERE l.id = :id
|
||||
");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$data = $stmt->fetch();
|
||||
if (!$data) jsonResponse(false, 'Laporan tidak ditemukan.', [], 404);
|
||||
jsonResponse(true, 'OK', ['data' => $data]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
// Untuk layer marker laporan di peta
|
||||
$stmt = $pdo->query("
|
||||
SELECT id, nama_pelapor, pelapor_id, kategori, tingkat_urgensi, status,
|
||||
latitude, longitude, alamat, created_at
|
||||
FROM laporan
|
||||
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
|
||||
AND status NOT IN ('selesai','ditolak')
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$color = match($r['tingkat_urgensi']) {
|
||||
'darurat' => '#dc2626',
|
||||
'tinggi' => '#ea580c',
|
||||
'sedang' => '#ca8a04',
|
||||
default => '#2563eb',
|
||||
};
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type' => 'Point', 'coordinates' => [(float)$r['longitude'], (float)$r['latitude']]],
|
||||
'properties' => array_merge($r, ['color' => $color, 'layer' => 'laporan'])
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type' => 'FeatureCollection', 'features' => $features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'stats') {
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
SUM(status='pending') AS pending,
|
||||
SUM(status='diverifikasi') AS diverifikasi,
|
||||
SUM(status='diproses') AS diproses,
|
||||
SUM(status='selesai') AS selesai,
|
||||
SUM(tingkat_urgensi='darurat') AS darurat,
|
||||
COUNT(*) AS total
|
||||
FROM laporan
|
||||
")->fetch();
|
||||
jsonResponse(true, 'OK', ['data' => $stats]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST: buat laporan baru (bisa dilakukan masyarakat umum)
|
||||
// ============================================================
|
||||
if ($method === 'POST') {
|
||||
$deskripsi = sanitizeStr($_POST['deskripsi'] ?? '', 2000);
|
||||
$kategori = sanitizeStr($_POST['kategori'] ?? 'butuh_bantuan_lain');
|
||||
$urgensi = sanitizeStr($_POST['tingkat_urgensi'] ?? 'sedang');
|
||||
$lat = !empty($_POST['latitude']) ? (float)$_POST['latitude'] : null;
|
||||
$lng = !empty($_POST['longitude']) ? (float)$_POST['longitude'] : null;
|
||||
|
||||
if (strlen($deskripsi) < 10) jsonResponse(false, 'Deskripsi laporan terlalu singkat.', [], 400);
|
||||
|
||||
$fotoNama = null;
|
||||
if (!empty($_FILES['foto']['name'])) {
|
||||
try {
|
||||
$fotoNama = uploadFile($_FILES['foto'], UPLOAD_LAPORAN, ALLOWED_IMG_EXT);
|
||||
} catch (RuntimeException $e) {
|
||||
jsonResponse(false, $e->getMessage(), [], 400);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO laporan
|
||||
(nama_pelapor, pelapor_id, nik_terdampak, deskripsi, kategori, tingkat_urgensi,
|
||||
latitude, longitude, alamat, foto, status)
|
||||
VALUES
|
||||
(:np, :pid, :nik, :dsk, :kat, :urg, :lat, :lng, :almt, :foto, 'pending')
|
||||
");
|
||||
$stmt->execute([
|
||||
':np' => sanitizeStr($_POST['nama_pelapor'] ?? '') ?: null,
|
||||
':pid' => $currentUser['id'] ?? null,
|
||||
':nik' => sanitizeStr($_POST['nik_terdampak'] ?? '') ?: null,
|
||||
':dsk' => $deskripsi,
|
||||
':kat' => $kategori,
|
||||
':urg' => $urgensi,
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':almt' => sanitizeStr($_POST['alamat'] ?? '') ?: null,
|
||||
':foto' => $fotoNama,
|
||||
]);
|
||||
|
||||
$laporanId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Kirim notifikasi ke pengurus jika darurat
|
||||
if (in_array($urgensi, ['darurat', 'tinggi'])) {
|
||||
$pengurus = $pdo->query("SELECT id FROM users WHERE role_id = 2")->fetchAll();
|
||||
foreach ($pengurus as $p) {
|
||||
$pdo->prepare("
|
||||
INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id)
|
||||
VALUES (:uid, :judul, :isi, 'laporan', :ref)
|
||||
")->execute([
|
||||
':uid' => $p['id'],
|
||||
':judul' => '🚨 Laporan ' . strtoupper($urgensi) . ' masuk!',
|
||||
':isi' => "Ada laporan baru kategori '$kategori'. Segera ditangani.",
|
||||
':ref' => $laporanId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'LAPORAN', 'laporan', (string)$laporanId);
|
||||
|
||||
jsonResponse(true, 'Laporan berhasil dikirim. Terima kasih!', ['id' => $laporanId], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: Update status laporan (hanya pengurus/admin)
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canVerify($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if ($id <= 0) jsonResponse(false, 'ID tidak valid.', [], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$status = sanitizeStr($body['status'] ?? '');
|
||||
$valid = ['diverifikasi', 'diproses', 'selesai', 'ditolak'];
|
||||
|
||||
if (!in_array($status, $valid)) jsonResponse(false, 'Status tidak valid.', [], 400);
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM laporan WHERE id = :id");
|
||||
$old->execute([':id' => $id]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Laporan tidak ditemukan.', [], 404);
|
||||
|
||||
$sets = ['status = :status', 'updated_at = NOW()'];
|
||||
$params = [':id' => $id, ':status' => $status];
|
||||
|
||||
if ($status === 'diverifikasi' && $currentUser) {
|
||||
$sets[] = 'diverifikasi_oleh = :verif';
|
||||
$params[':verif'] = $currentUser['id'];
|
||||
}
|
||||
if ($status === 'diproses' && $currentUser) {
|
||||
$sets[] = 'ditangani_oleh = :tangani';
|
||||
$params[':tangani'] = $currentUser['id'];
|
||||
}
|
||||
if ($status === 'selesai') {
|
||||
$sets[] = 'tanggal_selesai = NOW()';
|
||||
}
|
||||
if (!empty($body['catatan_verifikasi'])) {
|
||||
$sets[] = 'catatan_verifikasi = :catatan';
|
||||
$params[':catatan'] = sanitizeStr($body['catatan_verifikasi'], 1000);
|
||||
}
|
||||
|
||||
$pdo->prepare("UPDATE laporan SET " . implode(', ', $sets) . " WHERE id = :id")->execute($params);
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE', 'laporan', (string)$id, $dataLama, $body);
|
||||
|
||||
jsonResponse(true, "Status laporan diperbarui menjadi '$status'.", ['id' => $id]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT: Update koordinat (via drag map)
|
||||
// ============================================================
|
||||
if ($method === 'PUT' && $action === 'update_koordinat') {
|
||||
if (!canVerify($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$lat = isset($body['latitude']) ? (float)$body['latitude'] : null;
|
||||
$lng = isset($body['longitude']) ? (float)$body['longitude'] : null;
|
||||
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Latitude dan Longitude wajib diisi.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE laporan SET latitude = :lat, longitude = :lng, updated_at = NOW() WHERE id = :id")->execute([
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'], 'UPDATE_KOORDINAT', 'laporan', (string)$id);
|
||||
jsonResponse(true, 'Koordinat laporan berhasil diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Penduduk
|
||||
// GET ?action=list|detail&nik=...
|
||||
// POST action=create
|
||||
// PUT action=update&nik=...
|
||||
// DELETE action=delete&nik=...
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// ============================================================
|
||||
// GET
|
||||
// ============================================================
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
$where = ['p.status_hidup = "hidup"'];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status_ekonomi'])) {
|
||||
$where[] = 'p.status_ekonomi = :se';
|
||||
$params[':se'] = $_GET['status_ekonomi'];
|
||||
}
|
||||
if (!empty($_GET['id_keluarga'])) {
|
||||
$where[] = 'p.id_keluarga = :kk';
|
||||
$params[':kk'] = (int)$_GET['id_keluarga'];
|
||||
}
|
||||
if (!empty($_GET['belum_dibantu'])) {
|
||||
$where[] = 'NOT EXISTS (SELECT 1 FROM histori_bantuan hb WHERE hb.nik = p.nik AND hb.status_penyaluran="disalurkan")';
|
||||
}
|
||||
if (!empty($_GET['kondisi_kesehatan'])) {
|
||||
$where[] = 'p.kondisi_kesehatan = :kk2';
|
||||
$params[':kk2'] = $_GET['kondisi_kesehatan'];
|
||||
}
|
||||
if (!empty($_GET['search'])) {
|
||||
$where[] = '(p.nama_lengkap LIKE :q OR p.nik LIKE :q2)';
|
||||
$params[':q'] = '%' . $_GET['search'] . '%';
|
||||
$params[':q2'] = '%' . $_GET['search'] . '%';
|
||||
}
|
||||
|
||||
$whereStr = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
p.nik, p.nama_lengkap, p.jenis_kelamin, p.tanggal_lahir,
|
||||
p.umur, p.status_keluarga, p.status_perkawinan,
|
||||
p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.pekerjaan, p.penghasilan,
|
||||
p.pendidikan_terakhir, p.status_pendidikan,
|
||||
p.alamat, p.latitude, p.longitude, p.foto,
|
||||
p.id_keluarga,
|
||||
k.no_kk, k.nama_kk, k.status_ekonomi AS status_ekonomi_kk,
|
||||
ri.nama AS nama_rumah_ibadah, ri.jenis AS jenis_rumah_ibadah,
|
||||
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.nik = p.nik AND hb.status_penyaluran='disalurkan') AS jumlah_bantuan_diterima,
|
||||
(SELECT MAX(hb2.tanggal) FROM histori_bantuan hb2 WHERE hb2.nik = p.nik AND hb2.status_penyaluran='disalurkan') AS terakhir_dibantu
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
$whereStr
|
||||
ORDER BY
|
||||
CASE p.status_ekonomi WHEN 'miskin' THEN 1 WHEN 'rentan' THEN 2 ELSE 3 END ASC,
|
||||
CASE p.kondisi_kesehatan WHEN 'sakit_parah' THEN 1 WHEN 'disabilitas' THEN 2 WHEN 'sakit_ringan' THEN 3 ELSE 4 END ASC
|
||||
LIMIT 1000
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
// Hitung prioritas untuk setiap penduduk
|
||||
foreach ($rows as &$r) {
|
||||
$belumDibantu = (int)$r['jumlah_bantuan_diterima'] === 0;
|
||||
$r['prioritas_bantuan'] = hitungPrioritasBantuan($r['status_ekonomi'], $r['kondisi_kesehatan'], $belumDibantu);
|
||||
}
|
||||
unset($r);
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
|
||||
if ($action === 'detail') {
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, k.no_kk, k.nama_kk, ri.nama AS nama_ri
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = k.rumah_ibadah_id
|
||||
WHERE p.nik = :nik
|
||||
");
|
||||
$stmt->execute([':nik' => $nik]);
|
||||
$penduduk = $stmt->fetch();
|
||||
if (!$penduduk) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
// Histori bantuan
|
||||
$stmtHB = $pdo->prepare("
|
||||
SELECT hb.*, jb.nama AS nama_bantuan, jb.kategori, ri.nama AS nama_ri
|
||||
FROM histori_bantuan hb
|
||||
JOIN jenis_bantuan jb ON jb.id = hb.id_jenis_bantuan
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = hb.rumah_ibadah_id
|
||||
WHERE hb.nik = :nik
|
||||
ORDER BY hb.tanggal DESC
|
||||
");
|
||||
$stmtHB->execute([':nik' => $nik]);
|
||||
$histori = $stmtHB->fetchAll();
|
||||
|
||||
// Pelatihan
|
||||
$stmtPl = $pdo->prepare("SELECT * FROM riwayat_pelatihan WHERE nik = :nik ORDER BY tanggal_mulai DESC");
|
||||
$stmtPl->execute([':nik' => $nik]);
|
||||
$pelatihan = $stmtPl->fetchAll();
|
||||
|
||||
// Validasi pendidikan
|
||||
$validasi = validasiPendidikanUmur(
|
||||
(int)$penduduk['umur'],
|
||||
$penduduk['pendidikan_terakhir'],
|
||||
$penduduk['status_pendidikan']
|
||||
);
|
||||
|
||||
$belumDibantu = empty($histori);
|
||||
$penduduk['prioritas_bantuan'] = hitungPrioritasBantuan($penduduk['status_ekonomi'], $penduduk['kondisi_kesehatan'], $belumDibantu);
|
||||
|
||||
jsonResponse(true, 'OK', [
|
||||
'data' => $penduduk,
|
||||
'histori' => $histori,
|
||||
'pelatihan' => $pelatihan,
|
||||
'validasi' => $validasi,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($action === 'geojson') {
|
||||
// Untuk layer Leaflet
|
||||
$stmt = $pdo->query("
|
||||
SELECT p.nik, p.nama_lengkap, p.status_ekonomi, p.kondisi_kesehatan,
|
||||
p.latitude, p.longitude,
|
||||
k.nama_kk,
|
||||
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.nik=p.nik AND hb.status_penyaluran='disalurkan') AS jml_bantuan
|
||||
FROM penduduk p
|
||||
LEFT JOIN keluarga k ON k.id = p.id_keluarga
|
||||
WHERE p.latitude IS NOT NULL AND p.longitude IS NOT NULL
|
||||
AND p.status_hidup = 'hidup'
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$color = match($r['status_ekonomi']) {
|
||||
'miskin' => '#ef4444',
|
||||
'rentan' => '#f59e0b',
|
||||
default => '#22c55e'
|
||||
};
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => [
|
||||
'type' => 'Point',
|
||||
'coordinates' => [(float)$r['longitude'], (float)$r['latitude']]
|
||||
],
|
||||
'properties' => array_merge($r, ['color' => $color])
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type' => 'FeatureCollection', 'features' => $features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST - Create
|
||||
// ============================================================
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$nik = sanitizeStr($body['nik'] ?? '');
|
||||
$nama = sanitizeStr($body['nama_lengkap'] ?? '');
|
||||
$jk = sanitizeStr($body['jenis_kelamin'] ?? '');
|
||||
$tgl = sanitizeStr($body['tanggal_lahir'] ?? '');
|
||||
$idKk = !empty($body['id_keluarga']) ? (int)$body['id_keluarga'] : null;
|
||||
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'Format NIK harus 16 digit angka.', [], 400);
|
||||
if (strlen($nama) < 3) jsonResponse(false, 'Nama terlalu pendek.', [], 400);
|
||||
if (!in_array($jk, ['L','P'])) jsonResponse(false, 'Jenis kelamin tidak valid.', [], 400);
|
||||
if (!strtotime($tgl)) jsonResponse(false, 'Format tanggal lahir tidak valid.', [], 400);
|
||||
|
||||
// Cek duplikat NIK
|
||||
$cek = $pdo->prepare("SELECT nik FROM penduduk WHERE nik = :nik");
|
||||
$cek->execute([':nik' => $nik]);
|
||||
if ($cek->fetch()) jsonResponse(false, 'NIK sudah terdaftar.', [], 409);
|
||||
|
||||
// Hitung umur untuk validasi pendidikan
|
||||
$umur = (int)(new DateTime())->diff(new DateTime($tgl))->y;
|
||||
$statusPendidikan = sanitizeStr($body['status_pendidikan'] ?? 'tidak_sekolah');
|
||||
$pendidikan = sanitizeStr($body['pendidikan_terakhir'] ?? 'tidak_sekolah');
|
||||
$validasi = validasiPendidikanUmur($umur, $pendidikan, $statusPendidikan);
|
||||
|
||||
$lat = !empty($body['latitude']) ? (float)$body['latitude'] : null;
|
||||
$lng = !empty($body['longitude']) ? (float)$body['longitude'] : null;
|
||||
if ($lat !== null && !validateCoord((string)$lat, 'lat')) jsonResponse(false, 'Latitude tidak valid.', [], 400);
|
||||
if ($lng !== null && !validateCoord((string)$lng, 'lng')) jsonResponse(false, 'Longitude tidak valid.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO penduduk
|
||||
(nik, id_keluarga, nama_lengkap, jenis_kelamin, tanggal_lahir,
|
||||
status_keluarga, status_perkawinan, status_hidup,
|
||||
pendidikan_terakhir, status_pendidikan,
|
||||
pekerjaan, penghasilan, status_ekonomi,
|
||||
kondisi_kesehatan, catatan_kesehatan,
|
||||
no_telp, alamat, latitude, longitude)
|
||||
VALUES
|
||||
(:nik,:kk,:nama,:jk,:tgl,
|
||||
:sk,:sp,:sh,
|
||||
:ptk,:spd,
|
||||
:pkr,:pgn,:se,
|
||||
:kkh,:ckkh,
|
||||
:tlp,:almt,:lat,:lng)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nik' => $nik,
|
||||
':kk' => $idKk,
|
||||
':nama' => $nama,
|
||||
':jk' => $jk,
|
||||
':tgl' => $tgl,
|
||||
':sk' => sanitizeStr($body['status_keluarga'] ?? 'anggota_lain'),
|
||||
':sp' => sanitizeStr($body['status_perkawinan'] ?? 'belum_kawin'),
|
||||
':sh' => sanitizeStr($body['status_hidup'] ?? 'hidup'),
|
||||
':ptk' => $pendidikan,
|
||||
':spd' => $statusPendidikan,
|
||||
':pkr' => sanitizeStr($body['pekerjaan'] ?? '') ?: null,
|
||||
':pgn' => !empty($body['penghasilan']) ? (float)$body['penghasilan'] : 0,
|
||||
':se' => sanitizeStr($body['status_ekonomi'] ?? 'rentan'),
|
||||
':kkh' => sanitizeStr($body['kondisi_kesehatan'] ?? 'sehat'),
|
||||
':ckkh' => sanitizeStr($body['catatan_kesehatan'] ?? '') ?: null,
|
||||
':tlp' => sanitizeStr($body['no_telp'] ?? '') ?: null,
|
||||
':almt' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
]);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'CREATE', 'penduduk', $nik, null, $body);
|
||||
|
||||
jsonResponse(true, 'Penduduk berhasil ditambahkan.', [
|
||||
'nik' => $nik,
|
||||
'validasi' => $validasi
|
||||
], 201);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUT - Update
|
||||
// ============================================================
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
// Ambil data lama
|
||||
$old = $pdo->prepare("SELECT * FROM penduduk WHERE nik = :nik");
|
||||
$old->execute([':nik' => $nik]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$sets = [];
|
||||
$params = [':nik' => $nik];
|
||||
|
||||
$fieldMap = [
|
||||
'id_keluarga' => ':kk', 'nama_lengkap' => ':nama', 'jenis_kelamin' => ':jk',
|
||||
'tanggal_lahir' => ':tgl', 'status_keluarga' => ':sk', 'status_perkawinan' => ':sp',
|
||||
'status_hidup' => ':sh', 'pendidikan_terakhir' => ':ptk', 'status_pendidikan' => ':spd',
|
||||
'pekerjaan' => ':pkr', 'penghasilan' => ':pgn', 'status_ekonomi' => ':se',
|
||||
'kondisi_kesehatan' => ':kkh', 'catatan_kesehatan' => ':ckkh',
|
||||
'no_telp' => ':tlp', 'alamat' => ':almt', 'latitude' => ':lat', 'longitude' => ':lng',
|
||||
];
|
||||
|
||||
foreach ($fieldMap as $field => $param) {
|
||||
if (array_key_exists($field, $body)) {
|
||||
$sets[] = "$field = $param";
|
||||
$params[$param] = $body[$field] === '' ? null : $body[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sets)) jsonResponse(false, 'Tidak ada data yang diubah.', [], 400);
|
||||
|
||||
$sql = "UPDATE penduduk SET " . implode(', ', $sets) . " WHERE nik = :nik";
|
||||
$pdo->prepare($sql)->execute($params);
|
||||
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'UPDATE', 'penduduk', $nik, $dataLama, $body);
|
||||
|
||||
jsonResponse(true, 'Data penduduk berhasil diperbarui.', ['nik' => $nik]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DELETE
|
||||
// ============================================================
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin yang bisa menghapus data.', [], 403);
|
||||
|
||||
$nik = sanitizeStr($_GET['nik'] ?? '');
|
||||
if (!validateNIK($nik)) jsonResponse(false, 'NIK tidak valid.', [], 400);
|
||||
|
||||
$old = $pdo->prepare("SELECT * FROM penduduk WHERE nik = :nik");
|
||||
$old->execute([':nik' => $nik]);
|
||||
$dataLama = $old->fetch();
|
||||
if (!$dataLama) jsonResponse(false, 'Data tidak ditemukan.', [], 404);
|
||||
|
||||
$pdo->prepare("DELETE FROM penduduk WHERE nik = :nik")->execute([':nik' => $nik]);
|
||||
logAktivitas($pdo, $currentUser['id'] ?? null, 'DELETE', 'penduduk', $nik, $dataLama, null);
|
||||
|
||||
jsonResponse(true, 'Data penduduk dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Pesan / Chat antar Pengurus Rumah Ibadah
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
// Harus login untuk fitur chat
|
||||
if (!$currentUser) jsonResponse(false, 'Harus login.', [], 401);
|
||||
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'inbox') {
|
||||
$riId = !empty($_GET['rumah_ibadah_id']) ? (int)$_GET['rumah_ibadah_id'] : null;
|
||||
|
||||
if ($riId) {
|
||||
// Chat diskusi per rumah ibadah
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, u.nama AS pengirim, u.role_id,
|
||||
ri.nama AS nama_ri
|
||||
FROM pesan p
|
||||
JOIN users u ON u.id = p.dari_user_id
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = p.ke_rumah_ibadah_id
|
||||
WHERE p.ke_rumah_ibadah_id = :ri
|
||||
ORDER BY p.created_at ASC
|
||||
LIMIT 200
|
||||
");
|
||||
$stmt->execute([':ri' => $riId]);
|
||||
} else {
|
||||
// Pesan langsung ke user
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, u.nama AS pengirim
|
||||
FROM pesan p
|
||||
JOIN users u ON u.id = p.dari_user_id
|
||||
WHERE p.ke_user_id = :uid OR p.dari_user_id = :uid2
|
||||
ORDER BY p.created_at ASC
|
||||
LIMIT 200
|
||||
");
|
||||
$stmt->execute([':uid' => $currentUser['id'], ':uid2' => $currentUser['id']]);
|
||||
}
|
||||
$pesan = $stmt->fetchAll();
|
||||
|
||||
// Tandai sudah dibaca
|
||||
if ($riId) {
|
||||
$pdo->prepare("UPDATE pesan SET dibaca=1 WHERE ke_rumah_ibadah_id=:ri AND dari_user_id != :uid")
|
||||
->execute([':ri' => $riId, ':uid' => $currentUser['id']]);
|
||||
} else {
|
||||
$pdo->prepare("UPDATE pesan SET dibaca=1 WHERE ke_user_id=:uid")
|
||||
->execute([':uid' => $currentUser['id']]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'OK', ['data' => $pesan]);
|
||||
}
|
||||
|
||||
if ($action === 'notifikasi') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT * FROM notifikasi
|
||||
WHERE user_id = :uid
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 30
|
||||
");
|
||||
$stmt->execute([':uid' => $currentUser['id']]);
|
||||
$notif = $stmt->fetchAll();
|
||||
$unread = (int)$pdo->prepare("SELECT COUNT(*) FROM notifikasi WHERE user_id=:uid AND dibaca=0")
|
||||
->execute([':uid' => $currentUser['id']]);
|
||||
jsonResponse(true, 'OK', ['data' => $notif, 'unread' => array_sum(array_column($notif, 'dibaca') === array_map(fn($r) => $r['dibaca'] == 0, $notif))]);
|
||||
}
|
||||
|
||||
if ($action === 'pengurus_list') {
|
||||
// Daftar pengurus untuk pilihan chat
|
||||
$rows = $pdo->query("
|
||||
SELECT u.id, u.nama, u.email, ri.nama AS nama_ri, ri.jenis AS jenis_ri
|
||||
FROM users u
|
||||
LEFT JOIN rumah_ibadah ri ON ri.id = u.rumah_ibadah_id
|
||||
WHERE u.role_id IN (1,2) AND u.aktif=1
|
||||
ORDER BY u.nama
|
||||
")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
// Kirim pesan
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$isi = sanitizeStr($body['isi'] ?? '', 2000);
|
||||
if (strlen(trim($isi)) < 1) jsonResponse(false, 'Pesan tidak boleh kosong.', [], 400);
|
||||
|
||||
$keUserId = !empty($body['ke_user_id']) ? (int)$body['ke_user_id'] : null;
|
||||
$keRiId = !empty($body['ke_rumah_ibadah_id']) ? (int)$body['ke_rumah_ibadah_id'] : null;
|
||||
|
||||
if (!$keUserId && !$keRiId) jsonResponse(false, 'Tujuan pesan harus diisi.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO pesan (dari_user_id, ke_user_id, ke_rumah_ibadah_id, isi)
|
||||
VALUES (:dari, :ke_user, :ke_ri, :isi)
|
||||
");
|
||||
$stmt->execute([
|
||||
':dari' => $currentUser['id'],
|
||||
':ke_user' => $keUserId,
|
||||
':ke_ri' => $keRiId,
|
||||
':isi' => $isi,
|
||||
]);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Notifikasi ke penerima
|
||||
if ($keUserId) {
|
||||
$pdo->prepare("INSERT INTO notifikasi (user_id, judul, isi, tipe, ref_id) VALUES (:u,:j,:i,'pesan',:r)")
|
||||
->execute([':u'=>$keUserId, ':j'=>"Pesan dari {$currentUser['role']}", ':i'=>substr($isi,0,100), ':r'=>$newId]);
|
||||
}
|
||||
|
||||
jsonResponse(true, 'Pesan terkirim.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API: Rumah Ibadah
|
||||
// ============================================================
|
||||
require_once __DIR__ . '/../middleware/auth.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$currentUser = getApiUser($pdo);
|
||||
|
||||
if ($method === 'GET') {
|
||||
if ($action === 'list') {
|
||||
$rows = $pdo->query("
|
||||
SELECT ri.*,
|
||||
COUNT(k.id) AS jumlah_kk,
|
||||
SUM(CASE WHEN k.status_ekonomi='miskin' THEN 1 ELSE 0 END) AS kk_miskin
|
||||
FROM rumah_ibadah ri
|
||||
LEFT JOIN keluarga k ON k.rumah_ibadah_id = ri.id
|
||||
GROUP BY ri.id
|
||||
ORDER BY ri.nama
|
||||
")->fetchAll();
|
||||
jsonResponse(true, 'OK', ['data' => $rows]);
|
||||
}
|
||||
if ($action === 'geojson') {
|
||||
$rows = $pdo->query("SELECT * FROM rumah_ibadah")->fetchAll();
|
||||
$features = [];
|
||||
foreach ($rows as $r) {
|
||||
$features[] = [
|
||||
'type' => 'Feature',
|
||||
'geometry' => ['type'=>'Point','coordinates'=>[(float)$r['longitude'],(float)$r['latitude']]],
|
||||
'properties' => $r
|
||||
];
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['type'=>'FeatureCollection','features'=>$features], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
jsonResponse(false, 'Action tidak dikenal.', [], 400);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
$nama = sanitizeStr($body['nama'] ?? '');
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (strlen($nama) < 3) jsonResponse(false, 'Nama terlalu pendek.', [], 400);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat wajib diisi.', [], 400);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO rumah_ibadah (nama, jenis, kontak, radius_meter, latitude, longitude, alamat, kelurahan, kecamatan)
|
||||
VALUES (:nama,:jenis,:kontak,:radius,:lat,:lng,:alamat,:kel,:kec)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':jenis' => sanitizeStr($body['jenis'] ?? 'Lainnya'),
|
||||
':kontak' => sanitizeStr($body['kontak'] ?? '') ?: null,
|
||||
':radius' => (float)($body['radius_meter'] ?? 300),
|
||||
':lat' => $lat, ':lng' => $lng,
|
||||
':alamat' => sanitizeStr($body['alamat'] ?? '') ?: null,
|
||||
':kel' => sanitizeStr($body['kelurahan'] ?? '') ?: null,
|
||||
':kec' => sanitizeStr($body['kecamatan'] ?? '') ?: null,
|
||||
]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'CREATE', 'rumah_ibadah', (string)$newId, null, $body);
|
||||
jsonResponse(true, 'Rumah ibadah ditambahkan.', ['id' => $newId], 201);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
if (!canEdit($currentUser)) jsonResponse(false, 'Akses ditolak.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
if ($action === 'update_koordinat') {
|
||||
$lat = (float)($body['latitude'] ?? 0);
|
||||
$lng = (float)($body['longitude'] ?? 0);
|
||||
if (!$lat || !$lng) jsonResponse(false, 'Koordinat tidak valid.', [], 400);
|
||||
|
||||
$pdo->prepare("UPDATE rumah_ibadah SET latitude=:lat, longitude=:lng WHERE id=:id")
|
||||
->execute([':lat'=>$lat, ':lng'=>$lng, ':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'rumah_ibadah', (string)$id, null, ['lat'=>$lat,'lng'=>$lng]);
|
||||
jsonResponse(true, 'Koordinat diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
$pdo->prepare("
|
||||
UPDATE rumah_ibadah SET nama=:nama,jenis=:jenis,kontak=:kontak,radius_meter=:radius,
|
||||
latitude=:lat,longitude=:lng,alamat=:alamat,kelurahan=:kel,kecamatan=:kec WHERE id=:id
|
||||
")->execute([
|
||||
':nama'=>sanitizeStr($body['nama']??''), ':jenis'=>sanitizeStr($body['jenis']??'Lainnya'),
|
||||
':kontak'=>sanitizeStr($body['kontak']??'')?: null, ':radius'=>(float)($body['radius_meter']??300),
|
||||
':lat'=>(float)($body['latitude']??0), ':lng'=>(float)($body['longitude']??0),
|
||||
':alamat'=>sanitizeStr($body['alamat']??'')?: null, ':kel'=>sanitizeStr($body['kelurahan']??'')?: null,
|
||||
':kec'=>sanitizeStr($body['kecamatan']??'')?: null, ':id'=>$id,
|
||||
]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'UPDATE', 'rumah_ibadah', (string)$id);
|
||||
jsonResponse(true, 'Rumah ibadah diperbarui.', ['id' => $id]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!isAdmin($currentUser)) jsonResponse(false, 'Hanya admin.', [], 403);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$pdo->prepare("DELETE FROM rumah_ibadah WHERE id=:id")->execute([':id'=>$id]);
|
||||
logAktivitas($pdo, $currentUser['id']??null, 'DELETE', 'rumah_ibadah', (string)$id);
|
||||
jsonResponse(true, 'Dihapus.');
|
||||
}
|
||||
|
||||
jsonResponse(false, 'Method tidak didukung.', [], 405);
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// Konfigurasi & Koneksi Database
|
||||
// WebGIS Pengentasan Kemiskinan v2.0
|
||||
// ============================================================
|
||||
|
||||
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
|
||||
define('DB_PORT', getenv('DB_PORT') ?: '3306');
|
||||
define('DB_NAME', getenv('DB_NAME') ?: 'webgis_kemiskinan');
|
||||
define('DB_USER', getenv('DB_USER') ?: 'root');
|
||||
define('DB_PASS', getenv('DB_PASSWORD') ?: '');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
define('APP_NAME', 'WebGIS Pengentasan Kemiskinan');
|
||||
define('APP_VERSION', '2.0.0');
|
||||
define('APP_URL', 'http://localhost/webgis-kemiskinan');
|
||||
define('APP_ENV', 'development'); // Ganti ke 'production' saat deploy
|
||||
|
||||
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
|
||||
define('UPLOAD_BUKTI', UPLOAD_PATH . 'bukti/');
|
||||
define('UPLOAD_LAPORAN', UPLOAD_PATH . 'foto_laporan/');
|
||||
define('UPLOAD_FOTO', UPLOAD_PATH . 'foto_profil/');
|
||||
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5 MB
|
||||
define('ALLOWED_IMG_EXT', ['jpg','jpeg','png','webp']);
|
||||
define('ALLOWED_FILE_EXT', ['jpg','jpeg','png','webp','pdf']);
|
||||
|
||||
// Buat folder upload jika belum ada
|
||||
foreach ([UPLOAD_BUKTI, UPLOAD_LAPORAN, UPLOAD_FOTO] as $dir) {
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Koneksi PDO
|
||||
// ============================================================
|
||||
|
||||
$dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||
|
||||
$pdoOptions = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, DB_USER, DB_PASS, $pdoOptions);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
// Jangan expose detail error di produksi
|
||||
$errMsg = (defined('APP_ENV') && APP_ENV === 'development') ? $e->getMessage() : 'Hubungi administrator sistem.';
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Koneksi database gagal: ' . DB_NAME . '@' . DB_HOST . ' tidak ditemukan. Pastikan database sudah dibuat dan nama database benar.',
|
||||
'error' => $errMsg
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Response JSON
|
||||
// ============================================================
|
||||
|
||||
function jsonResponse(bool $success, string $message, array $data = [], int $httpCode = 200): void {
|
||||
http_response_code($httpCode);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
echo json_encode(array_merge(['success' => $success, 'message' => $message], $data), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Sanitasi & Validasi
|
||||
// ============================================================
|
||||
|
||||
function h(string $s): string {
|
||||
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function sanitizeStr(?string $v, int $max = 255): string {
|
||||
return mb_substr(trim($v ?? ''), 0, $max);
|
||||
}
|
||||
|
||||
function validateNIK(string $nik): bool {
|
||||
return preg_match('/^\d{16}$/', $nik) === 1;
|
||||
}
|
||||
|
||||
function validateCoord(string $val, string $type = 'lat'): bool {
|
||||
$f = (float)$val;
|
||||
return $type === 'lat' ? ($f >= -90 && $f <= 90) : ($f >= -180 && $f <= 180);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Upload File
|
||||
// ============================================================
|
||||
|
||||
function uploadFile(array $file, string $destDir, array $allowedExt = null): ?string {
|
||||
if ($allowedExt === null) $allowedExt = ALLOWED_FILE_EXT;
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) return null;
|
||||
if ($file['size'] > MAX_FILE_SIZE) throw new RuntimeException('Ukuran file melebihi 5MB.');
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowedExt, true)) throw new RuntimeException("Format file .$ext tidak diizinkan.");
|
||||
$filename = uniqid('img_', true) . '.' . $ext;
|
||||
$dest = rtrim($destDir, '/') . '/' . $filename;
|
||||
if (!move_uploaded_file($file['tmp_name'], $dest)) throw new RuntimeException('Gagal menyimpan file.');
|
||||
return $filename;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Log Aktivitas
|
||||
// ============================================================
|
||||
|
||||
function logAktivitas(PDO $pdo, ?int $userId, string $aksi, string $tabel = '', string $recordId = '', $dataLama = null, $dataBaru = null): void {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO log_aktivitas (user_id, aksi, tabel, record_id, data_lama, data_baru, ip_address, user_agent)
|
||||
VALUES (:u, :a, :t, :r, :dl, :db, :ip, :ua)
|
||||
");
|
||||
$stmt->execute([
|
||||
':u' => $userId,
|
||||
':a' => $aksi,
|
||||
':t' => $tabel,
|
||||
':r' => $recordId,
|
||||
':dl' => $dataLama ? json_encode($dataLama, JSON_UNESCAPED_UNICODE) : null,
|
||||
':db' => $dataBaru ? json_encode($dataBaru, JSON_UNESCAPED_UNICODE) : null,
|
||||
':ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
':ua' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
|
||||
]);
|
||||
} catch (Exception) { /* log gagal tidak menghentikan proses */ }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Validasi Logika Pendidikan vs Umur
|
||||
// ============================================================
|
||||
|
||||
function validasiPendidikanUmur(int $umur, string $pendidikan, string $statusPendidikan): array {
|
||||
$warnings = [];
|
||||
$errors = [];
|
||||
|
||||
if ($umur >= 7 && $umur <= 12) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SD.";
|
||||
}
|
||||
} elseif ($umur >= 13 && $umur <= 15) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMP.";
|
||||
}
|
||||
} elseif ($umur >= 16 && $umur <= 18) {
|
||||
if ($statusPendidikan !== 'sekolah') {
|
||||
$warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMA/SMK.";
|
||||
}
|
||||
}
|
||||
// Umur > 18: bebas, boleh tidak sekolah, boleh pelatihan
|
||||
|
||||
return ['warnings' => $warnings, 'errors' => $errors];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Prioritas Bantuan
|
||||
// ============================================================
|
||||
|
||||
function hitungPrioritasBantuan(string $statusEkonomi, string $kondisiKesehatan, bool $belumPernahDibantu): string {
|
||||
if ($statusEkonomi === 'miskin' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) {
|
||||
return 'KRITIS';
|
||||
}
|
||||
if ($statusEkonomi === 'miskin' && $belumPernahDibantu) {
|
||||
return 'TINGGI';
|
||||
}
|
||||
if ($statusEkonomi === 'miskin') {
|
||||
return 'SEDANG';
|
||||
}
|
||||
if ($statusEkonomi === 'rentan' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) {
|
||||
return 'SEDANG';
|
||||
}
|
||||
return 'RENDAH';
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// Middleware Autentikasi & Otorisasi
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../koneksi.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start([
|
||||
'cookie_httponly' => true,
|
||||
'cookie_samesite' => 'Strict',
|
||||
'use_strict_mode' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Fungsi login / logout
|
||||
// ============================================================
|
||||
|
||||
function doLogin(PDO $pdo, string $username, string $password): array {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT u.*, r.nama AS role_nama
|
||||
FROM users u
|
||||
JOIN roles r ON r.id = u.role_id
|
||||
WHERE u.username = :u AND u.aktif = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':u' => $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
return ['success' => false, 'message' => 'Username atau password salah.'];
|
||||
}
|
||||
|
||||
// Update last_login
|
||||
$pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = :id")
|
||||
->execute([':id' => $user['id']]);
|
||||
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_nama'] = $user['nama'];
|
||||
$_SESSION['role'] = $user['role_nama'];
|
||||
$_SESSION['role_id'] = $user['role_id'];
|
||||
$_SESSION['rumah_ibadah_id'] = $user['rumah_ibadah_id'];
|
||||
session_regenerate_id(true);
|
||||
|
||||
logAktivitas($pdo, $user['id'], 'LOGIN', 'users', (string)$user['id']);
|
||||
|
||||
return ['success' => true, 'role' => $user['role_nama']];
|
||||
}
|
||||
|
||||
function doLogout(PDO $pdo): void {
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
logAktivitas($pdo, $_SESSION['user_id'], 'LOGOUT', 'users', (string)$_SESSION['user_id']);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guard: wajib login
|
||||
// ============================================================
|
||||
|
||||
function requireLogin(array $allowedRoles = []): array {
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
// Jika request API
|
||||
if (!empty($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
jsonResponse(false, 'Sesi habis. Silakan login kembali.', [], 401);
|
||||
}
|
||||
header('Location: ' . APP_URL . '/admin/login.php');
|
||||
exit;
|
||||
}
|
||||
if (!empty($allowedRoles) && !in_array($_SESSION['role'], $allowedRoles, true)) {
|
||||
if (!empty($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
jsonResponse(false, 'Tidak punya akses.', [], 403);
|
||||
}
|
||||
header('Location: ' . APP_URL . '/admin/forbidden.php');
|
||||
exit;
|
||||
}
|
||||
return [
|
||||
'id' => (int)$_SESSION['user_id'],
|
||||
'nama' => $_SESSION['user_nama'],
|
||||
'role' => $_SESSION['role'],
|
||||
'role_id' => (int)$_SESSION['role_id'],
|
||||
'rumah_ibadah_id' => $_SESSION['rumah_ibadah_id'] ? (int)$_SESSION['rumah_ibadah_id'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guard API: token-based (opsional, untuk mobile app nanti)
|
||||
// ============================================================
|
||||
|
||||
function getApiUser(PDO $pdo): ?array {
|
||||
// Cek session dulu
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
return [
|
||||
'id' => (int)$_SESSION['user_id'],
|
||||
'role' => $_SESSION['role'],
|
||||
'role_id' => (int)$_SESSION['role_id'],
|
||||
'rumah_ibadah_id' => $_SESSION['rumah_ibadah_id'] ? (int)$_SESSION['rumah_ibadah_id'] : null,
|
||||
];
|
||||
}
|
||||
return null; // belum login = null
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Cek izin per fitur
|
||||
// ============================================================
|
||||
|
||||
function canEdit(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus'], true);
|
||||
}
|
||||
|
||||
function canVerify(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus'], true);
|
||||
}
|
||||
|
||||
function canViewAll(?array $user): bool {
|
||||
if (!$user) return false;
|
||||
return in_array($user['role'], ['admin', 'pengurus', 'pimpinan'], true);
|
||||
}
|
||||
|
||||
function isAdmin(?array $user): bool {
|
||||
return $user && $user['role'] === 'admin';
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
-- ============================================================
|
||||
-- WebGIS Pengentasan Kemiskinan Berbasis Masyarakat & Rumah Ibadah
|
||||
-- Schema Lengkap v2.0 — FIXED VERSION
|
||||
-- Engine: MySQL / MariaDB | Charset: utf8mb4
|
||||
-- ============================================================
|
||||
|
||||
-- PENTING: Buat database terlebih dahulu jika belum ada
|
||||
CREATE DATABASE IF NOT EXISTS webgis_kemiskinan CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE webgis_kemiskinan;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- DROP TABLES (urutan terbalik dari dependensi FK)
|
||||
-- ============================================================
|
||||
DROP TABLE IF EXISTS notifikasi;
|
||||
DROP TABLE IF EXISTS log_aktivitas;
|
||||
DROP TABLE IF EXISTS pesan;
|
||||
DROP TABLE IF EXISTS laporan;
|
||||
DROP TABLE IF EXISTS histori_bantuan;
|
||||
DROP TABLE IF EXISTS jenis_bantuan;
|
||||
DROP TABLE IF EXISTS riwayat_pelatihan;
|
||||
DROP TABLE IF EXISTS penduduk;
|
||||
DROP TABLE IF EXISTS keluarga;
|
||||
DROP TABLE IF EXISTS rumah_ibadah;
|
||||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF EXISTS roles;
|
||||
DROP TABLE IF EXISTS jalan_lines;
|
||||
DROP TABLE IF EXISTS parsil_tanah;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. ROLES & USERS
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE roles (
|
||||
id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(50) NOT NULL UNIQUE,
|
||||
label VARCHAR(80) NOT NULL,
|
||||
deskripsi TEXT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT INTO roles (nama, label) VALUES
|
||||
('admin', 'Administrator Sistem'),
|
||||
('pengurus', 'Pengurus Rumah Ibadah'),
|
||||
('pimpinan', 'Pimpinan Daerah'),
|
||||
('masyarakat', 'Masyarakat Umum');
|
||||
|
||||
-- ============================================================
|
||||
-- 2. RUMAH IBADAH (dibuat sebelum users karena users FK ke sini)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE rumah_ibadah (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(200) NOT NULL,
|
||||
jenis ENUM('Masjid','Musholla','Gereja Protestan','Gereja Katolik','Pura','Vihara','Klenteng','Lainnya') NOT NULL DEFAULT 'Lainnya',
|
||||
kontak VARCHAR(120) NULL,
|
||||
radius_meter DOUBLE NOT NULL DEFAULT 300,
|
||||
latitude DECIMAL(10,7) NOT NULL,
|
||||
longitude DECIMAL(10,7) NOT NULL,
|
||||
alamat TEXT NULL,
|
||||
kelurahan VARCHAR(120) NULL,
|
||||
kecamatan VARCHAR(120) NULL,
|
||||
kota VARCHAR(120) NULL,
|
||||
deskripsi TEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_ri_jenis (jenis),
|
||||
INDEX idx_ri_latlng (latitude, longitude)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. USERS (setelah roles & rumah_ibadah)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE users (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
role_id TINYINT UNSIGNED NOT NULL DEFAULT 4,
|
||||
nama VARCHAR(150) NOT NULL,
|
||||
username VARCHAR(60) NOT NULL UNIQUE,
|
||||
email VARCHAR(150) NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
no_telp VARCHAR(20) NULL,
|
||||
foto VARCHAR(255) NULL,
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
rumah_ibadah_id INT UNSIGNED NULL,
|
||||
last_login DATETIME NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_users_role (role_id),
|
||||
CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles(id),
|
||||
CONSTRAINT fk_users_ri FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 4. KELUARGA (KK)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE keluarga (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
no_kk VARCHAR(20) NOT NULL UNIQUE,
|
||||
nama_kk VARCHAR(200) NOT NULL,
|
||||
alamat TEXT NULL,
|
||||
kelurahan VARCHAR(120) NULL,
|
||||
kecamatan VARCHAR(120) NULL,
|
||||
kota VARCHAR(120) NULL,
|
||||
latitude DECIMAL(10,7) NULL,
|
||||
longitude DECIMAL(10,7) NULL,
|
||||
status_ekonomi ENUM('miskin','rentan','mampu') NOT NULL DEFAULT 'rentan',
|
||||
rumah_ibadah_id INT UNSIGNED NULL,
|
||||
jarak_ri_meter DOUBLE NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_kk_status (status_ekonomi),
|
||||
INDEX idx_kk_latlng (latitude, longitude),
|
||||
INDEX idx_kk_ri (rumah_ibadah_id),
|
||||
CONSTRAINT fk_kk_ri FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. PENDUDUK
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE penduduk (
|
||||
nik VARCHAR(16) NOT NULL PRIMARY KEY,
|
||||
id_keluarga INT UNSIGNED NULL,
|
||||
nama_lengkap VARCHAR(200) NOT NULL,
|
||||
jenis_kelamin ENUM('L','P') NOT NULL,
|
||||
tanggal_lahir DATE NOT NULL,
|
||||
umur TINYINT UNSIGNED GENERATED ALWAYS AS (
|
||||
TIMESTAMPDIFF(YEAR, tanggal_lahir, CURDATE())
|
||||
) VIRTUAL,
|
||||
status_keluarga ENUM('kepala_keluarga','istri','anak','menantu','cucu','orang_tua','mertua','anggota_lain') NOT NULL DEFAULT 'anggota_lain',
|
||||
status_perkawinan ENUM('belum_kawin','kawin','cerai_hidup','cerai_mati') NOT NULL DEFAULT 'belum_kawin',
|
||||
status_hidup ENUM('hidup','meninggal') NOT NULL DEFAULT 'hidup',
|
||||
pendidikan_terakhir ENUM('tidak_sekolah','SD','SMP','SMA','SMK','D1','D2','D3','S1','S2','S3') NOT NULL DEFAULT 'tidak_sekolah',
|
||||
status_pendidikan ENUM('sekolah','tidak_sekolah','lulus') NOT NULL DEFAULT 'tidak_sekolah',
|
||||
pekerjaan VARCHAR(150) NULL,
|
||||
penghasilan DECIMAL(15,2) NULL DEFAULT 0,
|
||||
status_ekonomi ENUM('miskin','rentan','mampu') NOT NULL DEFAULT 'rentan',
|
||||
kondisi_kesehatan ENUM('sehat','sakit_ringan','sakit_parah','disabilitas') NOT NULL DEFAULT 'sehat',
|
||||
catatan_kesehatan TEXT NULL,
|
||||
no_telp VARCHAR(20) NULL,
|
||||
alamat TEXT NULL,
|
||||
latitude DECIMAL(10,7) NULL,
|
||||
longitude DECIMAL(10,7) NULL,
|
||||
foto VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_pddk_keluarga (id_keluarga),
|
||||
INDEX idx_pddk_status_ek (status_ekonomi),
|
||||
INDEX idx_pddk_status_hid (status_hidup),
|
||||
INDEX idx_pddk_latlng (latitude, longitude),
|
||||
CONSTRAINT fk_pddk_keluarga FOREIGN KEY (id_keluarga) REFERENCES keluarga(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. RIWAYAT PELATIHAN
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE riwayat_pelatihan (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nik VARCHAR(16) NOT NULL,
|
||||
nama_pelatihan VARCHAR(200) NOT NULL,
|
||||
penyelenggara VARCHAR(150) NULL,
|
||||
tanggal_mulai DATE NULL,
|
||||
tanggal_selesai DATE NULL,
|
||||
sertifikat VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_platihan_nik (nik),
|
||||
CONSTRAINT fk_platihan_nik FOREIGN KEY (nik) REFERENCES penduduk(nik) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 7. JENIS BANTUAN (master)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE jenis_bantuan (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(150) NOT NULL,
|
||||
kategori ENUM('sembako','pendidikan','kesehatan','ekonomi','perumahan','lainnya') NOT NULL DEFAULT 'lainnya',
|
||||
sumber VARCHAR(150) NULL,
|
||||
satuan VARCHAR(50) NULL,
|
||||
deskripsi TEXT NULL,
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT INTO jenis_bantuan (nama, kategori, sumber, satuan) VALUES
|
||||
('Sembako Bulanan', 'sembako', 'Rumah Ibadah', '1 paket/bulan'),
|
||||
('Beasiswa Pendidikan', 'pendidikan','BAZNAS', 'Rp/semester'),
|
||||
('Bantuan Kesehatan', 'kesehatan', 'Puskesmas', '1 kali/kunjungan'),
|
||||
('Modal Usaha Mikro', 'ekonomi', 'Baznas', 'Rp/program'),
|
||||
('Renovasi Rumah', 'perumahan', 'Pemerintah', 'paket/unit');
|
||||
|
||||
-- ============================================================
|
||||
-- 8. HISTORI BANTUAN
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE histori_bantuan (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nik VARCHAR(16) NOT NULL,
|
||||
id_jenis_bantuan INT UNSIGNED NOT NULL,
|
||||
id_keluarga INT UNSIGNED NULL,
|
||||
rumah_ibadah_id INT UNSIGNED NULL,
|
||||
tanggal DATE NOT NULL,
|
||||
jumlah VARCHAR(100) NULL,
|
||||
nilai_rupiah DECIMAL(15,2) NULL,
|
||||
status_penyaluran ENUM('dijadwalkan','disalurkan','dibatalkan') NOT NULL DEFAULT 'dijadwalkan',
|
||||
catatan TEXT NULL,
|
||||
bukti_file VARCHAR(255) NULL,
|
||||
disalurkan_oleh INT UNSIGNED NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_hb_nik (nik),
|
||||
INDEX idx_hb_tanggal (tanggal),
|
||||
INDEX idx_hb_status (status_penyaluran),
|
||||
CONSTRAINT fk_hb_nik FOREIGN KEY (nik) REFERENCES penduduk(nik) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_hb_jenis FOREIGN KEY (id_jenis_bantuan) REFERENCES jenis_bantuan(id),
|
||||
CONSTRAINT fk_hb_kk FOREIGN KEY (id_keluarga) REFERENCES keluarga(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_hb_ri FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_hb_user FOREIGN KEY (disalurkan_oleh) REFERENCES users(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. LAPORAN MASYARAKAT
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE laporan (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_pelapor VARCHAR(150) NULL,
|
||||
pelapor_id INT UNSIGNED NULL,
|
||||
nik_terdampak VARCHAR(16) NULL,
|
||||
deskripsi TEXT NOT NULL,
|
||||
kategori ENUM('darurat_pangan','darurat_kesehatan','darurat_ekonomi','butuh_bantuan_lain') NOT NULL DEFAULT 'butuh_bantuan_lain',
|
||||
tingkat_urgensi ENUM('rendah','sedang','tinggi','darurat') NOT NULL DEFAULT 'sedang',
|
||||
latitude DECIMAL(10,7) NULL,
|
||||
longitude DECIMAL(10,7) NULL,
|
||||
alamat TEXT NULL,
|
||||
foto VARCHAR(255) NULL,
|
||||
status ENUM('pending','diverifikasi','diproses','selesai','ditolak') NOT NULL DEFAULT 'pending',
|
||||
catatan_verifikasi TEXT NULL,
|
||||
diverifikasi_oleh INT UNSIGNED NULL,
|
||||
ditangani_oleh INT UNSIGNED NULL,
|
||||
tanggal_selesai DATETIME NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_lap_status (status),
|
||||
INDEX idx_lap_urgensi (tingkat_urgensi),
|
||||
INDEX idx_lap_latlng (latitude, longitude),
|
||||
CONSTRAINT fk_lap_nik FOREIGN KEY (nik_terdampak) REFERENCES penduduk(nik) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_lap_pelapor FOREIGN KEY (pelapor_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_lap_verif FOREIGN KEY (diverifikasi_oleh) REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_lap_tangan FOREIGN KEY (ditangani_oleh) REFERENCES users(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 10. PESAN / CHAT ANTAR PENGURUS
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE pesan (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
dari_user_id INT UNSIGNED NOT NULL,
|
||||
ke_user_id INT UNSIGNED NULL,
|
||||
ke_rumah_ibadah_id INT UNSIGNED NULL,
|
||||
isi TEXT NOT NULL,
|
||||
dibaca TINYINT(1) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_pesan_dari (dari_user_id),
|
||||
INDEX idx_pesan_ke (ke_user_id),
|
||||
CONSTRAINT fk_pesan_dari FOREIGN KEY (dari_user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_pesan_ke FOREIGN KEY (ke_user_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_pesan_ri FOREIGN KEY (ke_rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 11. LOG AKTIVITAS (audit trail)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE log_aktivitas (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT UNSIGNED NULL,
|
||||
aksi VARCHAR(50) NOT NULL,
|
||||
tabel VARCHAR(60) NULL,
|
||||
record_id VARCHAR(32) NULL,
|
||||
data_lama JSON NULL,
|
||||
data_baru JSON NULL,
|
||||
ip_address VARCHAR(45) NULL,
|
||||
user_agent VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_log_user (user_id),
|
||||
INDEX idx_log_tabel (tabel),
|
||||
INDEX idx_log_aksi (aksi),
|
||||
INDEX idx_log_time (created_at),
|
||||
CONSTRAINT fk_log_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 12. LAYER SPASIAL TAMBAHAN
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE jalan_lines (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(200) NOT NULL,
|
||||
status_jalan ENUM('Nasional','Provinsi','Kabupaten','Desa') NOT NULL DEFAULT 'Kabupaten',
|
||||
panjang_meter DOUBLE NOT NULL DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_jalan_status (status_jalan)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE parsil_tanah (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
status_kepemilikan ENUM('SHM','HGB','HGU','HP','Wakaf','Lainnya') NOT NULL DEFAULT 'SHM',
|
||||
luas_m2 DOUBLE NOT NULL DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_parsil_status (status_kepemilikan)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 13. NOTIFIKASI
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE notifikasi (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT UNSIGNED NOT NULL,
|
||||
judul VARCHAR(200) NOT NULL,
|
||||
isi TEXT NOT NULL,
|
||||
tipe ENUM('laporan','bantuan','sistem','pesan') NOT NULL DEFAULT 'sistem',
|
||||
ref_id INT UNSIGNED NULL,
|
||||
dibaca TINYINT(1) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_notif_user (user_id),
|
||||
INDEX idx_notif_dibaca (dibaca),
|
||||
CONSTRAINT fk_notif_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ============================================================
|
||||
-- DATA DUMMY
|
||||
-- ============================================================
|
||||
|
||||
INSERT IGNORE INTO users (role_id, nama, username, email, password_hash) VALUES
|
||||
(1, 'Administrator', 'admin', 'admin@webgis.local', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
|
||||
(2, 'H. Ahmad Fauzi', 'pengurus1', 'pengurus1@webgis.local', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
|
||||
(3, 'Camat Pontianak Selatan','pimpinan1', 'pimpinan@webgis.local', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
|
||||
(4, 'Budi Santoso', 'warga1', 'warga1@webgis.local', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi');
|
||||
|
||||
INSERT IGNORE INTO rumah_ibadah (nama, jenis, kontak, radius_meter, latitude, longitude, alamat, kelurahan, kecamatan) VALUES
|
||||
('Masjid Al-Falah', 'Masjid', '08113001234', 400, -0.0200, 109.3410, 'Jl. Gajah Mada No. 1', 'Benua Melayu Darat', 'Pontianak Selatan'),
|
||||
('Masjid Raya Mujahidin', 'Masjid', '08113005678', 600, -0.0250, 109.3430, 'Jl. Ahmad Yani No. 10', 'Akcaya', 'Pontianak Selatan'),
|
||||
('GKE Pontianak', 'Gereja Protestan', '08113009999', 300, -0.0280, 109.3480, 'Jl. Nusa Indah', 'Sungai Bangkong', 'Pontianak Kota'),
|
||||
('Vihara Bodhicitta', 'Vihara', '08113004321', 250, -0.0310, 109.3520, 'Jl. Tanjungpura No. 5', 'Mariana', 'Pontianak Kota');
|
||||
|
||||
INSERT IGNORE INTO keluarga (no_kk, nama_kk, alamat, kelurahan, kecamatan, latitude, longitude, status_ekonomi, rumah_ibadah_id) VALUES
|
||||
('6171010101010001', 'Ahmad Firdaus', 'Jl. Melati No. 3', 'Benua Melayu Darat', 'Pontianak Selatan', -0.0210, 109.3415, 'miskin', 1),
|
||||
('6171010101010002', 'Siti Rahma', 'Jl. Kenanga No. 5', 'Benua Melayu Darat', 'Pontianak Selatan', -0.0220, 109.3420, 'miskin', 1),
|
||||
('6171010101010003', 'Budi Hermawan','Jl. Mawar No. 7', 'Akcaya', 'Pontianak Selatan', -0.0240, 109.3435, 'rentan', 2),
|
||||
('6171010101010004', 'Maria Goretti','Jl. Nusa Indah No.2', 'Sungai Bangkong', 'Pontianak Kota', -0.0275, 109.3475, 'miskin', 3),
|
||||
('6171010101010005', 'Tan Ah Kow', 'Jl. Tanjungpura 12', 'Mariana', 'Pontianak Kota', -0.0305, 109.3515, 'rentan', 4);
|
||||
|
||||
INSERT IGNORE INTO penduduk (nik, id_keluarga, nama_lengkap, jenis_kelamin, tanggal_lahir, status_keluarga, status_perkawinan, status_ekonomi, pekerjaan, penghasilan, kondisi_kesehatan, alamat, latitude, longitude) VALUES
|
||||
('6171010101010001', 1, 'Ahmad Firdaus', 'L', '1980-03-15', 'kepala_keluarga', 'kawin', 'miskin', 'Buruh Harian', 800000, 'sehat', 'Jl. Melati No. 3', -0.0210, 109.3415),
|
||||
('6171010101010002', 1, 'Siti Aisyah', 'P', '1983-07-22', 'istri', 'kawin', 'miskin', 'Ibu Rumah Tangga', 0, 'sehat', 'Jl. Melati No. 3', -0.0210, 109.3415),
|
||||
('6171010101010003', 1, 'Reza Firdaus', 'L', '2010-11-05', 'anak', 'belum_kawin', 'miskin', 'Pelajar', 0, 'sehat', 'Jl. Melati No. 3', -0.0210, 109.3415),
|
||||
('6171020202020001', 2, 'Siti Rahma', 'P', '1975-01-10', 'kepala_keluarga', 'cerai_mati', 'miskin', 'Pedagang Kecil', 500000, 'sakit_ringan','Jl. Kenanga No. 5', -0.0220, 109.3420),
|
||||
('6171020202020002', 2, 'Dewi Rahma', 'P', '2008-05-30', 'anak', 'belum_kawin', 'miskin', 'Pelajar', 0, 'sehat', 'Jl. Kenanga No. 5', -0.0220, 109.3420),
|
||||
('6171030303030001', 3, 'Budi Hermawan', 'L', '1990-09-20', 'kepala_keluarga', 'kawin', 'rentan', 'Ojek Online', 1500000, 'sehat', 'Jl. Mawar No. 7', -0.0240, 109.3435),
|
||||
('6171040404040001', 4, 'Yohanes Goretti', 'L', '1970-12-01', 'kepala_keluarga', 'kawin', 'miskin', 'Tidak bekerja', 0, 'sakit_parah', 'Jl. Nusa Indah No.2', -0.0275, 109.3475),
|
||||
('6171050505050001', 5, 'Tan Ah Kow', 'L', '1968-06-18', 'kepala_keluarga', 'kawin', 'rentan', 'Pedagang', 1200000, 'sehat', 'Jl. Tanjungpura 12', -0.0305, 109.3515);
|
||||
|
||||
INSERT IGNORE INTO histori_bantuan (nik, id_jenis_bantuan, id_keluarga, rumah_ibadah_id, tanggal, jumlah, status_penyaluran, catatan) VALUES
|
||||
('6171010101010001', 1, 1, 1, CURDATE() - INTERVAL 30 DAY, '1 paket sembako', 'disalurkan', 'Penyaluran rutin bulan lalu'),
|
||||
('6171020202020001', 2, 2, 1, CURDATE() - INTERVAL 15 DAY, 'Rp 500.000', 'disalurkan', 'Bantuan pendidikan anak'),
|
||||
('6171040404040001', 3, 4, 3, CURDATE() - INTERVAL 7 DAY, '1 kali pemeriksaan', 'disalurkan', 'Kunjungan kesehatan darurat');
|
||||
|
||||
INSERT IGNORE INTO laporan (nama_pelapor, nik_terdampak, deskripsi, kategori, tingkat_urgensi, latitude, longitude, alamat, status) VALUES
|
||||
('Anonim', '6171020202020001', 'Ibu Siti sudah 3 hari tidak makan, anaknya juga sakit', 'darurat_pangan', 'darurat', -0.0220, 109.3420, 'Jl. Kenanga No. 5', 'pending'),
|
||||
('Pak RT 03', NULL, 'Ada lansia sebatang kara tidak ada yang merawat di RT 03','butuh_bantuan_lain','tinggi', -0.0265, 109.3460, 'RT 03 Sungai Bangkong','diverifikasi'),
|
||||
('Maria Magdalena', '6171040404040001', 'Pak Yohanes sakit parah, tidak ada biaya berobat', 'darurat_kesehatan', 'darurat', -0.0275, 109.3475, 'Jl. Nusa Indah No.2', 'diproses');
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/php/koneksi.php';
|
||||
|
||||
try {
|
||||
// Tambahkan kolom pelapor_id jika belum ada
|
||||
$pdo->exec("ALTER TABLE laporan ADD COLUMN pelapor_id INT UNSIGNED NULL AFTER nama_pelapor");
|
||||
$pdo->exec("ALTER TABLE laporan ADD CONSTRAINT fk_lap_pelapor FOREIGN KEY (pelapor_id) REFERENCES users(id) ON DELETE SET NULL");
|
||||
echo "Kolom pelapor_id berhasil ditambahkan.\n";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error atau kolom sudah ada: " . $e->getMessage() . "\n";
|
||||
}
|
||||
Reference in New Issue
Block a user