Compare commits
10 Commits
7a6d552bad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 26bf1e7b2a | |||
| 94e4ef2da4 | |||
| c507033b9e | |||
| b81a0166fa | |||
| 0d995aee1e | |||
| a9785feeb5 | |||
| 90f6166219 | |||
| 2b1dd67616 | |||
| 2f4a375b9d | |||
| 10b62c6560 |
+14
@@ -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
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -879,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>
|
||||
|
||||
@@ -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([
|
||||
|
||||
+2
-10
@@ -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">
|
||||
© <script>document.write(new Date().getFullYear())</script> Kementerian Analisis Spasial
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ $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']);
|
||||
@@ -24,11 +25,21 @@ if ($fasilitas_publik_id === '') {
|
||||
|
||||
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]);
|
||||
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 {
|
||||
$stmt = $pdo->prepare("UPDATE penduduk_miskin SET latitude = ?, longitude = ? WHERE id = ?");
|
||||
$stmt->execute([$latitude, $longitude, $id]);
|
||||
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']);
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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));
|
||||
@@ -0,0 +1,5 @@
|
||||
[phases.build]
|
||||
cmds = [
|
||||
"mkdir -p /app/kemiskinan/uploads",
|
||||
"chmod -R 777 /app/kemiskinan/uploads"
|
||||
]
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
+83
-67
@@ -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 */
|
||||
|
||||
+31
-10
@@ -572,7 +572,7 @@ function renderPenduduk(item) {
|
||||
|
||||
let fotoHtml = '';
|
||||
if (item.foto_rumah) {
|
||||
fotoHtml += `<img src="uploads/${item.foto_rumah}" style="width:100%;height:120px;object-fit:cover;border-radius:8px;margin-bottom:8px;">`;
|
||||
fotoHtml += `<img src="../kemiskinan/uploads/${item.foto_rumah}" style="width:100%;height:120px;object-fit:cover;border-radius:8px;margin-bottom:8px;">`;
|
||||
}
|
||||
|
||||
const popup = `
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
+8
-2
@@ -83,7 +83,7 @@ if (!$modul) {
|
||||
// Aplikasi Publik (Tidak butuh login)
|
||||
$currentUser = 'Pengunjung Publik';
|
||||
$currentRole = 'petugas'; // Berikan role petugas agar pengunjung dapat mengedit data
|
||||
|
||||
$_SESSION['role'] = 'petugas';
|
||||
|
||||
include 'koneksi.php';
|
||||
|
||||
@@ -393,7 +393,7 @@ $modulTitle = $modulNames[$modul] ?? 'Analisis Spasial';
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($modul === 'digitasi' || $modul === 'utuh'): ?>
|
||||
<?php if ($currentRole === 'admin'): ?>
|
||||
<?php if ($currentRole === 'admin' || $currentRole === 'petugas'): ?>
|
||||
<button class="tool-btn" id="btnToolJalan" data-mode="jalan" title="Gambar Jalan">
|
||||
<span class="tool-icon">🛣️</span>
|
||||
<span>Jalan</span>
|
||||
@@ -942,6 +942,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>
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user