This commit is contained in:
Nama Kamu
2026-06-10 19:40:58 +07:00
parent 9d23137b3f
commit 9404230e0b
42 changed files with 7972 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
<?php
include 'koneksi.php';
$result = $koneksi->query("SELECT * FROM spbu");
$data = [];
while($row = $result->fetch_assoc()){
$data[] = $row;
}
echo json_encode($data);
?>
+69
View File
@@ -0,0 +1,69 @@
-- phpMyAdmin SQL Dump
-- version 5.2.1
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1
-- Waktu pembuatan: 07 Jun 2026 pada 10.53
-- Versi server: 10.4.32-MariaDB
-- Versi PHP: 8.2.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `db_spbu`
--
-- --------------------------------------------------------
--
-- Struktur dari tabel `spbu`
--
CREATE TABLE `spbu` (
`id` int(11) NOT NULL,
`nama_spbu` varchar(255) DEFAULT NULL,
`lat` double DEFAULT NULL,
`lng` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Dumping data untuk tabel `spbu`
--
INSERT INTO `spbu` (`id`, `nama_spbu`, `lat`, `lng`) VALUES
(1, 'spbu kobar', -0.02197187355969219, 109.31207858442205),
(3, 'spbu paris', 0.007368924759898348, 109.37010012983221),
(4, 'spbu tanray', 0.016651739943910275, 109.29988329640928);
--
-- Indexes for dumped tables
--
--
-- Indeks untuk tabel `spbu`
--
ALTER TABLE `spbu`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT untuk tabel yang dibuang
--
--
-- AUTO_INCREMENT untuk tabel `spbu`
--
ALTER TABLE `spbu`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+10
View File
@@ -0,0 +1,10 @@
<?php
include 'koneksi.php';
$id = $_POST['id'];
$stmt = $koneksi->prepare("DELETE FROM spbu WHERE id=?");
$stmt->bind_param("i", $id);
echo $stmt->execute() ? "success" : "error";
?>
+203
View File
@@ -0,0 +1,203 @@
<!DOCTYPE html>
<html>
<head>
<title>Peta SPBU</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<style>
#map { height: 100vh; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map('map').setView([-0.02, 109.34], 13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19
}).addTo(map);
let layer = L.layerGroup().addTo(map);
let data = [];
// =====================
// NOTIFIKASI POPUP
// =====================
function showNotif(text) {
let popup = L.popup()
.setLatLng(map.getCenter())
.setContent(`<div style="padding:6px;">${text}</div>`)
.openOn(map);
setTimeout(() => {
map.closePopup(popup);
}, 1500);
}
// =====================
// LOAD DATA
// =====================
function load() {
fetch('ambil_data.php')
.then(res => res.json())
.then(res => {
data = res;
render();
});
}
// =====================
// RENDER MARKER
// =====================
function render() {
layer.clearLayers();
data.forEach(d => {
let marker = L.marker([d.lat, d.lng], {
draggable: true
}).addTo(layer);
marker.bindPopup(`
<b>${d.nama_spbu}</b><br><br>
<button onclick="edit(${d.id}, '${d.nama_spbu}', ${d.lat}, ${d.lng})">
Edit
</button>
<button onclick="hapus(${d.id})">
Hapus
</button>
`);
// =====================
// DRAG UPDATE
// =====================
marker.on('dragend', function(e){
let pos = e.target.getLatLng();
fetch('update.php', {
method: 'POST',
body: new URLSearchParams({
id: d.id,
nama: d.nama_spbu,
lat: pos.lat,
lng: pos.lng
})
})
.then(res => res.text())
.then(res => {
if(res.trim() === "success"){
showNotif("📍 Lokasi berhasil diupdate");
load();
} else {
showNotif("❌ Gagal update");
}
});
});
});
}
load();
// =====================
// TAMBAH DATA (klik peta)
// =====================
map.on('click', function(e){
let lat = e.latlng.lat;
let lng = e.latlng.lng;
let popup = L.popup()
.setLatLng(e.latlng)
.setContent(`
<form id="form">
<input name="nama" placeholder="Nama SPBU" required>
<button type="submit">Simpan</button>
</form>
`)
.openOn(map);
setTimeout(() => {
document.getElementById("form").onsubmit = function(ev){
ev.preventDefault();
let fd = new FormData(this);
fd.append("lat", lat);
fd.append("lng", lng);
fetch('simpan.php', {
method: 'POST',
body: fd
})
.then(res => res.text())
.then(res => {
if(res.trim() === "success"){
showNotif("✅ SPBU berhasil ditambahkan");
load();
map.closePopup();
} else {
showNotif("❌ Gagal menyimpan");
}
});
}
}, 100);
});
// =====================
// EDIT DATA
// =====================
function edit(id, nama, lat, lng){
let newNama = prompt("Nama SPBU:", nama);
if(newNama){
fetch('update.php', {
method: 'POST',
body: new URLSearchParams({
id,
nama: newNama,
lat,
lng
})
})
.then(res => res.text())
.then(res => {
if(res.trim() === "success"){
showNotif("✏️ Data berhasil diupdate");
load();
} else {
showNotif("❌ Gagal update");
}
});
}
}
// =====================
// HAPUS DATA
// =====================
function hapus(id){
if(confirm("Yakin mau hapus data ini?")){
fetch('hapus.php', {
method: 'POST',
body: new URLSearchParams({id})
})
.then(res => res.text())
.then(res => {
if(res.trim() === "success"){
showNotif("🗑️ Data berhasil dihapus");
load();
} else {
showNotif("❌ Gagal hapus");
}
});
}
}
</script>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<?php
$host = getenv('DB_HOST') ?: 'localhost';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASSWORD') ?: '';
$db = getenv('DB_NAME') ?: 'db_spbu';
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) {
die("Koneksi database gagal: " . mysqli_connect_error());
}
$koneksi = $conn;
?>
+12
View File
@@ -0,0 +1,12 @@
<?php
include 'koneksi.php';
$nama = $_POST['nama'];
$lat = $_POST['lat'];
$lng = $_POST['lng'];
$stmt = $koneksi->prepare("INSERT INTO spbu (nama_spbu, lat, lng) VALUES (?, ?, ?)");
$stmt->bind_param("sdd", $nama, $lat, $lng);
echo $stmt->execute() ? "success" : "error";
?>
+13
View File
@@ -0,0 +1,13 @@
<?php
include 'koneksi.php';
$id = $_POST['id'];
$nama = $_POST['nama'];
$lat = $_POST['lat'];
$lng = $_POST['lng'];
$stmt = $koneksi->prepare("UPDATE spbu SET nama_spbu=?, lat=?, lng=? WHERE id=?");
$stmt->bind_param("sddi", $nama, $lat, $lng, $id);
echo $stmt->execute() ? "success" : "error";
?>
+29
View File
@@ -0,0 +1,29 @@
CREATE DATABASE IF NOT EXISTS 02_kerusakan;
USE 02_kerusakan;
CREATE TABLE jalan(
id INT AUTO_INCREMENT PRIMARY KEY,
nama_jalan VARCHAR(150),
status VARCHAR(50),
panjang DOUBLE,
geom LINESTRING NOT NULL,
SPATIAL INDEX idx_jalan(geom)
);
CREATE TABLE parsil(
id INT AUTO_INCREMENT PRIMARY KEY,
pemilik VARCHAR(100),
status VARCHAR(50),
luas DOUBLE,
geom POLYGON NOT NULL,
SPATIAL INDEX idx_parsil(geom)
);
CREATE TABLE jalan_rusak(
id INT AUTO_INCREMENT PRIMARY KEY,
nama_titik VARCHAR(100),
keterangan TEXT,
geom POINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
SPATIAL INDEX idx_rusak(geom)
);
+14
View File
@@ -0,0 +1,14 @@
<?php
$host = getenv('DB_HOST') ?: 'localhost';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASSWORD') ?: '';
$db = getenv('DB_NAME') ?: '02_kerusakan';
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) {
die("Koneksi database gagal: " . mysqli_connect_error());
}
?>
+62
View File
@@ -0,0 +1,62 @@
<?php
include "db.php";
$data=json_decode(
file_get_contents("php://input"),
true
);
$id=$data['id'];
$type=$data['type'];
if($type=="jalan"){
$sql="
DELETE FROM jalan
WHERE id=$id
";
}
elseif($type=="parsil"){
$sql="
DELETE FROM parsil
WHERE id=$id
";
}
elseif($type=="rusak"){
$sql="
DELETE FROM jalan_rusak
WHERE id=$id
";
}
if(mysqli_query($conn,$sql)){
echo "Data berhasil dihapus";
}else{
echo mysqli_error($conn);
}
?>
+830
View File
@@ -0,0 +1,830 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS Jalan Parsil Kerusakan</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css">
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<style>
*{
box-sizing:border-box;
font-family:Segoe UI,Arial;
}
body{
margin:0;
background:#020617;
}
#container{
display:flex;
height:100vh;
}
#sidebar{
width:360px;
background:#0f172a;
color:white;
padding:20px;
overflow:auto;
}
.logo{
font-size:22px;
font-weight:bold;
margin-bottom:20px;
}
.menu button{
width:100%;
padding:12px;
margin-bottom:8px;
border:none;
border-radius:10px;
background:#1e293b;
color:white;
cursor:pointer;
}
.menu button:hover{
background:#2563eb;
}
.card{
background:#1e293b;
padding:15px;
border-radius:15px;
margin-top:12px;
box-shadow:0 5px 15px #0005;
}
.card b{
color:#38bdf8;
}
#map{
flex:1;
}
#info{
background:#020617;
padding:10px;
color:#94a3b8;
border-radius:10px;
margin-top:10px;
}
</style>
</head>
<body>
<div id="container">
<div id="sidebar">
<div class="logo">
🗺 WebGIS
</div>
<div class="menu">
<button onclick="filterData('all')">
Semua Data
</button>
<button onclick="filterData('jalan')">
🛣 Jalan
</button>
<button onclick="filterData('parsil')">
🏠 Parsil
</button>
<button onclick="filterData('rusak')">
Kerusakan
</button>
</div>
<div id="info">
Memuat data...
</div>
<div id="list"></div>
</div>
<div id="map"></div>
</div>
<script>
const map=L.map('map')
.setView([-0.0263,109.3425],14);
L.tileLayer(
'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
).addTo(map);
let group=L.featureGroup().addTo(map);
let dataGlobal=[];
let tempLayer=null;
let tempType=null;
// LOAD DATA
fetch("load.php")
.then(r=>r.json())
.then(data=>{
dataGlobal=data;
document.getElementById("info").innerHTML=
"Total data : "+data.length;
tampil(data);
})
.catch(e=>{
document.getElementById("info").innerHTML=
"Load error "+e;
});
// TAMPIL PETA
function tampil(data){
group.clearLayers();
let html="";
data.forEach((d,i)=>{
if(!d.geom)return;
let geo=JSON.parse(d.geom);
let layer=L.geoJSON(geo);
let popup="";
// JALAN
if(d.type=="jalan"){
layer.setStyle({
color:"#22c55e",
weight:6
});
popup=`
<h3>🛣 Jalan</h3>
<b>Nama Jalan:</b> ${d.nama_jalan}<br>
<b>Status:</b> ${d.status || '-'}<br>
<b>Panjang:</b> ${d.panjang || '-'} meter
<br><br>
<button onclick="editData(${d.id},'jalan')">
Edit
</button>
<button onclick="hapusData(${d.id},'jalan')">
Hapus
</button>
`;
}
// PARSIL
if(d.type=="parsil"){
layer.setStyle({
color:"#6366f1",
fillOpacity:.4
});
popup=`
<h3>🏠 Parsil</h3>
<b>Area:</b> ${d.nama_area}<br>
<b>Pemilik:</b> ${d.pemilik || '-'}<br>
<b>Status:</b> ${d.status || '-'}<br>
<b>Luas:</b> ${d.luas || 0} m²<br>
<b>Keterangan:</b> ${d.keterangan || '-'}
`;
}
// RUSAK
if(d.type=="rusak"){
popup=`
<h3>⚠ Kerusakan</h3>
<b>Lokasi:</b> ${d.nama_titik}<br>
<b>Keterangan:</b> ${d.keterangan}
`;
}
layer.bindPopup(popup);
layer.addTo(group);
html+=`
<div class="card"
onclick="zoomData(${i})">
<b>${d.type}</b>
<br>
${d.nama_jalan ||
d.nama_area ||
d.nama_titik}
</div>
`;
});
document.getElementById("list")
.innerHTML=html;
if(group.getLayers().length)
map.fitBounds(group.getBounds());
}
function zoomData(i){
let geo=JSON.parse(dataGlobal[i].geom);
map.fitBounds(
L.geoJSON(geo).getBounds()
);
}
function filterData(type){
if(type=="all"){
tampil(dataGlobal);
return;
}
tampil(
dataGlobal.filter(x=>x.type==type)
);
}
// ================= DRAW EDIT =================
const draw=new L.Control.Draw({
edit:{
featureGroup:group,
edit:true,
remove:false
},
draw:{
marker:true,
polyline:true,
polygon:true,
circle:false,
rectangle:false
}
});
map.addControl(draw);
// saat geometry digeser / diedit
map.on(
'draw:edited',
function(e){
e.layers.eachLayer(function(layer){
let geo=layer.toGeoJSON();
fetch("update.php",{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
id:layer._id,
type:layer._type,
geom:geo
})
});
});
alert("Lokasi berhasil diperbarui");
});
// FORM INPUT
function openForm(){
let html="";
if(tempType=="rusak"){
html=`
<h3>Kerusakan Jalan</h3>
<input id="nama"
placeholder="Nama lokasi">
<br>
<textarea id="ket"
placeholder="Keterangan"></textarea>
`;
}
if(tempType=="jalan"){
html=`
<h3>Data Jalan</h3>
<input id="nama"
placeholder="Nama jalan">
<select id="status">
<option>Nasional</option>
<option>Provinsi</option>
<option>Kabupaten</option>
</select>
`;
}
if(tempType=="parsil"){
html=`
<h3>Data Parsil</h3>
<input id="nama"
placeholder="Nama area">
<input id="pemilik"
placeholder="Nama pemilik">
<select id="status">
<option>SHM</option>
<option>HGB</option>
<option>HGU</option>
</select>
<input id="luas"
placeholder="Luas tanah">
<textarea id="ket"
placeholder="Keterangan">
</textarea>
`;
}
let modal=document.createElement("div");
modal.id="inputBox";
modal.style.position="fixed";
modal.style.top="50%";
modal.style.left="50%";
modal.style.transform="translate(-50%,-50%)";
modal.style.background="white";
modal.style.padding="20px";
modal.style.zIndex="9999";
modal.innerHTML=
html+
`
<br>
<button onclick="simpanData()">
Simpan
</button>
<button onclick="batal()">
Batal
</button>
`;
document.body.appendChild(modal);
}
function batal(){
document.getElementById("inputBox").remove();
tempLayer.remove();
}
function simpanData(){
let geo=tempLayer.toGeoJSON();
let data={
type:tempType,
geom:geo
};
if(tempType=="rusak"){
data.nama_titik=
document.getElementById("nama").value;
data.keterangan=
document.getElementById("ket").value;
}
if(tempType=="jalan"){
data.nama=
document.getElementById("nama").value;
data.status=
document.getElementById("status").value;
}
if(tempType=="parsil"){
data.nama_area=
document.getElementById("nama").value;
data.pemilik=
document.getElementById("pemilik").value;
data.status=
document.getElementById("status").value;
data.luas=
document.getElementById("luas").value;
data.keterangan=
document.getElementById("ket").value;
}
fetch("save.php",{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify(data)
})
.then(r=>r.text())
.then(r=>{
alert(r);
location.reload();
});
}
function hapusData(id,type){
if(!confirm("Yakin hapus data ini?"))
return;
fetch("delete.php",{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
id:id,
type:type
})
})
.then(r=>r.text())
.then(r=>{
alert(r);
location.reload();
});
}
</script>
</body>
</html>
+141
View File
@@ -0,0 +1,141 @@
<?php
include "db.php";
header("Content-Type: application/json; charset=UTF-8");
$data = [];
// cek koneksi
if(!$conn){
echo json_encode([
"error"=>"Database gagal konek"
]);
exit;
}
// ================= JALAN =================
$q = mysqli_query($conn,"
SELECT
id,
nama_jalan,
status,
ST_AsGeoJSON(geom) AS geom
FROM jalan
");
if($q){
while($row=mysqli_fetch_assoc($q)){
$data[]=[
"id"=>(int)$row['id'],
"type"=>"jalan",
"nama_jalan"=>$row['nama_jalan'],
"status"=>"Kabupaten",
"panjang"=>0,
"geom"=>$row['geom']
];
}
}
// ================= PARSIL =================
$q = mysqli_query($conn,"
SELECT
id,
nama_area,
ST_AsGeoJSON(geom) AS geom
FROM parsil
");
if($q){
while($row=mysqli_fetch_assoc($q)){
$data[]=[
"id"=>(int)$row['id'],
"type"=>"parsil",
"pemilik"=>$row['nama_area'],
"status"=>"SHM",
"luas"=>0,
"geom"=>$row['geom']
];
}
}
// ================= KERUSAKAN =================
$q = mysqli_query($conn,"
SELECT
id,
nama_titik,
keterangan,
ST_AsGeoJSON(geom) AS geom
FROM jalan_rusak
");
if($q){
while($row=mysqli_fetch_assoc($q)){
$data[]=[
"id"=>(int)$row['id'],
"type"=>"rusak",
"nama_titik"=>$row['nama_titik'],
"keterangan"=>$row['keterangan'],
"geom"=>$row['geom']
];
}
}
echo json_encode($data);
?>
+194
View File
@@ -0,0 +1,194 @@
<?php
include "db.php";
header("Content-Type: text/plain");
$data=json_decode(file_get_contents("php://input"),true);
$type=$data['type'];
$geom=json_encode($data['geom']);
$coords=$data['geom']['geometry']['coordinates'];
// ================= JALAN =================
if($type=="jalan"){
$nama=mysqli_real_escape_string(
$conn,
$data['nama']
);
$status=mysqli_real_escape_string(
$conn,
$data['status']
);
$line="LINESTRING(";
foreach($coords as $c){
$line .= $c[0]." ".$c[1].",";
}
$line=rtrim($line,",").")";
$sql="
INSERT INTO jalan
(
nama_jalan,
geom
)
VALUES
(
'$nama',
ST_GeomFromText('$line')
)
";
}
// ================= PARSIL =================
elseif($type=="parsil"){
$nama=mysqli_real_escape_string(
$conn,
$data['nama_area']
);
$pemilik=mysqli_real_escape_string(
$conn,
$data['pemilik']
);
$status=mysqli_real_escape_string(
$conn,
$data['status']
);
$poly="POLYGON((";
foreach($coords[0] as $c){
$poly .= $c[0]." ".$c[1].",";
}
$poly=rtrim($poly,",")."))";
$sql="
INSERT INTO parsil
(
nama_area,
pemilik,
status,
geom
)
VALUES
(
'$nama',
'$pemilik',
'$status',
ST_GeomFromText('$poly')
)
";
}
// ================= RUSAK =================
elseif($type=="rusak"){
$nama=mysqli_real_escape_string(
$conn,
$data['nama_titik']
);
$ket=mysqli_real_escape_string(
$conn,
$data['keterangan']
);
$p=$coords;
$point =
"POINT(".$p[0]." ".$p[1].")";
$sql="
INSERT INTO jalan_rusak
(
nama_titik,
keterangan,
geom
)
VALUES
(
'$nama',
'$ket',
ST_GeomFromText('$point')
)
";
}
if(mysqli_query($conn,$sql)){
echo "Data berhasil disimpan";
}else{
echo "ERROR : ".mysqli_error($conn);
}
?>
+98
View File
@@ -0,0 +1,98 @@
<?php
include "db.php";
$data=json_decode(file_get_contents("php://input"),true);
$id=$data['id'];
$geom=$data['geom']['geometry'];
$type=$data['type'];
if($type=="rusak"){
$x=$geom['coordinates'][0];
$y=$geom['coordinates'][1];
$sql="
UPDATE jalan_rusak
SET geom =
ST_GeomFromText('POINT($x $y)')
WHERE id=$id
";
}
if($type=="jalan"){
$list=[];
foreach($geom['coordinates'] as $c){
$list[]=$c[0]." ".$c[1];
}
$line="LINESTRING(".implode(",",$list).")";
$sql="
UPDATE jalan
SET geom =
ST_GeomFromText('$line')
WHERE id=$id
";
}
if($type=="parsil"){
$list=[];
foreach($geom['coordinates'][0] as $c){
$list[]=$c[0]." ".$c[1];
}
$poly="POLYGON((".implode(",",$list)."))";
$sql="
UPDATE parsil
SET geom =
ST_GeomFromText('$poly')
WHERE id=$id
";
}
if(mysqli_query($conn,$sql)){
echo "OK";
}else{
echo mysqli_error($conn);
}
?>
+8
View File
File diff suppressed because one or more lines are too long
+275
View File
@@ -0,0 +1,275 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGIS Choropleth Kota Pontianak</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
html,body{
margin:0;
padding:0;
height:100%;
font-family:Arial, Helvetica, sans-serif;
}
#map{
width:100%;
height:100%;
}
.info{
padding:10px 14px;
background:white;
box-shadow:0 0 15px rgba(0,0,0,0.2);
border-radius:10px;
line-height:1.5;
}
.info h4{
margin:0 0 8px;
color:#444;
}
.legend{
background:white;
padding:10px;
line-height:18px;
color:#555;
border-radius:10px;
box-shadow:0 0 15px rgba(0,0,0,0.2);
}
.legend i{
width:18px;
height:18px;
float:left;
margin-right:8px;
opacity:0.8;
}
.titlebox{
position:absolute;
top:10px;
left:50%;
transform:translateX(-50%);
z-index:999;
background:#0d6efd;
color:white;
padding:10px 18px;
border-radius:10px;
font-weight:bold;
}
.selector{
position:absolute;
top:60px;
left:10px;
z-index:999;
background:white;
padding:10px;
border-radius:10px;
box-shadow:0 0 10px rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<div class="titlebox">WEBGIS CHOROPLETH KOTA PONTIANAK</div>
<div class="selector">
<b>Pilih Tema:</b><br>
<select id="mode" onchange="updateMap()">
<option value="density">Kepadatan Penduduk</option>
<option value="population">Jumlah Penduduk</option>
</select>
</div>
<div id="map"></div>
<script src="pontianak-state.js"></script>
<script>
const map = L.map('map').setView([-0.03,109.34],12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{
maxZoom:19,
attribution:'&copy; OpenStreetMap'
}).addTo(map);
let geojson;
/* ======================
INFO PANEL
====================== */
const info = L.control();
info.onAdd = function(){
this._div = L.DomUtil.create('div','info');
this.update();
return this._div;
};
info.update = function(props){
let mode = document.getElementById("mode").value;
if(props){
this._div.innerHTML = `
<h4>Data Kecamatan</h4>
<b>${props.name}</b><br>
Kepadatan : ${props.density.toLocaleString()} jiwa/km²<br>
Penduduk : ${props.population.toLocaleString()} jiwa`;
}else{
this._div.innerHTML = `<h4>${mode=="density"?"Kepadatan":"Penduduk"}</h4>Arahkan cursor ke wilayah`;
}
};
info.addTo(map);
/* ======================
WARNA
====================== */
function getColorDensity(d){
return d > 10000 ? '#800026' :
d > 8000 ? '#BD0026' :
d > 6000 ? '#E31A1C' :
d > 5000 ? '#FC4E2A' :
d > 4000 ? '#FD8D3C' :
d > 3000 ? '#FEB24C' :
'#FED976';
}
function getColorPopulation(d){
return d > 120000 ? '#00441b' :
d > 100000 ? '#006d2c' :
d > 80000 ? '#238b45' :
d > 60000 ? '#41ab5d' :
d > 50000 ? '#74c476' :
d > 40000 ? '#a1d99b' :
'#c7e9c0';
}
function style(feature){
let mode = document.getElementById("mode").value;
return {
weight:2,
opacity:1,
color:'white',
dashArray:'3',
fillOpacity:0.75,
fillColor:
mode=="density"
? getColorDensity(feature.properties.density)
: getColorPopulation(feature.properties.population)
};
}
/* ======================
INTERAKSI
====================== */
function highlightFeature(e){
const layer = e.target;
layer.setStyle({
weight:4,
color:'#222',
dashArray:'',
fillOpacity:0.9
});
layer.bringToFront();
info.update(layer.feature.properties);
}
function resetHighlight(e){
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e){
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer){
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
layer.bindPopup(`
<b>${feature.properties.name}</b><br>
Kepadatan : ${feature.properties.density.toLocaleString()} jiwa/km²<br>
Penduduk : ${feature.properties.population.toLocaleString()} jiwa
`);
}
/* ======================
LOAD DATA
====================== */
geojson = L.geoJson(statesData,{
style:style,
onEachFeature:onEachFeature
}).addTo(map);
map.fitBounds(geojson.getBounds());
/* ======================
LEGENDA
====================== */
const legend = L.control({position:'bottomright'});
legend.onAdd = function(){
this._div = L.DomUtil.create('div','legend');
this.update();
return this._div;
};
legend.update = function(){
let mode = document.getElementById("mode").value;
let grades, labels=[];
if(mode=="density"){
grades=[0,3000,4000,5000,6000,8000,10000];
for(let i=0;i<grades.length;i++){
let from=grades[i];
let to=grades[i+1];
labels.push(
'<i style="background:'+getColorDensity(from+1)+'"></i> '+
from+(to?'&ndash;'+to:'+')
);
}
this._div.innerHTML="<b>Kepadatan</b><br>"+labels.join("<br>");
}else{
grades=[0,40000,50000,60000,80000,100000,120000];
for(let i=0;i<grades.length;i++){
let from=grades[i];
let to=grades[i+1];
labels.push(
'<i style="background:'+getColorPopulation(from+1)+'"></i> '+
from+(to?'&ndash;'+to:'+')
);
}
this._div.innerHTML="<b>Penduduk</b><br>"+labels.join("<br>");
}
};
legend.addTo(map);
/* ======================
UPDATE MAP
====================== */
function updateMap(){
geojson.setStyle(style);
legend.update();
info.update();
}
</script>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
var statesData = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"name": "Pontianak Barat", "density": 4200},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.30,-0.01],[109.33,-0.01],[109.33,-0.05],[109.30,-0.05],[109.30,-0.01]
]]
}
},
{
"type": "Feature",
"properties": {"name": "Pontianak Kota", "density": 9500},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.33,-0.01],[109.36,-0.01],[109.36,-0.05],[109.33,-0.05],[109.33,-0.01]
]]
}
},
{
"type": "Feature",
"properties": {"name": "Pontianak Selatan", "density": 7800},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.33,-0.05],[109.36,-0.05],[109.36,-0.08],[109.33,-0.08],[109.33,-0.05]
]]
}
},
{
"type": "Feature",
"properties": {"name": "Pontianak Tenggara", "density": 5200},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.36,-0.05],[109.40,-0.05],[109.40,-0.08],[109.36,-0.08],[109.36,-0.05]
]]
}
},
{
"type": "Feature",
"properties": {"name": "Pontianak Timur", "density": 4600},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.36,-0.01],[109.40,-0.01],[109.40,-0.05],[109.36,-0.05],[109.36,-0.01]
]]
}
},
{
"type": "Feature",
"properties": {"name": "Pontianak Utara", "density": 3000},
"geometry": {
"type": "Polygon",
"coordinates": [[
[109.33,0.01],[109.38,0.01],[109.38,-0.01],[109.33,-0.01],[109.33,0.01]
]]
}
}
]
};
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
<?php
include 'koneksi.php';
$data = [];
$result = $conn->query("SELECT * FROM spbu");
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data);
?>
+23
View File
@@ -0,0 +1,23 @@
-- =============================================
-- DATABASE: db_24jam
-- =============================================
CREATE DATABASE IF NOT EXISTS `db_24jam`
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE `db_24jam`;
-- ---------------------------------------------
-- Tabel: spbu
-- ---------------------------------------------
CREATE TABLE IF NOT EXISTS `spbu` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`nama_spbu` VARCHAR(100) NOT NULL,
`no_wa` VARCHAR(20) NOT NULL,
`status` ENUM('yes','no') NOT NULL DEFAULT 'no',
`latitude` DOUBLE NOT NULL,
`longitude` DOUBLE NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+15
View File
@@ -0,0 +1,15 @@
<?php
ob_start();
include 'koneksi.php';
ob_clean();
$id = $conn->real_escape_string($_POST['id'] ?? '');
$query = "DELETE FROM spbu WHERE id='$id'";
if ($conn->query($query)) {
echo "success";
} else {
echo "error";
}
?>
+466
View File
@@ -0,0 +1,466 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Peta SPBU</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<style>
*{
margin:0;
padding:0;
box-sizing:border-box;
}
body{
font-family:'Segoe UI',sans-serif;
}
#map{
height:100vh;
}
/* ========================= */
/* POPUP UMUM */
/* ========================= */
.leaflet-popup-content-wrapper{
border-radius:20px;
overflow:hidden;
padding:0;
}
.leaflet-popup-content{
margin:0;
width:290px !important;
}
.leaflet-popup-tip{
background:white;
}
/* ========================= */
/* POPUP INFO SPBU */
/* ========================= */
.spbu-card{
padding:14px;
background:#fff;
}
.spbu-header{
background:linear-gradient(135deg,#ff9800,#ff5b00);
color:white;
padding:16px;
border-radius:16px;
text-align:center;
font-size:20px;
font-weight:800;
margin-bottom:12px;
}
.spbu-info{
background:#f4f4f4;
border-radius:14px;
padding:14px;
margin-bottom:10px;
font-size:16px;
display:flex;
gap:8px;
align-items:center;
}
.status-open{
color:#18a84c;
font-weight:bold;
}
.status-close{
color:#ff2020;
font-weight:bold;
}
.spbu-actions{
display:grid;
grid-template-columns:1fr 1fr;
gap:10px;
}
.btn{
border:none;
padding:12px;
border-radius:14px;
font-size:15px;
font-weight:700;
color:white;
cursor:pointer;
}
.btn-edit{
background:#2995f0;
}
.btn-delete{
background:#ff4336;
}
/* ========================= */
/* FORM */
/* ========================= */
.form-box{
padding:16px;
background:white;
}
.form-title{
text-align:center;
font-size:19px;
font-weight:800;
color:#ff7300;
margin-bottom:14px;
}
.form-group{
margin-bottom:12px;
}
.form-group label{
display:block;
margin-bottom:6px;
font-size:14px;
font-weight:700;
color:#444;
}
.form-control{
width:100%;
padding:10px 12px;
border:1px solid #ddd;
border-radius:12px;
font-size:14px;
outline:none;
}
.form-control:focus{
border-color:#ff9800;
}
.btn-save{
width:100%;
margin-top:4px;
border:none;
background:linear-gradient(135deg,#ff9800,#ff5b00);
color:white;
padding:12px;
border-radius:14px;
font-size:15px;
font-weight:800;
cursor:pointer;
}
/* layer control */
.leaflet-control-layers{
border-radius:14px !important;
overflow:hidden;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map('map').setView([-0.02,109.34],13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{
maxZoom:19
}).addTo(map);
/* layer */
let buka24 = L.layerGroup().addTo(map);
let tidak24 = L.layerGroup().addTo(map);
/* marker icon */
function markerIcon(color){
return L.icon({
iconUrl:`https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-${color}.png`,
shadowUrl:'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png',
iconSize:[25,41],
iconAnchor:[12,41]
});
}
/* ========================= */
/* LOAD DATA */
/* ========================= */
fetch('ambil_data.php')
.then(r=>r.json())
.then(data=>{
data.forEach(d=>{
let warna = d.status === 'yes' ? 'green' : 'red';
let marker = L.marker(
[d.latitude,d.longitude],
{
icon:markerIcon(warna),
draggable:true
}
);
marker.bindPopup(`
<div class="spbu-card">
<div class="spbu-header">
⛽ ${d.nama_spbu}
</div>
<div class="spbu-info">
📞 WA : ${d.no_wa}
</div>
<div class="spbu-info">
🕒 Status :
<span class="${d.status=='yes' ? 'status-open':'status-close'}">
${d.status=='yes' ? 'Buka 24 Jam':'Tidak 24 Jam'}
</span>
</div>
<div class="spbu-actions">
<button class="btn btn-edit"
onclick="editData(${d.id},'${d.nama_spbu}','${d.no_wa}','${d.status}',${d.latitude},${d.longitude})">
✏️ Edit
</button>
<button class="btn btn-delete"
onclick="hapusData(${d.id})">
🗑 Hapus
</button>
</div>
</div>
`);
marker.on('dragend',function(e){
let pos = e.target.getLatLng();
fetch('update.php',{
method:'POST',
body:new URLSearchParams({
id:d.id,
nama:d.nama_spbu,
wa:d.no_wa,
status:d.status,
lat:pos.lat,
lng:pos.lng
})
})
.then(r=>r.text())
.then(res=>{
if(res=="success"){
alert("Lokasi berhasil diupdate");
location.reload();
}
});
});
if(d.status=="yes"){
marker.addTo(buka24);
}else{
marker.addTo(tidak24);
}
});
});
/* layer control */
L.control.layers(null,{
"🟢 SPBU Buka 24 Jam": buka24,
"🔴 SPBU Tidak Buka 24 Jam": tidak24
},{collapsed:false}).addTo(map);
/* ========================= */
/* TAMBAH DATA */
/* ========================= */
map.on('click',function(e){
let lat = e.latlng.lat;
let lng = e.latlng.lng;
L.popup({maxWidth:310})
.setLatLng(e.latlng)
.setContent(`
<div class="form-box">
<div class="form-title">Tambah SPBU</div>
<form id="formTambah">
<div class="form-group">
<label>Nama SPBU</label>
<input class="form-control" type="text" name="nama" required>
</div>
<div class="form-group">
<label>No WhatsApp</label>
<input class="form-control" type="text" name="wa" required>
</div>
<div class="form-group">
<label>Status 24 Jam</label>
<select class="form-control" name="status">
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</div>
<button class="btn-save">💾 Simpan</button>
</form>
</div>
`)
.openOn(map);
setTimeout(()=>{
document.getElementById("formTambah").onsubmit=function(ev){
ev.preventDefault();
let fd = new FormData(this);
fd.append("lat",lat);
fd.append("lng",lng);
fetch('simpan.php',{
method:'POST',
body:fd
})
.then(r=>r.text())
.then(res=>{
if(res=="success"){
alert("Data berhasil disimpan");
location.reload();
}else{
alert("Gagal menyimpan");
}
});
}
},100);
});
/* ========================= */
/* EDIT DATA */
/* ========================= */
function editData(id,nama,wa,status,lat,lng){
L.popup({maxWidth:310})
.setLatLng([lat,lng])
.setContent(`
<div class="form-box">
<div class="form-title">Edit SPBU</div>
<form id="formEdit">
<div class="form-group">
<label>Nama SPBU</label>
<input class="form-control" type="text" name="nama" value="${nama}" required>
</div>
<div class="form-group">
<label>No WhatsApp</label>
<input class="form-control" type="text" name="wa" value="${wa}" required>
</div>
<div class="form-group">
<label>Status 24 Jam</label>
<select class="form-control" name="status">
<option value="yes" ${status=='yes'?'selected':''}>Yes</option>
<option value="no" ${status=='no'?'selected':''}>No</option>
</select>
</div>
<button class="btn-save">💾 Update</button>
</form>
</div>
`)
.openOn(map);
setTimeout(()=>{
document.getElementById("formEdit").onsubmit=function(ev){
ev.preventDefault();
let fd = new FormData(this);
fetch('update.php',{
method:'POST',
body:new URLSearchParams({
id:id,
nama:fd.get("nama"),
wa:fd.get("wa"),
status:fd.get("status"),
lat:lat,
lng:lng
})
})
.then(r=>r.text())
.then(res=>{
if(res=="success"){
alert("Data berhasil diupdate");
location.reload();
}else{
alert("Gagal update");
}
});
}
},100);
}
/* ========================= */
/* HAPUS DATA */
/* ========================= */
function hapusData(id){
if(confirm("Yakin ingin hapus data ini?")){
fetch('hapus.php',{
method:'POST',
body:new URLSearchParams({
id:id
})
})
.then(r=>r.text())
.then(res=>{
if(res=="success"){
alert("Data berhasil dihapus");
location.reload();
}
});
}
}
</script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<?php
$host = getenv('DB_HOST') ?: 'localhost';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASSWORD') ?: '';
$db = getenv('DB_NAME') ?: 'db_24jam';
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) {
die("Koneksi database gagal: " . mysqli_connect_error());
}
?>
+20
View File
@@ -0,0 +1,20 @@
<?php
ob_start();
include 'koneksi.php';
ob_clean();
$nama = $conn->real_escape_string($_POST['nama'] ?? '');
$wa = $conn->real_escape_string($_POST['wa'] ?? '');
$status = $conn->real_escape_string($_POST['status'] ?? '');
$lat = $conn->real_escape_string($_POST['lat'] ?? '');
$lng = $conn->real_escape_string($_POST['lng'] ?? '');
$query = "INSERT INTO spbu (nama_spbu, no_wa, status, latitude, longitude)
VALUES ('$nama','$wa','$status','$lat','$lng')";
if ($conn->query($query)) {
echo "success";
} else {
echo "error";
}
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
ob_start();
include 'koneksi.php';
ob_clean();
$id = $conn->real_escape_string($_POST['id'] ?? '');
$nama = $conn->real_escape_string($_POST['nama'] ?? '');
$wa = $conn->real_escape_string($_POST['wa'] ?? '');
$status = $conn->real_escape_string($_POST['status'] ?? '');
$lat = $conn->real_escape_string($_POST['lat'] ?? '');
$lng = $conn->real_escape_string($_POST['lng'] ?? '');
$query = "UPDATE spbu SET
nama_spbu='$nama',
no_wa='$wa',
status='$status',
latitude='$lat',
longitude='$lng'
WHERE id='$id'";
if ($conn->query($query)) {
echo "success";
} else {
echo "error";
}
?>
+53
View File
@@ -0,0 +1,53 @@
# WebGIS Pengentasan Kemiskinan — UAS 06 (FIXED)
## 📦 Struktur Folder WAJIB
```
webgis_uas06/ ← taruh di htdocs/
├── database_uas_06.sql ← Import ke MySQL PERTAMA
├── koneksi.php
├── auth.php
├── ambil.php
├── index.php
├── login.php
├── uploads/ ← buat folder ini (chmod 777)
└── api/
├── dashboard.php
├── penduduk.php
├── bantuan.php
├── laporan.php
├── ibadah.php
├── user.php
└── chat.php
```
> ⚠️ PENTING: File dashboard.php, penduduk.php, dll HARUS berada di folder `api/`
> bukan di root. Jika salah letak → semua halaman gagal memuat.
## 🚀 Cara Install
1. Import database: buka phpMyAdmin → Import → `database_uas_06.sql`
2. Copy folder `webgis_uas06/` ke `C:\xampp\htdocs\`
3. Pastikan folder `uploads/` ada di dalam webgis_uas06/
4. Akses: `http://localhost/webgis_uas06/login.php`
## 👤 Akun Demo (password: `password`)
| Role | Email |
|------|-------|
| Pengurus (Admin) | admin@uas.id |
| Pimpinan Daerah | pimpinan@uas.id |
| Masyarakat | warga@uas.id |
## 🐛 Bug yang Diperbaiki
- ✅ Dashboard gagal memuat → fixed null-check pada query result
- ✅ Penduduk gagal memuat & filter miskin/rentan tidak ada data → fixed
- ✅ Simpan penduduk baru gagal → fixed bind_param & NULL handling
- ✅ Edit penduduk gagal → fixed query UPDATE
- ✅ Laporan filter tidak berfungsi → fixed query WHERE
- ✅ Bantuan/histori gagal muat → fixed error handling
- ✅ Chat gagal (unread error saat belum login) → fixed null check user_id
- ✅ Ibadah & User gagal memuat → fixed error response
- ✅ Detail penduduk gagal → sudah benar, path api/ fixed
- ✅ Semua file API dipindah ke folder api/ sesuai path di index.php
+39
View File
@@ -0,0 +1,39 @@
<?php
include 'koneksi.php';
$tipe=$_GET['tipe']??'semua';
$data=[];
if ($tipe==='semua'||$tipe==='ibadah') {
$res=$conn->query("SELECT ri.*,
(SELECT COUNT(*) FROM penduduk p WHERE p.rumah_ibadah_id=ri.id AND p.status_hidup='hidup') AS total_penduduk,
(SELECT COUNT(*) FROM penduduk p WHERE p.rumah_ibadah_id=ri.id AND p.status_ekonomi='miskin') AS total_miskin
FROM rumah_ibadah ri");
if($res) while($r=$res->fetch_assoc()){$r['tipe']='ibadah';$data[]=$r;}
}
if ($tipe==='semua'||$tipe==='penduduk') {
$where=["p.lat IS NOT NULL","p.lng IS NOT NULL","p.status_hidup='hidup'"];
if(!empty($_GET['status_ekonomi'])){$se=$conn->real_escape_string($_GET['status_ekonomi']);$where[]="p.status_ekonomi='$se'";}
if(!empty($_GET['rumah_ibadah_id'])){$ri_id=(int)$_GET['rumah_ibadah_id'];$where[]="p.rumah_ibadah_id=$ri_id";}
if(!empty($_GET['status_bantuan'])){
if($_GET['status_bantuan']==='sudah') $where[]="p.id IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')";
else $where[]="p.id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')";
}
$w=implode(' AND ',$where);
$res=$conn->query("SELECT p.id,p.nik,p.nama,p.jenis_kelamin,
TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,
p.status_ekonomi,p.pekerjaan,p.penghasilan,p.lat,p.lng,p.rumah_ibadah_id,
ri.nama AS nama_ibadah,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') AS total_bantuan
FROM penduduk p
LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id
WHERE $w");
if($res) while($r=$res->fetch_assoc()){$r['tipe']='penduduk';$r['penghasilan']=(float)$r['penghasilan'];$data[]=$r;}
}
if ($tipe==='semua'||$tipe==='laporan') {
$res=$conn->query("SELECT id,pelapor,deskripsi,lat,lng,urgensi,status,created_at FROM laporan WHERE lat IS NOT NULL AND lng IS NOT NULL");
if($res) while($r=$res->fetch_assoc()){$r['tipe']='laporan';$data[]=$r;}
}
json_out($data);
+31
View File
@@ -0,0 +1,31 @@
<?php
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'cek';
if ($aksi === 'cek') {
if (!empty($_SESSION['user_id'])) json_out(['login'=>true,'user'=>$_SESSION['user']]);
else json_out(['login'=>false]);
}
if ($aksi === 'login') {
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (!$email || !$password) json_out(['error'=>'Email dan password wajib diisi'],400);
$stmt = $conn->prepare("SELECT u.*,r.nama AS role_nama FROM user u JOIN role r ON u.role_id=r.id WHERE u.email=? AND u.aktif=1 LIMIT 1");
$stmt->bind_param('s',$email);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$stmt->close();
if (!$user || !password_verify($password,$user['password'])) json_out(['error'=>'Email atau password salah'],401);
unset($user['password']);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user'] = $user;
log_aktivitas($conn,$user['id'],'LOGIN','user',$user['id'],'Login berhasil');
json_out(['login'=>true,'user'=>$user]);
}
if ($aksi === 'logout') {
session_destroy();
json_out(['logout'=>true]);
}
+116
View File
@@ -0,0 +1,116 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'list_bantuan';
if ($aksi === 'list_bantuan') {
$res=$conn->query("SELECT * FROM bantuan ORDER BY jenis,nama");
if(!$res) json_out(['error'=>$conn->error],500);
$rows=[]; while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'simpan_master') {
$nm=$conn->real_escape_string($_POST['nama']??'');
$jn=$conn->real_escape_string($_POST['jenis']??'sembako');
$sm=$conn->real_escape_string($_POST['sumber']??'');
$bt=$conn->real_escape_string($_POST['bentuk']??'');
$conn->query("INSERT INTO bantuan (nama,jenis,sumber,bentuk) VALUES ('$nm','$jn','$sm','$bt')");
if($conn->error) json_out(['error'=>$conn->error],500);
json_out(['success'=>true,'id'=>$conn->insert_id]);
}
if ($aksi === 'edit_master') {
$id=(int)($_POST['id']??0);
$nm=$conn->real_escape_string($_POST['nama']??'');
$jn=$conn->real_escape_string($_POST['jenis']??'sembako');
$sm=$conn->real_escape_string($_POST['sumber']??'');
$bt=$conn->real_escape_string($_POST['bentuk']??'');
$conn->query("UPDATE bantuan SET nama='$nm',jenis='$jn',sumber='$sm',bentuk='$bt' WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'hapus_master') {
$id=(int)($_POST['id']??0);
$conn->query("DELETE FROM histori_bantuan WHERE bantuan_id=$id");
$conn->query("DELETE FROM bantuan WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'histori') {
$where=['1=1']; $params=[]; $types='';
if(!empty($_GET['penduduk_id'])){$where[]="hb.penduduk_id=?";$params[]=(int)$_GET['penduduk_id'];$types.='i';}
if(!empty($_GET['bantuan_id'])){$where[]="hb.bantuan_id=?";$params[]=(int)$_GET['bantuan_id'];$types.='i';}
if(!empty($_GET['status'])){$where[]="hb.status=?";$params[]=$_GET['status'];$types.='s';}
if(!empty($_GET['tgl_dari'])){$where[]="hb.tanggal>=?";$params[]=$_GET['tgl_dari'];$types.='s';}
if(!empty($_GET['tgl_sampai'])){$where[]="hb.tanggal<=?";$params[]=$_GET['tgl_sampai'];$types.='s';}
$w=implode(' AND ',$where);
$sql="SELECT hb.*,b.nama AS nama_bantuan,b.jenis,b.sumber,p.nama AS nama_penduduk,p.status_ekonomi,p.nik,u.nama AS nama_pengurus
FROM histori_bantuan hb
JOIN bantuan b ON hb.bantuan_id=b.id
JOIN penduduk p ON hb.penduduk_id=p.id
LEFT JOIN user u ON hb.disalurkan_oleh=u.id
WHERE $w ORDER BY hb.tanggal DESC,hb.id DESC";
if($params){$stmt=$conn->prepare($sql);$stmt->bind_param($types,...$params);$stmt->execute();$res=$stmt->get_result();$stmt->close();}
else $res=$conn->query($sql);
$rows=[]; while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'salurkan') {
$pid=(int)($_POST['penduduk_id']??0);
$bid=(int)($_POST['bantuan_id']??0);
$tgl=$conn->real_escape_string($_POST['tanggal']??date('Y-m-d'));
$jml=$conn->real_escape_string($_POST['jumlah']??'');
$ket=$conn->real_escape_string($_POST['keterangan']??'');
$uid=!empty($_SESSION['user_id'])?(int)$_SESSION['user_id']:'NULL';
$conn->query("INSERT INTO histori_bantuan (penduduk_id,bantuan_id,tanggal,jumlah,status,disalurkan_oleh,keterangan) VALUES ($pid,$bid,'$tgl','$jml','disalurkan',$uid,'$ket')");
if($conn->error) json_out(['error'=>$conn->error],500);
$new_id=$conn->insert_id;
if($uid!=='NULL'){
log_aktivitas($conn,$uid,'CREATE','histori_bantuan',$new_id,'Salurkan bantuan ke penduduk id='.$pid);
}
json_out(['success'=>true,'id'=>$new_id]);
}
if ($aksi === 'edit_histori') {
$id=(int)($_POST['id']??0);
$status=$conn->real_escape_string($_POST['status']??'disalurkan');
$jml=$conn->real_escape_string($_POST['jumlah']??'');
$ket=$conn->real_escape_string($_POST['keterangan']??'');
$conn->query("UPDATE histori_bantuan SET status='$status',jumlah='$jml',keterangan='$ket' WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'hapus_histori') {
$id=(int)($_POST['id']??0);
$conn->query("DELETE FROM histori_bantuan WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'belum_dibantu') {
$sql="SELECT p.id,p.nik,p.nama,p.status_ekonomi,TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,ri.nama AS nama_ibadah,k.kelurahan
FROM penduduk p
LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id
LEFT JOIN keluarga k ON p.keluarga_id=k.id
WHERE p.status_hidup='hidup'
AND p.status_ekonomi IN ('miskin','rentan')
AND p.id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')
ORDER BY FIELD(p.status_ekonomi,'miskin','rentan'),p.nama";
$res=$conn->query($sql); $rows=[];
while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'statistik') {
$sql="SELECT b.id,b.nama,b.jenis,COUNT(hb.id) AS total_salur,SUM(hb.status='disalurkan') AS berhasil
FROM bantuan b
LEFT JOIN histori_bantuan hb ON hb.bantuan_id=b.id
GROUP BY b.id ORDER BY total_salur DESC";
$res=$conn->query($sql); $rows=[];
while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
+51
View File
@@ -0,0 +1,51 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'inbox';
$uid = (int)($_SESSION['user_id']??0);
if ($aksi === 'inbox') {
if(!$uid) json_out([]);
$sql="SELECT p.*,u.nama AS nama_pengirim
FROM pesan p
JOIN user u ON p.dari_user=u.id
WHERE (p.ke_user=$uid OR p.ke_user IS NULL)
ORDER BY p.created_at DESC LIMIT 80";
$res=$conn->query($sql); $rows=[];
if($res) while($r=$res->fetch_assoc()) $rows[]=$r;
// tandai dibaca
$conn->query("UPDATE pesan SET dibaca=1 WHERE ke_user=$uid AND dibaca=0");
json_out(array_reverse($rows));
}
if ($aksi === 'history') {
$ke=(int)($_GET['ke']??0);
$res=$conn->query("SELECT p.*,u.nama AS nama_pengirim FROM pesan p JOIN user u ON p.dari_user=u.id WHERE (p.dari_user=$uid AND p.ke_user=$ke) OR (p.dari_user=$ke AND p.ke_user=$uid) ORDER BY p.created_at ASC LIMIT 100");
$rows=[]; if($res) while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'kirim') {
if(!$uid) json_out(['error'=>'Belum login'],401);
$ke=!empty($_POST['ke_user'])?(int)$_POST['ke_user']:'NULL';
$isi=$conn->real_escape_string(trim($_POST['isi']??''));
if(!$isi) json_out(['error'=>'Pesan kosong'],400);
$conn->query("INSERT INTO pesan (dari_user,ke_user,isi) VALUES ($uid,$ke,'$isi')");
if($conn->error) json_out(['error'=>$conn->error],500);
json_out(['success'=>true,'id'=>$conn->insert_id,'time'=>date('H:i')]);
}
if ($aksi === 'kontak') {
$res=$conn->query("SELECT u.id,u.nama,r.nama AS role FROM user u JOIN role r ON u.role_id=r.id WHERE u.aktif=1".($uid?" AND u.id!=$uid":'')." ORDER BY u.nama");
$rows=[]; if($res) while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'unread') {
if(!$uid){ json_out(['count'=>0]); }
$res=$conn->query("SELECT COUNT(*) c FROM pesan WHERE (ke_user=$uid OR ke_user IS NULL) AND dibaca=0 AND dari_user!=$uid");
json_out(['count'=>(int)($res->fetch_assoc()['c']??0)]);
}
+43
View File
@@ -0,0 +1,43 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
ob_clean();
$tp=$conn->query("SELECT COUNT(*) c FROM penduduk WHERE status_hidup='hidup'")->fetch_assoc()['c'];
$tm=$conn->query("SELECT COUNT(*) c FROM penduduk WHERE status_ekonomi='miskin' AND status_hidup='hidup'")->fetch_assoc()['c'];
$tr=$conn->query("SELECT COUNT(*) c FROM penduduk WHERE status_ekonomi='rentan' AND status_hidup='hidup'")->fetch_assoc()['c'];
$ta=$conn->query("SELECT COUNT(*) c FROM penduduk WHERE status_ekonomi='mampu' AND status_hidup='hidup'")->fetch_assoc()['c'];
$tk=$conn->query("SELECT COUNT(*) c FROM keluarga")->fetch_assoc()['c'];
$tl=$conn->query("SELECT COUNT(*) c FROM laporan")->fetch_assoc()['c'];
$tld=$conn->query("SELECT COUNT(*) c FROM laporan WHERE urgensi='darurat' AND status='pending'")->fetch_assoc()['c'];
$tlp=$conn->query("SELECT COUNT(*) c FROM laporan WHERE status='pending'")->fetch_assoc()['c'];
$tbs=$conn->query("SELECT COUNT(*) c FROM histori_bantuan WHERE status='disalurkan'")->fetch_assoc()['c'];
$tbd=$conn->query("SELECT COUNT(*) c FROM penduduk WHERE status_ekonomi IN ('miskin','rentan') AND status_hidup='hidup' AND id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')")->fetch_assoc()['c'];
$twi=$conn->query("SELECT COUNT(DISTINCT kelurahan) c FROM keluarga")->fetch_assoc()['c'];
$tri_res=$conn->query("SELECT COUNT(DISTINCT CONCAT(rt,rw,kelurahan)) c FROM keluarga")->fetch_assoc()['c'];
// per-bulan bantuan (6 bln terakhir)
$tren=[]; $res=$conn->query("SELECT DATE_FORMAT(tanggal,'%Y-%m') bln,COUNT(*) jml FROM histori_bantuan WHERE tanggal>=DATE_SUB(CURDATE(),INTERVAL 6 MONTH) GROUP BY bln ORDER BY bln");
if($res) while($r=$res->fetch_assoc()) $tren[]=$r;
// statistik ibadah
$stat_ibadah=[]; $res=$conn->query("SELECT * FROM v_statistik_ibadah");
if($res) while($r=$res->fetch_assoc()) $stat_ibadah[]=$r;
// prioritas
$prioritas=[]; $res=$conn->query("SELECT p.id,p.nik,p.nama,p.status_ekonomi,TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,ri.nama AS nama_ibadah,(SELECT COUNT(*) FROM laporan la WHERE la.penduduk_id=p.id AND la.urgensi IN ('tinggi','darurat') AND la.status!='selesai') AS lap_darurat FROM penduduk p LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id WHERE p.status_hidup='hidup' AND p.status_ekonomi IN ('miskin','rentan') ORDER BY lap_darurat DESC,FIELD(p.status_ekonomi,'miskin','rentan') LIMIT 10");
if($res) while($r=$res->fetch_assoc()) $prioritas[]=$r;
// aktivitas
$aktivitas=[]; $res=$conn->query("SELECT la.*,u.nama AS nama_user FROM log_aktivitas la LEFT JOIN user u ON la.user_id=u.id ORDER BY la.created_at DESC LIMIT 15");
if($res) while($r=$res->fetch_assoc()) $aktivitas[]=$r;
json_out([
'total_penduduk'=>(int)$tp,'total_miskin'=>(int)$tm,'total_rentan'=>(int)$tr,'total_mampu'=>(int)$ta,
'total_keluarga'=>(int)$tk,'total_laporan'=>(int)$tl,'total_darurat'=>(int)$tld,'laporan_pending'=>(int)$tlp,
'total_bantuan_salur'=>(int)$tbs,'belum_dibantu'=>(int)$tbd,'total_wilayah'=>(int)$twi,'total_rt_rw'=>(int)$tri_res,
'tren_bantuan'=>$tren,'stat_ibadah'=>$stat_ibadah,'prioritas'=>$prioritas,'aktivitas'=>$aktivitas,
]);
+285
View File
@@ -0,0 +1,285 @@
-- ============================================================
-- WebGIS Pengentasan Kemiskinan | Database: uas_06
-- Jalankan sekali untuk setup awal
-- ============================================================
CREATE DATABASE IF NOT EXISTS uas_06 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE uas_06;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS log_aktivitas;
DROP TABLE IF EXISTS pesan;
DROP TABLE IF EXISTS histori_bantuan;
DROP TABLE IF EXISTS bantuan;
DROP TABLE IF EXISTS laporan;
DROP TABLE IF EXISTS pelatihan;
DROP TABLE IF EXISTS penduduk;
DROP TABLE IF EXISTS keluarga;
DROP TABLE IF EXISTS rumah_ibadah;
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS role;
SET FOREIGN_KEY_CHECKS = 1;
-- ── 1. ROLE ─────────────────────────────────────────────────
CREATE TABLE role (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(50) NOT NULL,
deskripsi TEXT
) ENGINE=InnoDB;
INSERT INTO role (nama, deskripsi) VALUES
('masyarakat', 'Hanya dapat melihat data dan membuat laporan'),
('pengurus', 'Input & update data penduduk, verifikasi laporan, salurkan bantuan'),
('pimpinan', 'Read-only, monitoring dashboard');
-- ── 2. USER ─────────────────────────────────────────────────
CREATE TABLE user (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role_id INT NOT NULL DEFAULT 1,
aktif TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (role_id) REFERENCES role(id)
) ENGINE=InnoDB;
-- Password untuk semua akun demo: "password"
INSERT INTO user (nama, email, password, role_id) VALUES
('Admin Utama', 'admin@uas.id', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 2),
('Pimpinan Daerah', 'pimpinan@uas.id', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 3),
('Warga Biasa', 'warga@uas.id', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 1),
('Pengurus Masjid', 'pengurus2@uas.id','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 2);
-- ── 3. RUMAH IBADAH ─────────────────────────────────────────
CREATE TABLE rumah_ibadah (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
jenis ENUM('masjid','gereja','pura','vihara','klenteng','lainnya') DEFAULT 'masjid',
kontak VARCHAR(20),
alamat VARCHAR(255),
lat DOUBLE NOT NULL,
lng DOUBLE NOT NULL,
radius DOUBLE DEFAULT 500,
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO rumah_ibadah (nama, jenis, kontak, alamat, lat, lng, radius, user_id) VALUES
('Masjid Al-Ikhlas', 'masjid', '081234567890', 'Jl. Merdeka No.1, Pontianak Kota', 0.0269, 109.3425, 600, 1),
('Gereja Santo Yosef', 'gereja', '081298765432', 'Jl. Veteran No.5, Pontianak Selatan', 0.0310, 109.3300, 500, 1),
('Masjid Al-Furqon', 'masjid', '085678901234', 'Jl. Sudirman No.10, Pontianak Barat', 0.0200, 109.3500, 550, 4);
-- ── 4. KELUARGA (Kartu Keluarga) ────────────────────────────
CREATE TABLE keluarga (
id INT AUTO_INCREMENT PRIMARY KEY,
no_kk VARCHAR(20) NOT NULL UNIQUE,
alamat VARCHAR(255),
rt VARCHAR(5),
rw VARCHAR(5),
kelurahan VARCHAR(100),
kecamatan VARCHAR(100),
lat DOUBLE,
lng DOUBLE,
rumah_ibadah_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO keluarga (no_kk, alamat, rt, rw, kelurahan, kecamatan, lat, lng, rumah_ibadah_id) VALUES
('6171010101010001','Jl. Dahlia No.3', '001','001','Sungai Bangkong','Pontianak Kota', 0.0250,109.3410,1),
('6171010101010002','Jl. Melati No.7', '002','001','Sungai Bangkong','Pontianak Kota', 0.0280,109.3440,1),
('6171010101010003','Jl. Kenanga No.12','001','002','Benua Melayu', 'Pontianak Selatan',0.0215,109.3480,2),
('6171010101010004','Jl. Mawar No.5', '003','002','Akcaya', 'Pontianak Selatan',0.0195,109.3460,2),
('6171010101010005','Jl. Anggrek No.8', '001','003','Bansir Laut', 'Pontianak Tenggara',0.0175,109.3510,3);
-- ── 5. PENDUDUK ──────────────────────────────────────────────
CREATE TABLE penduduk (
id INT AUTO_INCREMENT PRIMARY KEY,
nik VARCHAR(16) NOT NULL UNIQUE,
nama VARCHAR(150) NOT NULL,
jenis_kelamin ENUM('L','P') NOT NULL DEFAULT 'L',
tanggal_lahir DATE NOT NULL,
status_keluarga ENUM('kepala_keluarga','anggota') DEFAULT 'anggota',
status_perkawinan ENUM('belum_kawin','kawin','cerai_hidup','cerai_mati') DEFAULT 'belum_kawin',
status_hidup ENUM('hidup','meninggal') DEFAULT 'hidup',
pekerjaan VARCHAR(100),
penghasilan DECIMAL(15,2) DEFAULT 0,
status_ekonomi ENUM('miskin','rentan','mampu') DEFAULT 'rentan',
pendidikan_terakhir ENUM('tidak_sekolah','SD','SMP','SMA','D3','S1','S2','S3') DEFAULT 'tidak_sekolah',
status_pendidikan ENUM('sekolah','tidak_sekolah','lulus') DEFAULT 'tidak_sekolah',
keluarga_id INT,
rumah_ibadah_id INT,
lat DOUBLE,
lng DOUBLE,
no_hp VARCHAR(20),
jenis_bantuan VARCHAR(255),
foto VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (keluarga_id) REFERENCES keluarga(id) ON DELETE SET NULL,
FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO penduduk (nik,nama,jenis_kelamin,tanggal_lahir,status_keluarga,status_perkawinan,pekerjaan,penghasilan,status_ekonomi,pendidikan_terakhir,status_pendidikan,keluarga_id,rumah_ibadah_id,lat,lng,no_hp,jenis_bantuan) VALUES
('6171010101010001','Ahmad Fauzi', 'L','1985-03-15','kepala_keluarga','kawin', 'Buruh Harian', 800000, 'miskin','SD', 'lulus', 1,1,0.02500,109.34100,'081234567891','sembako'),
('6171010101010002','Siti Aminah', 'P','1988-07-20','anggota', 'kawin', 'Ibu RT', 0, 'miskin','SMP','lulus', 1,1,0.02510,109.34110,NULL, 'sembako'),
('6171010101010003','Budi Santoso', 'L','2012-01-10','anggota', 'belum_kawin','Pelajar', 0, 'miskin','SMP','sekolah', 1,1,0.02520,109.34090,NULL, NULL),
('6171010101010004','Dewi Rahayu', 'P','1979-11-05','kepala_keluarga','cerai_mati','Pedagang', 1500000,'rentan','SMA','lulus', 2,1,0.02800,109.34400,'085678901235','kesehatan'),
('6171010101010005','Rizky Pratama', 'L','2008-06-22','anggota', 'belum_kawin','Pelajar', 0, 'rentan','SMP','sekolah', 2,1,0.02790,109.34420,NULL, NULL),
('6171010101010006','Hendra Wijaya', 'L','1992-09-30','kepala_keluarga','kawin', 'Wiraswasta', 3000000,'mampu', 'S1', 'lulus', 3,2,0.02150,109.34800,'087890123456',NULL),
('6171010101010007','Lina Marlina', 'P','1995-04-18','anggota', 'kawin', 'Guru Honorer', 1800000,'rentan','S1', 'lulus', 3,2,0.02155,109.34810,'081122334455','pendidikan'),
('6171010101010008','Samsul Bahri', 'L','1970-08-25','kepala_keluarga','kawin', 'Nelayan', 600000, 'miskin','SD', 'lulus', 4,2,0.01950,109.34600,'089988776655','sembako'),
('6171010101010009','Aisyah Putri', 'P','1975-12-30','anggota', 'kawin', 'Ibu RT', 0, 'miskin','SD', 'lulus', 4,2,0.01960,109.34590,NULL, NULL),
('6171010101010010','Muhammad Yusuf','L','2005-09-15','anggota', 'belum_kawin','Pelajar', 0, 'miskin','SMA','sekolah', 4,2,0.01970,109.34580,NULL, NULL),
('6171010101010011','Surya Darma', 'L','1988-02-14','kepala_keluarga','kawin', 'Tukang Ojek', 1200000,'rentan','SMP','lulus', 5,3,0.01750,109.35100,'082233445566','ekonomi'),
('6171010101010012','Ratna Sari', 'P','1990-06-08','anggota', 'kawin', 'Pedagang Kecil', 800000, 'rentan','SMA','lulus', 5,3,0.01760,109.35110,NULL, NULL);
-- ── 6. PELATIHAN ────────────────────────────────────────────
CREATE TABLE pelatihan (
id INT AUTO_INCREMENT PRIMARY KEY,
penduduk_id INT NOT NULL,
nama_pelatihan VARCHAR(150),
penyelenggara VARCHAR(100),
tahun YEAR,
keterangan TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (penduduk_id) REFERENCES penduduk(id) ON DELETE CASCADE
) ENGINE=InnoDB;
INSERT INTO pelatihan (penduduk_id, nama_pelatihan, penyelenggara, tahun) VALUES
(1,'Pelatihan Menjahit','Dinas Tenaga Kerja',2023),
(4,'Pelatihan UMKM','Kementerian Koperasi',2022),
(11,'Pelatihan Ojek Online','Grab/Gojek',2023);
-- ── 7. BANTUAN (Master) ─────────────────────────────────────
CREATE TABLE bantuan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
jenis ENUM('sembako','pendidikan','kesehatan','ekonomi','perumahan','lainnya') DEFAULT 'sembako',
sumber VARCHAR(150),
bentuk VARCHAR(255),
keterangan TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
INSERT INTO bantuan (nama, jenis, sumber, bentuk) VALUES
('Sembako Bulanan', 'sembako', 'Rumah Ibadah', '10kg beras + minyak goreng 1L + gula 1kg'),
('Beasiswa Pendidikan', 'pendidikan', 'Pemerintah Daerah','Rp 500.000/bulan'),
('BPJS Kesehatan', 'kesehatan', 'Pemerintah Pusat', 'Kartu BPJS PBI gratis'),
('Modal Usaha Mikro', 'ekonomi', 'Donatur/CSR', 'Rp 1.000.000 Rp 5.000.000 sekali'),
('Perbaikan Rumah', 'perumahan', 'Pemerintah Daerah','Material bangunan senilai Rp 10.000.000');
-- ── 8. HISTORI BANTUAN ──────────────────────────────────────
CREATE TABLE histori_bantuan (
id INT AUTO_INCREMENT PRIMARY KEY,
penduduk_id INT NOT NULL,
bantuan_id INT NOT NULL,
tanggal DATE NOT NULL,
jumlah VARCHAR(100),
status ENUM('direncanakan','disalurkan','ditolak') DEFAULT 'direncanakan',
disalurkan_oleh INT,
keterangan TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (penduduk_id) REFERENCES penduduk(id) ON DELETE CASCADE,
FOREIGN KEY (bantuan_id) REFERENCES bantuan(id) ON DELETE CASCADE,
FOREIGN KEY (disalurkan_oleh) REFERENCES user(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO histori_bantuan (penduduk_id,bantuan_id,tanggal,jumlah,status,disalurkan_oleh,keterangan) VALUES
(1,1,'2024-01-10','10kg beras + minyak 1L','disalurkan',1,'Penyaluran Januari 2024'),
(1,3,'2024-02-01','1 kartu BPJS', 'disalurkan',1,'Didaftarkan BPJS PBI'),
(2,1,'2024-01-10','10kg beras + minyak 1L','disalurkan',1,'Penyaluran Januari 2024'),
(4,2,'2024-03-01','Rp 500.000', 'disalurkan',1,'Beasiswa anak sekolah'),
(8,1,'2024-01-15','10kg beras', 'disalurkan',4,'Bantuan Masjid Al-Furqon'),
(9,3,'2024-02-15','1 kartu BPJS', 'disalurkan',4,'BPJS untuk ibu'),
(11,4,'2024-04-01','Rp 2.000.000', 'disalurkan',4,'Modal usaha ojek online');
-- ── 9. LAPORAN MASYARAKAT ────────────────────────────────────
CREATE TABLE laporan (
id INT AUTO_INCREMENT PRIMARY KEY,
pelapor VARCHAR(100),
penduduk_id INT,
deskripsi TEXT NOT NULL,
lat DOUBLE,
lng DOUBLE,
foto VARCHAR(255),
urgensi ENUM('rendah','sedang','tinggi','darurat') DEFAULT 'sedang',
status ENUM('pending','diverifikasi','diproses','selesai') DEFAULT 'pending',
diverifikasi_oleh INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (penduduk_id) REFERENCES penduduk(id) ON DELETE SET NULL,
FOREIGN KEY (diverifikasi_oleh) REFERENCES user(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO laporan (pelapor,penduduk_id,deskripsi,lat,lng,urgensi,status) VALUES
('Anonim', 1,'Pak Ahmad sakit keras sudah seminggu dan tidak punya biaya berobat. Kondisi sangat memprihatinkan.',0.02505,109.34105,'darurat','pending'),
('Pak RT 02', 8,'Keluarga Pak Samsul di Jl. Mawar sudah 3 hari tidak makan. Sangat membutuhkan bantuan pangan segera.',0.01955,109.34605,'darurat','diverifikasi'),
('Bu Dewi', NULL,'Ada anak putus sekolah di RT 003 karena orang tuanya tidak mampu bayar uang gedung.',0.02810,109.34410,'tinggi','pending'),
('Pengurus Masjid',10,'Muhammad Yusuf butuh beasiswa lanjut SMA, keluarga tidak mampu.',0.01970,109.34580,'sedang','diproses'),
('Warga RT 001', NULL,'Jalan di Jl. Anggrek rusak parah, bahaya bagi warga lansia.',0.01760,109.35115,'rendah','pending');
-- ── 10. LOG AKTIVITAS ────────────────────────────────────────
CREATE TABLE log_aktivitas (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
aksi VARCHAR(50),
tabel VARCHAR(50),
data_id INT,
keterangan TEXT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO log_aktivitas (user_id,aksi,tabel,data_id,keterangan) VALUES
(1,'LOGIN','user',1,'Login pertama kali'),
(1,'CREATE','histori_bantuan',1,'Salurkan sembako untuk Ahmad Fauzi'),
(4,'CREATE','histori_bantuan',5,'Salurkan sembako untuk Samsul Bahri'),
(1,'UPDATE','laporan',2,'Verifikasi laporan darurat');
-- ── 11. PESAN / CHAT ────────────────────────────────────────
CREATE TABLE pesan (
id INT AUTO_INCREMENT PRIMARY KEY,
dari_user INT NOT NULL,
ke_user INT,
isi TEXT NOT NULL,
dibaca TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (dari_user) REFERENCES user(id) ON DELETE CASCADE,
FOREIGN KEY (ke_user) REFERENCES user(id) ON DELETE SET NULL
) ENGINE=InnoDB;
INSERT INTO pesan (dari_user,ke_user,isi,dibaca) VALUES
(1,NULL,'Selamat datang di sistem WebGIS Kemiskinan UAS 06. Semua pengurus harap aktif memperbarui data.',1),
(4,NULL,'Sudah ada 3 laporan darurat baru masuk. Mohon segera ditindaklanjuti.',0),
(1,NULL,'Penyaluran bantuan bulan Januari 2024 sudah selesai untuk wilayah Pontianak Kota.',1);
-- ── 12. VIEWS ───────────────────────────────────────────────
CREATE OR REPLACE VIEW v_penduduk AS
SELECT
p.*,
TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,
k.no_kk, k.rt, k.rw, k.kelurahan, k.kecamatan,
ri.nama AS nama_ibadah, ri.jenis AS jenis_ibadah,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') AS total_bantuan_diterima
FROM penduduk p
LEFT JOIN keluarga k ON p.keluarga_id=k.id
LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id;
CREATE OR REPLACE VIEW v_statistik_ibadah AS
SELECT
ri.id, ri.nama, ri.jenis,
COUNT(p.id) AS total_penduduk,
SUM(p.status_ekonomi='miskin') AS total_miskin,
SUM(p.status_ekonomi='rentan') AS total_rentan,
SUM(p.status_ekonomi='mampu') AS total_mampu
FROM rumah_ibadah ri
LEFT JOIN penduduk p ON p.rumah_ibadah_id=ri.id AND p.status_hidup='hidup'
GROUP BY ri.id;
-- ── SELESAI ──────────────────────────────────────────────────
SELECT 'Database uas_06 berhasil dibuat!' AS status;
+49
View File
@@ -0,0 +1,49 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'list';
if ($aksi === 'list') {
$res=$conn->query("SELECT ri.*,
(SELECT COUNT(*) FROM penduduk p WHERE p.rumah_ibadah_id=ri.id AND p.status_hidup='hidup') AS total_penduduk,
(SELECT COUNT(*) FROM penduduk p WHERE p.rumah_ibadah_id=ri.id AND p.status_ekonomi='miskin' AND p.status_hidup='hidup') AS total_miskin
FROM rumah_ibadah ri ORDER BY ri.nama");
if(!$res) json_out(['error'=>$conn->error],500);
$rows=[]; while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'simpan') {
$nm=$conn->real_escape_string($_POST['nama']??'');
$jn=$conn->real_escape_string($_POST['jenis']??'masjid');
$kt=$conn->real_escape_string($_POST['kontak']??'');
$al=$conn->real_escape_string($_POST['alamat']??'');
$lat=(float)($_POST['lat']??0);
$lng=(float)($_POST['lng']??0);
$rad=(float)($_POST['radius']??500);
$conn->query("INSERT INTO rumah_ibadah (nama,jenis,kontak,alamat,lat,lng,radius) VALUES ('$nm','$jn','$kt','$al',$lat,$lng,$rad)");
if($conn->error) json_out(['error'=>$conn->error],500);
json_out(['success'=>true,'id'=>$conn->insert_id]);
}
if ($aksi === 'edit') {
$id=(int)($_POST['id']??0);
$nm=$conn->real_escape_string($_POST['nama']??'');
$jn=$conn->real_escape_string($_POST['jenis']??'masjid');
$kt=$conn->real_escape_string($_POST['kontak']??'');
$al=$conn->real_escape_string($_POST['alamat']??'');
$rad=(float)($_POST['radius']??500);
$conn->query("UPDATE rumah_ibadah SET nama='$nm',jenis='$jn',kontak='$kt',alamat='$al',radius=$rad WHERE id=$id");
if($conn->error) json_out(['error'=>$conn->error],500);
json_out(['success'=>true]);
}
if ($aksi === 'hapus') {
$id=(int)($_POST['id']??0);
$conn->query("UPDATE penduduk SET rumah_ibadah_id=NULL WHERE rumah_ibadah_id=$id");
$conn->query("DELETE FROM rumah_ibadah WHERE id=$id");
json_out(['success'=>true]);
}
+1671
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
<?php
// ============================================================
// koneksi.php Database uas_06
// ============================================================
$host = getenv('DB_HOST') ?: 'localhost';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASSWORD') ?: '';
$db = getenv('DB_NAME') ?: 'uas_06';
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) {
http_response_code(500);
header('Content-Type: application/json');
die(json_encode(['error' => 'Koneksi database gagal: ' . mysqli_connect_error()]));
}
$conn->set_charset('utf8mb4');
function log_aktivitas($conn, $user_id, $aksi, $tabel, $data_id = null, $ket = '') {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$stmt = $conn->prepare("INSERT INTO log_aktivitas (user_id,aksi,tabel,data_id,keterangan,ip_address) VALUES (?,?,?,?,?,?)");
if($stmt){
$stmt->bind_param('ississ', $user_id, $aksi, $tabel, $data_id, $ket, $ip);
$stmt->execute();
$stmt->close();
}
}
function json_out($data, $code = 200) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function requireLogin() {
session_start();
if (empty($_SESSION['user_id'])) {
json_out(['error' => 'Belum login', 'redirect' => 'login.php'], 401);
}
return $_SESSION['user'];
}
+85
View File
@@ -0,0 +1,85 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'list';
if ($aksi === 'list') {
$where=['1=1'];
if(!empty($_GET['status'])){$where[]="l.status='".$conn->real_escape_string($_GET['status'])."'";}
if(!empty($_GET['urgensi'])){$where[]="l.urgensi='".$conn->real_escape_string($_GET['urgensi'])."'";}
if(!empty($_GET['q'])){
$q=$conn->real_escape_string($_GET['q']);
$where[]="(l.pelapor LIKE '%$q%' OR l.deskripsi LIKE '%$q%')";
}
$w=implode(' AND ',$where);
$sql="SELECT l.*,p.nama AS nama_penduduk,p.status_ekonomi,p.nik,u.nama AS nama_verifikator
FROM laporan l
LEFT JOIN penduduk p ON l.penduduk_id=p.id
LEFT JOIN user u ON l.diverifikasi_oleh=u.id
WHERE $w
ORDER BY FIELD(l.urgensi,'darurat','tinggi','sedang','rendah'),l.created_at DESC";
$res=$conn->query($sql);
if(!$res) json_out(['error'=>$conn->error],500);
$rows=[]; while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'detail') {
$id=(int)($_GET['id']??0);
$res=$conn->query("SELECT l.*,p.nama AS nama_penduduk,u.nama AS nama_verifikator FROM laporan l LEFT JOIN penduduk p ON l.penduduk_id=p.id LEFT JOIN user u ON l.diverifikasi_oleh=u.id WHERE l.id=$id LIMIT 1");
json_out($res->fetch_assoc()??['error'=>'Tidak ditemukan']);
}
if ($aksi === 'kirim') {
$pel=$conn->real_escape_string(trim($_POST['pelapor']??'Anonim'));
if(empty($pel)) $pel='Anonim';
$desk=$conn->real_escape_string(trim($_POST['deskripsi']??''));
if(!$desk) json_out(['error'=>'Deskripsi wajib'],400);
$lat=!empty($_POST['lat'])?(float)$_POST['lat']:'NULL';
$lng=!empty($_POST['lng'])?(float)$_POST['lng']:'NULL';
$urg=$conn->real_escape_string($_POST['urgensi']??'sedang');
$pid=!empty($_POST['penduduk_id'])?(int)$_POST['penduduk_id']:'NULL';
$foto='NULL';
if(!empty($_FILES['foto']['name'])){
$ext=pathinfo($_FILES['foto']['name'],PATHINFO_EXTENSION);
$fname='laporan_'.time().'.'.$ext;
$uploadDir=__DIR__.'/uploads/bukti/';
if(!is_dir($uploadDir)) mkdir($uploadDir,0777,true);
move_uploaded_file($_FILES['foto']['tmp_name'],$uploadDir.$fname);
$foto="'".$fname."'";
}
$conn->query("INSERT INTO laporan (pelapor,penduduk_id,deskripsi,lat,lng,foto,urgensi) VALUES ('$pel',$pid,'$desk',$lat,$lng,$foto,'$urg')");
if($conn->error) json_out(['error'=>$conn->error],500);
if(!empty($_SESSION['user_id'])){
log_aktivitas($conn,(int)$_SESSION['user_id'],'CREATE','laporan',$conn->insert_id,'Kirim laporan oleh '.$pel);
}
json_out(['success'=>true,'id'=>$conn->insert_id]);
}
if ($aksi === 'edit') {
$id=(int)($_POST['id']??0);
$desk=$conn->real_escape_string($_POST['deskripsi']??'');
$urg=$conn->real_escape_string($_POST['urgensi']??'sedang');
$pel=$conn->real_escape_string($_POST['pelapor']??'');
$status=$conn->real_escape_string($_POST['status']??'pending');
$uid=!empty($_SESSION['user_id'])?(int)$_SESSION['user_id']:'NULL';
$conn->query("UPDATE laporan SET pelapor='$pel',deskripsi='$desk',urgensi='$urg',status='$status',diverifikasi_oleh=$uid WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'update_status') {
$id=(int)($_POST['id']??0);
$status=$conn->real_escape_string($_POST['status']??'pending');
$uid=!empty($_SESSION['user_id'])?(int)$_SESSION['user_id']:'NULL';
$conn->query("UPDATE laporan SET status='$status',diverifikasi_oleh=$uid WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'hapus') {
$id=(int)($_POST['id']??0);
$conn->query("DELETE FROM laporan WHERE id=$id");
json_out(['success'=>true]);
}
+110
View File
@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Login WebGIS Kemiskinan UAS 06</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
<style>
:root{--bg:#0d1117;--surface:#161b22;--card:#1c2230;--border:#30363d;--text:#e6edf3;--muted:#8b949e;--accent:#58a6ff;--red:#f85149;--green:#3fb950}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Plus Jakarta Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
.wrap{width:100%;max-width:900px;display:grid;grid-template-columns:1fr 1fr;gap:0;border-radius:16px;overflow:hidden;box-shadow:0 8px 48px rgba(0,0,0,.7);border:1px solid var(--border)}
.left{background:linear-gradient(135deg,#0d1117 0%,#1a2332 100%);padding:48px 40px;display:flex;flex-direction:column;justify-content:center;position:relative;overflow:hidden}
.left::before{content:'';position:absolute;top:-60px;right:-60px;width:240px;height:240px;background:rgba(88,166,255,.06);border-radius:50%}
.left::after{content:'';position:absolute;bottom:-40px;left:-40px;width:180px;height:180px;background:rgba(63,185,80,.05);border-radius:50%}
.left-ico{font-size:40px;margin-bottom:20px;display:block}
.left h1{font-size:26px;font-weight:800;letter-spacing:-0.5px;margin-bottom:10px;line-height:1.2}
.left p{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:28px}
.feat{display:flex;align-items:center;gap:10px;margin-bottom:10px;font-size:12px;color:var(--muted)}
.feat i{color:var(--accent);width:16px}
.right{background:var(--surface);padding:48px 40px}
.right h2{font-size:20px;font-weight:800;margin-bottom:6px}
.right .sub{font-size:12px;color:var(--muted);margin-bottom:28px}
.fg{margin-bottom:16px}
.fg label{font-size:11px;font-weight:700;color:var(--muted);display:block;margin-bottom:5px;text-transform:uppercase;letter-spacing:.4px}
.fg input{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 13px;border-radius:8px;font-size:13px;font-family:inherit;outline:none;transition:border .15s}
.fg input:focus{border-color:var(--accent)}
.btn-login{width:100%;background:var(--accent);color:#0d1117;border:none;padding:11px;border-radius:8px;font-size:14px;font-weight:800;font-family:inherit;cursor:pointer;transition:background .15s;margin-top:4px}
.btn-login:hover{background:#79c0ff}
.btn-login:disabled{opacity:.6;cursor:not-allowed}
.err{background:rgba(248,81,73,.08);border:1px solid rgba(248,81,73,.25);border-radius:7px;padding:9px 12px;font-size:12px;color:var(--red);margin-bottom:14px;display:none;align-items:center;gap:7px}
.demo-box{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:14px;margin-top:20px}
.demo-box strong{display:block;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
.demo-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border);font-size:12px}
.demo-row:last-child{border:none}
.demo-row .role{color:var(--muted);font-size:11px}
.demo-row .creds{font-size:11px;color:var(--accent);cursor:pointer}
.demo-row .creds:hover{text-decoration:underline}
@media(max-width:650px){.wrap{grid-template-columns:1fr}.left{display:none}}
</style>
</head>
<body>
<div class="wrap">
<div class="left">
<span class="left-ico">🗺️</span>
<h1>WebGIS Pengentasan Kemiskinan</h1>
<p>Sistem informasi geografis berbasis masyarakat untuk monitoring dan pengentasan kemiskinan yang terintegrasi dengan rumah ibadah.</p>
<div class="feat"><i class="fas fa-map-marked-alt"></i> Peta sebaran penduduk real-time</div>
<div class="feat"><i class="fas fa-gift"></i> Manajemen bantuan sosial</div>
<div class="feat"><i class="fas fa-flag"></i> Sistem laporan masyarakat</div>
<div class="feat"><i class="fas fa-chart-bar"></i> Dashboard statistik kemiskinan</div>
<div class="feat"><i class="fas fa-comments"></i> Komunikasi antar pengurus</div>
<div class="feat"><i class="fas fa-user-shield"></i> Sistem multi-role & autentikasi</div>
</div>
<div class="right">
<h2>Masuk ke Sistem</h2>
<p class="sub">Silakan login dengan akun yang telah terdaftar</p>
<div class="err" id="err"><i class="fas fa-exclamation-circle"></i><span id="err-txt"></span></div>
<form onsubmit="doLogin(event)">
<div class="fg"><label>Email</label><input type="email" id="email" placeholder="email@domain.com" required autofocus></div>
<div class="fg"><label>Password</label><input type="password" id="pass" placeholder="••••••••" required></div>
<button type="submit" class="btn-login" id="btn-submit"><i class="fas fa-sign-in-alt"></i> Masuk</button>
</form>
<div class="demo-box">
<strong>Akun Demo klik untuk isi otomatis</strong>
<div class="demo-row">
<div><div style="font-weight:700">Admin/Pengurus</div><div class="role">Akses penuh input & verifikasi</div></div>
<span class="creds" onclick="fillDemo('admin@uas.id','password')">Gunakan </span>
</div>
<div class="demo-row">
<div><div style="font-weight:700">Pimpinan Daerah</div><div class="role">Read-only + dashboard</div></div>
<span class="creds" onclick="fillDemo('pimpinan@uas.id','password')">Gunakan </span>
</div>
<div class="demo-row">
<div><div style="font-weight:700">Masyarakat</div><div class="role">Hanya lihat & laporan</div></div>
<span class="creds" onclick="fillDemo('warga@uas.id','password')">Gunakan </span>
</div>
</div>
</div>
</div>
<script>
function fillDemo(e,p){
document.getElementById('email').value=e;
document.getElementById('pass').value=p;
document.getElementById('email').focus();
}
function doLogin(e){
e.preventDefault();
const btn=document.getElementById('btn-submit');
btn.disabled=true; btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> Memverifikasi...';
document.getElementById('err').style.display='none';
const body=new URLSearchParams({aksi:'login',email:document.getElementById('email').value,password:document.getElementById('pass').value});
fetch('auth.php',{method:'POST',body}).then(r=>r.json()).then(d=>{
if(d.login){ window.location.href='index.php'; }
else {
const el=document.getElementById('err');
document.getElementById('err-txt').textContent=d.error||'Login gagal';
el.style.display='flex';
btn.disabled=false; btn.innerHTML='<i class="fas fa-sign-in-alt"></i> Masuk';
}
}).catch(()=>{
document.getElementById('err-txt').textContent='Tidak dapat terhubung ke server';
document.getElementById('err').style.display='flex';
btn.disabled=false; btn.innerHTML='<i class="fas fa-sign-in-alt"></i> Masuk';
});
}
</script>
</body>
</html>
+188
View File
@@ -0,0 +1,188 @@
<?php
error_reporting(0);
ini_set('display_errors', 0);
ob_start();
session_start();
include 'koneksi.php';
ob_clean(); // buang output apapun sebelum json_out
$aksi = $_REQUEST['aksi'] ?? 'list';
// ── LIST ─────────────────────────────────────────────────────
if ($aksi === 'list') {
$where = ["p.status_hidup='hidup'"];
$params = []; $types = '';
if (!empty($_GET['status_ekonomi'])) { $where[] = "p.status_ekonomi=?"; $params[] = $_GET['status_ekonomi']; $types .= 's'; }
if (!empty($_GET['rumah_ibadah_id'])) { $where[] = "p.rumah_ibadah_id=?"; $params[] = (int)$_GET['rumah_ibadah_id']; $types .= 'i'; }
if (!empty($_GET['status_bantuan'])) {
if ($_GET['status_bantuan'] === 'sudah') $where[] = "p.id IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')";
else $where[] = "p.id NOT IN (SELECT DISTINCT penduduk_id FROM histori_bantuan WHERE status='disalurkan')";
}
if (!empty($_GET['kelurahan'])) { $where[] = "k.kelurahan LIKE ?"; $params[] = '%'.$_GET['kelurahan'].'%'; $types .= 's'; }
if (!empty($_GET['penghasilan_min'])) { $where[] = "p.penghasilan>=?"; $params[] = (float)$_GET['penghasilan_min']; $types .= 'd'; }
if (!empty($_GET['penghasilan_max'])) { $where[] = "p.penghasilan<=?"; $params[] = (float)$_GET['penghasilan_max']; $types .= 'd'; }
if (!empty($_GET['q'])) { $like = '%'.$_GET['q'].'%'; $where[] = "(p.nama LIKE ? OR p.nik LIKE ?)"; $params[] = $like; $params[] = $like; $types .= 'ss'; }
$w = implode(' AND ', $where);
$sql = "SELECT p.id,p.nik,p.nama,p.jenis_kelamin,p.tanggal_lahir,
TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,
p.status_keluarga,p.status_ekonomi,p.status_hidup,
p.pekerjaan,p.penghasilan,p.pendidikan_terakhir,p.status_pendidikan,
p.lat,p.lng,p.no_hp,p.jenis_bantuan,p.created_at,
k.no_kk,k.rt,k.rw,k.kelurahan,k.kecamatan,
ri.nama AS nama_ibadah,
(SELECT COUNT(*) FROM histori_bantuan hb WHERE hb.penduduk_id=p.id AND hb.status='disalurkan') AS total_bantuan
FROM penduduk p
LEFT JOIN keluarga k ON p.keluarga_id=k.id
LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id
WHERE $w ORDER BY p.nama";
if ($params) {
$stmt = $conn->prepare($sql);
$stmt->bind_param($types,...$params);
$stmt->execute(); $res = $stmt->get_result(); $stmt->close();
} else {
$res = $conn->query($sql);
}
if(!$res) json_out(['error'=>$conn->error],500);
$rows = [];
while ($r = $res->fetch_assoc()) { $r['penghasilan']=(float)$r['penghasilan']; $rows[]=$r; }
json_out($rows);
}
// ── DETAIL ─────────────────────────────────────────────
if ($aksi === 'detail') {
$id = (int)($_GET['id']??0);
$stmt = $conn->prepare("SELECT p.*,TIMESTAMPDIFF(YEAR,p.tanggal_lahir,CURDATE()) AS umur,k.no_kk,k.rt,k.rw,k.kelurahan,k.kecamatan,ri.nama AS nama_ibadah,ri.jenis AS jenis_ibadah FROM penduduk p LEFT JOIN keluarga k ON p.keluarga_id=k.id LEFT JOIN rumah_ibadah ri ON p.rumah_ibadah_id=ri.id WHERE p.id=?");
$stmt->bind_param('i',$id); $stmt->execute();
$data = $stmt->get_result()->fetch_assoc(); $stmt->close();
if (!$data) json_out(['error'=>'Tidak ditemukan'],404);
if ($data['keluarga_id']) {
$stmt2 = $conn->prepare("SELECT p2.id,p2.nik,p2.nama,p2.jenis_kelamin,TIMESTAMPDIFF(YEAR,p2.tanggal_lahir,CURDATE()) AS umur,p2.status_keluarga,p2.pekerjaan,p2.status_ekonomi FROM penduduk p2 WHERE p2.keluarga_id=? AND p2.id!=?");
$stmt2->bind_param('ii',$data['keluarga_id'],$id); $stmt2->execute();
$res2=$stmt2->get_result(); $anggota=[];
while($r=$res2->fetch_assoc()) $anggota[]=$r;
$stmt2->close(); $data['anggota_keluarga']=$anggota;
} else $data['anggota_keluarga']=[];
$stmt3 = $conn->prepare("SELECT hb.*,b.nama AS nama_bantuan,b.jenis FROM histori_bantuan hb JOIN bantuan b ON hb.bantuan_id=b.id WHERE hb.penduduk_id=? ORDER BY hb.tanggal DESC");
$stmt3->bind_param('i',$id); $stmt3->execute();
$res3=$stmt3->get_result(); $histori=[];
while($r=$res3->fetch_assoc()) $histori[]=$r;
$stmt3->close(); $data['histori_bantuan']=$histori;
json_out($data);
}
// ── SIMPAN ─────────────────────────────────────────────
if ($aksi === 'simpan') {
$nik = trim($_POST['nik']??'');
$nama = trim($_POST['nama']??'');
$jk = $_POST['jenis_kelamin']??'L';
$tgl = $_POST['tanggal_lahir']??'';
$stk = $_POST['status_keluarga']??'anggota';
$stm = $_POST['status_perkawinan']??'belum_kawin';
$pek = $_POST['pekerjaan']??'';
$pgh = (float)($_POST['penghasilan']??0);
$sek = $_POST['status_ekonomi']??'rentan';
$pend = $_POST['pendidikan_terakhir']??'tidak_sekolah';
$spend= $_POST['status_pendidikan']??'tidak_sekolah';
$kid = !empty($_POST['keluarga_id'])?(int)$_POST['keluarga_id']:null;
$lat = !empty($_POST['lat'])?(float)$_POST['lat']:null;
$lng = !empty($_POST['lng'])?(float)$_POST['lng']:null;
$nohp = $_POST['no_hp']??'';
$jban = $_POST['jenis_bantuan']??'';
if (!$nik||!$nama||!$tgl) json_out(['error'=>'NIK, nama, dan tanggal lahir wajib diisi'],400);
$cek=$conn->prepare("SELECT id FROM penduduk WHERE nik=?");
$cek->bind_param('s',$nik); $cek->execute();
if($cek->get_result()->num_rows>0) { $cek->close(); json_out(['error'=>'NIK sudah terdaftar'],400); }
$cek->close();
// cari ibadah terdekat
$ibadah_id=null;
if($lat!==null && $lng!==null){
$si=$conn->prepare("SELECT id FROM rumah_ibadah ORDER BY (6371*acos(cos(radians(?))*cos(radians(lat))*cos(radians(lng)-radians(?))+sin(radians(?))*sin(radians(lat)))) ASC LIMIT 1");
$si->bind_param('ddd',$lat,$lng,$lat); $si->execute();
$ri=$si->get_result()->fetch_assoc(); $si->close();
$ibadah_id=$ri['id']??null;
}
// Build query with NULL handling
$lat_sql = $lat!==null ? $lat : 'NULL';
$lng_sql = $lng!==null ? $lng : 'NULL';
$kid_sql = $kid!==null ? $kid : 'NULL';
$ibd_sql = $ibadah_id!==null ? $ibadah_id : 'NULL';
$conn->query("INSERT INTO penduduk
(nik,nama,jenis_kelamin,tanggal_lahir,status_keluarga,status_perkawinan,
pekerjaan,penghasilan,status_ekonomi,pendidikan_terakhir,status_pendidikan,
keluarga_id,rumah_ibadah_id,lat,lng,no_hp,jenis_bantuan)
VALUES (
'".$conn->real_escape_string($nik)."',
'".$conn->real_escape_string($nama)."',
'".$conn->real_escape_string($jk)."',
'".$conn->real_escape_string($tgl)."',
'".$conn->real_escape_string($stk)."',
'".$conn->real_escape_string($stm)."',
'".$conn->real_escape_string($pek)."',
$pgh,
'".$conn->real_escape_string($sek)."',
'".$conn->real_escape_string($pend)."',
'".$conn->real_escape_string($spend)."',
$kid_sql,$ibd_sql,$lat_sql,$lng_sql,
'".$conn->real_escape_string($nohp)."',
'".$conn->real_escape_string($jban)."'
)");
if($conn->error) json_out(['error'=>$conn->error],500);
$new_id=$conn->insert_id;
if(!empty($_SESSION['user_id'])){
log_aktivitas($conn,(int)$_SESSION['user_id'],'CREATE','penduduk',$new_id,'Tambah penduduk: '.$nama);
}
$umur=(int)date_diff(date_create($tgl),date_create('today'))->y;
$warning=null;
if($umur>=7&&$umur<=12&&$spend!='sekolah') $warning='Umur 7-12 th seharusnya masih SD.';
elseif($umur>=13&&$umur<=15&&$spend!='sekolah') $warning='Umur 13-15 th seharusnya masih SMP.';
elseif($umur>=16&&$umur<=18&&$spend=='tidak_sekolah') $warning='Umur 16-18 th biasanya masih SMA/SMK, mohon verifikasi.';
json_out(['success'=>true,'id'=>$new_id,'warning'=>$warning]);
}
// ── EDIT ───────────────────────────────────────────────
if ($aksi === 'edit') {
$id=(int)($_POST['id']??0);
$nm = $conn->real_escape_string($_POST['nama']??'');
$jk = $conn->real_escape_string($_POST['jenis_kelamin']??'L');
$tgl = $conn->real_escape_string($_POST['tanggal_lahir']??'');
$stk = $conn->real_escape_string($_POST['status_keluarga']??'anggota');
$stm = $conn->real_escape_string($_POST['status_perkawinan']??'belum_kawin');
$sh = $conn->real_escape_string($_POST['status_hidup']??'hidup');
$pek = $conn->real_escape_string($_POST['pekerjaan']??'');
$pgh = (float)($_POST['penghasilan']??0);
$sek = $conn->real_escape_string($_POST['status_ekonomi']??'rentan');
$pend= $conn->real_escape_string($_POST['pendidikan_terakhir']??'tidak_sekolah');
$spend=$conn->real_escape_string($_POST['status_pendidikan']??'tidak_sekolah');
$nohp= $conn->real_escape_string($_POST['no_hp']??'');
$jban= $conn->real_escape_string($_POST['jenis_bantuan']??'');
$conn->query("UPDATE penduduk SET
nama='$nm',jenis_kelamin='$jk',tanggal_lahir='$tgl',
status_keluarga='$stk',status_perkawinan='$stm',status_hidup='$sh',
pekerjaan='$pek',penghasilan=$pgh,status_ekonomi='$sek',
pendidikan_terakhir='$pend',status_pendidikan='$spend',
no_hp='$nohp',jenis_bantuan='$jban'
WHERE id=$id");
if($conn->error) json_out(['error'=>$conn->error],500);
if(!empty($_SESSION['user_id'])){
log_aktivitas($conn,(int)$_SESSION['user_id'],'UPDATE','penduduk',$id,'Edit penduduk id='.$id);
}
json_out(['success'=>true]);
}
// ── HAPUS ──────────────────────────────────────────────
if ($aksi === 'hapus') {
$id=(int)($_POST['id']??0);
$conn->query("DELETE FROM histori_bantuan WHERE penduduk_id=$id");
$conn->query("DELETE FROM pelatihan WHERE penduduk_id=$id");
$conn->query("DELETE FROM laporan WHERE penduduk_id=$id");
$conn->query("DELETE FROM penduduk WHERE id=$id");
json_out(['success'=>true]);
}
+52
View File
@@ -0,0 +1,52 @@
<?php
error_reporting(0);
ini_set("display_errors", 0);
ob_start();
session_start();
include 'koneksi.php';
$aksi = $_REQUEST['aksi'] ?? 'list';
if ($aksi === 'list') {
$res=$conn->query("SELECT u.id,u.nama,u.email,u.aktif,u.created_at,r.nama AS role_nama,r.id AS role_id FROM user u JOIN role r ON u.role_id=r.id ORDER BY u.nama");
if(!$res) json_out(['error'=>$conn->error],500);
$rows=[]; while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
if ($aksi === 'simpan') {
$nm=$conn->real_escape_string($_POST['nama']??'');
$em=$conn->real_escape_string($_POST['email']??'');
$pw=password_hash($_POST['password']??'password',PASSWORD_DEFAULT);
$rid=(int)($_POST['role_id']??1);
$cek=$conn->query("SELECT id FROM user WHERE email='$em'")->num_rows;
if($cek>0) json_out(['error'=>'Email sudah digunakan'],400);
$conn->query("INSERT INTO user (nama,email,password,role_id) VALUES ('$nm','$em','$pw',$rid)");
if($conn->error) json_out(['error'=>$conn->error],500);
json_out(['success'=>true,'id'=>$conn->insert_id]);
}
if ($aksi === 'edit') {
$id=(int)($_POST['id']??0);
$nm=$conn->real_escape_string($_POST['nama']??'');
$em=$conn->real_escape_string($_POST['email']??'');
$rid=(int)($_POST['role_id']??1);
$akt=(int)($_POST['aktif']??1);
$conn->query("UPDATE user SET nama='$nm',email='$em',role_id=$rid,aktif=$akt WHERE id=$id");
if(!empty($_POST['password'])){
$pw=password_hash($_POST['password'],PASSWORD_DEFAULT);
$conn->query("UPDATE user SET password='$pw' WHERE id=$id");
}
json_out(['success'=>true]);
}
if ($aksi === 'hapus') {
$id=(int)($_POST['id']??0);
$conn->query("DELETE FROM user WHERE id=$id");
json_out(['success'=>true]);
}
if ($aksi === 'roles') {
$res=$conn->query("SELECT * FROM role"); $rows=[];
while($r=$res->fetch_assoc()) $rows[]=$r;
json_out($rows);
}
+11
View File
@@ -0,0 +1,11 @@
FROM php:8.2-apache
RUN docker-php-ext-install mysqli pdo pdo_mysql \
&& a2enmod rewrite
WORKDIR /var/www/html
COPY . /var/www/html/
RUN mkdir -p /var/www/html/05/uploads/bukti \
&& chown -R www-data:www-data /var/www/html
+803
View File
@@ -0,0 +1,803 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portal WebGIS Terpadu</title>
<style>
:root {
--bg: #f6f9fb;
--surface: rgba(255, 255, 255, 0.76);
--surface-strong: rgba(255, 255, 255, 0.92);
--text: #16212d;
--muted: #657486;
--line: rgba(154, 170, 185, 0.36);
--chrome: #dbe4ec;
--silver: #f7fafc;
--green: #0f8c7c;
--teal: #119eb3;
--blue: #3e6fb6;
--amber: #c98d28;
--rose: #b95473;
--shadow: 0 24px 60px rgba(42, 58, 74, 0.17);
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
color: var(--text);
background:
linear-gradient(90deg, rgba(72, 105, 130, 0.1) 1px, transparent 1px),
linear-gradient(rgba(72, 105, 130, 0.08) 1px, transparent 1px),
linear-gradient(115deg, rgba(255, 255, 255, 0.9), rgba(221, 233, 241, 0.52) 42%, rgba(255, 255, 255, 0.84)),
radial-gradient(circle at 16% 18%, rgba(17, 158, 179, 0.18), transparent 30%),
radial-gradient(circle at 84% 12%, rgba(201, 141, 40, 0.16), transparent 26%),
var(--bg);
background-size: 52px 52px, 52px 52px, auto, auto, auto, auto;
overflow-x: hidden;
}
body::before {
position: fixed;
inset: 0;
z-index: -1;
content: "";
background:
linear-gradient(128deg, transparent 0 43%, rgba(255, 255, 255, 0.9) 43% 44%, rgba(77, 114, 145, 0.16) 44% 45%, transparent 45%),
linear-gradient(35deg, transparent 0 58%, rgba(17, 158, 179, 0.12) 58% 59%, rgba(255, 255, 255, 0.7) 59% 60%, transparent 60%);
opacity: 0.86;
pointer-events: none;
animation: drift-lines 16s ease-in-out infinite alternate;
}
body::after {
position: fixed;
inset: -20%;
z-index: -2;
content: "";
background:
conic-gradient(from 130deg at 50% 50%, rgba(255, 255, 255, 0), rgba(182, 199, 214, 0.34), rgba(255, 255, 255, 0), rgba(17, 158, 179, 0.15), rgba(255, 255, 255, 0));
filter: blur(26px);
opacity: 0.8;
animation: rotate-halo 26s linear infinite;
}
.page {
width: min(1120px, calc(100% - 40px));
margin: 0 auto;
padding: 56px 0 48px;
animation: page-rise 650ms ease both;
}
.map-orbit {
position: fixed;
top: 34px;
right: max(20px, calc((100vw - 1120px) / 2));
z-index: -1;
width: 310px;
aspect-ratio: 1;
border: 1px solid rgba(78, 100, 120, 0.13);
border-radius: 50%;
background:
repeating-radial-gradient(circle, transparent 0 28px, rgba(61, 95, 123, 0.08) 29px 30px),
linear-gradient(145deg, rgba(255, 255, 255, 0.42), rgba(190, 207, 221, 0.16));
box-shadow: inset 0 0 34px rgba(255, 255, 255, 0.72);
opacity: 0.88;
pointer-events: none;
animation: float-orbit 8s ease-in-out infinite;
}
.map-orbit span {
position: absolute;
width: 11px;
height: 11px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 0 0 5px rgba(17, 158, 179, 0.14), 0 0 22px rgba(17, 158, 179, 0.48);
}
.map-orbit span:nth-child(1) {
top: 22%;
left: 52%;
}
.map-orbit span:nth-child(2) {
top: 64%;
left: 30%;
box-shadow: 0 0 0 5px rgba(201, 141, 40, 0.16), 0 0 22px rgba(201, 141, 40, 0.42);
}
.map-orbit span:nth-child(3) {
top: 48%;
right: 17%;
box-shadow: 0 0 0 5px rgba(62, 111, 182, 0.14), 0 0 22px rgba(62, 111, 182, 0.42);
}
header {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
margin-bottom: 28px;
}
.title-block {
max-width: 720px;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
color: #0e7f88;
font-size: 0.84rem;
font-weight: 700;
letter-spacing: 0;
text-transform: uppercase;
}
.eyebrow::before {
width: 10px;
height: 10px;
border: 3px solid #20a8b3;
border-radius: 50%;
content: "";
background: var(--surface);
box-shadow: 0 0 18px rgba(32, 168, 179, 0.5);
animation: pulse-dot 2.4s ease-in-out infinite;
}
h1 {
margin: 0;
color: transparent;
background: linear-gradient(95deg, #15212d 0%, #627689 34%, #f8fbfd 47%, #184d62 62%, #172534 100%);
background-clip: text;
-webkit-background-clip: text;
font-size: clamp(2rem, 4vw, 4rem);
line-height: 1.05;
letter-spacing: 0;
text-shadow: 0 12px 36px rgba(46, 68, 88, 0.13);
}
.description {
max-width: 620px;
margin: 16px 0 0;
color: var(--muted);
font-size: 1.05rem;
line-height: 1.6;
}
.status-panel {
width: 250px;
min-width: 250px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(219, 228, 236, 0.7) 45%, rgba(255, 255, 255, 0.86));
box-shadow: 0 16px 34px rgba(42, 58, 74, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.88);
backdrop-filter: blur(16px);
}
.pontianak-map {
position: relative;
overflow: hidden;
min-height: 154px;
border: 1px solid rgba(154, 170, 185, 0.3);
border-radius: 8px;
background:
linear-gradient(90deg, rgba(89, 119, 145, 0.1) 1px, transparent 1px),
linear-gradient(rgba(89, 119, 145, 0.08) 1px, transparent 1px),
linear-gradient(145deg, #f9fcfd, #dfe9f0 48%, #ffffff);
background-size: 18px 18px, 18px 18px, auto;
}
.pontianak-map::before {
position: absolute;
inset: -45%;
content: "";
background: conic-gradient(from 0deg, transparent, rgba(255, 255, 255, 0.96), transparent, rgba(17, 158, 179, 0.16), transparent);
animation: rotate-halo 11s linear infinite;
}
.pontianak-map svg {
position: relative;
z-index: 1;
display: block;
width: 100%;
height: auto;
filter: drop-shadow(0 12px 18px rgba(42, 58, 74, 0.16));
}
.map-region {
stroke: rgba(255, 255, 255, 0.94);
stroke-width: 2;
transition: transform 220ms ease, filter 220ms ease;
transform-origin: center;
}
.pontianak-map:hover .map-region {
filter: brightness(1.05);
transform: translateY(-1px);
}
.map-river {
fill: none;
stroke: rgba(30, 136, 163, 0.7);
stroke-width: 8;
stroke-linecap: round;
stroke-linejoin: round;
}
.map-pin {
animation: pulse-pin 2.2s ease-in-out infinite;
transform-origin: 124px 78px;
}
.map-caption {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 9px 4px 0;
color: #395065;
font-size: 0.82rem;
font-weight: 700;
}
.map-caption span:last-child {
color: #0e7f88;
font-weight: 800;
}
.project-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.project-card {
position: relative;
display: flex;
min-height: 220px;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
padding: 22px;
border: 1px solid var(--line);
border-radius: 8px;
color: inherit;
text-decoration: none;
background:
linear-gradient(140deg, rgba(255, 255, 255, 0.96), rgba(224, 233, 241, 0.78) 48%, rgba(255, 255, 255, 0.9)),
var(--surface);
box-shadow: 0 10px 26px rgba(42, 58, 74, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.9);
backdrop-filter: blur(18px);
transform: translateY(0);
transition: transform 220ms ease, box-shadow 220ms ease, border-color 220ms ease, background-position 220ms ease;
animation: card-enter 560ms ease both;
}
.project-card::before {
position: absolute;
inset: -30%;
content: "";
background:
linear-gradient(105deg, transparent 0 34%, rgba(255, 255, 255, 0.95) 45%, transparent 56%),
radial-gradient(circle at 20% 10%, var(--accent-soft), transparent 36%);
opacity: 0.68;
pointer-events: none;
transform: translateX(-34%);
transition: transform 650ms ease;
}
.project-card::after {
position: absolute;
inset: 0;
content: "";
background:
linear-gradient(90deg, rgba(90, 115, 138, 0.08) 1px, transparent 1px),
linear-gradient(rgba(90, 115, 138, 0.06) 1px, transparent 1px);
background-size: 18px 18px;
mask-image: linear-gradient(180deg, transparent, #000 18%, transparent 82%);
opacity: 0.34;
pointer-events: none;
}
.project-card:hover,
.project-card:focus-visible {
border-color: var(--accent);
box-shadow: var(--shadow);
transform: translateY(-7px) scale(1.01);
outline: none;
}
.project-card:hover::before,
.project-card:focus-visible::before {
transform: translateX(34%);
}
.project-card:focus-visible {
box-shadow: 0 0 0 4px rgba(31, 154, 138, 0.2), var(--shadow);
}
.card-top,
.card-bottom {
position: relative;
z-index: 1;
}
.icon-wrap {
display: grid;
width: 52px;
height: 52px;
place-items: center;
margin-bottom: 22px;
border-radius: 8px;
color: #ffffff;
background:
linear-gradient(145deg, var(--accent), rgba(255, 255, 255, 0.42)),
var(--accent);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 12px 26px var(--accent-glow);
animation: icon-hover 4.6s ease-in-out infinite;
}
.icon-wrap svg {
width: 29px;
height: 29px;
stroke: currentColor;
stroke-width: 2;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
h2 {
margin: 0;
font-size: 1.35rem;
line-height: 1.28;
letter-spacing: 0;
}
.meta {
margin: 10px 0 0;
color: var(--muted);
font-size: 0.93rem;
line-height: 1.45;
}
.card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-top: 28px;
color: var(--accent);
font-weight: 700;
font-size: 0.95rem;
}
.arrow {
display: grid;
width: 34px;
height: 34px;
flex: 0 0 auto;
place-items: center;
border-radius: 50%;
color: #ffffff;
background: linear-gradient(135deg, var(--accent), rgba(255, 255, 255, 0.46));
box-shadow: 0 10px 20px var(--accent-glow);
transition: transform 180ms ease;
}
.project-card:hover .arrow,
.project-card:focus-visible .arrow {
transform: translateX(3px);
}
.arrow svg {
width: 18px;
height: 18px;
stroke: currentColor;
stroke-width: 2.4;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.spbu {
--accent: var(--green);
--accent-soft: rgba(20, 125, 100, 0.13);
--accent-glow: rgba(15, 140, 124, 0.28);
}
.jalan {
--accent: var(--blue);
--accent-soft: rgba(49, 95, 155, 0.13);
--accent-glow: rgba(62, 111, 182, 0.28);
}
.choro {
--accent: var(--teal);
--accent-soft: rgba(31, 154, 138, 0.14);
--accent-glow: rgba(17, 158, 179, 0.28);
}
.upgrade {
--accent: var(--amber);
--accent-soft: rgba(199, 119, 29, 0.15);
--accent-glow: rgba(201, 141, 40, 0.3);
}
.kemiskinan {
--accent: var(--rose);
--accent-soft: rgba(184, 69, 90, 0.13);
--accent-glow: rgba(185, 84, 115, 0.28);
}
.project-card:nth-child(1) {
animation-delay: 80ms;
}
.project-card:nth-child(2) {
animation-delay: 160ms;
}
.project-card:nth-child(3) {
animation-delay: 240ms;
}
.project-card:nth-child(4) {
animation-delay: 320ms;
}
.project-card:nth-child(5) {
animation-delay: 400ms;
}
footer {
margin-top: 28px;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
}
footer a {
color: #0e7f88;
font-weight: 700;
text-decoration: none;
}
footer a:hover,
footer a:focus-visible {
text-decoration: underline;
}
@keyframes page-rise {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes card-enter {
from {
opacity: 0;
transform: translateY(22px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes rotate-halo {
to {
transform: rotate(360deg);
}
}
@keyframes drift-lines {
from {
transform: translate3d(-14px, -8px, 0);
}
to {
transform: translate3d(14px, 8px, 0);
}
}
@keyframes float-orbit {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(18px) rotate(4deg);
}
}
@keyframes pulse-dot {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.18);
}
}
@keyframes pulse-pin {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.18);
opacity: 0.78;
}
}
@keyframes icon-hover {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 1ms !important;
}
}
@media (max-width: 900px) {
header {
align-items: stretch;
flex-direction: column;
}
.status-panel {
width: min(100%, 280px);
min-width: 0;
}
.project-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 620px) {
.page {
width: min(100% - 28px, 1120px);
padding: 34px 0 30px;
}
header {
margin-bottom: 20px;
}
.description {
font-size: 0.98rem;
}
.project-grid {
grid-template-columns: 1fr;
gap: 14px;
}
.project-card {
min-height: 190px;
padding: 20px;
}
}
</style>
</head>
<body>
<div class="map-orbit" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<main class="page">
<header>
<div class="title-block">
<div class="eyebrow">Katalog aplikasi peta</div>
<h1>Portal WebGIS Terpadu</h1>
<p class="description">Pilih salah satu aplikasi WebGIS untuk membuka peta interaktif.</p>
</div>
<div class="status-panel" aria-label="Gambar peta Pontianak">
<div class="pontianak-map">
<svg viewBox="0 0 240 150" role="img" aria-label="Peta Pontianak">
<defs>
<linearGradient id="mapSilver" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#ffffff"></stop>
<stop offset="0.52" stop-color="#d8e4ec"></stop>
<stop offset="1" stop-color="#f7fbfd"></stop>
</linearGradient>
<linearGradient id="mapCyan" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#7bd7df"></stop>
<stop offset="1" stop-color="#0f8c9c"></stop>
</linearGradient>
</defs>
<path class="map-region" fill="url(#mapSilver)" d="M93 15 133 9 164 27 156 58 128 69 99 58 76 35Z"></path>
<path class="map-region" fill="#c7d6e1" d="M43 46 76 35 99 58 91 91 55 96 31 73Z"></path>
<path class="map-region" fill="#eef4f8" d="M156 58 198 45 221 71 204 102 164 101 128 69Z"></path>
<path class="map-region" fill="#d7e7ee" d="M55 96 91 91 117 118 91 141 51 132 28 105Z"></path>
<path class="map-region" fill="#bfcfdc" d="M91 91 128 69 164 101 153 135 117 118Z"></path>
<path class="map-region" fill="#f5fafc" d="M164 101 204 102 218 126 191 143 153 135Z"></path>
<path class="map-river" d="M24 84 C62 78 83 70 108 82 S151 104 215 91"></path>
<path class="map-river" d="M118 20 C112 51 115 78 130 132"></path>
<g class="map-pin">
<circle cx="124" cy="78" r="14" fill="rgba(17, 158, 179, 0.18)"></circle>
<circle cx="124" cy="78" r="6" fill="url(#mapCyan)" stroke="#ffffff" stroke-width="3"></circle>
</g>
</svg>
<div class="map-caption">
<span>Peta Pontianak</span>
<span>Preview GIS</span>
</div>
</div>
</div>
</header>
<section class="project-grid" aria-label="Daftar project WebGIS">
<a class="project-card spbu" href="01/index.php">
<div class="card-top">
<div class="icon-wrap" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M4 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16"></path>
<path d="M8 7h4"></path>
<path d="M8 11h4"></path>
<path d="M16 8h2l2 2v8a2 2 0 0 1-4 0v-4"></path>
<path d="M3 21h14"></path>
</svg>
</div>
<h2>Webgis SPBU Point</h2>
<p class="meta">Pemetaan titik lokasi SPBU dengan informasi spasial.</p>
</div>
<div class="card-bottom">
<span>Buka project</span>
<span class="arrow" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 12h14"></path>
<path d="m13 6 6 6-6 6"></path>
</svg>
</span>
</div>
</a>
<a class="project-card jalan" href="02/index.php">
<div class="card-top">
<div class="icon-wrap" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M4 19c4-5 4-9 8-14"></path>
<path d="M12 19c2-5 4-9 8-14"></path>
<path d="M7 14h4"></path>
<path d="M12 9h4"></path>
<path d="M3 21h18"></path>
</svg>
</div>
<h2>Webgis Jalan Parsil</h2>
<p class="meta">Inventarisasi dan visualisasi data jalan berbasis peta.</p>
</div>
<div class="card-bottom">
<span>Buka project</span>
<span class="arrow" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 12h14"></path>
<path d="m13 6 6 6-6 6"></path>
</svg>
</span>
</div>
</a>
<a class="project-card choro" href="03/choropleth.html">
<div class="card-top">
<div class="icon-wrap" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="m3 6 6-3 6 3 6-3v15l-6 3-6-3-6 3Z"></path>
<path d="M9 3v15"></path>
<path d="M15 6v15"></path>
<path d="M6 12h3"></path>
<path d="M15 12h3"></path>
</svg>
</div>
<h2>Webgis Choropleth</h2>
<p class="meta">Peta tematik untuk membaca pola wilayah secara visual.</p>
</div>
<div class="card-bottom">
<span>Buka project</span>
<span class="arrow" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 12h14"></path>
<path d="m13 6 6 6-6 6"></path>
</svg>
</span>
</div>
</a>
<a class="project-card upgrade" href="04/index.php">
<div class="card-top">
<div class="icon-wrap" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M12 21s7-5.2 7-11a7 7 0 0 0-14 0c0 5.8 7 11 7 11Z"></path>
<path d="M12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"></path>
<path d="M12 16v-3"></path>
<path d="m9.8 8.8 2.2-2.2 2.2 2.2"></path>
</svg>
</div>
<h2>Webgis SPBU 24 Ja Upgrade</h2>
<p class="meta">Versi pengembangan untuk pengelolaan titik SPBU.</p>
</div>
<div class="card-bottom">
<span>Buka project</span>
<span class="arrow" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 12h14"></path>
<path d="m13 6 6 6-6 6"></path>
</svg>
</span>
</div>
</a>
<a class="project-card kemiskinan" href="05/login.php">
<div class="card-top">
<div class="icon-wrap" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M4 19V5"></path>
<path d="M4 19h17"></path>
<path d="M8 16v-5"></path>
<path d="M13 16V8"></path>
<path d="M18 16v-9"></path>
<path d="M7 7h12"></path>
</svg>
</div>
<h2>Webgis Kemiskinan</h2>
<p class="meta">Data wilayah dan indikator sosial dalam tampilan spasial.</p>
</div>
<div class="card-bottom">
<span>Buka project</span>
<span class="arrow" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 12h14"></path>
<path d="m13 6 6 6-6 6"></path>
</svg>
</span>
</div>
</a>
</section>
<footer>
Portal utama untuk memilih aplikasi WebGIS pada folder project gabungan. <a href="manual-book.html">Manual book</a>
</footer>
</main>
</body>
</html>
+157
View File
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manual Book WebGIS UAS</title>
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
color: #16212d;
background: #f6f9fb;
line-height: 1.6;
}
main {
width: min(920px, calc(100% - 32px));
margin: 0 auto;
padding: 42px 0;
}
h1,
h2 {
line-height: 1.2;
letter-spacing: 0;
}
h1 {
margin: 0 0 10px;
font-size: clamp(2rem, 4vw, 3.2rem);
}
h2 {
margin-top: 32px;
font-size: 1.35rem;
}
p {
color: #556575;
}
a {
color: #0e7f88;
font-weight: 700;
}
.panel {
margin-top: 22px;
padding: 22px;
border: 1px solid rgba(154, 170, 185, 0.36);
border-radius: 8px;
background: #ffffff;
box-shadow: 0 16px 34px rgba(42, 58, 74, 0.08);
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 14px;
overflow: hidden;
border-radius: 8px;
}
th,
td {
padding: 12px;
border-bottom: 1px solid #e2e9ee;
text-align: left;
vertical-align: top;
}
th {
background: #edf4f7;
}
code {
padding: 2px 5px;
border-radius: 5px;
background: #edf4f7;
color: #243746;
}
ul {
padding-left: 20px;
}
</style>
</head>
<body>
<main>
<a href="index.html">Kembali ke Portal WebGIS Terpadu</a>
<h1>Manual Book WebGIS UAS</h1>
<p>Dokumen ringkas ini menjelaskan struktur project, daftar aplikasi, dan konfigurasi environment database untuk deployment Docker atau Coolify.</p>
<section class="panel">
<h2>Struktur Folder</h2>
<p>Struktur root project disiapkan seperti berikut:</p>
<ul>
<li><code>index.html</code> sebagai landing page utama.</li>
<li><code>manual-book.html</code> sebagai dokumentasi singkat.</li>
<li><code>01</code> sampai <code>05</code> sebagai folder aplikasi WebGIS.</li>
</ul>
</section>
<section class="panel">
<h2>Daftar Aplikasi</h2>
<table>
<thead>
<tr>
<th>Folder</th>
<th>Nama Aplikasi</th>
<th>Entry Point</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>01</code></td>
<td>Webgis SPBU Point</td>
<td><code>index.php</code></td>
</tr>
<tr>
<td><code>02</code></td>
<td>Webgis Jalan Parsil</td>
<td><code>index.php</code></td>
</tr>
<tr>
<td><code>03</code></td>
<td>Webgis Choropleth</td>
<td><code>choropleth.html</code></td>
</tr>
<tr>
<td><code>04</code></td>
<td>Webgis SPBU 24 Ja Upgrade</td>
<td><code>index.php</code></td>
</tr>
<tr>
<td><code>05</code></td>
<td>Webgis Kemiskinan</td>
<td><code>login.php</code></td>
</tr>
</tbody>
</table>
</section>
<section class="panel">
<h2>Environment Database</h2>
<p>Setiap aplikasi PHP memakai environment variable berikut untuk koneksi database:</p>
<ul>
<li><code>DB_HOST</code></li>
<li><code>DB_USER</code></li>
<li><code>DB_PASSWORD</code></li>
<li><code>DB_NAME</code></li>
</ul>
<p>Jika database dipisah sebagai resource Coolify, isi <code>DB_HOST</code> dengan hostname service database, bukan <code>localhost</code>.</p>
</section>
</main>
</body>
</html>