Compare commits

..

12 Commits

74 changed files with 11691 additions and 278 deletions
+14
View File
@@ -0,0 +1,14 @@
# Folder Uploads
kemiskinan/uploads/*
publik/uploads/*
!kemiskinan/uploads/.gitkeep
!publik/uploads/.gitkeep
# Dokumen Sementara / Word / Text
*.txt
*.docx
~$*.txt
~$*.docx
# Environment
.env
+30 -74
View File
@@ -1,81 +1,37 @@
# WebGIS - Sistem Analisis Geospasial Pengentasan Kemiskinan
# WebGIS - Sistem Pengentasan Kemiskinan Terpadu
Sebuah aplikasi WebGIS interaktif yang dirancang untuk melakukan analisis spasial terkait sebaran penduduk miskin dan infrastruktur pendukung (seperti fasilitas publik, jalan, parsil tanah, dll) di suatu wilayah. Aplikasi ini dibangun dengan tema visual Merah Putih yang modern.
Repositori ini berisi dua aplikasi WebGIS yang dikembangkan secara terpisah namun berada dalam satu *monorepo*:
1. **Aplikasi Kemiskinan (Privat/Internal)**: Digunakan oleh admin dan petugas lapangan untuk mendata penduduk miskin, verifikasi bantuan, dan analisis spasial radius fasilitas publik.
2. **Aplikasi Publik (Terbuka)**: Portal informasi geospasial publik (Peta SPBU, Peta Tematik Nasional/Provinsi/Kabupaten, dan Digitasi Lahan).
## Fitur Utama
## Arsitektur & Teknologi
- **Frontend**: HTML5, Vanilla CSS, TailwindCSS (via CDN), JavaScript (ES6+).
- **Pemetaan**: Leaflet.js
- **Backend**: PHP (Native PDO & MySQLi legacy support)
- **Database**: MariaDB / MySQL
- **Deployment**: Coolify (Nixpacks)
- **Peta Interaktif**: Visualisasi peta secara *real-time* menggunakan Leaflet.js.
- **Manajemen Data Geospasial**:
- 📍 Tambah/Edit Titik (Point)
- 🛣️ Gambar Jaringan Jalan (Polyline)
- ⬠ Gambar Area/Parsil Tanah (Polygon)
- 🕌 Manajemen Data Fasilitas Publik (dengan radius jangkauan otomatis)
- 👤 Pendataan Penduduk Miskin
- **Analisis Spasial (Radius)**: Secara otomatis mendeteksi apakah seorang Penduduk Miskin berada **di dalam** atau **di luar** jangkauan radius bantuan (berbasis lokasi fasilitas publik terdekat).
- **Dashboard Statistik**: Rangkuman total data penduduk, fasilitas publik, dan persentase cakupan penanganan.
- **Pencarian Cepat (Search)**: Fitur untuk mencari data spesifik (berdasarkan nama, NIK, dll) langsung dari *sidebar*.
## Teknologi yang Digunakan
- **Front-end**: HTML5, CSS3 (Native / Tema Merah Putih), JavaScript (ES6+).
- **Library Peta**: [Leaflet.js](https://leafletjs.com/)
- **Back-end**: PHP
- **Database**: MySQL / MariaDB (via XAMPP)
## Panduan Instalasi (Localhost)
Ikuti langkah-langkah berikut untuk menjalankan aplikasi ini di komputer Anda menggunakan XAMPP:
### 1. Persiapan Environment
Pastikan Anda sudah menginstal aplikasi server lokal, seperti **[XAMPP](https://www.apachefriends.org/index.html)**.
### 2. Kloning / Salin File
1. Unduh atau *clone* repository ini:
```bash
git clone https://github.com/USERNAME_ANDA/webgis-pengentasan-kemiskinan.git
```
2. Pindahkan folder project (misalnya bernama `webgis`) ke dalam folder `htdocs` pada instalasi XAMPP Anda.
- Contoh direktori XAMPP di Windows: `C:\xampp\htdocs\webgis`
### 3. Konfigurasi Database
1. Buka aplikasi **XAMPP Control Panel**.
2. Jalankan modul **Apache** dan **MySQL** (Klik tombol *Start*).
3. Buka browser dan akses **phpMyAdmin**: `http://localhost/phpmyadmin`
4. Buat sebuah database baru dengan nama: **`webgis`**
5. Impor (*Import*) file database yang disertakan:
- Klik tab **Import** di phpMyAdmin.
- Pilih file **`webgis.sql`** yang terdapat di dalam folder project ini.
- Klik **Go** / **Kirim** di bagian bawah halaman.
### 4. Konfigurasi Koneksi (Opsional)
Jika Anda menggunakan *password* untuk root MySQL Anda, Anda perlu menyesuaikan file koneksinya.
1. Buka file `koneksi.php` dengan *text editor*.
2. Ubah konfigurasi `$user` dan `$pass` sesuai dengan pengaturan database Anda (secara *default* di XAMPP user: `root`, pass: *kosong*).
### 5. Jalankan Aplikasi
Buka web browser favorit Anda dan akses URL berikut:
## Struktur Direktori
```text
http://localhost/webgis
/kemiskinan -> Modul tertutup untuk petugas dan admin (Wajib Login).
/publik -> Modul terbuka untuk masyarakat luas.
```
## Cara Penggunaan
- **Menambah Data**: Buka *Sidebar* (ikon di sebelah kiri), pilih salah satu *"Alat Peta"* (Titik, Jalan, Polygon, dll), lalu klik di area peta untuk menentukan lokasi. Sebuah *form pop-up* akan muncul untuk mengisi detailnya.
- **Melihat Data**: Klik pada titik, jalan, atau area yang sudah ada di peta untuk melihat detail informasinya.
- **Menghapus Data**: Pada *pop-up* informasi data di peta, klik tombol "Hapus".
## Panduan Instalasi (Localhost)
1. *Clone* repositori ini ke dalam folder `htdocs` (jika menggunakan XAMPP).
2. Buat database baru bernama `default` atau `webgis` di phpMyAdmin.
3. Import file `.sql` yang tersedia.
4. Akses melalui browser:
- `http://localhost/webgis/kemiskinan`
- `http://localhost/webgis/publik`
## Kontribusi
Jika Anda ingin mengembangkan atau berkontribusi pada aplikasi ini, silakan buat *Pull Request* atau *Fork* repositori ini.
---
*Dibuat untuk keperluan Analisis Geospasial.*
## Cara Upload ke GitHub
Jika Anda ingin mengunggah (upload) project ini ke GitHub Anda sendiri, ikuti langkah berikut:
1. **Buat Repository di GitHub**: Buka github.com, login, lalu buat *New Repository* (beri nama misalnya \webgis-fasilitas-publik\). Biarkan kosong tanpa *README* dari GitHub.
2. **Buka Terminal/Command Prompt**: Arahkan ke folder project ini di komputer Anda (\C:\xampp\htdocs\webgis\).
3. **Inisialisasi Git**: Jalankan perintah \git init\.
4. **Tambahkan File**: Jalankan \git add .\
5. **Simpan Perubahan**: Jalankan \git commit -m "Initial commit webgis"\
6. **Hubungkan ke GitHub**: Copy perintah \git remote add origin https://github.com/USERNAME_ANDA/NAMA_REPO_ANDA.git\ dari halaman GitHub Anda dan jalankan di terminal.
7. **Upload (Push)**: Jalankan \git branch -M main\ lalu \git push -u origin main\.
## Panduan Deployment (Coolify)
1. Buat **dua aplikasi terpisah** di dalam proyek Coolify Anda dari sumber repositori ini.
2. Atur **Base Directory** masing-masing aplikasi menjadi `/kemiskinan` dan `/publik`.
3. Tambahkan 4 variabel *Environment* ini ke kedua aplikasi:
- `DB_HOST`: [internal-mariadb-host-id]
- `DB_USER`: mariadb
- `DB_PASS`: [password-anda]
- `DB_NAME`: default
4. Tambahkan **Persistent Storage** pada aplikasi Kemiskinan ke **`/app/uploads`** agar file gambar (*upload*) tidak hilang saat *redeploy*.
5. Klik **Deploy**!
@@ -1211,23 +1211,22 @@ body {
background: rgba(225,29,72,0.45);
}
/* ─── DATA CARD (Ultra Compact) ─── */
/* ─── DATA CARD (Premium Modern) ─── */
.data-card {
background: #ffffff;
border: 1px solid rgba(0,0,0,0.05);
border-radius: 8px;
padding: 6px 8px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.8);
border-radius: 16px;
padding: 14px 16px;
cursor: pointer;
transition:
transform 0.3s cubic-bezier(0.25, 1, 0.5, 1),
border-color 0.2s ease,
box-shadow 0.3s cubic-bezier(0.25, 1, 0.5, 1),
background-color 0.2s ease;
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
display: flex;
flex-direction: column;
gap: 0px;
gap: 8px;
position: relative;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04), inset 0 1px 0 rgba(255,255,255,0.6);
flex-shrink: 0;
}
@@ -1235,53 +1234,61 @@ body {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
width: 5px;
background: linear-gradient(180deg, var(--accent), #fb7185);
border-radius: 3px 0 0 3px;
border-radius: 16px 0 0 16px;
opacity: 0;
transition: opacity 0.2s ease;
transform: scaleY(0);
transform-origin: center;
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease;
}
.data-card:hover {
transform: translateY(-2px) scale(1.005);
border-color: rgba(225, 29, 72, 0.25);
box-shadow: 0 8px 24px rgba(225, 29, 72, 0.08), 0 2px 6px rgba(0,0,0,0.03);
background: #fafaf9;
transform: translateY(-4px);
border-color: rgba(225, 29, 72, 0.4);
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 12px 30px rgba(225, 29, 72, 0.12), 0 4px 8px rgba(0,0,0,0.06);
}
.data-card:hover::before {
opacity: 1;
transform: scaleY(1);
}
.data-card:active {
transform: translateY(0px) scale(0.98);
box-shadow: 0 2px 8px rgba(225, 29, 72, 0.05);
transform: translateY(1px) scale(0.98);
box-shadow: 0 4px 12px rgba(225, 29, 72, 0.08);
}
.data-card-header {
display: flex;
align-items: flex-start;
gap: 6px;
align-items: center;
gap: 12px;
}
.data-card-icon-wrap {
width: 24px;
height: 24px;
border-radius: 6px;
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-size: 18px;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.04);
box-shadow: 0 4px 10px rgba(0,0,0,0.06);
transition: transform 0.3s ease;
}
.dc-icon-spbu { background: linear-gradient(135deg, #dcfce7, #bbf7d0); }
.dc-icon-pm { background: linear-gradient(135deg, #ffe4e6, #fecdd3); }
.dc-icon-fp { background: linear-gradient(135deg, #dbeafe, #bfdbfe); }
.dc-icon-jalan { background: linear-gradient(135deg, #fef9c3, #fef08a); }
.dc-icon-poly { background: linear-gradient(135deg, #f3e8ff, #e9d5ff); }
.dc-icon-geo { background: linear-gradient(135deg, #e0f2fe, #bae6fd); }
.data-card:hover .data-card-icon-wrap {
transform: scale(1.1) rotate(5deg);
}
.dc-icon-spbu { background: linear-gradient(135deg, #dcfce7, #bbf7d0); color: #166534; }
.dc-icon-pm { background: linear-gradient(135deg, #ffe4e6, #fecdd3); color: #991b1b; }
.dc-icon-fp { background: linear-gradient(135deg, #dbeafe, #bfdbfe); color: #1e40af; }
.dc-icon-jalan { background: linear-gradient(135deg, #fef9c3, #fef08a); color: #854d0e; }
.dc-icon-poly { background: linear-gradient(135deg, #f3e8ff, #e9d5ff); color: #6b21a8; }
.dc-icon-geo { background: linear-gradient(135deg, #e0f2fe, #bae6fd); color: #075985; }
.data-card-body {
flex: 1;
@@ -1292,69 +1299,78 @@ body {
}
.data-card-name {
font-size: 12.5px;
font-weight: 700;
color: #1e293b;
line-height: 1.2;
font-size: 14.5px;
font-weight: 800;
color: #0f172a;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
font-family: var(--font);
}
.data-card-sub {
font-size: 10.5px;
color: var(--sidebar-text-muted);
font-size: 11.5px;
color: #64748b;
font-weight: 500;
margin-top: 1px;
margin-top: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.data-card-badge {
font-size: 9px;
font-size: 10px;
font-weight: 800;
padding: 2px 6px;
border-radius: 99px;
letter-spacing: 0.3px;
padding: 4px 10px;
border-radius: 8px;
letter-spacing: 0.5px;
text-transform: uppercase;
flex-shrink: 0;
align-self: flex-start;
margin-top: 2px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.badge-24jam { background: #dcfce7; color: #166534; }
.badge-tidak24 { background: #fee2e2; color: #991b1b; }
.badge-dalam-jk { background: #dcfce7; color: #166534; }
.badge-luar-jk { background: #fee2e2; color: #991b1b; }
.badge-shm { background: #dcfce7; color: #166534; }
.badge-hgb { background: #dbeafe; color: #1e40af; }
.badge-hgu { background: #fef9c3; color: #854d0e; }
.badge-hp { background: #f3e8ff; color: #6b21a8; }
.badge-geo { background: #f1f5f9; color: #475569; }
.badge-24jam { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0;}
.badge-tidak24 { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca;}
.badge-dalam-jk { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0;}
.badge-luar-jk { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca;}
.badge-shm { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0;}
.badge-hgb { background: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe;}
.badge-hgu { background: #fef9c3; color: #854d0e; border: 1px solid #fef08a;}
.badge-hp { background: #f3e8ff; color: #6b21a8; border: 1px solid #e9d5ff;}
.badge-geo { background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0;}
.data-card-coords {
font-size: 9.5px;
color: #94a3b8;
font-size: 11px;
color: #64748b;
font-family: var(--font-mono);
font-weight: 500;
display: flex;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;
opacity: 0.8;
padding-left: 0;
margin-top: 2px;
gap: 6px;
background: #f8fafc;
padding: 6px 10px;
border-radius: 8px;
border: 1px solid #e2e8f0;
margin-top: 6px;
align-self: flex-start;
transition: background 0.3s ease;
}
.data-card:hover .data-card-coords {
background: #f1f5f9;
color: #334155;
}
.data-card-footer {
margin-top: 4px;
padding-top: 4px;
border-top: 1px dashed rgba(0,0,0,0.06);
margin-top: 8px;
padding-top: 10px;
border-top: 1px solid rgba(0,0,0,0.04);
width: 100%;
}
.data-card-footer span, .data-card-footer span[class^="badge-"] {
font-size: 9.5px !important;
font-size: 10px !important;
}
/* Tab System for Modul Utuh */

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -583,6 +583,7 @@ function renderPenduduk(item) {
<span class="popup-badge ${badgeClass}">${cek.status}</span>
</div>
<div class="popup-row"><strong>NIK</strong><span>${item.nik || '-'}</span></div>
<div class="popup-row"><strong>Alamat</strong><span>${item.alamat || '-'}</span></div>
<div class="popup-row"><strong>Ditangani</strong><span>${cek.nama || item.fp_nama || '-'}</span></div>
<div class="popup-row"><strong>Keterangan</strong><span>${item.keterangan || '-'}</span></div>
</div>
@@ -740,7 +741,7 @@ Promise.all([
title: 'Sungai Besar', color: '#0d47a1', fillColor: '#90caf9',
weight: 2, fitBounds: false, addToMap: false
}),
loadGeoJSON('data/admin_kecamatan.geojson', {
loadGeoJSON('data/Admin_Kecamatan.geojson', {
title: 'Admin Kecamatan', dynamicColor: true,
color: '#ef6c00', weight: 1.5, fitBounds: false, addToMap: false
}),
@@ -763,19 +764,27 @@ Promise.all([
------------------------------------------------------- */
function reverseGeocode(lat, lng) {
el('fp_alamat').value = '';
el('geocodeStatus').textContent = '\u23F3 Mencari alamat...';
if (el('fp_alamat')) el('fp_alamat').value = '';
if (el('pm_alamat')) el('pm_alamat').value = '';
const statusEl = el('geocodeStatus');
if (statusEl) statusEl.textContent = '\u23F3 Mencari alamat...';
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=id`)
.then(r => r.json())
.then(data => {
el('fp_alamat').value = data.display_name || 'Alamat tidak ditemukan';
el('geocodeStatus').textContent = '\u2713';
setTimeout(() => { el('geocodeStatus').textContent = ''; }, 2000);
const alamat = data.display_name || 'Alamat tidak ditemukan';
if (el('fp_alamat')) el('fp_alamat').value = alamat;
if (el('pm_alamat')) el('pm_alamat').value = alamat;
if (statusEl) {
statusEl.textContent = '\u2713';
setTimeout(() => { statusEl.textContent = ''; }, 2000);
}
})
.catch(() => {
el('fp_alamat').value = 'Gagal mengambil alamat';
el('geocodeStatus').textContent = '\u2715';
if (el('fp_alamat')) el('fp_alamat').value = 'Gagal mengambil alamat';
if (el('pm_alamat')) el('pm_alamat').value = 'Gagal mengambil alamat';
if (statusEl) statusEl.textContent = '\u2715';
});
}
@@ -840,6 +849,7 @@ map.on('click', e => {
previewMarker = L.marker(e.latlng, {
icon: createPendudukIcon(cek.status === 'Dalam Jangkauan')
}).addTo(map);
reverseGeocode(lat, lng);
showModal('pendudukModal');
return;
}
@@ -1317,7 +1327,7 @@ window.hapusData = function (tipe, id, pesan) {
/* -------------------------------------------------------
GLOBAL: UPDATE LOKASI DARI DRAG MARKER
------------------------------------------------------- */
window.updateLocation = function(tipe, id, lat, lng, nama) {
window.updateLocation = async function(tipe, id, lat, lng, nama) {
const urlMap = {
point: 'point/update_lokasi.php',
penduduk_miskin: 'penduduk_miskin/update_lokasi.php'
@@ -1332,6 +1342,17 @@ window.updateLocation = function(tipe, id, lat, lng, nama) {
const cek = cekJangkauan(lat, lng);
fd.append('status_jangkauan', cek.status);
fd.append('fasilitas_publik_id', cek.id || '');
// Ambil alamat baru
try {
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=id`);
const data = await r.json();
if (data && data.display_name) {
fd.append('alamat', data.display_name);
}
} catch(e) {
console.warn("Gagal mendapatkan alamat saat drag");
}
}
postData(urlMap[tipe], fd).then(res => {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -17
View File
@@ -4,26 +4,17 @@
// ============================================================
session_start();
$modul = $_GET['modul'] ?? 'utuh';
$modul = $_GET['modul'] ?? 'bantuan';
// TAHAP 1: Modul publik diizinkan tanpa login
$public_modules = ['spbu', 'tematik', 'digitasi'];
$is_public_access = in_array($modul, $public_modules);
if ($is_public_access) {
// Jika akses publik, berikan role petugas agar pengunjung dapat mengedit data
$currentUser = $_SESSION['username'] ?? 'Pengunjung Kelas';
$currentRole = $_SESSION['role'] ?? 'petugas';
} else {
// Modul privat wajib login
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$currentUser = $_SESSION['username'];
$currentRole = $_SESSION['role'];
// Aplikasi Kemiskinan Wajib Login Sepenuhnya
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$currentUser = $_SESSION['username'];
$currentRole = $_SESSION['role'];
include 'koneksi.php';
// Eksekusi ALTER TABLE secara diam-diam jika kolom belum ada
@@ -888,6 +879,12 @@ $modulTitle = $modulNames[$modul] ?? 'Analisis Spasial';
<div class="sj-value" id="pm_status"></div>
<div class="sj-ditangani" id="pm_ditangani"></div>
</div>
<div class="form-group">
<label class="form-label">Alamat / Lokasi <small>(otomatis dari koordinat)</small></label>
<div class="geocode-wrap">
<textarea class="form-input" id="pm_alamat" rows="2" placeholder="Menunggu lokasi peta..."></textarea>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Latitude</label>
+15
View File
@@ -59,6 +59,21 @@ try {
// 4. Masukkan data default settings jika belum ada
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES ('show_demo_accounts', '1')");
// 5. Migrasi Tabel Penduduk Miskin (Tambah foto_rumah, foto_kk)
$pm_cols = $pdo->query("SHOW COLUMNS FROM penduduk_miskin")->fetchAll(PDO::FETCH_COLUMN);
if (!in_array('alamat', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN alamat TEXT NULL AFTER fasilitas_publik_id");
}
if (!in_array('foto_rumah', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN foto_rumah VARCHAR(255) NULL AFTER fasilitas_publik_id");
}
if (!in_array('foto_kk', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN foto_kk VARCHAR(255) NULL AFTER foto_rumah");
}
if (!in_array('status_bantuan', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN status_bantuan ENUM('Belum Disalurkan', 'Proses', 'Sudah Disalurkan') DEFAULT 'Belum Disalurkan'");
}
} catch (PDOException $e) {
http_response_code(500);
die(json_encode([
+21 -29
View File
@@ -134,27 +134,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</p>
</div>
<!-- Bottom / Public Shortcuts -->
<div>
<h2 class="mb-3 flex items-center gap-2 text-sm font-bold text-white/90">
<svg viewBox="0 0 24 24" fill="none" class="h-4.5 w-4.5 text-rose-300" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12h4l2-9 5 18 3-10h4"></path>
<!-- Bottom / Features Info -->
<div class="mt-8">
<h2 class="mb-4 flex items-center gap-2 text-sm font-bold text-white/90 uppercase tracking-wider">
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5 text-rose-300" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
Project Kelas (Akses Publik):
Sistem Pengamanan Tertutup
</h2>
<div class="flex flex-col gap-3">
<a href="index.php?modul=spbu" class="group flex items-center gap-3 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm font-bold text-white transition-all hover:-translate-y-0.5 hover:border-white/30 hover:bg-white/20">
<span class="text-xl transition-transform group-hover:scale-110"></span>
Peta Lokasi SPBU
</a>
<a href="index.php?modul=tematik" class="group flex items-center gap-3 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm font-bold text-white transition-all hover:-translate-y-0.5 hover:border-white/30 hover:bg-white/20">
<span class="text-xl transition-transform group-hover:scale-110">🗺️</span>
Peta Tematik
</a>
<a href="index.php?modul=digitasi" class="group flex items-center gap-3 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm font-bold text-white transition-all hover:-translate-y-0.5 hover:border-white/30 hover:bg-white/20">
<span class="text-xl transition-transform group-hover:scale-110">📐</span>
Digitasi Lahan Kota
</a>
<div class="flex flex-col gap-4 text-white/80 text-sm">
<div class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-white/20 p-1"><svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg></div>
<p><strong>Database Terpusat:</strong> Melacak data penduduk miskin berdasarkan NIK secara real-time.</p>
</div>
<div class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-white/20 p-1"><svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg></div>
<p><strong>Tracking Bantuan:</strong> Verifikasi silang penyaluran BLT dan PKH di berbagai wilayah.</p>
</div>
<div class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-white/20 p-1"><svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg></div>
<p><strong>Analisis Spasial:</strong> Pemetaan kantong kemiskinan untuk kebijakan tepat sasaran.</p>
</div>
</div>
</div>
</div>
@@ -179,7 +179,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
<input type="text" id="username" name="username" class="block w-full rounded-lg border border-border bg-white py-2 pl-9 pr-3 text-sm transition-all focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 hover:border-primary/60" placeholder="admin" required autocomplete="username">
<input type="text" id="username" name="username" class="block w-full rounded-lg border border-border bg-white py-2 pl-9 pr-3 text-sm transition-all focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 hover:border-primary/60" placeholder="contoh@email.com atau username" required autocomplete="username">
</div>
</div>
@@ -223,15 +223,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Belum punya akun? <a href="register.php" class="font-semibold text-primary hover:underline">Daftar sekarang</a>
</div>
<?php if ($show_demo === 1): ?>
<!-- Inline Demo Accounts -->
<div class="mt-5 flex items-center justify-center gap-2 text-xs">
<span class="font-medium text-muted-foreground">Isi Cepat Demo:</span>
<button type="button" onclick="fillDemo('admin', 'password')" class="rounded bg-rose-100 px-2 py-1 font-semibold text-rose-700 transition hover:bg-rose-200">👑 Admin</button>
<button type="button" onclick="fillDemo('petugas', 'password')" class="rounded bg-slate-100 px-2 py-1 font-semibold text-slate-700 transition hover:bg-slate-200">📝 Petugas</button>
<button type="button" onclick="fillDemo('guest', 'password')" class="rounded bg-slate-100 px-2 py-1 font-semibold text-slate-700 transition hover:bg-slate-200">👁️ Guest</button>
</div>
<?php endif; ?>
<!-- Akun demo telah dinonaktifkan -->
<p class="mt-8 text-center text-[10px] text-muted-foreground uppercase tracking-wider">
&copy; <script>document.write(new Date().getFullYear())</script> Kementerian Analisis Spasial
+5
View File
@@ -0,0 +1,5 @@
[phases.build]
cmds = [
"mkdir -p /app/uploads",
"chmod -R 777 /app/uploads"
]
@@ -13,6 +13,7 @@ header('Content-Type: application/json');
$nama = trim($_POST['nama'] ?? '');
$nik = trim($_POST['nik'] ?? '');
$keterangan = trim($_POST['keterangan'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
$status_jangkauan= trim($_POST['status_jangkauan']?? 'Luar Jangkauan');
@@ -35,34 +36,49 @@ function uploadFoto($file_input_name) {
$file_tmp_path = $_FILES[$file_input_name]['tmp_name'];
$file_name = $_FILES[$file_input_name]['name'];
$file_size = $_FILES[$file_input_name]['size'];
$file_type = mime_content_type($file_tmp_path);
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg'];
$max_size = 2 * 1024 * 1024; // 2MB
$file_type = 'unknown';
if (function_exists('mime_content_type')) {
$file_type = mime_content_type($file_tmp_path);
} else {
$file_type = $_FILES[$file_input_name]['type'];
}
if (in_array($file_type, $allowed_mime_types) && $file_size <= $max_size) {
$file_extension = pathinfo($file_name, PATHINFO_EXTENSION);
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
$max_size = 10 * 1024 * 1024; // 10MB
if ($file_size <= $max_size) {
$file_extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$new_file_name = uniqid($file_input_name . '_', true) . '.' . $file_extension;
$upload_file_dir = '../uploads/';
$upload_file_dir = __DIR__ . '/../uploads/';
if (!is_dir($upload_file_dir)) {
@mkdir($upload_file_dir, 0777, true);
}
$dest_path = $upload_file_dir . $new_file_name;
if (move_uploaded_file($file_tmp_path, $dest_path)) {
if (@move_uploaded_file($file_tmp_path, $dest_path)) {
return $new_file_name;
} else {
throw new Exception("Gagal memindahkan file upload ke: " . $dest_path);
}
} else {
throw new Exception("Ukuran file melebihi batas 10MB.");
}
} else if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_NO_FILE) {
throw new Exception("Error upload PHP: " . $_FILES[$file_input_name]['error']);
}
return null;
}
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
try {
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
$stmt = $pdo->prepare(
"INSERT INTO penduduk_miskin (nama, nik, keterangan, latitude, longitude, status_jangkauan, fasilitas_publik_id, foto_rumah, foto_kk, status_bantuan)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
"INSERT INTO penduduk_miskin (nama, nik, keterangan, alamat, latitude, longitude, status_jangkauan, fasilitas_publik_id, foto_rumah, foto_kk, status_bantuan)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([$nama, $nik, $keterangan, $latitude, $longitude, $status_jangkauan, $fp_id, $foto_rumah, $foto_kk, $status_bantuan]);
$stmt->execute([$nama, $nik, $keterangan, $alamat, $latitude, $longitude, $status_jangkauan, $fp_id, $foto_rumah, $foto_kk, $status_bantuan]);
$id = $pdo->lastInsertId();
$stmtSelect = $pdo->prepare("SELECT pm.*, fp.nama AS fp_nama FROM penduduk_miskin pm LEFT JOIN fasilitas_publik fp ON pm.fasilitas_publik_id = fp.id WHERE pm.id = ?");
@@ -70,7 +86,7 @@ try {
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (PDOException $e) {
} catch (Exception $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
@@ -33,31 +33,48 @@ function uploadFoto($file_input_name) {
$file_tmp_path = $_FILES[$file_input_name]['tmp_name'];
$file_name = $_FILES[$file_input_name]['name'];
$file_size = $_FILES[$file_input_name]['size'];
$file_type = mime_content_type($file_tmp_path);
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg'];
$max_size = 2 * 1024 * 1024; // 2MB
$file_type = 'unknown';
if (function_exists('mime_content_type')) {
$file_type = mime_content_type($file_tmp_path);
} else {
$file_type = $_FILES[$file_input_name]['type'];
}
if (in_array($file_type, $allowed_mime_types) && $file_size <= $max_size) {
$file_extension = pathinfo($file_name, PATHINFO_EXTENSION);
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
$max_size = 10 * 1024 * 1024; // 10MB
if ($file_size <= $max_size) {
$file_extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$new_file_name = uniqid($file_input_name . '_', true) . '.' . $file_extension;
$upload_file_dir = '../uploads/';
$upload_file_dir = __DIR__ . '/../uploads/';
if (!is_dir($upload_file_dir)) {
@mkdir($upload_file_dir, 0777, true);
}
$dest_path = $upload_file_dir . $new_file_name;
if (move_uploaded_file($file_tmp_path, $dest_path)) {
if (@move_uploaded_file($file_tmp_path, $dest_path)) {
return $new_file_name;
} else {
throw new Exception("Gagal memindahkan file upload ke: " . $dest_path);
}
} else {
throw new Exception("Ukuran file melebihi batas 10MB.");
}
} else if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_NO_FILE) {
throw new Exception("Error upload PHP: " . $_FILES[$file_input_name]['error']);
}
return null;
}
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
$alamat = trim($_POST['alamat'] ?? '');
try {
$query = "UPDATE penduduk_miskin SET nama=?, nik=?, keterangan=?, latitude=?, longitude=?, status_jangkauan=?, fasilitas_publik_id=?, status_bantuan=?";
$params = [$nama, $nik, $keterangan, $latitude, $longitude, $status_jangkauan, $fp_id, $status_bantuan];
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
$query = "UPDATE penduduk_miskin SET nama=?, nik=?, keterangan=?, alamat=?, latitude=?, longitude=?, status_jangkauan=?, fasilitas_publik_id=?, status_bantuan=?";
$params = [$nama, $nik, $keterangan, $alamat, $latitude, $longitude, $status_jangkauan, $fp_id, $status_bantuan];
if ($foto_rumah) {
$query .= ", foto_rumah=?";
@@ -79,7 +96,7 @@ try {
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
} catch (Exception $e) {
echo json_encode(['status'=>'error','message'=>'Gagal memperbarui: '.$e->getMessage()]);
}
?>
@@ -0,0 +1,48 @@
<?php
session_start();
include '../koneksi.php';
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
$id = $_POST['id'] ?? '';
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
$status_jangkauan = $_POST['status_jangkauan'] ?? '';
$fasilitas_publik_id = $_POST['fasilitas_publik_id'] ?? null;
$alamat = $_POST['alamat'] ?? null;
if (!$id || !$latitude || !$longitude) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
exit;
}
if ($fasilitas_publik_id === '') {
$fasilitas_publik_id = null;
}
try {
if ($status_jangkauan !== '') {
if ($alamat !== null) {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, status_jangkauan = ?, fasilitas_publik_id = ?, alamat = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $status_jangkauan, $fasilitas_publik_id, $alamat, $id]);
} else {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, status_jangkauan = ?, fasilitas_publik_id = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $status_jangkauan, $fasilitas_publik_id, $id]);
}
} else {
if ($alamat !== null) {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, alamat = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $alamat, $id]);
} else {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $id]);
}
}
echo json_encode(['status' => 'success']);
} catch (PDOException $e) {
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
}
-17
View File
@@ -213,23 +213,6 @@ $google_auth_url = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" . G
<!-- Card -->
<div class="rounded-2xl border border-white/60 bg-white/70 p-6 shadow-[0_8px_30px_rgb(0,0,0,0.04)] backdrop-blur-xl sm:p-8">
<!-- Google Auth Button -->
<button type="button" onclick="startGoogleAuth()" class="flex h-11 w-full items-center justify-center gap-3 rounded-xl border border-border bg-white text-sm font-semibold text-foreground shadow-sm hover:bg-gray-50 transition-colors mb-6">
<svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
</svg>
Daftar dengan Google
</button>
<div class="relative flex items-center mb-6">
<div class="flex-grow border-t border-border"></div>
<span class="flex-shrink-0 mx-4 text-xs text-muted-foreground uppercase tracking-wider font-semibold">atau dengan email</span>
<div class="flex-grow border-t border-border"></div>
</div>
<form action="register.php" method="POST" class="flex flex-col gap-4">
<input type="hidden" name="register" value="1">
+1
View File
@@ -0,0 +1 @@
<?php require 'koneksi.php'; print_r($pdo->query('SELECT id, nama, foto_rumah, foto_kk FROM penduduk_miskin ORDER BY id DESC LIMIT 5')->fetchAll(PDO::FETCH_ASSOC));

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 374 KiB

+5
View File
@@ -0,0 +1,5 @@
[phases.build]
cmds = [
"mkdir -p /app/kemiskinan/uploads",
"chmod -R 777 /app/kemiskinan/uploads"
]
+120
View File
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panduan Pengguna WebGIS</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 21cm; /* Ukuran A4 */
margin: 0 auto;
padding: 2cm;
background: white;
}
h1 { text-align: center; color: #e11d48; margin-bottom: 5px; }
.subtitle { text-align: center; font-size: 1.2em; font-weight: bold; margin-bottom: 40px; color: #555; }
h2 { border-bottom: 2px solid #e11d48; padding-bottom: 5px; margin-top: 30px; color: #1e293b; }
h3 { color: #334155; margin-top: 20px; }
p, li { font-size: 11pt; text-align: justify; }
ul { margin-bottom: 15px; }
.tip-box {
background-color: #fff1f2;
border-left: 5px solid #e11d48;
padding: 15px;
margin: 20px 0;
border-radius: 0 5px 5px 0;
}
.tip-box strong { color: #be123c; }
/* Pengaturan agar saat diprint (Save as PDF) rapi */
@media print {
body { padding: 0; margin: 1cm; max-width: none; }
.tip-box { border: 1px solid #e11d48; }
.no-print { display: none; }
}
</style>
</head>
<body>
<h1>PANDUAN PENGGUNA (USER MANUAL)</h1>
<div class="subtitle">Sistem Informasi Geospasial (WebGIS) Pengentasan Kemiskinan</div>
<h2>1. Pendahuluan</h2>
<p>Aplikasi WebGIS ini dirancang sebagai platform pemetaan cerdas untuk analisis spasial persebaran infrastruktur dan demografi kemiskinan. Sistem ini mengadopsi arsitektur <em>Monorepo</em> yang terbagi menjadi dua modul utama:</p>
<ul>
<li><strong>Modul Publik (/publik)</strong>: Portal terbuka untuk masyarakat umum guna melihat persebaran titik fasilitas, jaringan jalan, dan pemetaan poligon lahan.</li>
<li><strong>Modul Kemiskinan (/kemiskinan)</strong>: Sistem tertutup (memerlukan autentikasi) untuk admin dan verifikator lapangan dalam mendata penduduk miskin, menganalisis radius jangkauan bantuan, dan mengelola fasilitas publik.</li>
</ul>
<h2>2. Cara Mengakses Aplikasi</h2>
<ol>
<li>Buka <em>web browser</em> (disarankan menggunakan Google Chrome atau Mozilla Firefox).</li>
<li>Masukkan alamat URL (<em>link</em>) aplikasi yang telah di-deploy (misalnya: <code>https://webgis-kemiskinan.ifuntanhub.dev</code>).</li>
<li>Untuk masuk ke sistem pengelola, gunakan akun yang telah disetujui (Admin/Petugas).</li>
</ol>
<h2>3. Panduan Modul Publik (Tanpa Login)</h2>
<p>Modul ini dapat diakses oleh siapa saja. Pengguna dapat:</p>
<ul>
<li><strong>Melihat Peta</strong>: Melakukan <em>zoom in/out</em> dan menggeser peta (<em>panning</em>).</li>
<li><strong>Melihat Informasi Titik (Marker)</strong>: Klik pada <em>marker</em> (ikon) di peta untuk memunculkan kotak informasi (<em>pop-up</em>) mengenai fasilitas atau lokasi tersebut.</li>
<li><strong>Melihat Garis dan Area</strong>: Klik pada rute jalan (garis) atau area tanah (poligon) untuk melihat luas dan detail spesifiknya.</li>
</ul>
<h2>4. Panduan Modul Kemiskinan (Khusus Admin/Petugas)</h2>
<h3>4.1. Registrasi dan Login</h3>
<ul>
<li><strong>Login</strong>: Masukkan <em>Username/Email</em> dan <em>Password</em> pada halaman Login. Jika berhasil, Anda akan diarahkan ke Dashboard.</li>
<li><strong>Registrasi</strong>: Jika belum memiliki akun, klik "Daftar sekarang". Pilih peran sebagai <code>Petugas</code>. Akun petugas memerlukan persetujuan dari Admin Utama sebelum bisa digunakan.</li>
</ul>
<h3>4.2. Navigasi Sidebar dan Alat Peta (Toolbar)</h3>
<p>Di halaman Dashboard, seluruh menu navigasi dan alat-alat digitasi terletak pada <strong>Sidebar</strong> di sebelah kiri layar.</p>
<div class="tip-box">
<strong>💡 INFO PENTING:</strong><br>
Sidebar ini dapat di-<em>scroll</em> (digulir) ke bawah! Jika Anda membuka dari laptop berlayar kecil, <strong>silakan scroll sidebar ke bawah</strong> untuk menemukan menu-menu tambahan yang tersembunyi seperti Modul Bantuan, Pencarian, dan Manajemen Pengguna.
</div>
<p>Berikut adalah alat-alat pemetaan yang tersedia di Sidebar:</p>
<ul>
<li><strong>📍 Mode Tambah Titik</strong>: Klik alat ini, lalu klik di sembarang tempat pada peta untuk menambahkan titik lokasi baru (Fasilitas Publik atau SPBU).</li>
<li><strong>🛣️ Mode Gambar Jalan</strong>: Klik alat ini, lalu klik titik awal pada peta. Lanjutkan mengklik untuk membentuk rute jalan. Klik ganda (<em>double-click</em>) untuk mengakhiri rute.</li>
<li><strong>⬠ Mode Gambar Area</strong>: Klik alat ini, klik titik-titik sudut area yang ingin dibentuk, lalu klik kembali titik pertama untuk menutup area (Poligon).</li>
</ul>
<h3>4.3. Modul Bantuan & Penduduk Miskin</h3>
<p>Fitur utama dari aplikasi ini adalah memetakan rumah penduduk miskin dan menganalisis radius bantuan secara otomatis.</p>
<ol>
<li>Pilih <strong>Modul Bantuan</strong> dari Sidebar.</li>
<li>Aktifkan <strong>Mode Tambah Titik</strong>.</li>
<li>Klik pada lokasi rumah penduduk miskin di peta.</li>
<li>Pada form isian, masukkan Nama Kepala Keluarga & NIK, Foto Rumah, Foto KK, dan pilih <strong>Fasilitas Publik Terdekat</strong>.</li>
<li>Sistem akan otomatis menghitung apakah rumah tersebut masuk dalam <strong>Radius Jangkauan</strong> bantuan fasilitas publik tersebut atau tidak.</li>
</ol>
<h3>4.4. Analisis Spasial Radius</h3>
<ul>
<li>Saat melihat data fasilitas publik (seperti Kantor Desa atau Posko Bantuan), sistem akan memunculkan <strong>Lingkaran Radius</strong> (berwarna merah/biru transparan) di peta.</li>
<li>Rumah penduduk miskin yang berada di dalam lingkaran tersebut otomatis berstatus <strong>"Dalam Jangkauan"</strong>, sedangkan yang di luar otomatis <strong>"Luar Jangkauan"</strong>.</li>
</ul>
<h3>4.5. Mengelola Akun (Khusus Admin)</h3>
<ol>
<li>Buka menu <strong>Manajemen Pengguna</strong> di bagian bawah Sidebar.</li>
<li>Anda dapat melihat daftar semua pengguna yang terdaftar.</li>
<li>Untuk akun Petugas yang berstatus Pending, Admin dapat menekan tombol <strong>Setujui (Approve)</strong> atau Tolak.</li>
</ol>
<h2>5. Penyelesaian Masalah (Troubleshooting)</h2>
<ul>
<li><strong>Peta tidak muncul / Blank Putih</strong>: Pastikan koneksi internet Anda stabil karena aplikasi memerlukan koneksi ke server <em>Leaflet/OpenStreetMap</em>.</li>
<li><strong>Gagal Upload Foto</strong>: Pastikan ukuran foto Rumah/KK tidak melebihi 2 Megabyte (MB) dan berformat <code>.jpg</code> atau <code>.png</code>.</li>
</ul>
</body>
</html>
-37
View File
@@ -1,37 +0,0 @@
<?php
session_start();
include '../koneksi.php';
if (!isset($_SESSION['user_id'])) {
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
$id = $_POST['id'] ?? '';
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
$status_jangkauan = $_POST['status_jangkauan'] ?? '';
$fasilitas_publik_id = $_POST['fasilitas_publik_id'] ?? null;
if (!$id || !$latitude || !$longitude) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
exit;
}
if ($fasilitas_publik_id === '') {
$fasilitas_publik_id = null;
}
try {
if ($status_jangkauan !== '') {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, status_jangkauan = ?, fasilitas_publik_id = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $status_jangkauan, $fasilitas_publik_id, $id]);
} else {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $id]);
}
echo json_encode(['status' => 'success']);
} catch (PDOException $e) {
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
}
+30
View File
@@ -0,0 +1,30 @@
<?php
include '../koneksi.php';
header('Content-Type: application/json');
try {
// 1. Fetch all fasilitas_publik
$stmtFp = $pdo->query("SELECT * FROM fasilitas_publik ORDER BY id DESC");
$fasilitasPublik = $stmtFp->fetchAll();
// 2. Fetch all penduduk_miskin (dengan nama FP jika ada join)
$stmtPm = $pdo->query("
SELECT p.*, f.nama as fp_nama
FROM penduduk_miskin p
LEFT JOIN fasilitas_publik f ON p.fasilitas_publik_id = f.id
ORDER BY p.id DESC
");
$pendudukMiskin = $stmtPm->fetchAll();
echo json_encode([
'status' => 'success',
'fasilitasPublik' => $fasilitasPublik,
'pendudukMiskin' => $pendudukMiskin
]);
} catch (PDOException $e) {
echo json_encode([
'status' => 'error',
'message' => 'Database error: ' . $e->getMessage()
]);
}
?>
+20
View File
@@ -0,0 +1,20 @@
<?php
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_GET['id'] ?? 0);
if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); exit; }
try {
$stmt = $pdo->prepare("SELECT * FROM fasilitas_publik WHERE id=?");
$stmt->execute([$id]);
$data = $stmt->fetch();
if ($data) {
echo json_encode(['status'=>'success','data'=>$data]);
} else {
echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan.']);
}
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+24
View File
@@ -0,0 +1,24 @@
<?php
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_GET['id'] ?? 0);
if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); exit; }
try {
$stmt = $pdo->prepare(
"SELECT pm.*, fp.nama AS fp_nama FROM penduduk_miskin pm
LEFT JOIN fasilitas_publik fp ON pm.fasilitas_publik_id = fp.id
WHERE pm.id=?"
);
$stmt->execute([$id]);
$data = $stmt->fetch();
if ($data) {
echo json_encode(['status'=>'success','data'=>$data]);
} else {
echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan.']);
}
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+24
View File
@@ -0,0 +1,24 @@
<?php
include '../koneksi.php';
header('Content-Type: application/json');
$id = $_GET['id'] ?? null;
if (!$id) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak diberikan']);
exit;
}
try {
$stmt = $pdo->prepare("SELECT * FROM points WHERE id = ?");
$stmt->execute([$id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data) {
echo json_encode(['status' => 'success', 'data' => $data]);
} else {
echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan']);
}
} catch (PDOException $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
+23
View File
@@ -0,0 +1,23 @@
<?php
include '../koneksi.php';
header('Content-Type: application/json');
try {
$totalFP = $pdo->query("SELECT COUNT(*) FROM fasilitas_publik")->fetchColumn();
$totalPM = $pdo->query("SELECT COUNT(*) FROM penduduk_miskin")->fetchColumn();
$ditangani= $pdo->query("SELECT COUNT(*) FROM penduduk_miskin WHERE status_jangkauan='Dalam Jangkauan'")->fetchColumn();
$belum = $totalPM - $ditangani;
$persen = $totalPM > 0 ? round(($ditangani / $totalPM) * 100, 1) : 0;
echo json_encode([
'status' => 'success',
'total_ri' => (int)$totalFP,
'total_pm' => (int)$totalPM,
'ditangani' => (int)$ditangani,
'belum_ditangani'=> (int)$belum,
'persentase' => $persen,
]);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+28
View File
@@ -0,0 +1,28 @@
<?php
session_start();
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_POST['id'] ?? 0);
$status_bantuan = trim($_POST['status_bantuan'] ?? '');
if (!$id || !$status_bantuan) {
echo json_encode(['status'=>'error','message'=>'Data tidak valid.']);
exit;
}
try {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET status_bantuan=? WHERE id=?");
$stmt->execute([$status_bantuan, $id]);
echo json_encode(['status'=>'success']);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
<?php
session_start();
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak. Hanya admin yang dapat menghapus data.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); exit; }
try {
$pdo->prepare("DELETE FROM fasilitas_publik WHERE id=?")->execute([$id]);
echo json_encode(['status'=>'success']);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+48
View File
@@ -0,0 +1,48 @@
<?php
session_start();
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak.']);
exit;
}
// fasilitas_publik/simpan.php
include '../koneksi.php';
header('Content-Type: application/json');
$nama = trim($_POST['nama'] ?? '');
$jenis = trim($_POST['jenis'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$radius = (int)($_POST['radius'] ?? 300);
$kontak = trim($_POST['kontak'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
if (!$nama || !$jenis || !$latitude || !$longitude) {
echo json_encode(['status'=>'error','message'=>'Nama, Jenis, Latitude, dan Longitude wajib diisi.']);
exit;
}
if (!is_numeric($latitude) || !is_numeric($longitude) || !is_numeric($radius)) {
echo json_encode(['status'=>'error','message'=>'Koordinat dan radius harus berupa angka.']);
exit;
}
try {
$stmt = $pdo->prepare(
"INSERT INTO fasilitas_publik (nama, jenis, alamat, radius, kontak, latitude, longitude)
VALUES (?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([$nama, $jenis, $alamat, $radius, $kontak, $latitude, $longitude]);
$id = $pdo->lastInsertId();
$stmtSelect = $pdo->prepare("SELECT * FROM fasilitas_publik WHERE id = ?");
$stmtSelect->execute([$id]);
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>'Gagal menyimpan: '.$e->getMessage()]);
}
?>
+41
View File
@@ -0,0 +1,41 @@
<?php
session_start();
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_POST['id'] ?? 0);
$nama = trim($_POST['nama'] ?? '');
$jenis = trim($_POST['jenis'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$radius = (int)($_POST['radius'] ?? 300);
$kontak = trim($_POST['kontak'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
if (!$id || !$nama || !$jenis || !$latitude || !$longitude) {
echo json_encode(['status'=>'error','message'=>'Data tidak lengkap.']);
exit;
}
try {
$stmt = $pdo->prepare(
"UPDATE fasilitas_publik SET nama=?, jenis=?, alamat=?, radius=?, kontak=?, latitude=?, longitude=? WHERE id=?"
);
$stmt->execute([$nama, $jenis, $alamat, $radius, $kontak, $latitude, $longitude, $id]);
$stmtSelect = $pdo->prepare("SELECT * FROM fasilitas_publik WHERE id=?");
$stmtSelect->execute([$id]);
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+1104
View File
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
<?php
// ============================================================
// koneksi.php — Database Connection (PDO)
// ============================================================
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') !== false ? getenv('DB_PASS') : '');
define('DB_NAME', getenv('DB_NAME') ?: 'webgis');
define('DB_CHARSET', 'utf8mb4');
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=' . DB_CHARSET;
$options = [
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, $options);
// ============================================================
// AUTO-MIGRATION SCRIPT (Sesuai Permintaan)
// ============================================================
// 1. Buat tabel users jika belum ada (fallback)
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('admin','petugas','guest') NOT NULL DEFAULT 'guest',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB");
// 2. Cek dan tambahkan kolom baru pada tabel users
$columns = $pdo->query("SHOW COLUMNS FROM users")->fetchAll(PDO::FETCH_COLUMN);
if (!in_array('email', $columns)) {
$pdo->exec("ALTER TABLE users ADD COLUMN email VARCHAR(150) UNIQUE NULL AFTER username");
}
if (!in_array('nama_lengkap', $columns)) {
$pdo->exec("ALTER TABLE users ADD COLUMN nama_lengkap VARCHAR(150) NULL AFTER email");
}
if (!in_array('status', $columns)) {
// Update user lama menjadi active terlebih dahulu jika diperlukan, tapi defaultnya active
$pdo->exec("ALTER TABLE users ADD COLUMN status ENUM('pending', 'active') NOT NULL DEFAULT 'active' AFTER role");
}
if (!in_array('auth_provider', $columns)) {
$pdo->exec("ALTER TABLE users ADD COLUMN auth_provider ENUM('local', 'google') NOT NULL DEFAULT 'local' AFTER status");
}
// 3. Buat tabel settings
$pdo->exec("CREATE TABLE IF NOT EXISTS settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value VARCHAR(255) NOT NULL
) ENGINE=InnoDB");
// 4. Masukkan data default settings jika belum ada
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES ('show_demo_accounts', '1')");
// 5. Migrasi Tabel Penduduk Miskin (Tambah foto_rumah, foto_kk)
$pm_cols = $pdo->query("SHOW COLUMNS FROM penduduk_miskin")->fetchAll(PDO::FETCH_COLUMN);
if (!in_array('alamat', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN alamat TEXT NULL AFTER fasilitas_publik_id");
}
if (!in_array('foto_rumah', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN foto_rumah VARCHAR(255) NULL AFTER fasilitas_publik_id");
}
if (!in_array('foto_kk', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN foto_kk VARCHAR(255) NULL AFTER foto_rumah");
}
if (!in_array('status_bantuan', $pm_cols)) {
$pdo->exec("ALTER TABLE penduduk_miskin ADD COLUMN status_bantuan ENUM('Belum Disalurkan', 'Proses', 'Sudah Disalurkan') DEFAULT 'Belum Disalurkan'");
}
} catch (PDOException $e) {
http_response_code(500);
die(json_encode([
'status' => 'error',
'message' => 'Koneksi database gagal: ' . $e->getMessage()
]));
}
// Legacy mysqli compatibility (untuk file yang masih menggunakannya)
$conn = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn) {
mysqli_set_charset($conn, DB_CHARSET);
}
?>
+22
View File
@@ -0,0 +1,22 @@
<?php
session_start();
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak. Hanya admin yang dapat menghapus data.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); exit; }
try {
$pdo->prepare("DELETE FROM penduduk_miskin WHERE id=?")->execute([$id]);
echo json_encode(['status'=>'success']);
} catch (PDOException $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+92
View File
@@ -0,0 +1,92 @@
<?php
session_start();
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$nama = trim($_POST['nama'] ?? '');
$nik = trim($_POST['nik'] ?? '');
$keterangan = trim($_POST['keterangan'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
$status_jangkauan= trim($_POST['status_jangkauan']?? 'Luar Jangkauan');
$status_bantuan = trim($_POST['status_bantuan'] ?? 'Belum Disalurkan');
$fasilitas_publik_id = trim($_POST['fasilitas_publik_id'] ?? '');
if (!$nama || !$latitude || !$longitude) {
echo json_encode(['status'=>'error','message'=>'Nama dan lokasi wajib diisi.']);
exit;
}
$valid_status = ['Dalam Jangkauan','Luar Jangkauan'];
if (!in_array($status_jangkauan, $valid_status)) $status_jangkauan = 'Luar Jangkauan';
$fp_id = ($fasilitas_publik_id !== '') ? (int)$fasilitas_publik_id : null;
// Fungsi upload foto
function uploadFoto($file_input_name) {
if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES[$file_input_name]['tmp_name'];
$file_name = $_FILES[$file_input_name]['name'];
$file_size = $_FILES[$file_input_name]['size'];
$file_type = 'unknown';
if (function_exists('mime_content_type')) {
$file_type = mime_content_type($file_tmp_path);
} else {
$file_type = $_FILES[$file_input_name]['type'];
}
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
$max_size = 10 * 1024 * 1024; // 10MB
if ($file_size <= $max_size) {
$file_extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$new_file_name = uniqid($file_input_name . '_', true) . '.' . $file_extension;
$upload_file_dir = __DIR__ . '/../../kemiskinan/uploads/';
if (!is_dir($upload_file_dir)) {
@mkdir($upload_file_dir, 0777, true);
}
$dest_path = $upload_file_dir . $new_file_name;
if (@move_uploaded_file($file_tmp_path, $dest_path)) {
return $new_file_name;
} else {
throw new Exception("Gagal memindahkan file upload ke: " . $dest_path);
}
} else {
throw new Exception("Ukuran file melebihi batas 10MB.");
}
} else if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_NO_FILE) {
throw new Exception("Error upload PHP: " . $_FILES[$file_input_name]['error']);
}
return null;
}
try {
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
$stmt = $pdo->prepare(
"INSERT INTO penduduk_miskin (nama, nik, keterangan, alamat, latitude, longitude, status_jangkauan, fasilitas_publik_id, foto_rumah, foto_kk, status_bantuan)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([$nama, $nik, $keterangan, $alamat, $latitude, $longitude, $status_jangkauan, $fp_id, $foto_rumah, $foto_kk, $status_bantuan]);
$id = $pdo->lastInsertId();
$stmtSelect = $pdo->prepare("SELECT pm.*, fp.nama AS fp_nama FROM penduduk_miskin pm LEFT JOIN fasilitas_publik fp ON pm.fasilitas_publik_id = fp.id WHERE pm.id = ?");
$stmtSelect->execute([$id]);
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (Exception $e) {
echo json_encode(['status'=>'error','message'=>$e->getMessage()]);
}
?>
+102
View File
@@ -0,0 +1,102 @@
<?php
session_start();
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['status'=>'error','message'=>'Akses ditolak.']);
exit;
}
include '../koneksi.php';
header('Content-Type: application/json');
$id = (int)($_POST['id'] ?? 0);
$nama = trim($_POST['nama'] ?? '');
$nik = trim($_POST['nik'] ?? '');
$keterangan = trim($_POST['keterangan'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
$status_jangkauan= trim($_POST['status_jangkauan']?? 'Luar Jangkauan');
$status_bantuan = trim($_POST['status_bantuan'] ?? 'Belum Disalurkan');
$fasilitas_publik_id = trim($_POST['fasilitas_publik_id'] ?? '');
if (!$id || !$nama || !$latitude || !$longitude) {
echo json_encode(['status'=>'error','message'=>'Data tidak lengkap.']);
exit;
}
$fp_id = ($fasilitas_publik_id !== '') ? (int)$fasilitas_publik_id : null;
// Fungsi upload foto
function uploadFoto($file_input_name) {
if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES[$file_input_name]['tmp_name'];
$file_name = $_FILES[$file_input_name]['name'];
$file_size = $_FILES[$file_input_name]['size'];
$file_type = 'unknown';
if (function_exists('mime_content_type')) {
$file_type = mime_content_type($file_tmp_path);
} else {
$file_type = $_FILES[$file_input_name]['type'];
}
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
$max_size = 10 * 1024 * 1024; // 10MB
if ($file_size <= $max_size) {
$file_extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$new_file_name = uniqid($file_input_name . '_', true) . '.' . $file_extension;
$upload_file_dir = __DIR__ . '/../../kemiskinan/uploads/';
if (!is_dir($upload_file_dir)) {
@mkdir($upload_file_dir, 0777, true);
}
$dest_path = $upload_file_dir . $new_file_name;
if (@move_uploaded_file($file_tmp_path, $dest_path)) {
return $new_file_name;
} else {
throw new Exception("Gagal memindahkan file upload ke: " . $dest_path);
}
} else {
throw new Exception("Ukuran file melebihi batas 10MB.");
}
} else if (isset($_FILES[$file_input_name]) && $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_NO_FILE) {
throw new Exception("Error upload PHP: " . $_FILES[$file_input_name]['error']);
}
return null;
}
$alamat = trim($_POST['alamat'] ?? '');
try {
$foto_rumah = uploadFoto('foto_rumah');
$foto_kk = uploadFoto('foto_kk');
$query = "UPDATE penduduk_miskin SET nama=?, nik=?, keterangan=?, alamat=?, latitude=?, longitude=?, status_jangkauan=?, fasilitas_publik_id=?, status_bantuan=?";
$params = [$nama, $nik, $keterangan, $alamat, $latitude, $longitude, $status_jangkauan, $fp_id, $status_bantuan];
if ($foto_rumah) {
$query .= ", foto_rumah=?";
$params[] = $foto_rumah;
}
if ($foto_kk) {
$query .= ", foto_kk=?";
$params[] = $foto_kk;
}
$query .= " WHERE id=?";
$params[] = $id;
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$stmtSelect = $pdo->prepare("SELECT pm.*, fp.nama AS fp_nama FROM penduduk_miskin pm LEFT JOIN fasilitas_publik fp ON pm.fasilitas_publik_id = fp.id WHERE pm.id = ?");
$stmtSelect->execute([$id]);
$data = $stmtSelect->fetch(PDO::FETCH_ASSOC);
echo json_encode(['status'=>'success','data'=>$data]);
} catch (Exception $e) {
echo json_encode(['status'=>'error','message'=>'Gagal memperbarui: '.$e->getMessage()]);
}
?>
+48
View File
@@ -0,0 +1,48 @@
<?php
session_start();
include '../koneksi.php';
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
$id = $_POST['id'] ?? '';
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
$status_jangkauan = $_POST['status_jangkauan'] ?? '';
$fasilitas_publik_id = $_POST['fasilitas_publik_id'] ?? null;
$alamat = $_POST['alamat'] ?? null;
if (!$id || !$latitude || !$longitude) {
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
exit;
}
if ($fasilitas_publik_id === '') {
$fasilitas_publik_id = null;
}
try {
if ($status_jangkauan !== '') {
if ($alamat !== null) {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, status_jangkauan = ?, fasilitas_publik_id = ?, alamat = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $status_jangkauan, $fasilitas_publik_id, $alamat, $id]);
} else {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, status_jangkauan = ?, fasilitas_publik_id = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $status_jangkauan, $fasilitas_publik_id, $id]);
}
} else {
if ($alamat !== null) {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ?, alamat = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $alamat, $id]);
} else {
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ? WHERE id = ?");
$stmt->execute([$latitude, $longitude, $id]);
}
}
echo json_encode(['status' => 'success']);
} catch (PDOException $e) {
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
}
@@ -2,7 +2,7 @@
session_start();
include '../koneksi.php';
if (!isset($_SESSION['user_id'])) {
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], ['admin', 'petugas'])) {
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
+1
View File
@@ -0,0 +1 @@
# Gitkeep
Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB