2nd commit: Pemisahan halaman autentifikasi dengan halaman utama, modifikasi ikon, dan pemisahakan fitur berdasarkan role

This commit is contained in:
2026-06-12 22:45:38 +07:00
parent d556912ea5
commit 319a5c878f
15 changed files with 248 additions and 236 deletions
+58 -126
View File
@@ -1,117 +1,67 @@
# 🗺️ WebGIS Pemetaan Kemiskinan dan Distribusi Bantuan (Kota Pontianak) # 🗺️ WebGIS-Pontianak (Pemetaan Kemiskinan & Tata Ruang)
Sebuah Sistem Informasi Geografis (*Geographic Information System / GIS*) berbasis web yang dirancang dengan arsitektur *Single Page Application (SPA)* untuk: Sebuah Sistem Informasi Geografis (*Geographic Information System / GIS*) komprehensif berbasis web yang dirancang untuk memetakan infrastruktur kota dan demografi sosial, dengan fokus utama pada pemetaan kemiskinan dan distribusi bantuan di Pontianak.
* Memetakan demografi kemiskinan Sistem ini dikembangkan dengan arsitektur **Multi-Page Application** tersinkronisasi dan dilindungi oleh kontrol akses berbasis peran (*Role-Based Access Control / RBAC*) tingkat lanjut.
* Mengkalkulasi kelayakan bantuan secara otomatis
* Mengelola respons darurat musibah
* Memfasilitasi pendataan talenta untuk pemberdayaan warga
Sistem ini dibangun dengan kontrol otorisasi tingkat lanjut (*Role-Based Access Control / RBAC* & *Geofencing*) serta menggunakan algoritma *Sistem Pendukung Keputusan (SPK)* terintegrasi.
--- ---
## ✨ Fitur Utama (Core Features) ## ✨ Fitur Utama (Core Features)
### 🔐 Gatekeeper Authentication ### 🔐 Keamanan & Autentikasi Berlapis
- **Halaman Login Terisolasi:** Autentikasi dipisah sepenuhnya dari halaman utama peta (`login.php`) untuk mencegah kebocoran *resource* ke klien yang tidak sah.
- **Proteksi Kata Sandi BCRYPT:** Standar keamanan industri, divalidasi minimal 8 karakter baik di sisi klien (*frontend*) maupun server (*backend*).
- **Role-Based Access Control (RBAC):** Pemisahan hak akses ketat antara *Admin* (hak penuh) dan *User* biasa (hanya-baca & lapor musibah).
- **Server-side Gatekeeper:** Validasi *Session* ketat berbasis PHP di setiap akses masuk API maupun perlindungan pengalihan instan (*instant redirection*) di halaman utama.
- **Stylish Notification:** Notifikasi *pop-up* responsif berbasis SweetAlert2 untuk menyambut identitas pengguna.
* Sistem layar kunci persisten ### 🗺️ Pemetaan Spasial Multi-Layer
* Enkripsi kata sandi menggunakan standar *BCRYPT* - **Leaflet.js Interaktif:** Peta dinamis dengan *FontAwesome Icon* premium untuk visualisasi marker.
* Validasi sesi berbasis API (*Anti Client-Side Amnesia*) - **Peta Aset Beragam:** Menampilkan visualisasi tumpang tindih untuk *Polyline* (Jaringan Jalan), *Polygon* (Parsil Tanah), dan *Marker* (SPBU & Rumah Ibadah).
- **Z-Index & Event Handling Terkelola:** Penempatan *interactive: false* pada radius wewenang rumah ibadah agar tidak menutupi klik (pointer events) dari poligon jalan dan tanah di bawahnya.
- **Reverse Geocoding:** Pelacakan koordinat GPS *real-time* terintegrasi Nominatim API.
### 🗺️ Pemetaan Spasial & Geofencing ### 📊 Algoritma SPK Kemiskinan & Kesejahteraan
Sistem penentuan status keluarga (Sangat Miskin, Miskin, Rentan) yang dikalkulasi secara terpusat berdasarkan parameter *Garis Kemiskinan* dinamis. Modul ini menyertakan *Smart Autofill* untuk merekomendasikan jumlah logistik.
* Visualisasi interaktif menggunakan *Leaflet.js* ### 🚨 Manajemen Bantuan & Krisis Darurat
* Penentuan radius wewenang operasional - Pemantauan rumah tangga yang berada di dalam maupun luar radius cakupan wewenang rumah ibadah.
* *Reverse Geocoding* via *Nominatim API* - Peringatan darurat musibah dengan efek kelap-kelip visual pada marker (*SOS Blink*).
- Pencatatan riwayat distribusi bantuan dengan hak visibilitas CRUD yang otomatis disembunyikan dari antarmuka User biasa demi mencegah manipulasi data.
### 📊 SPK Kemiskinan Otomatis
* Klasifikasi tingkat kemiskinan:
* Sangat Miskin
* Miskin
* Rentan
* Menggunakan:
* Garis Kemiskinan Regional
* *Proxy Means Testing (PMT)*
### 📦 Distribusi Bantuan Cerdas (Smart Autofill)
* Rekomendasi logistik berbasis status kemiskinan
* Perhitungan proporsional (*pro-rata*)
### 🚨 Manajemen Krisis (Visual Alert)
* Peringatan dini berbasis geospasial
* Animasi visual (kelap-kelip merah) untuk lokasi darurat
### 🔄 Resolusi Musibah Otomatis (Auto-Resolve)
* Menggunakan *Database Transactions*
* Status darurat otomatis selesai setelah bantuan disalurkan
### 🎯 Filter Pelatihan Algoritmik
* Seleksi otomatis kandidat pelatihan berdasarkan:
* Usia produktif
* Tingkat pendidikan
* Status pengangguran
--- ---
## 🛠️ Stack Teknologi (Tech Stack) ## 🛠️ Stack Teknologi (Tech Stack)
### Frontend ### Frontend
* **UI/UX:** HTML5, CSS3 Murni (Custom RBAC rules, Smooth Animations, Glassmorphism), SweetAlert2
* HTML5 & CSS3 (*Native*) * **Peta Geospasial:** Leaflet.js
* JavaScript (*ES6+ / Vanilla JS*) * **Logika Aplikasi:** Vanilla JavaScript (ES6+), Fetch API
* *Leaflet.js*
* *Fetch API*
### Backend ### Backend
* **Logika Server & Sesi:** PHP 8.x (*Stateful Session Management*)
* PHP 8.x (API & Session Handling) * **Jembatan Database:** PDO (*PHP Data Objects*) dengan arsitektur *Prepared Statements*
* PDO (*PHP Data Objects*) * **Database Relasional:** MySQL / MariaDB
* MySQL / MariaDB
--- ---
## ⚙️ Persyaratan Sistem (Prerequisites) ## 🚀 Panduan Instalasi Lokal
Untuk menjalankan proyek ini secara lokal:
* Web Server (*XAMPP / MAMP / Laragon*)
* PHP ≥ 7.4 (disarankan 8.x)
* MySQL / MariaDB
* Koneksi internet (untuk OpenStreetMap & Nominatim API)
---
## 🚀 Panduan Instalasi (Installation Guide)
### 1. Clone Repositori
### 1. Kloning Repositori
```bash ```bash
git clone https://github.com/ChronosSilver/WebGis-SIG-UAS.git git clone https://github.com/ChronosSilver/SIG-Poverty-Mapping.git
``` ```
### 2. Konfigurasi Database ### 2. Persiapan Database
1. Buka *control panel* MySQL Anda (`http://localhost/phpmyadmin`)
* Buka: `http://localhost/phpmyadmin` 2. Buat database kosong baru dengan nama:
* Buat database: ```
webgis_pontianak
``` ```
webgis_kemiskinan 3. Import file cadangan `.sql` yang disertakan di repositori ini ke dalam database tersebut.
```
* Import file `.sql` dari repositori
### 3. Konfigurasi Koneksi PHP
Edit file `koneksi.php`:
### 3. Konfigurasi Sistem
Sistem ini menggunakan parameter *default* localhost (contoh: XAMPP). Jika *password* MySQL Anda kosong, tidak ada yang perlu diedit. Sesuaikan pengaturan ini di dalam file `koneksi.php`:
```php ```php
$host = "localhost"; $host = "localhost";
$user = "root"; $user = "root";
@@ -119,52 +69,34 @@ $pass = "";
$dbname = "webgis_pontianak"; $dbname = "webgis_pontianak";
``` ```
### 4. Jalankan Aplikasi ### 4. Eksekusi WebGIS
Akses direktori proyek melalui browser pilihan Anda:
Akses melalui browser:
``` ```
http://localhost/004(fin)/ http://localhost/SIG1/webgis/Poverty_Mapping/
``` ```
--- ---
## 🔒 Akses Pengujian (Dummy Credentials) ## 📂 Struktur Direktori Terbaru
Gunakan akun berikut:
``` ```
Username: admin 📦 Poverty_Mapping
Password: admin ┣ 📂 components # Kerangka antarmuka modular (Sidebar, Modal Template)
``` ┣ 📂 otak_auth # API gerbang keamanan & manajer sesi PHP
┣ 📂 otak_kemiskinan # Endpoint CRUD penduduk, bantuan & laporan musibah
> Anda juga dapat membuat akun baru melalui fitur registrasi. ┣ 📂 otak_jalan # Endpoint spasial penyimpan Polyline Jalan
┣ 📂 otak_tanah # Endpoint spasial penyimpan Polygon Parsil Tanah
--- ┣ 📂 otak_spbu # Endpoint penampung data POI SPBU
┣ 📜 app.js # Otak logika SPA Frontend & Leaflet Rendering
## 📂 Struktur Direktori ┣ 📜 style.css # Desain tema global & Filter Visibilitas RBAC
┣ 📜 koneksi.php # Jembatan asinkron ke MySQL PDO
``` ┣ 📜 login.php # Lobi isolasi layar kunci (Gatekeeper Entry)
📦 root-directory ┗ 📜 index.php # Kanvas Peta Utama WebGIS (Terproteksi Sesi)
┣ 📂 components # UI modular (Sidebar, Modal)
┣ 📂 otak_auth # API autentikasi
┣ 📂 otak_kemiskinan # CRUD & SPK warga
┣ 📂 otak_jalan # Data Polyline
┣ 📂 otak_tanah # Data Polygon
┣ 📂 otak_spbu # Data SPBU
┣ 📜 app.js # Logika utama sistem
┣ 📜 style.css # UI & animasi
┣ 📜 koneksi.php # Koneksi database
┗ 📜 index.php # Entry point
``` ```
--- ---
## 📝 Lisensi ## 📝 Pengembang & Lisensi
Sistem Informasi Geografis (*SIG*) berbasis web ini merupakan purwarupa penelitian/tugas akhir yang secara kontinu berevolusi dari tahun ke tahun.
**Hak Cipta (2026) oleh Nelson Davey (NIM: D1041231058) - Universitas Tanjungpura**.
Proyek ini dikembangkan sebagai bagian dari penelitian akademik / tugas perkuliahan Mendukung prinsip kode aman dengan akses terotentikasi berlapis. Segala bentuk salinan, distribusi, dan modifikasi harus mencantumkan sumber orisinalitas serta mengikuti kebijakan akademik yang berlaku.
Universitas Tanjungpura (2026) oleh Nelson Davey (D1041231058).
Segala bentuk distribusi dan modifikasi mengikuti kebijakan hak cipta yang berlaku.
---
+42 -63
View File
@@ -22,7 +22,7 @@ const state = {
// KUNCI AUTENTIKASI // KUNCI AUTENTIKASI
user: null, user: null,
authMode: 'login' // 'login' atau 'register'
}; };
const dataStore = { const dataStore = {
@@ -160,46 +160,6 @@ function triggerToastNotification(pesan) {
// ==================================================== // ====================================================
// AUTENTIKASI: GATEKEEPER & SINKRONISASI SESI // AUTENTIKASI: GATEKEEPER & SINKRONISASI SESI
// ==================================================== // ====================================================
window.switchAuthTab = (mode) => {
state.authMode = mode;
document.getElementById('tab-login').classList.toggle('active', mode === 'login');
document.getElementById('tab-register').classList.toggle('active', mode === 'register');
document.getElementById('btn-submit-auth').innerText = mode === 'login' ? 'Login Sistem' : 'Daftar Akun Baru';
document.getElementById('auth-error-msg').innerText = '';
};
window.eksekusiAuth = () => {
const user = document.getElementById('auth-username').value;
const pass = document.getElementById('auth-password').value;
const errMsg = document.getElementById('auth-error-msg');
if(!user || !pass) {
errMsg.innerText = "Username dan Password tidak boleh kosong!";
return;
}
errMsg.innerText = "Memproses request ke server...";
const endpoint = state.authMode === 'login' ? 'otak_auth/api_login.php' : 'otak_auth/api_register.php';
safeFetch(endpoint, { username: user, password: pass })
.then(res => {
if(state.authMode === 'register') {
triggerToastNotification(res.pesan);
switchAuthTab('login');
} else {
state.user = res.user;
document.getElementById('auth-screen').style.display = 'none';
triggerToastNotification(`Selamat datang, ${res.user}!`);
muatDataSemua(); // Booting peta dilanjutkan
}
})
.catch(err => {
errMsg.innerText = err.message;
});
};
window.eksekusiLogout = () => { window.eksekusiLogout = () => {
if(!confirm("Anda yakin ingin keluar dari sistem?")) return; if(!confirm("Anda yakin ingin keluar dari sistem?")) return;
@@ -207,7 +167,7 @@ window.eksekusiLogout = () => {
safeFetch('otak_auth/api_logout.php') safeFetch('otak_auth/api_logout.php')
.then(res => { .then(res => {
state.user = null; state.user = null;
document.getElementById('auth-screen').style.display = 'flex'; window.location.href = 'login.php';
document.getElementById('auth-username').value = ''; document.getElementById('auth-username').value = '';
document.getElementById('auth-password').value = ''; document.getElementById('auth-password').value = '';
triggerToastNotification("Sesi diakhiri secara aman."); triggerToastNotification("Sesi diakhiri secara aman.");
@@ -227,12 +187,30 @@ const cekSesiBackend = () => {
safeFetch('otak_auth/api_session.php') safeFetch('otak_auth/api_session.php')
.then(res => { .then(res => {
state.user = res.username; state.user = res.username;
document.getElementById('auth-screen').style.display = 'none'; window.USER_ROLE = res.role;
if(window.USER_ROLE !== 'admin') {
document.body.classList.add('role-user');
} else {
if(typeof RightToolbar !== 'undefined') map.addControl(new RightToolbar());
}
if (sessionStorage.getItem('just_logged_in') === 'true') {
Swal.fire({
title: 'Login Berhasil!',
html: `Selamat <b>${res.username}</b> masuk ke WebGIS-Pontianak sebagai <b>${res.role.toUpperCase()}</b>`,
icon: 'success',
timer: 3000,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
sessionStorage.removeItem('just_logged_in');
}
muatDataSemua(); muatDataSemua();
}) })
.catch(err => { .catch(err => {
console.log("Tertahan di gerbang autentikasi: " + err.message); console.log("Tertahan di gerbang autentikasi: " + err.message);
document.getElementById('auth-screen').style.display = 'flex'; window.location.href = 'login.php';
}); });
}; };
@@ -440,15 +418,15 @@ const RightToolbar = L.Control.extend({
onAdd: function() { onAdd: function() {
const c = L.DomUtil.create('div', 'custom-leaflet-toolbar'); const c = L.DomUtil.create('div', 'custom-leaflet-toolbar');
c.innerHTML = ` c.innerHTML = `
<button class="tool-btn" id="btn-tool-marker" title="SPBU" onclick="toggleMarkerMode()">📍</button> <button class="tool-btn" id="btn-tool-marker" title="SPBU" onclick="toggleMarkerMode()"><i class="fa-solid fa-gas-pump"></i></button>
<button class="tool-btn" id="btn-tool-jalan" title="Jalan" onclick="toggleRoadMode()">🛣️</button> <button class="tool-btn" id="btn-tool-jalan" title="Jalan" onclick="toggleRoadMode()"><i class="fa-solid fa-road"></i></button>
<button class="tool-btn" id="btn-tool-tanah" title="Tanah" onclick="toggleLandMode()">🟩</button> <button class="tool-btn" id="btn-tool-tanah" title="Tanah" onclick="toggleLandMode()"><i class="fa-solid fa-draw-polygon"></i></button>
`; `;
L.DomEvent.disableClickPropagation(c); L.DomEvent.disableClickPropagation(c);
return c; return c;
} }
}); });
map.addControl(new RightToolbar());
// ==================================================== // ====================================================
// EVENT LISTENER ROUTER (KLIK PETA UTAMA) // EVENT LISTENER ROUTER (KLIK PETA UTAMA)
@@ -495,7 +473,7 @@ map.on('click', async function(e) {
if(alamatInput) alamatInput.value = "Sedang melacak alamat dari satelit..."; if(alamatInput) alamatInput.value = "Sedang melacak alamat dari satelit...";
document.getElementById('spbu-action-wrapper').innerHTML = ` document.getElementById('spbu-action-wrapper').innerHTML = `
<button class="btn btn-save" id="btn-eksekusi-spbu" onclick="eksekusiSimpanBaru()" disabled>Tunggu Alamat...</button> <button class="btn btn-save admin-only" id="btn-eksekusi-spbu" onclick="eksekusiSimpanBaru()" disabled>Tunggu Alamat...</button>
<button class="btn btn-cancel" onclick="document.getElementById('modal-spbu').style.display='none'">Batal</button> <button class="btn btn-cancel" onclick="document.getElementById('modal-spbu').style.display='none'">Batal</button>
`; `;
document.getElementById('modal-spbu').style.display = 'flex'; document.getElementById('modal-spbu').style.display = 'flex';
@@ -702,8 +680,8 @@ function renderSPBU() {
document.getElementById('spbu-modal-title').innerText = "Detail & Kelola SPBU"; document.getElementById('spbu-modal-title').innerText = "Detail & Kelola SPBU";
document.getElementById('spbu-action-wrapper').innerHTML = ` document.getElementById('spbu-action-wrapper').innerHTML = `
<button class="btn btn-warning" onclick="eksekusiUpdate(${d.id})">Simpan Perubahan</button> <button class="btn btn-warning admin-only" onclick="eksekusiUpdate(${d.id})">Simpan Perubahan</button>
<button class="btn btn-danger" onclick="eksekusiHapus(${d.id})">Hapus Marker</button> <button class="btn btn-danger admin-only" onclick="eksekusiHapus(${d.id})">Hapus Marker</button>
<button class="btn btn-cancel" onclick="document.getElementById('modal-spbu').style.display='none'">Tutup</button> <button class="btn btn-cancel" onclick="document.getElementById('modal-spbu').style.display='none'">Tutup</button>
`; `;
document.getElementById('modal-spbu').style.display = 'flex'; document.getElementById('modal-spbu').style.display = 'flex';
@@ -904,7 +882,8 @@ function renderKemiskinan() {
fillColor: '#D69E2E', fillColor: '#D69E2E',
fillOpacity: 0.15, fillOpacity: 0.15,
radius: d.radius_meter, radius: d.radius_meter,
weight: 1 weight: 1,
interactive: false
}).addTo(map); }).addTo(map);
mapLayers.radiusIbadah[d.id] = circle; mapLayers.radiusIbadah[d.id] = circle;
@@ -1175,7 +1154,7 @@ window.selesaiGambarJalan = function() {
bukaGembokForm('jalan'); bukaGembokForm('jalan');
document.getElementById('jalan-action-wrapper').innerHTML = ` document.getElementById('jalan-action-wrapper').innerHTML = `
<button class="btn btn-save" onclick="eksekusiSimpanJalan()">Simpan Jalan</button> <button class="btn btn-save admin-only" onclick="eksekusiSimpanJalan()">Simpan Jalan</button>
<button class="btn btn-cancel" onclick="batalGambarJalan()">Batal</button> <button class="btn btn-cancel" onclick="batalGambarJalan()">Batal</button>
`; `;
@@ -1211,8 +1190,8 @@ window.handleRoadClick = function(id) {
document.getElementById('jalan-modal-title').innerText = "Detail & Kelola Jalan"; document.getElementById('jalan-modal-title').innerText = "Detail & Kelola Jalan";
document.getElementById('jalan-action-wrapper').innerHTML = ` document.getElementById('jalan-action-wrapper').innerHTML = `
<button class="btn btn-warning" onclick="eksekusiUpdateJalan(${id})">Simpan Perubahan</button> <button class="btn btn-warning admin-only" onclick="eksekusiUpdateJalan(${id})">Simpan Perubahan</button>
<button class="btn btn-danger" onclick="eksekusiHapusJalan(${id})">Hapus Jalan</button> <button class="btn btn-danger admin-only" onclick="eksekusiHapusJalan(${id})">Hapus Jalan</button>
<button class="btn btn-cancel" onclick="document.getElementById('modal-jalan').style.display='none'">Tutup</button> <button class="btn btn-cancel" onclick="document.getElementById('modal-jalan').style.display='none'">Tutup</button>
`; `;
@@ -1241,7 +1220,7 @@ window.selesaiGambarTanah = function() {
bukaGembokForm('tanah'); bukaGembokForm('tanah');
document.getElementById('tanah-action-wrapper').innerHTML = ` document.getElementById('tanah-action-wrapper').innerHTML = `
<button class="btn btn-save" onclick="eksekusiSimpanTanah()">Simpan Area</button> <button class="btn btn-save admin-only" onclick="eksekusiSimpanTanah()">Simpan Area</button>
<button class="btn btn-cancel" onclick="batalGambarTanah()">Batal</button> <button class="btn btn-cancel" onclick="batalGambarTanah()">Batal</button>
`; `;
@@ -1277,8 +1256,8 @@ window.handleLandClick = function(id) {
document.getElementById('tanah-modal-title').innerText = "Detail & Kelola Tanah"; document.getElementById('tanah-modal-title').innerText = "Detail & Kelola Tanah";
document.getElementById('tanah-action-wrapper').innerHTML = ` document.getElementById('tanah-action-wrapper').innerHTML = `
<button class="btn btn-warning" onclick="eksekusiUpdateTanah(${id})">Simpan Perubahan</button> <button class="btn btn-warning admin-only" onclick="eksekusiUpdateTanah(${id})">Simpan Perubahan</button>
<button class="btn btn-danger" onclick="eksekusiHapusTanah(${id})">Hapus Tanah</button> <button class="btn btn-danger admin-only" onclick="eksekusiHapusTanah(${id})">Hapus Tanah</button>
<button class="btn btn-cancel" onclick="document.getElementById('modal-tanah').style.display='none'">Tutup</button> <button class="btn btn-cancel" onclick="document.getElementById('modal-tanah').style.display='none'">Tutup</button>
`; `;
@@ -1825,7 +1804,7 @@ window.muatDaftarAnggota = (idPenduduk, isBernaung) => {
pelatihanHtml = ` pelatihanHtml = `
<div style="display:flex; gap:4px; align-items:center;"> <div style="display:flex; gap:4px; align-items:center;">
<select id="sel-pel-${m.id}" style="padding:4px; max-width:120px; font-size:0.75rem; border-radius:4px;">${opsi}</select> <select id="sel-pel-${m.id}" style="padding:4px; max-width:120px; font-size:0.75rem; border-radius:4px;">${opsi}</select>
<button class="btn btn-save" style="padding:4px 8px; font-size:0.75rem;" onclick="daftarPelatihanAnggota(${m.id})">Daftar</button> <button class="btn btn-save admin-only" style="padding:4px 8px; font-size:0.75rem;" onclick="daftarPelatihanAnggota(${m.id})">Daftar</button>
</div> </div>
`; `;
} else { } else {
@@ -1841,8 +1820,8 @@ window.muatDaftarAnggota = (idPenduduk, isBernaung) => {
<td>${m.pekerjaan || '<i style="color:#718096;">Menganggur</i>'}</td> <td>${m.pekerjaan || '<i style="color:#718096;">Menganggur</i>'}</td>
<td>${pelatihanHtml}</td> <td>${pelatihanHtml}</td>
<td> <td>
<button class="btn btn-warning" style="padding:4px 8px; font-size:0.75rem; margin-right:4px;" onclick="bukaFormEditAnggota(${m.id}, '${m.nik}', '${m.nama_lengkap}', '${m.tanggal_lahir}', '${m.pendidikan_terakhir}', '${m.pekerjaan || ''}')">Edit</button> <button class="btn btn-warning admin-only" style="padding:4px 8px; font-size:0.75rem; margin-right:4px;" onclick="bukaFormEditAnggota(${m.id}, '${m.nik}', '${m.nama_lengkap}', '${m.tanggal_lahir}', '${m.pendidikan_terakhir}', '${m.pekerjaan || ''}')">Edit</button>
<button class="btn btn-danger" style="padding:4px 8px; font-size:0.75rem;" onclick="eksekusiHapusAnggota(${m.id})">Hapus</button> <button class="btn btn-danger admin-only" style="padding:4px 8px; font-size:0.75rem;" onclick="eksekusiHapusAnggota(${m.id})">Hapus</button>
</td> </td>
</tr> </tr>
`; `;
@@ -1866,7 +1845,7 @@ window.resetFormAnggota = () => {
const btnContainer = document.getElementById('anggota-pekerjaan').parentElement.nextElementSibling; const btnContainer = document.getElementById('anggota-pekerjaan').parentElement.nextElementSibling;
if(btnContainer) { if(btnContainer) {
btnContainer.innerHTML = `<button class="btn btn-success" style="width:100%; padding: 10px;" onclick="eksekusiSimpanAnggota()">Tambah Anggota</button>`; btnContainer.innerHTML = `<button class="btn btn-success admin-only" style="width:100%; padding: 10px;" onclick="eksekusiSimpanAnggota()">Tambah Anggota</button>`;
} }
}; };
@@ -1883,7 +1862,7 @@ window.bukaFormEditAnggota = (id, nik, nama, lahir, pendidikan, pekerjaan) => {
const btnContainer = document.getElementById('anggota-pekerjaan').parentElement.nextElementSibling; const btnContainer = document.getElementById('anggota-pekerjaan').parentElement.nextElementSibling;
if(btnContainer) { if(btnContainer) {
btnContainer.innerHTML = ` btnContainer.innerHTML = `
<button class="btn btn-warning" style="flex:1; padding: 10px;" onclick="eksekusiUpdateAnggota(${id})">Update Data</button> <button class="btn btn-warning admin-only" style="flex:1; padding: 10px;" onclick="eksekusiUpdateAnggota(${id})">Update Data</button>
<button class="btn btn-cancel" style="padding: 10px;" onclick="resetFormAnggota()">Batal Edit</button> <button class="btn btn-cancel" style="padding: 10px;" onclick="resetFormAnggota()">Batal Edit</button>
`; `;
} }
+8 -32
View File
@@ -389,7 +389,7 @@
<hr class="pop-divider"> <hr class="pop-divider">
<div class="pop-action-main"> <div class="pop-action-main">
<button class="btn btn-success" id="btn-pd-bantuan" style="width:100%; padding: 12px; margin-bottom: 8px;">Catat Log Bantuan</button> <button class="btn btn-success admin-only" id="btn-pd-bantuan" style="width:100%; padding: 12px; margin-bottom: 8px;">Catat Log Bantuan</button>
</div> </div>
<div class="accordion-wrapper"> <div class="accordion-wrapper">
@@ -401,8 +401,8 @@
<button class="btn btn-success" id="btn-pd-riwayat-musibah" style="width:100%; margin-bottom:8px; padding: 10px;">Lihat Laporan Musibah</button> <button class="btn btn-success" id="btn-pd-riwayat-musibah" style="width:100%; margin-bottom:8px; padding: 10px;">Lihat Laporan Musibah</button>
<button class="btn btn-success" id="btn-pd-riwayat-bantuan" style="width:100%; margin-bottom:8px; padding: 10px;">Lihat Log Bantuan</button> <button class="btn btn-success" id="btn-pd-riwayat-bantuan" style="width:100%; margin-bottom:8px; padding: 10px;">Lihat Log Bantuan</button>
<div style="display:flex; gap:8px;"> <div style="display:flex; gap:8px;">
<button class="btn btn-warning" id="btn-pd-edit">Edit Data</button> <button class="btn btn-warning admin-only" id="btn-pd-edit">Edit Data</button>
<button class="btn btn-danger" id="btn-pd-hapus">Hapus</button> <button class="btn btn-danger admin-only" id="btn-pd-hapus">Hapus</button>
</div> </div>
</div> </div>
</div> </div>
@@ -424,8 +424,8 @@
<hr class="pop-divider"> <hr class="pop-divider">
<div style="display:flex; gap:8px;"> <div style="display:flex; gap:8px;">
<button class="btn btn-warning" id="btn-ib-edit">Edit Data</button> <button class="btn btn-warning admin-only" id="btn-ib-edit">Edit Data</button>
<button class="btn btn-danger" id="btn-ib-hapus">Hapus</button> <button class="btn btn-danger admin-only" id="btn-ib-hapus">Hapus</button>
</div> </div>
</div> </div>
</template> </template>
@@ -600,8 +600,8 @@
</table> </table>
</div> </div>
<h4 style="border-bottom:1px solid var(--border); padding-bottom:5px; color: var(--navy);">Tambah Anggota Baru</h4> <h4 class="admin-only" style="border-bottom:1px solid var(--border); padding-bottom:5px; color: var(--navy);">Tambah Anggota Baru</h4>
<div style="display:grid; grid-template-columns: 1fr 1fr 1fr; gap:10px;"> <div class="admin-only" style="display:grid; grid-template-columns: 1fr 1fr 1fr; gap:10px;">
<div class="form-group"><label>NIK</label><input type="text" id="anggota-nik" placeholder="16 Digit NIK"></div> <div class="form-group"><label>NIK</label><input type="text" id="anggota-nik" placeholder="16 Digit NIK"></div>
<div class="form-group"><label>Nama Lengkap</label><input type="text" id="anggota-nama"></div> <div class="form-group"><label>Nama Lengkap</label><input type="text" id="anggota-nama"></div>
<div class="form-group"><label>Tanggal Lahir</label><input type="date" id="anggota-lahir"></div> <div class="form-group"><label>Tanggal Lahir</label><input type="date" id="anggota-lahir"></div>
@@ -661,31 +661,7 @@
</div> </div>
</div> </div>
<div id="auth-screen" class="auth-overlay">
<div class="auth-box">
<h2 style="color: var(--navy); margin-top: 0; text-align: center; border-bottom: 2px solid var(--border); padding-bottom: 10px;">WebGIS Pontianak</h2>
<!-- Toggle Tabs Form -->
<div style="display:flex; margin-bottom: 20px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border);">
<button id="tab-login" class="auth-tab active" onclick="switchAuthTab('login')">Masuk</button>
<button id="tab-register" class="auth-tab" onclick="switchAuthTab('register')">Daftar</button>
</div>
<!-- Form Autentikasi -->
<div class="form-group">
<label>Username</label>
<input type="text" id="auth-username" placeholder="Masukkan username">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="auth-password" placeholder="Masukkan password">
</div>
<button id="btn-submit-auth" class="btn btn-save" style="width: 100%; padding: 12px; margin-top: 15px; font-size: 1rem;" onclick="eksekusiAuth()">Login Sistem</button>
<p id="auth-error-msg" style="color: var(--red-danger); font-size: 0.8rem; text-align: center; margin-top: 10px; font-weight: bold; min-height: 15px;"></p>
</div>
</div>
</body> </body>
</html> </html>
+10 -10
View File
@@ -7,7 +7,7 @@
</head> </head>
<body> <body>
<div id="sidebar-container"> <div id="sidebar-container">
<div id="sidebar-header">WebGis-Pontianak</div> <div id="sidebar-header">WebGIS-Pontianak</div>
<!-- Tab Navigasi --> <!-- Tab Navigasi -->
<div class="sidebar-tabs"> <div class="sidebar-tabs">
<button class="tab-btn active" onclick="switchTab('tab-kemiskinan')">Pemetaan</button> <button class="tab-btn active" onclick="switchTab('tab-kemiskinan')">Pemetaan</button>
@@ -16,12 +16,12 @@
<!-- TAB 1: KEMISKINAN (BARU) --> <!-- TAB 1: KEMISKINAN (BARU) -->
<div id="tab-kemiskinan" class="tab-content active"> <div id="tab-kemiskinan" class="tab-content active">
<div class="control-panel"> <div class="control-panel">
<button class="btn-gps" onclick="lokasiSaya()">📍 Arahkan ke Lokasi Saya</button> <button class="btn-gps" onclick="lokasiSaya()"><i class='fa-solid fa-location-crosshairs'></i> Arahkan ke Lokasi Saya</button>
<button class="btn btn-danger" onclick="eksekusiLogout()" style="margin-top: 15px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;"> <button class="btn btn-danger" onclick="eksekusiLogout()" style="margin-top: 15px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;">
<span style="font-size: 1.1rem;">🚪</span> Keluar Sesi (Logout) <span style="font-size: 1.1rem;">🚪</span> Keluar Sesi (Logout)
</button> </button>
<!-- Input Garis Kemiskinan --> <!-- Input Garis Kemiskinan -->
<div class="add-marker-box" style="margin-bottom: 0; padding: 10px 12px;"> <div class="add-marker-box admin-only" style="margin-bottom: 0; padding: 10px 12px;">
<label>Batas Garis Kemiskinan (Rp):</label> <label>Batas Garis Kemiskinan (Rp):</label>
<div style="display:flex; gap:8px; margin-top:5px;"> <div style="display:flex; gap:8px; margin-top:5px;">
<input type="number" id="input-garis-kemiskinan" placeholder="Loading..." class="modern-input"> <input type="number" id="input-garis-kemiskinan" placeholder="Loading..." class="modern-input">
@@ -29,15 +29,15 @@
</div> </div>
</div> </div>
<!-- Tombol Kelola Program Pelatihan --> <!-- Tombol Kelola Program Pelatihan -->
<button class="btn btn-warning" onclick="bukaModalPelatihan()" style="margin-bottom: 10px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;"> <button class="btn btn-warning admin-only" onclick="bukaModalPelatihan()" style="margin-bottom: 10px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;">
<span style="font-size: 1.2rem;">📋</span> Kelola Program Pelatihan <span style="font-size: 1.2rem;"><i class='fa-solid fa-clipboard-list'></i></span> Kelola Program Pelatihan
</button> </button>
<!-- Tombol Tambah Marker Modern --> <!-- Tombol Tambah Marker Modern -->
<div class="modern-action-box"> <div class="modern-action-box admin-only">
<label>Mode Penambahan Data:</label> <label>Mode Penambahan Data:</label>
<select id="pilihan-marker-baru" class="modern-select"> <select id="pilihan-marker-baru" class="modern-select">
<option value="ibadah">🕌 Pusat Radius (Rumah Ibadah)</option> <option value="ibadah"><i class='fa-solid fa-mosque'></i> Pusat Radius (Rumah Ibadah)</option>
<option value="penduduk">🏠 Data Keluarga (Penduduk)</option> <option value="penduduk"><i class='fa-solid fa-house-chimney-user'></i> Data Keluarga (Penduduk)</option>
</select> </select>
<button class="btn-modern-add" id="btn-add-kemiskinan" onclick="toggleKemiskinanMode()"> <button class="btn-modern-add" id="btn-add-kemiskinan" onclick="toggleKemiskinanMode()">
<span class="icon">+</span> Aktifkan Kursor Marker <span class="icon">+</span> Aktifkan Kursor Marker
@@ -58,9 +58,9 @@
<!-- TAB 2: FILTER & SPBU (LAMA) --> <!-- TAB 2: FILTER & SPBU (LAMA) -->
<div id="tab-filter" class="tab-content"> <div id="tab-filter" class="tab-content">
<div id="filter-bar"> <div id="filter-bar">
<button class="filter-tool-btn" onclick="bukaModalFilterLayer()">⚙️</button> <button class="filter-tool-btn" onclick="bukaModalFilterLayer()"><i class='fa-solid fa-gear'></i></button>
<span class="filter-label">Status SPBU:</span> <span class="filter-label">Status SPBU:</span>
<button class="filter-tool-btn" onclick="bukaModalFilterSPBU()">🔍</button> <button class="filter-tool-btn" onclick="bukaModalFilterSPBU()"><i class='fa-solid fa-filter'></i></button>
</div> </div>
<div id="card-container" class="card-list-area"></div> <div id="card-container" class="card-list-area"></div>
</div> </div>
+11 -2
View File
@@ -1,3 +1,10 @@
<?php
session_start();
if(!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id"> <html lang="id">
<head> <head>
@@ -8,14 +15,16 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head> </head>
<body> <body>
<?php include 'components/sidebar.php'; ?> <?php include 'components/sidebar.php'; ?>
<?php include 'components/modals.php'; ?> <?php include 'components/modals.php'; ?>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"> Selesai Gambar Jalan</button> <button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"><i class='fa-solid fa-check'></i> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"> Selesai Area Tanah</button> <button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"><i class='fa-solid fa-check'></i> Selesai Area Tanah</button>
<div id="map"></div> <div id="map"></div>
+91
View File
@@ -0,0 +1,91 @@
<?php
session_start();
if(isset($_SESSION['user_id'])) {
header("Location: index.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - WebGIS Pontianak V2</title>
<link rel="stylesheet" href="style.css">
<style>
body { margin: 0; background: #cbd5e0; display: flex; align-items: center; justify-content: center; height: 100vh; }
.auth-overlay { position: static; background: transparent; display: flex !important; }
</style>
</head>
<body>
<div id="auth-screen" class="auth-overlay">
<div class="auth-box">
<h2 style="color: var(--navy); margin-top: 0; text-align: center; border-bottom: 2px solid var(--border); padding-bottom: 10px;">WebGIS Pontianak</h2>
<!-- Toggle Tabs Form -->
<div style="display:flex; margin-bottom: 20px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border);">
<button id="tab-login" class="auth-tab active" onclick="switchAuthTab('login')">Masuk</button>
<button id="tab-register" class="auth-tab" onclick="switchAuthTab('register')">Daftar</button>
</div>
<!-- Form Autentikasi -->
<div class="form-group">
<label>Username</label>
<input type="text" id="auth-username" placeholder="Masukkan username">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="auth-password" placeholder="Masukkan password">
</div>
<button id="btn-submit-auth" class="btn btn-save" style="width: 100%; padding: 12px; margin-top: 15px; font-size: 1rem;" onclick="eksekusiAuth()">Login Sistem</button>
<p id="auth-error-msg" style="color: var(--red-danger); font-size: 0.8rem; text-align: center; margin-top: 10px; font-weight: bold; min-height: 15px;"></p>
</div>
</div>
<script>
let authMode = 'login';
function switchAuthTab(mode) {
authMode = mode;
document.getElementById('tab-login').classList.toggle('active', mode === 'login');
document.getElementById('tab-register').classList.toggle('active', mode === 'register');
document.getElementById('btn-submit-auth').innerText = mode === 'login' ? 'Login Sistem' : 'Daftar Akun Baru';
document.getElementById('auth-error-msg').innerText = '';
}
function eksekusiAuth() {
const user = document.getElementById('auth-username').value;
const pass = document.getElementById('auth-password').value;
const errMsg = document.getElementById('auth-error-msg');
if(!user || !pass) { errMsg.innerText = "Username dan Password wajib diisi!"; return; }
if(authMode === 'register' && pass.length < 8) {
errMsg.innerText = "Password minimal harus 8 karakter!";
return;
}
errMsg.innerText = "Memproses...";
const endpoint = authMode === 'login' ? 'otak_auth/api_login.php' : 'otak_auth/api_register.php';
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: user, password: pass })
})
.then(res => res.json().then(data => ({ok: res.ok, data})))
.then(res => {
if(!res.ok) throw new Error(res.data.error || "Terjadi kesalahan.");
if(authMode === 'register') {
alert(res.data.pesan);
switchAuthTab('login');
} else {
sessionStorage.setItem('just_logged_in', 'true');
window.location.href = 'index.php'; // Redirect ke utama
}
})
.catch(err => { errMsg.innerText = err.message; });
}
</script>
</body>
</html>
+6 -1
View File
@@ -7,6 +7,11 @@ require_once '../koneksi.php';
$data = json_decode(file_get_contents("php://input")); $data = json_decode(file_get_contents("php://input"));
if (!empty($data->username) && !empty($data->password)) { if (!empty($data->username) && !empty($data->password)) {
if (strlen($data->password) < 8) {
http_response_code(400);
die(json_encode(["error" => "Password minimal harus 8 karakter!"]));
}
try { try {
// Cek apakah username sudah ada // Cek apakah username sudah ada
$stmt = $conn->prepare("SELECT id FROM users WHERE username = :username"); $stmt = $conn->prepare("SELECT id FROM users WHERE username = :username");
@@ -17,7 +22,7 @@ if (!empty($data->username) && !empty($data->password)) {
} }
// Enkripsi password menggunakan BCRYPT // Enkripsi password menggunakan BCRYPT
$query = "INSERT INTO users (username, password) VALUES (:username, :password)"; $query = "INSERT INTO users (username, password, role) VALUES (:username, :password, 'user')";
$stmt = $conn->prepare($query); $stmt = $conn->prepare($query);
$stmt->bindValue(':username', htmlspecialchars(strip_tags($data->username))); $stmt->bindValue(':username', htmlspecialchars(strip_tags($data->username)));
$stmt->bindValue(':password', password_hash($data->password, PASSWORD_BCRYPT)); $stmt->bindValue(':password', password_hash($data->password, PASSWORD_BCRYPT));
+1 -1
View File
@@ -3,7 +3,7 @@ session_start();
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
if(isset($_SESSION['user_id'])) { if(isset($_SESSION['user_id'])) {
echo json_encode(["status" => "aktif", "username" => $_SESSION['username']]); echo json_encode(["status" => "aktif", "username" => $_SESSION['username'], "role" => $_SESSION['role'] ?? 'user']);
} else { } else {
http_response_code(401); http_response_code(401);
echo json_encode(["status" => "tidak_aktif", "error" => "Sesi kedaluwarsa atau belum login."]); echo json_encode(["status" => "tidak_aktif", "error" => "Sesi kedaluwarsa atau belum login."]);
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
require_once '../koneksi.php'; require_once '../koneksi.php';
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
require_once '../koneksi.php'; require_once '../koneksi.php';
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
require_once '../koneksi.php'; require_once '../koneksi.php';
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
require_once '../koneksi.php'; require_once '../koneksi.php';
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
// otak_spbu/api_hapus_spbu.php // otak_spbu/api_hapus_spbu.php
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
if(session_status() === PHP_SESSION_NONE) session_start();
if(!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); die(json_encode(['error'=>'Akses Ditolak: Anda bukan Admin!'])); }
header("Content-Type: application/json; charset=UTF-8"); header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST"); header("Access-Control-Allow-Methods: POST");
require_once '../koneksi.php'; require_once '../koneksi.php';
+3 -1
View File
@@ -737,4 +737,6 @@ input[type=range]::-webkit-slider-runnable-track {
.auth-tab.active { .auth-tab.active {
background: var(--blue); background: var(--blue);
color: var(--white); color: var(--white);
} }
/* RBAC CLASSES */
.role-user .admin-only { display: none !important; }