Project UAS WEBGIS SIG Talitha

This commit is contained in:
TalithaSyakirah
2026-06-09 15:29:53 +07:00
commit 7b48733283
16 changed files with 7482 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
.git
.env
.DS_Store
__MACOSX
*.zip
*.rar
*.7z
.vscode
.idea
*.log
+15
View File
@@ -0,0 +1,15 @@
# =========================================================
# CONTOH FILE ENVIRONMENT UNTUK WEBGIS POVERTY MAPPING
# =========================================================
# Simpan file asli dengan nama: .env
# File .env JANGAN dipush ke Git.
# Yang boleh dipush hanya .env.example.
#
# Isi value di bawah sesuai database lokal atau database Coolify.
# =========================================================
DB_HOST=127.0.0.1
DB_USER=root
DB_PASS=
DB_NAME=sig
DB_PORT=3306
+51
View File
@@ -0,0 +1,51 @@
# =========================================================
# GIT IGNORE UNTUK PROJECT UAS SIG / WEBGIS POVERTY MAPPING
# =========================================================
# macOS
.DS_Store
__MACOSX/
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Archive / file kompres
*.zip
*.rar
*.7z
*.tar
*.gz
# Environment / credential
.env
.env.local
.env.production
.env.development
# Log / temporary
*.log
logs/
tmp/
temp/
cache/
# Editor / IDE
.vscode/
.idea/
# Backup file
*.bak
*.backup
*.old
*.swp
# Database dump lokal yang berisi data sensitif
# Kalau file SQL untuk tugas memang perlu dikumpulkan, boleh hapus baris *.sql ini.
# Lebih aman simpan SQL final di folder database/ dan pastikan tidak berisi password.
# *.sql
# Node/PHP dependency jika suatu saat ada
node_modules/
vendor/
+567
View File
@@ -0,0 +1,567 @@
<?php
session_start();
require_once __DIR__ . '/../env.php';
$host = env('DB_HOST', '127.0.0.1');
$user = env('DB_USER', 'root');
$pass = env('DB_PASS', '');
$db = env('DB_NAME', 'sig');
$port = (int) env('DB_PORT', 3306);
$conn = new mysqli($host, $user, $pass, $db, $port);
if ($conn->connect_error) {
die("Koneksi database gagal.");
}
$conn->set_charset("utf8mb4");
if (isset($_GET['action'])) {
header('Content-Type: application/json; charset=utf-8');
$action = $_GET['action'];
if ($action == 'get_spbu') {
$data = [];
$result = $conn->query("SELECT * FROM spbu ORDER BY id DESC");
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data);
exit;
}
if ($action == 'add_spbu') {
$nama_spbu = $_POST['nama_spbu'] ?? '';
$no_telepon = $_POST['no_telepon'] ?? '';
$buka_24_jam = isset($_POST['buka_24_jam']) ? (int) $_POST['buka_24_jam'] : 0;
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
if ($nama_spbu == '' || $no_telepon == '' || $latitude == '' || $longitude == '') {
echo json_encode(['status' => false, 'message' => 'Data belum lengkap']);
exit;
}
$stmt = $conn->prepare("INSERT INTO spbu (nama_spbu, no_telepon, buka_24_jam, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("ssidd", $nama_spbu, $no_telepon, $buka_24_jam, $latitude, $longitude);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Data SPBU berhasil ditambahkan']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal menambahkan data']);
}
exit;
}
if ($action == 'update_spbu') {
$id = (int) ($_POST['id'] ?? 0);
$nama_spbu = $_POST['nama_spbu'] ?? '';
$no_telepon = $_POST['no_telepon'] ?? '';
$buka_24_jam = isset($_POST['buka_24_jam']) ? (int) $_POST['buka_24_jam'] : 0;
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
$stmt = $conn->prepare("UPDATE spbu SET nama_spbu=?, no_telepon=?, buka_24_jam=?, latitude=?, longitude=? WHERE id=?");
$stmt->bind_param("ssiddi", $nama_spbu, $no_telepon, $buka_24_jam, $latitude, $longitude, $id);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Data SPBU berhasil diupdate']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal update data']);
}
exit;
}
if ($action == 'delete_spbu') {
$id = (int) ($_POST['id'] ?? 0);
$stmt = $conn->prepare("DELETE FROM spbu WHERE id=?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Data SPBU berhasil dihapus']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal hapus data']);
}
exit;
}
if ($action == 'update_position') {
$id = (int) ($_POST['id'] ?? 0);
$latitude = $_POST['latitude'] ?? '';
$longitude = $_POST['longitude'] ?? '';
$stmt = $conn->prepare("UPDATE spbu SET latitude=?, longitude=? WHERE id=?");
$stmt->bind_param("ddi", $latitude, $longitude, $id);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Posisi berhasil diupdate']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal update posisi']);
}
exit;
}
if ($action == 'get_shapes') {
$data = [];
$result = $conn->query("SELECT * FROM map_shapes ORDER BY id DESC");
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data);
exit;
}
if ($action == 'save_shape') {
$shape_type = $_POST['shape_type'] ?? '';
$coordinates = $_POST['coordinates'] ?? '';
if ($shape_type == '' || $coordinates == '') {
echo json_encode(['status' => false, 'message' => 'Data shape tidak lengkap']);
exit;
}
$stmt = $conn->prepare("INSERT INTO map_shapes (shape_type, coordinates) VALUES (?, ?)");
$stmt->bind_param("ss", $shape_type, $coordinates);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Shape berhasil disimpan']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal menyimpan shape']);
}
exit;
}
if ($action == 'update_shape') {
$id = (int) ($_POST['id'] ?? 0);
$shape_type = $_POST['shape_type'] ?? '';
$coordinates = $_POST['coordinates'] ?? '';
if ($id <= 0 || $shape_type == '' || $coordinates == '') {
echo json_encode(['status' => false, 'message' => 'Data update shape tidak lengkap']);
exit;
}
$stmt = $conn->prepare("UPDATE map_shapes SET shape_type=?, coordinates=? WHERE id=?");
$stmt->bind_param("ssi", $shape_type, $coordinates, $id);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Shape berhasil diupdate']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal update shape']);
}
exit;
}
if ($action == 'delete_shape') {
$id = (int) ($_POST['id'] ?? 0);
$stmt = $conn->prepare("DELETE FROM map_shapes WHERE id=?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => true, 'message' => 'Shape berhasil dihapus']);
} else {
echo json_encode(['status' => false, 'message' => 'Gagal hapus shape']);
}
exit;
}
echo json_encode(['status' => false, 'message' => 'Action tidak dikenal']);
exit;
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>SPBU Pontianak - Leaflet CRUD</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="map"></div>
<div id="formPanel" class="form-panel hidden">
<h3 id="formTitle">Tambah SPBU</h3>
<input type="hidden" id="spbu_id">
<input type="text" id="nama_spbu" placeholder="Nama SPBU">
<input type="text" id="no_telepon" placeholder="No Telepon">
<label>
<input type="checkbox" id="buka_24_jam"> Buka 24 Jam
</label>
<input type="text" id="latitude" placeholder="Latitude" readonly>
<input type="text" id="longitude" placeholder="Longitude" readonly>
<div class="btn-group">
<button id="btnSimpan">Simpan</button>
<button id="btnUpdate" class="hidden">Update</button>
<button id="btnBatal" class="btn-secondary">Batal</button>
</div>
</div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<script>
const map = L.map('map').setView([-0.0263, 109.3425], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '&copy; OpenStreetMap'
}).addTo(map);
const formPanel = document.getElementById('formPanel');
const formTitle = document.getElementById('formTitle');
const spbuId = document.getElementById('spbu_id');
const namaSpbu = document.getElementById('nama_spbu');
const noTelepon = document.getElementById('no_telepon');
const buka24Jam = document.getElementById('buka_24_jam');
const latitude = document.getElementById('latitude');
const longitude = document.getElementById('longitude');
const btnSimpan = document.getElementById('btnSimpan');
const btnUpdate = document.getElementById('btnUpdate');
const btnBatal = document.getElementById('btnBatal');
let tempMarker = null;
let markers = {};
const drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
function createCustomIcon(is24Jam) {
let iconUrl = is24Jam == 1
? 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png'
: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png';
return L.icon({
iconUrl: iconUrl,
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
}
function showAddForm(lat, lng) {
formPanel.classList.remove('hidden');
formTitle.textContent = 'Tambah SPBU';
spbuId.value = '';
namaSpbu.value = '';
noTelepon.value = '';
buka24Jam.checked = false;
latitude.value = lat;
longitude.value = lng;
btnSimpan.classList.remove('hidden');
btnUpdate.classList.add('hidden');
}
function showEditForm(data) {
formPanel.classList.remove('hidden');
formTitle.textContent = 'Edit SPBU';
spbuId.value = data.id;
namaSpbu.value = data.nama_spbu;
noTelepon.value = data.no_telepon;
buka24Jam.checked = parseInt(data.buka_24_jam) === 1;
latitude.value = data.latitude;
longitude.value = data.longitude;
btnSimpan.classList.add('hidden');
btnUpdate.classList.remove('hidden');
}
function hideForm() {
formPanel.classList.add('hidden');
spbuId.value = '';
namaSpbu.value = '';
noTelepon.value = '';
buka24Jam.checked = false;
latitude.value = '';
longitude.value = '';
if (tempMarker) {
map.removeLayer(tempMarker);
tempMarker = null;
}
}
function loadSPBU() {
fetch('index.php?action=get_spbu')
.then(res => res.json())
.then(data => {
Object.values(markers).forEach(marker => map.removeLayer(marker));
markers = {};
data.forEach(item => {
const marker = L.marker([item.latitude, item.longitude], {
draggable: true,
icon: createCustomIcon(item.buka_24_jam)
}).addTo(map);
marker.bindPopup(`
<b>${item.nama_spbu}</b><br>
Telp: ${item.no_telepon}<br>
${parseInt(item.buka_24_jam) === 1 ? 'Buka 24 Jam' : 'Tidak 24 Jam'}<br>
Lat: ${item.latitude}<br>
Lng: ${item.longitude}<br><br>
<button onclick='editSPBU(${JSON.stringify(item)})'>Edit</button>
<button onclick='hapusSPBU(${item.id})'>Hapus</button>
`);
marker.on('dragend', function (e) {
const pos = e.target.getLatLng();
const formData = new URLSearchParams();
formData.append('id', item.id);
formData.append('latitude', pos.lat);
formData.append('longitude', pos.lng);
fetch('index.php?action=update_position', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
console.log(result.message);
loadSPBU();
});
});
markers[item.id] = marker;
});
});
}
window.editSPBU = function (data) {
showEditForm(data);
};
window.hapusSPBU = function (id) {
if (!confirm('Yakin ingin menghapus data SPBU ini?')) return;
const formData = new URLSearchParams();
formData.append('id', id);
fetch('index.php?action=delete_spbu', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
alert(result.message);
loadSPBU();
hideForm();
});
};
map.on('click', function (e) {
if (tempMarker) {
map.removeLayer(tempMarker);
}
tempMarker = L.marker(e.latlng).addTo(map);
showAddForm(e.latlng.lat, e.latlng.lng);
});
btnSimpan.addEventListener('click', function () {
const formData = new URLSearchParams();
formData.append('nama_spbu', namaSpbu.value);
formData.append('no_telepon', noTelepon.value);
formData.append('buka_24_jam', buka24Jam.checked ? 1 : 0);
formData.append('latitude', latitude.value);
formData.append('longitude', longitude.value);
fetch('index.php?action=add_spbu', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
alert(result.message);
if (result.status) {
loadSPBU();
hideForm();
}
});
});
btnUpdate.addEventListener('click', function () {
const formData = new URLSearchParams();
formData.append('id', spbuId.value);
formData.append('nama_spbu', namaSpbu.value);
formData.append('no_telepon', noTelepon.value);
formData.append('buka_24_jam', buka24Jam.checked ? 1 : 0);
formData.append('latitude', latitude.value);
formData.append('longitude', longitude.value);
fetch('index.php?action=update_spbu', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
alert(result.message);
if (result.status) {
loadSPBU();
hideForm();
}
});
});
btnBatal.addEventListener('click', function () {
hideForm();
});
const drawControl = new L.Control.Draw({
edit: {
featureGroup: drawnItems,
remove: true
},
draw: {
polyline: {
shapeOptions: {
color: 'black',
weight: 4
}
},
polygon: {
shapeOptions: {
color: 'green',
fillColor: 'green',
fillOpacity: 0.3
}
},
rectangle: false,
circle: false,
circlemarker: false,
marker: false
}
});
map.addControl(drawControl);
function loadShapes() {
drawnItems.clearLayers();
fetch('index.php?action=get_shapes')
.then(res => res.json())
.then(data => {
data.forEach(shape => {
let coords = JSON.parse(shape.coordinates);
let layer = null;
if (shape.shape_type === 'polyline') {
layer = L.polyline(coords, {
color: 'black',
weight: 4
});
} else if (shape.shape_type === 'polygon') {
layer = L.polygon(coords, {
color: 'green',
fillColor: 'green',
fillOpacity: 0.3
});
}
if (layer) {
layer._dbId = shape.id;
layer._shapeType = shape.shape_type;
drawnItems.addLayer(layer);
}
});
});
}
map.on(L.Draw.Event.CREATED, function (event) {
const layer = event.layer;
const type = event.layerType;
drawnItems.addLayer(layer);
let coordinates = [];
if (type === 'polyline') {
coordinates = layer.getLatLngs().map(latlng => [latlng.lat, latlng.lng]);
} else if (type === 'polygon') {
coordinates = layer.getLatLngs()[0].map(latlng => [latlng.lat, latlng.lng]);
}
const formData = new URLSearchParams();
formData.append('shape_type', type);
formData.append('coordinates', JSON.stringify(coordinates));
fetch('index.php?action=save_shape', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
console.log(result.message);
loadShapes();
});
});
map.on(L.Draw.Event.EDITED, function (event) {
event.layers.eachLayer(function (layer) {
if (!layer._dbId) return;
let coordinates = [];
let shapeType = '';
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)) {
shapeType = 'polyline';
coordinates = layer.getLatLngs().map(latlng => [latlng.lat, latlng.lng]);
} else if (layer instanceof L.Polygon) {
shapeType = 'polygon';
coordinates = layer.getLatLngs()[0].map(latlng => [latlng.lat, latlng.lng]);
}
const formData = new URLSearchParams();
formData.append('id', layer._dbId);
formData.append('shape_type', shapeType);
formData.append('coordinates', JSON.stringify(coordinates));
fetch('index.php?action=update_shape', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
console.log(result.message);
});
});
});
map.on(L.Draw.Event.DELETED, function (event) {
event.layers.eachLayer(function (layer) {
if (layer._dbId) {
const formData = new URLSearchParams();
formData.append('id', layer._dbId);
fetch('index.php?action=delete_shape', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
console.log(result.message);
});
}
});
});
loadSPBU();
loadShapes();
</script>
</body>
</html>
+111
View File
@@ -0,0 +1,111 @@
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: Arial, sans-serif;
}
#map {
width: 100%;
height: 100vh;
}
.form-panel {
position: fixed;
top: 20px;
right: 20px;
width: 320px;
background: #fff;
padding: 16px;
border-radius: 12px;
box-shadow: 0 4px 18px rgba(0,0,0,0.2);
z-index: 9999;
}
.form-panel h3 {
margin: 0 0 12px;
font-size: 20px;
}
.form-panel input[type="text"] {
width: 100%;
padding: 10px;
margin-bottom: 10px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 8px;
outline: none;
}
.form-panel input[type="text"]:focus {
border-color: #007bff;
}
.form-panel label {
display: block;
margin-bottom: 10px;
font-size: 14px;
}
.btn-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
button {
padding: 10px 14px;
border: none;
border-radius: 8px;
cursor: pointer;
background: #007bff;
color: #fff;
font-size: 14px;
}
button:hover {
opacity: 0.9;
}
.btn-secondary {
background: #6c757d;
}
.hidden {
display: none;
}
.leaflet-popup-content button {
margin-top: 6px;
margin-right: 5px;
padding: 6px 10px;
font-size: 12px;
}
.custom-div-icon {
background: transparent;
border: none;
}
@media (max-width: 768px) {
.form-panel {
top: auto;
right: 10px;
left: 10px;
bottom: 10px;
width: auto;
padding: 14px;
}
.form-panel h3 {
font-size: 18px;
}
.form-panel input[type="text"] {
font-size: 14px;
}
button {
flex: 1;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: Arial, sans-serif;
}
#map {
width: 100%;
height: 100vh;
}
.form-panel {
position: fixed;
top: 20px;
right: 20px;
width: 340px;
background: #fff;
padding: 16px;
border-radius: 12px;
box-shadow: 0 4px 18px rgba(0,0,0,0.2);
z-index: 9999;
}
.form-panel h3 {
margin: 0 0 12px;
font-size: 20px;
}
.form-panel input[type="text"],
.form-panel select {
width: 100%;
padding: 10px;
margin-bottom: 10px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 8px;
outline: none;
background: #fff;
}
.form-panel input[type="text"]:focus,
.form-panel select:focus {
border-color: #007bff;
}
.checkbox-label {
display: block;
margin-bottom: 10px;
font-size: 14px;
}
.btn-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
button {
padding: 10px 14px;
border: none;
border-radius: 8px;
cursor: pointer;
background: #007bff;
color: #fff;
font-size: 14px;
}
button:hover {
opacity: 0.9;
}
.btn-secondary {
background: #6c757d;
}
.hidden {
display: none;
}
.leaflet-popup-content button {
margin-top: 6px;
margin-right: 5px;
padding: 6px 10px;
font-size: 12px;
}
@media (max-width: 768px) {
.form-panel {
top: auto;
right: 10px;
left: 10px;
bottom: 10px;
width: auto;
padding: 14px;
}
.form-panel h3 {
font-size: 18px;
}
.form-panel input[type="text"],
.form-panel select {
font-size: 14px;
}
button {
flex: 1;
}
}
+739
View File
@@ -0,0 +1,739 @@
<?php
session_start();
function is_logged_in() {
return isset($_SESSION['user']);
}
function current_role() {
return $_SESSION['user']['role'] ?? 'masyarakat';
}
function current_instansi() {
return $_SESSION['user']['instansi'] ?? '';
}
function is_admin() {
return current_role() === 'admin';
}
function is_walikota() {
return current_role() === 'walikota';
}
function is_dinas() {
return current_role() === 'dinas';
}
function can_view_dashboard() {
return is_admin() || is_walikota() || is_dinas();
}
function can_add_histori() {
return is_admin() || is_dinas();
}
if (!can_view_dashboard()) {
header('Location: index.php');
exit;
}
$roleLabel = ['admin' => 'Admin Pengelola', 'walikota' => 'Wali Kota', 'dinas' => 'Dinas Terkait'][$_SESSION['user']['role'] ?? ''] ?? 'Masyarakat Umum';
$instansiLabel = current_instansi();
$admin = is_admin();
$canAddHistori = can_add_histori();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard WebGIS Kemiskinan Pontianak</title>
<link rel="stylesheet" href="style.css?v=fix-no-alert-bersih-1">
</head>
<body class="dashboard-body">
<div class="dashboard-shell dashboard-shell-wide">
<header class="dashboard-header">
<div>
<h1>Dashboard WebGIS Kemiskinan Pontianak</h1>
<p>
Login: <?= htmlspecialchars($_SESSION['user']['nama']) ?>
(<?= htmlspecialchars($roleLabel) ?><?= $instansiLabel ? ' - ' . htmlspecialchars($instansiLabel) : '' ?>)
</p>
</div>
<div class="dashboard-header-actions">
<a href="index.php">Kembali ke Peta</a>
<a href="index.php?logout=1">Logout</a>
</div>
</header>
<section class="dashboard-note">
Dashboard ini berisi ringkasan dan tabel utama sistem: laporan cepat, histori bantuan, masyarakat prasejahtera,
dan rumah ibadah. Pilih tombol data di bawah untuk membuka tabel yang ingin dilihat. Data SPBU, jalan,
dan parsil tanah tidak ditampilkan di dashboard ini.
</section>
<section class="dashboard-card dashboard-summary-card">
<div class="section-title-row">
<div>
<h2>Ringkasan Utama</h2>
<p class="section-subtitle">Angka penting untuk memantau laporan masyarakat, bantuan, dan data sosial.</p>
</div>
</div>
<div id="statCards" class="stat-grid compact-stat-grid"></div>
</section>
<section class="dashboard-card">
<h2>Pilih Data Dashboard</h2>
<p class="section-subtitle">Klik salah satu tombol. Tabel akan muncul penuh di bawah agar lebih mudah dibaca.</p>
<div class="dashboard-menu-grid">
<button class="dashboard-menu-btn active" data-target="laporan" onclick="showDashboardSection('laporan', this)">Laporan Cepat</button>
<button class="dashboard-menu-btn" data-target="histori" onclick="showDashboardSection('histori', this)">Histori Bantuan</button>
<button class="dashboard-menu-btn" data-target="penduduk" onclick="showDashboardSection('penduduk', this)">Masyarakat Prasejahtera</button>
<button class="dashboard-menu-btn" data-target="rumah" onclick="showDashboardSection('rumah', this)">Rumah Ibadah</button>
<button class="dashboard-menu-btn" data-target="download" onclick="showDashboardSection('download', this)">Download Data</button>
<?php if ($admin): ?>
<button class="dashboard-menu-btn" data-target="akun" onclick="showDashboardSection('akun', this)">Kelola Akun</button>
<?php endif; ?>
</div>
</section>
<section id="section-laporan" class="dashboard-card dashboard-section">
<div class="section-title-row">
<div>
<h2 id="laporanTitle">Laporan Cepat Masyarakat</h2>
<p id="laporanNote" class="section-subtitle"></p>
</div>
<a class="small-action" href="index.php?download=laporan">Download CSV</a>
</div>
<div id="laporanTable" class="dashboard-table-wrap"></div>
</section>
<section id="section-histori" class="dashboard-card dashboard-section hidden">
<div class="section-title-row">
<div>
<h2>Histori Bantuan</h2>
<p class="section-subtitle">Catatan bantuan yang sudah diterima masyarakat prasejahtera.</p>
</div>
<a class="small-action" href="index.php?download=histori">Download CSV</a>
</div>
<div id="historiForm"></div>
<div id="historiTable" class="dashboard-table-wrap"></div>
</section>
<section id="section-penduduk" class="dashboard-card dashboard-section hidden">
<div class="section-title-row">
<div>
<h2>Data Masyarakat Prasejahtera</h2>
<p class="section-subtitle">Data warga/keluarga yang menjadi objek pemetaan dan monitoring bantuan.</p>
</div>
<a class="small-action" href="index.php?download=penduduk">Download CSV</a>
</div>
<div id="pendudukTable" class="dashboard-table-wrap"></div>
</section>
<section id="section-rumah" class="dashboard-card dashboard-section hidden">
<div class="section-title-row">
<div>
<h2>Data Rumah Ibadah</h2>
<p class="section-subtitle">Data rumah ibadah dan radius jangkauan yang digunakan sebagai indikator spasial.</p>
</div>
<a class="small-action" href="index.php?download=rumah_ibadah">Download CSV</a>
</div>
<div id="rumahIbadahTable" class="dashboard-table-wrap"></div>
</section>
<section id="section-download" class="dashboard-card dashboard-section hidden">
<h2>Download Data</h2>
<p class="section-subtitle">Unduhan hanya untuk data utama sistem bantuan masyarakat prasejahtera.</p>
<div class="download-grid dashboard-download-grid">
<a href="index.php?download=laporan">Laporan Cepat</a>
<a href="index.php?download=histori">Histori Bantuan</a>
<a href="index.php?download=penduduk">Masyarakat Prasejahtera</a>
<a href="index.php?download=rumah_ibadah">Rumah Ibadah</a>
</div>
</section>
<?php if ($admin): ?>
<section id="section-akun" class="dashboard-card dashboard-section hidden">
<h2>Kelola Akun Pengguna</h2>
<p class="section-subtitle">Admin dapat menambahkan akun admin, wali kota, dan dinas terkait.</p>
<div id="akunForm"></div>
<div id="akunTable" class="dashboard-table-wrap"></div>
</section>
<?php endif; ?>
</div>
<script>
const IS_ADMIN = <?= $admin ? 'true' : 'false' ?>;
const CAN_ADD_HISTORI = <?= $canAddHistori ? 'true' : 'false' ?>;
const USER_ROLE = '<?= htmlspecialchars($_SESSION['user']['role'] ?? 'masyarakat', ENT_QUOTES) ?>';
const USER_INSTANSI = '<?= htmlspecialchars(current_instansi(), ENT_QUOTES) ?>';
let allData = {};
function esc(value) {
return String(value ?? '').replace(/[&<>\"]/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
}[char]));
}
function optionSet(options, selected) {
return options.map(option => `
<option value="${esc(option)}" ${String(option) === String(selected) ? 'selected' : ''}>
${esc(option)}
</option>
`).join('');
}
function numberFormat(value) {
return new Intl.NumberFormat('id-ID').format(Number(value || 0));
}
function rupiah(value) {
return 'Rp ' + new Intl.NumberFormat('id-ID').format(Number(value || 0));
}
function normalizeText(value) {
return String(value ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
}
function findMatchingPendudukForLaporan(item) {
const penduduk = allData.penduduk_kemiskinan || [];
const nikLaporan = normalizeText(item.data_nik || '');
const namaLaporan = normalizeText(item.data_nama_kepala_keluarga || '');
if (nikLaporan !== '') {
const byNik = penduduk.find(p => normalizeText(p.nik || '') === nikLaporan);
if (byNik) return { item: byNik, reason: 'NIK sama' };
}
if (namaLaporan !== '') {
const byName = penduduk.find(p => normalizeText(p.nama_kepala_keluarga || '') === namaLaporan);
if (byName) return { item: byName, reason: 'Nama sama' };
}
return null;
}
function showDashboardSection(sectionName, button = null) {
document.querySelectorAll('.dashboard-section').forEach(section => section.classList.add('hidden'));
const target = document.getElementById('section-' + sectionName);
if (target) target.classList.remove('hidden');
document.querySelectorAll('.dashboard-menu-btn').forEach(btn => btn.classList.remove('active'));
if (button) {
button.classList.add('active');
} else {
const autoButton = document.querySelector(`.dashboard-menu-btn[data-target="${sectionName}"]`);
if (autoButton) autoButton.classList.add('active');
}
}
function hitungStatistik() {
const laporan = allData.laporan_masyarakat || [];
const histori = allData.histori_bantuan || [];
const penduduk = allData.penduduk_kemiskinan || [];
const rumahIbadah = allData.masjid || [];
return {
laporanMasuk: laporan.filter(x => x.status_laporan === 'Masuk').length,
laporanDiproses: laporan.filter(x => ['Ditinjau', 'Diproses'].includes(x.status_laporan)).length,
laporanSelesai: laporan.filter(x => x.status_laporan === 'Selesai').length,
laporanDijadikan: laporan.filter(x => x.status_laporan === 'Dijadikan Data Masyarakat').length,
totalPenduduk: penduduk.length,
belumMenerima: penduduk.filter(x => x.status_bantuan === 'Belum menerima').length,
sudahMenerima: penduduk.filter(x => x.status_bantuan === 'Sudah menerima').length,
totalHistori: histori.length,
totalRumahIbadah: rumahIbadah.length
};
}
function renderStats() {
const s = hitungStatistik();
document.getElementById('statCards').innerHTML = `
<div class="stat-card important"><span>Masyarakat Prasejahtera</span><b>${numberFormat(s.totalPenduduk)}</b><small>Total data warga</small></div>
<div class="stat-card"><span>Belum Menerima Bantuan</span><b>${numberFormat(s.belumMenerima)}</b><small>Prioritas pengecekan bantuan</small></div>
<div class="stat-card"><span>Sudah Menerima Bantuan</span><b>${numberFormat(s.sudahMenerima)}</b><small>Warga tercatat menerima bantuan</small></div>
<div class="stat-card important"><span>Laporan Masuk</span><b>${numberFormat(s.laporanMasuk)}</b><small>Perlu ditinjau admin</small></div>
<div class="stat-card"><span>Ditinjau/Diproses</span><b>${numberFormat(s.laporanDiproses)}</b><small>Laporan sedang ditangani</small></div>
<div class="stat-card"><span>Laporan Selesai</span><b>${numberFormat(s.laporanSelesai)}</b><small>Laporan sudah selesai</small></div>
<div class="stat-card"><span>Jadi Data Masyarakat</span><b>${numberFormat(s.laporanDijadikan)}</b><small>Laporan valid yang dikonversi</small></div>
<div class="stat-card"><span>Histori Bantuan</span><b>${numberFormat(s.totalHistori)}</b><small>Jumlah bantuan tercatat</small></div>
<div class="stat-card"><span>Rumah Ibadah</span><b>${numberFormat(s.totalRumahIbadah)}</b><small>Potensi pemberi bantuan</small></div>
`;
}
function renderAutoLinkBox(item) {
const status = String(item.status_laporan || '');
if (item.penduduk_id) {
return `
<div class="match-note success">
Laporan ini sudah terhubung ke data masyarakat prasejahtera.
Jika bantuan diberikan, catat melalui tab <b>Histori Bantuan</b>.
</div>
`;
}
if (status !== 'Selesai') {
const match = findMatchingPendudukForLaporan(item);
const matchText = match
? `Terdeteksi data lama yang cocok: <b>${esc(match.item.nama_kepala_keluarga)}</b> (${esc(match.reason)}).`
: `Belum ada data masyarakat prasejahtera yang cocok.`;
return `
<div class="match-note muted">
${matchText}<br>
Laporan belum bisa dijadikan/diHubungkan ke data masyarakat karena status masih <b>${esc(status || 'Masuk')}</b>.
Ubah status menjadi <b>Selesai</b> setelah laporan benar-benar valid.
</div>
`;
}
const match = findMatchingPendudukForLaporan(item);
const matchedId = match?.item?.id ? String(match.item.id) : '';
const options = (allData.penduduk_kemiskinan || []).map(p => `
<option value="${p.id}" ${String(p.id) === matchedId ? 'selected' : ''}>
${esc(p.nama_kepala_keluarga)} - NIK: ${esc(p.nik || '-')} - ${esc(p.kelurahan || '-')}
</option>
`).join('');
const matchNote = match
? `<div class="match-note">Terdeteksi cocok otomatis: <b>${esc(match.item.nama_kepala_keluarga)}</b> (${esc(match.reason)}). Karena status sudah <b>Selesai</b>, klik <b>Hubungkan</b> agar laporan masuk ke data warga yang sama.</div>`
: `<div class="match-note muted">Belum ada data lama yang cocok. Jika warga memang belum terdaftar dan laporan sudah valid, gunakan <b>Jadikan Data Baru</b>.</div>`;
return `
${matchNote}
<div class="mini-link-box">
<select id="link_penduduk_${item.id}">
<option value="">-- Hubungkan ke data lama --</option>
${options}
</select>
<button onclick="linkLaporan(${item.id})">Hubungkan</button>
</div>
<button onclick="convertLaporan(${item.id})">Jadikan Data Baru</button>
`;
}
function renderLaporan() {
const data = allData.laporan_masyarakat || [];
document.getElementById('laporanTitle').textContent = USER_ROLE === 'dinas' ? 'Laporan Instansi' : 'Laporan Cepat Masyarakat';
document.getElementById('laporanNote').innerHTML = USER_ROLE === 'dinas'
? `Data yang tampil hanya laporan dengan tujuan <b>${esc(USER_INSTANSI || 'Dinas Sosial')}</b>.`
: 'Rekap laporan cepat yang dikirim masyarakat melalui titik lokasi pada peta.';
document.getElementById('laporanTable').innerHTML = `
<table class="dashboard-table wide-table">
<thead>
<tr>
<th>ID</th>
<th>Pelapor</th>
<th>Warga Dilaporkan</th>
<th>Alamat Warga</th>
<th>No HP</th>
<th>Kategori</th>
<th>Aktor Tujuan</th>
<th>Status</th>
<th>Deskripsi</th>
<th>Koordinat</th>
<th>Tanggal</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
${data.map(item => `
<tr>
<td>${esc(item.id)}</td>
<td>${esc(item.nama_pelapor)}</td>
<td>
<b>${esc(item.data_nama_kepala_keluarga || '-')}</b><br>
<small>NIK: ${esc(item.data_nik || '-')}</small><br>
<small>KK: ${esc(item.data_jumlah_kk || '-')}</small>
</td>
<td>
${esc(item.data_alamat || '-')}<br>
<small>${esc(item.data_kelurahan || '-')}, ${esc(item.data_kecamatan || '-')}</small><br>
<small>Kesehatan: ${esc(item.data_riwayat_penyakit || '-')}</small>
</td>
<td>${esc(item.no_hp)}</td>
<td>${esc(item.kategori_laporan)}</td>
<td>${esc(item.aktor_tujuan)}</td>
<td>
${IS_ADMIN ? `
<select id="status_laporan_${item.id}">
${optionSet(['Masuk', 'Ditinjau', 'Diproses', 'Selesai', 'Dijadikan Data Masyarakat', 'Ditolak'], item.status_laporan)}
</select>
` : `<b>${esc(item.status_laporan)}</b>`}
</td>
<td>${esc(item.deskripsi)}</td>
<td>${esc(item.latitude)}, ${esc(item.longitude)}</td>
<td>${esc(item.created_at)}</td>
<td>
${IS_ADMIN ? `
<button onclick="updateStatusLaporan(${item.id})">Update Status</button>
${(!item.penduduk_id && item.status_laporan !== 'Ditolak') ? `
${renderAutoLinkBox(item)}
` : `<small>Sudah terhubung/selesai</small>`}
<button class="danger" onclick="deleteData('laporan_masyarakat', ${item.id})">Hapus</button>
` : `<small>Hanya lihat</small>`}
</td>
</tr>
`).join('') || `<tr><td colspan="12">Belum ada data laporan.</td></tr>`}
</tbody>
</table>
`;
}
function renderHistori() {
const data = allData.histori_bantuan || [];
const options = (allData.penduduk_kemiskinan || []).map(item => `
<option value="${item.id}">${esc(item.nama_kepala_keluarga)}</option>
`).join('');
const rumahOptions = (allData.masjid || []).map(item => `
<option value="${item.id}">${esc(item.nama_masjid)} - ${esc(item.jenis_rumah_ibadah)}</option>
`).join('');
const sumberDefault = USER_ROLE === 'dinas' ? (USER_INSTANSI || 'Dinas Terkait') : 'Pemkot Pontianak / Rumah Ibadah / Donatur';
document.getElementById('historiForm').innerHTML = CAN_ADD_HISTORI ? `
<p class="info-note">Bantuan bisa diberikan berulang kali. Tambahkan histori baru untuk warga yang sama setiap kali bantuan diberikan.</p>
<div class="dashboard-inline-form">
<select id="penduduk_id">
<option value="">-- Pilih masyarakat penerima --</option>
${options}
</select>
<input id="jenis_bantuan" placeholder="Jenis bantuan, contoh: sembako / PKH / pemeriksaan kesehatan">
<input id="tanggal_bantuan" type="date">
<input id="nominal" type="number" placeholder="Nominal bantuan / 0 jika non-uang">
<input id="sumber_bantuan" value="${esc(sumberDefault)}">
<select id="masjid_id">
<option value="">-- Rumah ibadah pemberi bantuan, jika ada --</option>
${rumahOptions}
</select>
<input id="keterangan" placeholder="Keterangan bantuan">
<button onclick="addHistori()">Tambah Histori</button>
</div>
` : `<p class="info-note">Role ini hanya dapat melihat dan mengunduh histori bantuan.</p>`;
document.getElementById('historiTable').innerHTML = `
<table class="dashboard-table">
<thead>
<tr>
<th>ID</th>
<th>Kepala Keluarga</th>
<th>Jenis Bantuan</th>
<th>Tanggal</th>
<th>Nominal</th>
<th>Sumber</th>
<th>Rumah Ibadah</th>
<th>Keterangan</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
${data.map(item => `
<tr>
<td>${esc(item.id)}</td>
<td>${esc(item.nama_kepala_keluarga)}</td>
<td>${esc(item.jenis_bantuan)}</td>
<td>${esc(item.tanggal_bantuan)}</td>
<td>${rupiah(item.nominal)}</td>
<td>${esc(item.sumber_bantuan)}</td>
<td>${item.nama_masjid ? `${esc(item.nama_masjid)}<br><small>${esc(item.jenis_rumah_ibadah || '-')}</small>` : '-'}</td>
<td>${esc(item.keterangan)}</td>
<td>${IS_ADMIN ? `<button class="danger" onclick="deleteData('histori_bantuan', ${item.id})">Hapus</button>` : `<small>Hanya lihat</small>`}</td>
</tr>
`).join('') || `<tr><td colspan="9">Belum ada histori bantuan.</td></tr>`}
</tbody>
</table>
`;
}
function renderPenduduk() {
const data = allData.penduduk_kemiskinan || [];
document.getElementById('pendudukTable').innerHTML = `
<table class="dashboard-table wide-table">
<thead>
<tr>
<th>ID</th>
<th>Nama Kepala Keluarga</th>
<th>NIK</th>
<th>Tanggal Lahir</th>
<th>Pendidikan</th>
<th>Riwayat Penyakit</th>
<th>Jumlah KK</th>
<th>Alamat</th>
<th>Kecamatan</th>
<th>Kelurahan</th>
<th>Status Bantuan</th>
<th>Koordinat</th>
</tr>
</thead>
<tbody>
${data.map(item => `
<tr>
<td>${esc(item.id)}</td>
<td>${esc(item.nama_kepala_keluarga)}</td>
<td>${esc(item.nik || '-')}</td>
<td>${esc(item.tanggal_lahir || '-')}</td>
<td>${esc(item.pendidikan)}</td>
<td>${esc(item.riwayat_penyakit || '-')}</td>
<td>${esc(item.jumlah_kk)}</td>
<td>${esc(item.alamat)}</td>
<td>${esc(item.kecamatan || '-')}</td>
<td>${esc(item.kelurahan || '-')}</td>
<td><b>${esc(item.status_bantuan)}</b></td>
<td>${esc(item.latitude)}, ${esc(item.longitude)}</td>
</tr>
`).join('') || `<tr><td colspan="12">Belum ada data masyarakat prasejahtera.</td></tr>`}
</tbody>
</table>
`;
}
function renderRumahIbadah() {
const data = allData.masjid || [];
document.getElementById('rumahIbadahTable').innerHTML = `
<table class="dashboard-table">
<thead>
<tr>
<th>ID</th>
<th>Jenis</th>
<th>Nama Rumah Ibadah</th>
<th>PIC</th>
<th>Alamat</th>
<th>Radius</th>
<th>Koordinat</th>
</tr>
</thead>
<tbody>
${data.map(item => `
<tr>
<td>${esc(item.id)}</td>
<td>${esc(item.jenis_rumah_ibadah)}</td>
<td>${esc(item.nama_masjid)}</td>
<td>${esc(item.pic_masjid)}</td>
<td>${esc(item.alamat_masjid)}</td>
<td>${esc(item.radius_meter)} meter</td>
<td>${esc(item.latitude)}, ${esc(item.longitude)}</td>
</tr>
`).join('') || `<tr><td colspan="7">Belum ada data rumah ibadah.</td></tr>`}
</tbody>
</table>
`;
}
function renderAkun() {
if (!IS_ADMIN) return;
const users = allData.users || [];
document.getElementById('akunForm').innerHTML = `
<div class="dashboard-inline-form">
<input id="user_nama" placeholder="Nama akun / nama dinas">
<input id="user_username" placeholder="Username">
<input id="user_password" type="password" placeholder="Password">
<select id="user_role">
${optionSet(['dinas', 'walikota', 'admin'], 'dinas')}
</select>
<select id="user_instansi">
${optionSet(['Dinas Sosial', 'Dinas Kesehatan', 'Wali Kota', 'Admin Pengelola'], 'Dinas Sosial')}
</select>
<button onclick="addUser()">Tambah Akun</button>
</div>
`;
document.getElementById('akunTable').innerHTML = `
<table class="dashboard-table">
<thead>
<tr>
<th>ID</th>
<th>Nama</th>
<th>Username</th>
<th>Role</th>
<th>Instansi</th>
<th>Dibuat</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
${users.map(item => `
<tr>
<td>${esc(item.id)}</td>
<td>${esc(item.nama)}</td>
<td>${esc(item.username)}</td>
<td>${esc(item.role)}</td>
<td>${esc(item.instansi || '-')}</td>
<td>${esc(item.created_at || '-')}</td>
<td><button class="danger" onclick="deleteUser(${item.id})">Hapus</button></td>
</tr>
`).join('') || `<tr><td colspan="7">Belum ada data akun.</td></tr>`}
</tbody>
</table>
`;
}
function renderDashboard() {
renderStats();
renderLaporan();
renderHistori();
renderPenduduk();
renderRumahIbadah();
renderAkun();
}
function loadDashboard() {
fetch('index.php?action=get_all')
.then(async response => {
const text = await response.text();
try {
return JSON.parse(text);
} catch (error) {
console.error('Response dashboard bukan JSON. Cek output PHP:', text);
return {
penduduk_kemiskinan: [],
masjid: [],
laporan_masyarakat: [],
histori_bantuan: [],
users: []
};
}
})
.then(data => {
allData = data || {};
renderDashboard();
showDashboardSection('laporan');
})
.catch(error => {
console.error('Gagal memuat dashboard:', error);
allData = {
penduduk_kemiskinan: [],
masjid: [],
laporan_masyarakat: [],
histori_bantuan: [],
users: []
};
renderDashboard();
showDashboardSection('laporan');
});
}
function updateStatusLaporan(id) {
if (!IS_ADMIN) return;
const status = document.getElementById('status_laporan_' + id).value;
const formData = new URLSearchParams();
formData.append('id', id);
formData.append('status_laporan', status);
fetch('index.php?action=update_status_laporan', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function convertLaporan(id) {
if (!IS_ADMIN) return;
if (!confirm('Jadikan laporan ini sebagai data masyarakat prasejahtera? Bubble laporan akan disembunyikan dari peta, tetapi tetap masuk statistik dashboard.')) return;
const formData = new URLSearchParams();
formData.append('id', id);
fetch('index.php?action=convert_laporan_to_penduduk', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function linkLaporan(id) {
if (!IS_ADMIN) return;
const select = document.getElementById('link_penduduk_' + id);
const pendudukId = select ? select.value : '';
if (!pendudukId) {
alert('Pilih data masyarakat prasejahtera yang sudah ada terlebih dahulu.');
return;
}
if (!confirm('Hubungkan laporan ini ke data masyarakat yang sudah ada? Laporan tetap tersimpan untuk arsip/statistik, tetapi bubble laporan akan disembunyikan dari peta.')) return;
const formData = new URLSearchParams();
formData.append('id', id);
formData.append('penduduk_id', pendudukId);
fetch('index.php?action=link_laporan_to_penduduk', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function addHistori() {
if (!CAN_ADD_HISTORI) return;
const fields = ['penduduk_id', 'masjid_id', 'jenis_bantuan', 'tanggal_bantuan', 'nominal', 'sumber_bantuan', 'keterangan'];
const formData = new URLSearchParams();
fields.forEach(id => formData.append(id, document.getElementById(id).value));
fetch('index.php?action=add_histori', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function deleteData(table, id) {
if (!IS_ADMIN) return;
if (!confirm('Yakin ingin menghapus data ini?')) return;
const formData = new URLSearchParams();
formData.append('table', table);
formData.append('id', id);
fetch('index.php?action=delete', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function addUser() {
if (!IS_ADMIN) return;
const formData = new URLSearchParams();
formData.append('nama', document.getElementById('user_nama').value);
formData.append('username', document.getElementById('user_username').value);
formData.append('password', document.getElementById('user_password').value);
formData.append('role', document.getElementById('user_role').value);
formData.append('instansi', document.getElementById('user_instansi').value);
fetch('index.php?action=add_user', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
function deleteUser(id) {
if (!IS_ADMIN) return;
if (!confirm('Hapus akun ini?')) return;
const formData = new URLSearchParams();
formData.append('id', id);
fetch('index.php?action=delete_user', { method: 'POST', body: formData })
.then(response => response.json())
.then(result => {
alert(result.message);
loadDashboard();
});
}
loadDashboard();
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
# Cara Pakai File Environment dan Git Ignore
## 1. File yang perlu diletakkan di folder project utama
Letakkan file berikut di folder utama repo:
- `.gitignore`
- `.env.example`
File `.env.example` boleh dipush ke Git karena tidak berisi password asli.
## 2. Buat file `.env` untuk lokal
Copy file `.env.example`, lalu rename menjadi:
```text
.env
```
Isi sesuai database lokal kamu, misalnya:
```env
DB_HOST=127.0.0.1
DB_USER=root
DB_PASS=password_mysql_lokal_kamu
DB_NAME=sig
DB_PORT=3306
```
File `.env` jangan dipush ke Git karena sudah masuk `.gitignore`.
## 3. Untuk Coolify
Di Coolify, isi Environment Variables:
```env
DB_HOST=host_database_dari_coolify
DB_USER=user_database_dari_coolify
DB_PASS=password_database_dari_coolify
DB_NAME=nama_database_dari_coolify
DB_PORT=3306
```
## 4. Kalau PHP kamu belum otomatis membaca `.env`
Tambahkan file `env.php` ke folder `3_PovertyMapping/`.
Lalu di bagian paling atas `index.php`, sebelum koneksi database, tambahkan:
```php
require_once __DIR__ . '/env.php';
$host = env('DB_HOST', '127.0.0.1');
$user = env('DB_USER', 'root');
$pass = env('DB_PASS', '');
$db = env('DB_NAME', 'sig');
$port = (int) env('DB_PORT', 3306);
$conn = new mysqli($host, $user, $pass, $db, $port);
if ($conn->connect_error) {
die("Koneksi database gagal.");
}
$conn->set_charset("utf8mb4");
```
Lalu hapus koneksi lama yang masih berisi password hardcode.
## 5. Yang aman dipush
Boleh dipush:
- `.gitignore`
- `.env.example`
- `README.md`
- folder project
- SQL final jika tidak berisi password
Jangan dipush:
- `.env`
- ZIP
- `__MACOSX`
- `.DS_Store`
- file yang berisi password asli
+13
View File
@@ -0,0 +1,13 @@
FROM php:8.2-apache
RUN apt-get update \
&& apt-get install -y default-mysql-client \
&& docker-php-ext-install mysqli \
&& a2enmod rewrite \
&& rm -rf /var/lib/apt/lists/*
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
+410
View File
@@ -0,0 +1,410 @@
# WebGIS Poverty Mapping Kota Pontianak
## Informasi Proyek
| Keterangan | Detail |
|---|---|
| Nama Proyek | WebGIS Poverty Mapping Kota Pontianak |
| Jenis Sistem | Sistem Informasi Geografis berbasis Web |
| Mata Kuliah | Sistem Informasi Geografis (SIG) |
| Program Studi | Informatika, Fakultas Teknik, Universitas Tanjungpura |
| Pengembang | Talitha Syakirah Artanti |
| NIM | D1041231013 |
| Dosen Pengampu | Dr. Ir. Yus Sholva, ST., MT., CPro. |
## Deskripsi Singkat
WebGIS Poverty Mapping Kota Pontianak adalah aplikasi Sistem Informasi Geografis berbasis web yang digunakan untuk memetakan data masyarakat prasejahtera, laporan cepat masyarakat, histori bantuan, serta data rumah ibadah sebagai potensi pendukung penyaluran bantuan sosial.
Sistem ini dikembangkan menggunakan PHP native, MySQL, JavaScript, Leaflet.js, dan OpenStreetMap. Fokus utama aplikasi adalah membantu visualisasi persebaran masyarakat prasejahtera di Kota Pontianak, memantau laporan masyarakat, mencatat histori bantuan, serta menampilkan rumah ibadah dalam bentuk titik lokasi dan radius jangkauan.
## Tujuan Sistem
Tujuan dari sistem ini adalah:
1. Menampilkan persebaran masyarakat prasejahtera pada peta digital.
2. Menyediakan fitur laporan cepat dari masyarakat umum.
3. Membantu admin dalam memvalidasi dan mengelola laporan masyarakat.
4. Mencatat histori bantuan yang diterima masyarakat prasejahtera.
5. Menampilkan rumah ibadah sebagai potensi pemberi atau pendukung bantuan.
6. Menyediakan dashboard ringkasan bagi admin, wali kota, dan dinas terkait.
7. Menyediakan fitur unduh data dalam format CSV untuk kebutuhan monitoring.
## Struktur Folder
```text
UAS_SIG_Talitha/
├── 1_Pemetaan_SPBU/
├── 2_Jalan_ParsilTanah_dan_Layer/
├── 3_PovertyMapping/
├── env.php
├── .env.example
├── .gitignore
└── README.md
```
Keterangan:
- `1_Pemetaan_SPBU/` berisi pengembangan awal WebGIS untuk pemetaan SPBU.
- `2_Jalan_ParsilTanah_dan_Layer/` berisi pengembangan awal WebGIS untuk data jalan, parsil tanah, dan layer peta.
- `3_PovertyMapping/` merupakan aplikasi utama/final yang digunakan untuk sistem WebGIS Poverty Mapping Kota Pontianak.
- `env.php` digunakan untuk membaca konfigurasi database dari environment variable.
- `.env.example` berisi contoh konfigurasi environment.
- `.gitignore` digunakan untuk mencegah file sensitif dan file tidak diperlukan masuk ke repository.
- `README.md` berisi dokumentasi proyek.
## Fokus Aplikasi Final
Aplikasi final berada pada folder:
```text
3_PovertyMapping/
```
Aplikasi ini tidak lagi menampilkan data SPBU, jalan, dan parsil tanah sebagai fitur utama. Folder SPBU dan jalan/parsil tetap disertakan sebagai bagian dari riwayat pengembangan tugas, tetapi sistem final yang digunakan untuk penilaian adalah Poverty Mapping.
## Fitur Utama
### 1. Peta Digital
Sistem menampilkan peta digital Kota Pontianak menggunakan Leaflet.js dan OpenStreetMap sebagai basemap. Peta digunakan untuk menampilkan titik masyarakat prasejahtera, titik laporan masyarakat, dan titik rumah ibadah.
### 2. Data Masyarakat Prasejahtera
Admin dapat mengelola data masyarakat prasejahtera, meliputi:
- nama kepala keluarga;
- NIK;
- tanggal lahir;
- pendidikan;
- riwayat penyakit;
- jumlah KK;
- alamat;
- kecamatan;
- kelurahan;
- status bantuan;
- latitude dan longitude.
Data masyarakat prasejahtera ditampilkan dalam bentuk marker pada peta dan tabel pada dashboard.
### 3. Laporan Cepat Masyarakat
Masyarakat umum dapat mengirim laporan tanpa login melalui peta. Data laporan meliputi data pelapor, kategori laporan, tujuan laporan, deskripsi, data warga yang dilaporkan, serta titik lokasi.
Status laporan terdiri dari:
- `Masuk`
- `Ditinjau`
- `Diproses`
- `Selesai`
- `Dijadikan Data Masyarakat`
- `Ditolak`
Laporan hanya dapat dijadikan data masyarakat prasejahtera setelah statusnya menjadi `Selesai`.
### 4. Auto Terhubung NIK/Nama
Sistem memiliki logika pencocokan otomatis berdasarkan NIK atau nama kepala keluarga. Jika laporan masyarakat memiliki NIK atau nama yang sama dengan data masyarakat prasejahtera yang sudah ada, sistem dapat menghubungkan laporan tersebut ke data lama agar tidak terjadi data ganda.
### 5. Histori Bantuan
Sistem mencatat histori bantuan yang diterima masyarakat prasejahtera. Data histori bantuan meliputi:
- penerima bantuan;
- jenis bantuan;
- tanggal bantuan;
- nominal bantuan;
- sumber bantuan;
- keterangan.
Histori bantuan dapat ditambahkan lebih dari satu kali untuk satu masyarakat prasejahtera, sehingga riwayat bantuan dapat dipantau secara berkala.
### 6. Rumah Ibadah
Sistem menampilkan data rumah ibadah sebagai titik lokasi dan radius jangkauan. Rumah ibadah dapat berupa:
- Masjid;
- Gereja;
- Vihara;
- Kelenteng;
- Pura.
Data rumah ibadah digunakan sebagai indikator potensi pendukung bantuan sosial. Radius rumah ibadah tidak otomatis berarti masyarakat menerima bantuan, tetapi menjadi acuan spasial untuk melihat potensi jangkauan bantuan.
### 7. Relasi Bantuan dengan Rumah Ibadah
Dalam pengembangan terbaru, histori bantuan dapat dikembangkan agar memiliki relasi dengan rumah ibadah tertentu melalui kolom `masjid_id`. Dengan relasi ini, sistem dapat mencatat bahwa suatu masyarakat prasejahtera menerima bantuan dari rumah ibadah tertentu.
Konsep relasinya adalah:
```text
penduduk_id = masyarakat penerima bantuan
masjid_id = rumah ibadah pemberi bantuan
```
Jika bantuan berasal dari dinas atau sumber lain, maka `masjid_id` dapat dikosongkan. Jika bantuan berasal dari rumah ibadah, maka `masjid_id` diisi sesuai rumah ibadah pemberi bantuan.
### 8. Dashboard
Dashboard digunakan oleh pengguna yang memiliki akses login, yaitu admin, wali kota, dan dinas. Dashboard menampilkan:
- ringkasan jumlah masyarakat prasejahtera;
- jumlah laporan masuk, diproses, selesai, dan dijadikan data masyarakat;
- jumlah histori bantuan;
- jumlah rumah ibadah;
- tabel laporan masyarakat;
- tabel histori bantuan;
- tabel masyarakat prasejahtera;
- tabel rumah ibadah;
- fitur download data CSV;
- fitur kelola akun untuk admin.
### 9. Download Data
Sistem menyediakan fitur unduh data dalam format CSV untuk data utama, yaitu:
- laporan masyarakat;
- histori bantuan;
- masyarakat prasejahtera;
- rumah ibadah.
## Hak Akses Pengguna
| Role | Hak Akses |
|---|---|
| Masyarakat Umum | Mengirim laporan cepat tanpa login |
| Admin | Mengelola data masyarakat, laporan, histori bantuan, rumah ibadah, akun pengguna, dashboard, dan download data |
| Wali Kota | Melihat dashboard dan mengunduh data |
| Dinas Sosial | Melihat laporan sesuai instansi dan menambahkan histori bantuan sosial |
| Dinas Kesehatan | Melihat laporan sesuai instansi dan menambahkan histori bantuan terkait kesehatan |
## Alur Sistem
### Alur Laporan Masyarakat
1. Masyarakat membuka halaman peta.
2. Masyarakat memilih lokasi pada peta.
3. Masyarakat mengisi form laporan cepat.
4. Sistem menyimpan laporan dengan status awal `Masuk`.
5. Admin meninjau laporan.
6. Status laporan dapat diubah menjadi `Ditinjau`, `Diproses`, atau `Selesai`.
7. Setelah status `Selesai`, laporan dapat dihubungkan ke data masyarakat lama atau dijadikan data masyarakat baru.
8. Data yang sudah valid masuk ke data masyarakat prasejahtera dan dapat digunakan untuk monitoring bantuan.
### Alur Histori Bantuan
1. Admin atau dinas memilih data masyarakat prasejahtera.
2. Admin atau dinas mengisi jenis bantuan, tanggal, nominal, sumber bantuan, dan keterangan.
3. Sistem menyimpan bantuan ke tabel histori bantuan.
4. Data bantuan dapat dilihat pada dashboard dan diunduh dalam format CSV.
5. Jika bantuan berasal dari rumah ibadah, sistem dapat mencatat rumah ibadah pemberi bantuan melalui `masjid_id`.
### Alur Rumah Ibadah
1. Admin menambahkan data rumah ibadah pada peta.
2. Sistem menyimpan jenis, nama, PIC, alamat, koordinat, dan radius rumah ibadah.
3. Sistem menampilkan marker dan radius rumah ibadah.
4. Radius digunakan sebagai indikator potensi jangkauan bantuan.
## Struktur Database Utama
### Tabel `users`
Menyimpan data akun pengguna sistem.
| Kolom | Keterangan |
|---|---|
| id | ID pengguna |
| nama | Nama pengguna |
| username | Username login |
| password | Password terenkripsi |
| role | Role pengguna |
| instansi | Nama instansi untuk role dinas |
| created_at | Waktu data dibuat |
### Tabel `penduduk_kemiskinan`
Menyimpan data masyarakat prasejahtera.
| Kolom | Keterangan |
|---|---|
| id | ID masyarakat |
| nama_kepala_keluarga | Nama kepala keluarga |
| nik | Nomor Induk Kependudukan |
| tanggal_lahir | Tanggal lahir |
| pendidikan | Pendidikan terakhir |
| riwayat_penyakit | Riwayat penyakit |
| jumlah_kk | Jumlah anggota keluarga |
| alamat | Alamat lengkap |
| kecamatan | Kecamatan |
| kelurahan | Kelurahan |
| status_bantuan | Status bantuan |
| latitude | Koordinat latitude |
| longitude | Koordinat longitude |
| created_at | Waktu data dibuat |
### Tabel `laporan_masyarakat`
Menyimpan laporan cepat dari masyarakat umum.
| Kolom | Keterangan |
|---|---|
| id | ID laporan |
| penduduk_id | Relasi ke data masyarakat jika sudah terhubung |
| nama_pelapor | Nama pelapor |
| no_hp | Nomor HP pelapor |
| kategori_laporan | Kategori laporan |
| deskripsi | Deskripsi laporan |
| aktor_tujuan | Tujuan laporan |
| status_laporan | Status laporan |
| data_nama_kepala_keluarga | Nama warga yang dilaporkan |
| data_nik | NIK warga yang dilaporkan |
| data_tanggal_lahir | Tanggal lahir warga |
| data_pendidikan | Pendidikan warga |
| data_riwayat_penyakit | Riwayat penyakit warga |
| data_jumlah_kk | Jumlah anggota keluarga |
| data_alamat | Alamat warga |
| data_kecamatan | Kecamatan |
| data_kelurahan | Kelurahan |
| data_status_bantuan | Status bantuan awal |
| latitude | Koordinat latitude |
| longitude | Koordinat longitude |
| created_at | Waktu laporan dibuat |
### Tabel `histori_bantuan`
Menyimpan riwayat bantuan yang diterima masyarakat prasejahtera.
| Kolom | Keterangan |
|---|---|
| id | ID histori |
| penduduk_id | Relasi ke masyarakat penerima bantuan |
| masjid_id | Relasi ke rumah ibadah pemberi bantuan jika sumber bantuan berasal dari rumah ibadah |
| jenis_bantuan | Jenis bantuan |
| tanggal_bantuan | Tanggal bantuan |
| nominal | Nominal bantuan |
| sumber_bantuan | Sumber bantuan |
| keterangan | Keterangan tambahan |
| created_at | Waktu data dibuat |
### Tabel `masjid`
Menyimpan data rumah ibadah. Nama tabel tetap menggunakan `masjid` untuk menyesuaikan struktur kode, tetapi isi tabel dapat memuat berbagai jenis rumah ibadah.
| Kolom | Keterangan |
|---|---|
| id | ID rumah ibadah |
| jenis_rumah_ibadah | Jenis rumah ibadah |
| nama_masjid | Nama rumah ibadah |
| pic_masjid | PIC atau penanggung jawab |
| alamat_masjid | Alamat rumah ibadah |
| latitude | Koordinat latitude |
| longitude | Koordinat longitude |
| radius_meter | Radius jangkauan |
| created_at | Waktu data dibuat |
## Teknologi yang Digunakan
| Teknologi | Fungsi |
|---|---|
| PHP Native | Backend dan pengolahan request |
| MySQL | Penyimpanan data |
| JavaScript | Interaksi antarmuka dan peta |
| Leaflet.js | Library peta interaktif |
| OpenStreetMap | Basemap |
| HTML | Struktur halaman |
| CSS | Tampilan antarmuka |
| Git | Version control |
| Coolify | Platform deployment aplikasi |
## Konfigurasi Environment
Project menggunakan file environment agar konfigurasi database tidak ditulis langsung di kode.
Contoh isi `.env.example`:
```env
DB_HOST=127.0.0.1
DB_USER=root
DB_PASS=
DB_NAME=sig
DB_PORT=3306
```
File `.env` digunakan untuk konfigurasi lokal dan tidak boleh diunggah ke repository Git.
## File yang Tidak Diunggah ke Git
File berikut tidak perlu diunggah ke repository:
```text
.env
.env.*
!.env.example
.DS_Store
__MACOSX/
*.zip
```
Tujuannya adalah agar password database, file sistem Mac, dan file arsip tidak masuk ke repository.
## Cara Menjalankan di Localhost
1. Letakkan folder project pada direktori web server, misalnya `htdocs`.
2. Buat database MySQL dengan nama `sig`.
3. Import file SQL database yang tersedia.
4. Salin `.env.example` menjadi `.env`.
5. Sesuaikan konfigurasi database pada file `.env`.
6. Jalankan Apache dan MySQL.
7. Akses aplikasi melalui browser.
Contoh alamat akses:
```text
http://localhost/UAS_SIG_Talitha/3_PovertyMapping/
```
## Cara Deploy ke Coolify
1. Push project ke repository Git.
2. Pastikan file `.env` tidak ikut terunggah.
3. Buat aplikasi baru di Coolify dari repository Git.
4. Arahkan deployment ke folder `3_PovertyMapping`.
5. Buat database MySQL pada Coolify.
6. Import file SQL database.
7. Isi Environment Variables pada Coolify:
```env
DB_HOST=host_database
DB_USER=user_database
DB_PASS=password_database
DB_NAME=nama_database
DB_PORT=3306
```
8. Jalankan deploy aplikasi.
9. Akses aplikasi melalui domain yang diberikan oleh Coolify.
## Catatan Keamanan
1. Password pengguna disimpan dalam bentuk hash.
2. File `.env` tidak diunggah ke repository.
3. Query database menggunakan prepared statement pada proses utama.
4. Hak akses dibedakan berdasarkan role pengguna.
5. Data masyarakat prasejahtera hanya digunakan untuk kebutuhan pemetaan dan monitoring bantuan.
## Catatan Pengembangan
Sistem ini dikembangkan untuk kebutuhan tugas mata kuliah Sistem Informasi Geografis. Aplikasi masih dapat dikembangkan lebih lanjut, seperti:
- penambahan filter berdasarkan kecamatan atau kelurahan;
- integrasi laporan PDF;
- analisis spasial lebih lanjut;
- notifikasi tindak lanjut laporan;
- validasi data kependudukan yang lebih lengkap;
- visualisasi bantuan berdasarkan sumber bantuan;
- relasi histori bantuan dengan rumah ibadah secara penuh melalui `masjid_id`.
## Kesimpulan
WebGIS Poverty Mapping Kota Pontianak merupakan aplikasi pemetaan berbasis web yang membantu visualisasi data masyarakat prasejahtera, laporan masyarakat, histori bantuan, dan rumah ibadah. Sistem ini mendukung proses monitoring bantuan melalui peta digital, dashboard, role pengguna, dan data yang dapat diunduh. Dengan adanya fitur laporan cepat dan histori bantuan, sistem dapat menjadi media pendukung dalam pengelolaan data sosial masyarakat prasejahtera di Kota Pontianak.
+300
View File
@@ -0,0 +1,300 @@
-- MySQL dump 10.13 Distrib 8.0.46, for macos15 (arm64)
--
-- Host: localhost Database: sig
-- ------------------------------------------------------
-- Server version 8.0.46
/*!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 */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `histori_bantuan`
--
DROP TABLE IF EXISTS `histori_bantuan`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `histori_bantuan` (
`id` int NOT NULL AUTO_INCREMENT,
`penduduk_id` int NOT NULL,
`masjid_id` int DEFAULT NULL,
`jenis_bantuan` varchar(150) NOT NULL,
`tanggal_bantuan` date NOT NULL,
`nominal` decimal(14,2) NOT NULL DEFAULT '0.00',
`sumber_bantuan` varchar(150) NOT NULL DEFAULT 'Pemkot Pontianak / Dinas / Rumah Ibadah / Donatur',
`keterangan` text,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `fk_histori_bantuan_penduduk` (`penduduk_id`),
KEY `fk_histori_bantuan_masjid` (`masjid_id`),
CONSTRAINT `fk_histori_bantuan_masjid` FOREIGN KEY (`masjid_id`) REFERENCES `masjid` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_histori_bantuan_penduduk` FOREIGN KEY (`penduduk_id`) REFERENCES `penduduk_kemiskinan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `histori_bantuan`
--
LOCK TABLES `histori_bantuan` WRITE;
/*!40000 ALTER TABLE `histori_bantuan` DISABLE KEYS */;
INSERT INTO `histori_bantuan` VALUES (1,1,1,'Sembako','2026-06-07',100000.00,'Pemkot Pontianak / Rumah Ibadah / Donatur','Bantuan sembako tahap pertama','2026-06-09 07:25:39','2026-06-09 07:25:39'),(2,1,1,'Sembako','2026-06-09',100000.00,'Pemkot Pontianak / Rumah Ibadah / Donatur','Bantuan sembako tahap kedua','2026-06-09 07:26:05','2026-06-09 07:26:05');
/*!40000 ALTER TABLE `histori_bantuan` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `jalan`
--
DROP TABLE IF EXISTS `jalan`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jalan` (
`id` int NOT NULL AUTO_INCREMENT,
`nama_jalan` varchar(150) NOT NULL,
`status_jalan` enum('Jalan Nasional','Jalan Provinsi','Jalan Kabupaten') NOT NULL,
`panjang_meter` double NOT NULL DEFAULT '0',
`geojson` longtext NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `jalan`
--
LOCK TABLES `jalan` WRITE;
/*!40000 ALTER TABLE `jalan` DISABLE KEYS */;
INSERT INTO `jalan` VALUES (3,'JALAN Pontianak','Jalan Nasional',50952.09,'{\"type\":\"LineString\",\"coordinates\":[[109.409494,-0.1436],[109.408743,-0.142956],[109.407606,-0.142312],[109.407885,-0.141712],[109.407949,-0.138407],[109.407799,-0.134952],[109.40752,-0.133707],[109.406576,-0.132059],[109.404817,-0.129441],[109.403014,-0.127552],[109.401598,-0.125278],[109.399924,-0.123131],[109.397435,-0.120857],[109.395032,-0.118539],[109.393058,-0.116264],[109.391513,-0.112873],[109.390483,-0.110813],[109.389067,-0.10768],[109.387608,-0.105234],[109.385848,-0.102616],[109.38396,-0.100985],[109.380612,-0.098109],[109.373875,-0.091843],[109.371343,-0.08931],[109.369712,-0.087208],[109.368467,-0.083602],[109.36718,-0.079182],[109.366579,-0.078323],[109.366665,-0.077422],[109.364734,-0.073215],[109.359326,-0.067121],[109.355507,-0.06283],[109.3507,-0.057165],[109.350014,-0.056391],[109.349735,-0.055747],[109.349048,-0.05534],[109.34907,-0.054803],[109.346323,-0.051455],[109.342074,-0.046691],[109.344606,-0.041797],[109.347868,-0.039308],[109.35173,-0.036904],[109.354219,-0.035531],[109.359198,-0.032354],[109.360914,-0.030123],[109.362545,-0.025315],[109.362888,-0.020766],[109.362888,-0.015358],[109.359541,-0.015529],[109.355936,-0.016216],[109.353104,-0.017418],[109.3501,-0.018448],[109.346838,-0.018362],[109.344435,-0.01819],[109.342031,-0.017418],[109.340401,-0.015959],[109.334264,-0.009867],[109.328728,-0.003944],[109.324865,-0.001111],[109.323106,0.000306],[109.322462,0.001336],[109.320059,0.00125],[109.316626,0.001894],[109.314222,0.003095],[109.312291,0.004083],[109.311476,0.005113],[109.300404,0.007559],[109.295211,0.010177],[109.288173,0.012924],[109.283366,0.014684],[109.278603,0.017002],[109.275041,0.019062],[109.273839,0.020006],[109.268389,0.021981],[109.265299,0.023182],[109.261136,0.024771],[109.257703,0.026402],[109.254484,0.027904],[109.253712,0.028805],[109.251094,0.03417],[109.250493,0.034814],[109.244742,0.03696],[109.240837,0.038419],[109.237189,0.040179],[109.234056,0.041767],[109.231181,0.044171],[109.228048,0.04636],[109.225602,0.047476],[109.224057,0.048163],[109.221354,0.050266],[109.211783,0.057476],[109.206719,0.061168],[109.204144,0.063142],[109.203415,0.063442],[109.198051,0.067348],[109.19539,0.069408],[109.193072,0.070911],[109.192429,0.071597],[109.190969,0.074044],[109.186463,0.078765],[109.182215,0.082886],[109.180155,0.085203],[109.178352,0.087779],[109.170284,0.098509],[109.168911,0.100483],[109.167624,0.101771],[109.166164,0.103359],[109.165864,0.104389],[109.165864,0.106235],[109.16522,0.107437],[109.164619,0.108467],[109.156079,0.125034],[109.156036,0.133103],[109.156036,0.147868],[109.157453,0.149928],[109.158611,0.150658],[109.159598,0.150958],[109.160371,0.152203],[109.160028,0.153534]]}','2026-06-09 06:33:08','2026-06-09 06:33:08'),(4,'JALAN Adisucipto','Jalan Provinsi',28365.68,'{\"type\":\"LineString\",\"coordinates\":[[109.369411,-0.251746],[109.370098,-0.249941],[109.371085,-0.24831],[109.371901,-0.247451],[109.37366,-0.245348],[109.375076,-0.243159],[109.384603,-0.228352],[109.385376,-0.227321],[109.385548,-0.22539],[109.386964,-0.221527],[109.4034,-0.192641],[109.405804,-0.188263],[109.410782,-0.179551],[109.412627,-0.175816],[109.414473,-0.171868],[109.417648,-0.161567],[109.417434,-0.1553],[109.417477,-0.152138],[109.413786,-0.150164],[109.412413,-0.149048],[109.411082,-0.146644],[109.410439,-0.145357],[109.410396,-0.142996],[109.410009,-0.134026],[109.41031,-0.129047],[109.40855,-0.116128],[109.40855,-0.113595],[109.409108,-0.11072],[109.409838,-0.108273],[109.410095,-0.10617],[109.409151,-0.10411],[109.407477,-0.102393],[109.404001,-0.099431],[109.402714,-0.099303],[109.401212,-0.099346],[109.397693,-0.097715],[109.396491,-0.096856],[109.395804,-0.09574],[109.395032,-0.094581],[109.394431,-0.093122],[109.39383,-0.092049],[109.392715,-0.091105],[109.392457,-0.090118],[109.390612,-0.08604],[109.389539,-0.084237],[109.388208,-0.082649],[109.386234,-0.080932],[109.37439,-0.069902],[109.367352,-0.062991],[109.366579,-0.061446],[109.366407,-0.060416],[109.364219,-0.057884],[109.362845,-0.055437],[109.360228,-0.052089],[109.358125,-0.050373],[109.354949,-0.047754],[109.354177,-0.046467],[109.353018,-0.044664],[109.352202,-0.043806],[109.350572,-0.04269],[109.349241,-0.041359],[109.348254,-0.0399]]}','2026-06-09 06:35:23','2026-06-09 06:35:23'),(5,'Jl Pontianal','Jalan Kabupaten',7463.65,'{\"type\":\"LineString\",\"coordinates\":[[109.342117,-0.046739],[109.338512,-0.042361],[109.334135,-0.037081],[109.330788,-0.032317],[109.326839,-0.02545],[109.325552,-0.023647],[109.326625,-0.019012],[109.329114,-0.018368],[109.330316,-0.018282],[109.332633,-0.017338],[109.336796,-0.020685],[109.337997,-0.021587],[109.342546,-0.026265],[109.344435,-0.02987],[109.346366,-0.034163],[109.34701,-0.037811],[109.347825,-0.039356]]}','2026-06-09 06:46:41','2026-06-09 06:46:41');
/*!40000 ALTER TABLE `jalan` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `laporan_masyarakat`
--
DROP TABLE IF EXISTS `laporan_masyarakat`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `laporan_masyarakat` (
`id` int NOT NULL AUTO_INCREMENT,
`penduduk_id` int DEFAULT NULL,
`nama_pelapor` varchar(150) NOT NULL,
`no_hp` varchar(30) NOT NULL,
`kategori_laporan` enum('Data kemiskinan','Bantuan belum diterima','Kondisi kesehatan','Data tidak sesuai','Lainnya') NOT NULL DEFAULT 'Data kemiskinan',
`deskripsi` text NOT NULL,
`aktor_tujuan` enum('RT/RW','Kelurahan','Kecamatan','Dinas Sosial','Dinas Kesehatan','Wali Kota') NOT NULL DEFAULT 'Dinas Sosial',
`status_laporan` enum('Masuk','Ditinjau','Diproses','Selesai','Dijadikan Data Masyarakat','Ditolak') NOT NULL DEFAULT 'Masuk',
`data_nama_kepala_keluarga` varchar(150) DEFAULT NULL,
`data_nik` varchar(20) DEFAULT NULL,
`data_tanggal_lahir` date DEFAULT NULL,
`data_pendidikan` enum('Tidak sekolah','SD','SMP','SMA/SMK','Diploma','S1','S2','S3','Tidak diketahui') NOT NULL DEFAULT 'Tidak diketahui',
`data_riwayat_penyakit` text,
`data_jumlah_kk` int NOT NULL DEFAULT '1',
`data_alamat` text,
`data_kecamatan` varchar(100) DEFAULT NULL,
`data_kelurahan` varchar(100) DEFAULT NULL,
`data_status_bantuan` enum('Belum menerima','Sudah menerima','Perlu verifikasi') NOT NULL DEFAULT 'Belum menerima',
`latitude` decimal(12,9) NOT NULL,
`longitude` decimal(12,9) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_laporan_penduduk_id` (`penduduk_id`),
KEY `idx_laporan_data_nik` (`data_nik`),
KEY `idx_laporan_data_nama` (`data_nama_kepala_keluarga`),
CONSTRAINT `fk_laporan_penduduk` FOREIGN KEY (`penduduk_id`) REFERENCES `penduduk_kemiskinan` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `laporan_masyarakat`
--
LOCK TABLES `laporan_masyarakat` WRITE;
/*!40000 ALTER TABLE `laporan_masyarakat` DISABLE KEYS */;
INSERT INTO `laporan_masyarakat` VALUES (1,1,'DUMMY Rina Pelapor','081234567001','Data kemiskinan','Butuh bantuan','Dinas Sosial','Dijadikan Data Masyarakat','DUMMY Siti Aminah','6171010101010001','1978-04-12','SD','Diabetes',4,'Jl. Gajah Mada Gg. Mawar No. 12','Pontianak Selatan','Benua Melayu Darat','Belum menerima',-0.033941503,109.340894222,'2026-06-06 22:18:11','2026-06-06 22:34:46'),(2,1,'DUMMY Rina Pelapor','081234567001','Bantuan belum diterima','Sembako bulan ini belum di terima','Dinas Sosial','Selesai','DUMMY Siti Aminah','6171010101010001','1978-04-12','SD','Diabetes',4,'Jl. Gajah Mada Gg. Mawar No. 12','Pontianak Selatan','Benua Melayu Darat','Belum menerima',-0.033203467,109.340475798,'2026-06-06 22:40:20','2026-06-06 22:41:30'),(3,NULL,'DUMMY Andi Pelapor','081234567002','Kondisi kesehatan','Asma','Dinas Kesehatan','Masuk','DUMMY Budi Santoso','6171010202020002','1982-09-21','SMP','',1,'Jl. Ahmad Yani Gg. Kenanga No. 7','Pontianak Tenggara','Bansir Laut','Belum menerima',-0.058536749,109.350700455,'2026-06-06 22:49:11','2026-06-09 06:58:00'),(4,2,'DUMMY Wawan Pelapor','081234567004','Data kemiskinan','layak masuk sebagai masyarakat prasejahtera.','Dinas Sosial','Dijadikan Data Masyarakat','DUMMY Maimunah','6171010606060006','1975-03-22','SD','Lansia dan sakit sendi',3,'Jl. Kom Yos Sudarso Gg. Sejahtera No. 4','Pontianak Barat','Sungai Jawi Luar','Belum menerima',-0.017598041,109.331774712,'2026-06-06 22:51:47','2026-06-06 22:52:42'),(5,NULL,'DUMMY Sari Pelapor','081234567003','Data kemiskinan','Keluarga ini belum terdata dan membutuhkan bantuan kebutuhan pokok.','Dinas Sosial','Diproses','DUMMY Nurhayati','6171010505050005','1985-07-17','SD','Tidak ada',6,'Jl. Tanjungpura Gg. Anggrek No. 9','Pontianak Kota','Tengah','Belum menerima',-0.025534733,109.342546463,'2026-06-06 22:57:12','2026-06-07 15:22:43');
/*!40000 ALTER TABLE `laporan_masyarakat` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `masjid`
--
DROP TABLE IF EXISTS `masjid`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `masjid` (
`id` int NOT NULL AUTO_INCREMENT,
`jenis_rumah_ibadah` enum('Masjid','Gereja','Vihara','Kelenteng','Pura') NOT NULL DEFAULT 'Masjid',
`nama_masjid` varchar(150) NOT NULL,
`pic_masjid` varchar(150) NOT NULL,
`alamat_masjid` text NOT NULL,
`latitude` decimal(12,9) NOT NULL,
`longitude` decimal(12,9) NOT NULL,
`radius_meter` int NOT NULL DEFAULT '500',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `masjid`
--
LOCK TABLES `masjid` WRITE;
/*!40000 ALTER TABLE `masjid` DISABLE KEYS */;
INSERT INTO `masjid` VALUES (1,'Masjid','DUMMY Masjid Al-Ikhlas','Pak Rahman','Jl. Gajah Mada, Pontianak',-0.034199201,109.341076612,700,'2026-06-06 22:31:24','2026-06-06 22:31:24'),(2,'Masjid','DUMMY Masjid Nur Fajar','Pak Yusuf','Jl. Imam Bonjol, Pontianak',-0.048442001,109.356252551,800,'2026-06-06 22:31:54','2026-06-06 22:31:54'),(3,'Gereja','DUMMY Gereja Kasih','Ibu Maria','Jl. Ahmad Yani, Pontianak',-0.059465140,109.353318214,600,'2026-06-06 22:32:35','2026-06-06 22:32:35'),(4,'Vihara','DUMMY Vihara Damai','Pak Hendra','Jl. Tanjungpura, Pontianak',-0.023626150,109.342106581,500,'2026-06-06 22:32:56','2026-06-06 22:33:14'),(5,'Kelenteng','Kelenteng Tri Darma Adipati Sia Ng Bio','Darma','Jl. H.Agus Salim No.104, Benua Melayu Darat, Kec. Pontianak Sel',-0.028300905,109.338428061,500,'2026-06-09 06:56:07','2026-06-09 06:56:07'),(6,'Pura','Pura Giripati Mulawarman','Warman','Jl. Adi Sucipto, Sungai Raya, Kec. Sungai Raya',-0.061771122,109.366432311,500,'2026-06-09 06:57:43','2026-06-09 06:57:43');
/*!40000 ALTER TABLE `masjid` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `parsil_tanah`
--
DROP TABLE IF EXISTS `parsil_tanah`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `parsil_tanah` (
`id` int NOT NULL AUTO_INCREMENT,
`nama_pemilik` varchar(150) NOT NULL,
`status_kepemilikan` enum('SHM','HGB','HGU','HP') NOT NULL,
`luas_meter` double NOT NULL DEFAULT '0',
`geojson` longtext NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `parsil_tanah`
--
LOCK TABLES `parsil_tanah` WRITE;
/*!40000 ALTER TABLE `parsil_tanah` DISABLE KEYS */;
INSERT INTO `parsil_tanah` VALUES (2,'B','HGB',45886.23,'{\"type\":\"Polygon\",\"coordinates\":[[[109.348093,-0.043441],[109.349627,-0.044568],[109.34952,-0.044739],[109.350035,-0.045179],[109.349241,-0.046209],[109.347235,-0.044578],[109.348093,-0.043441]]]}','2026-06-09 06:36:47','2026-06-09 06:36:47'),(7,'Ayani','HGU',16708.46,'{\"type\":\"Polygon\",\"coordinates\":[[[109.345186,-0.050855],[109.345615,-0.051177],[109.345851,-0.051596],[109.344982,-0.052401],[109.344156,-0.051725],[109.345186,-0.050855]]]}','2026-06-09 06:39:33','2026-06-09 06:39:33'),(8,'Ayani','HGU',3753.41,'{\"type\":\"Polygon\",\"coordinates\":[[[109.344553,-0.050018],[109.345154,-0.050705],[109.344875,-0.05092],[109.344317,-0.050244],[109.344553,-0.050018]]]}','2026-06-09 06:39:50','2026-06-09 06:39:50'),(9,'Ayani','HGU',4836.21,'{\"type\":\"Polygon\",\"coordinates\":[[[109.346012,-0.051735],[109.346226,-0.051961],[109.34539,-0.052712],[109.345154,-0.05239],[109.346012,-0.051735]]]}','2026-06-09 06:40:18','2026-06-09 06:40:18'),(10,'Ayani','HGU',3761.97,'{\"type\":\"Polygon\",\"coordinates\":[[[109.344199,-0.05034],[109.344778,-0.051016],[109.344478,-0.05122],[109.343952,-0.050587],[109.344199,-0.05034]]]}','2026-06-09 06:40:32','2026-06-09 06:40:32'),(11,'Ayani','HGU',1759.01,'{\"type\":\"Polygon\",\"coordinates\":[[[109.344167,-0.05107],[109.344403,-0.051327],[109.344048,-0.051596],[109.343855,-0.05136],[109.344167,-0.05107]]]}','2026-06-09 06:40:47','2026-06-09 06:40:47'),(12,'Ayani','HGU',2265.46,'{\"type\":\"Polygon\",\"coordinates\":[[[109.343866,-0.050684],[109.344081,-0.050952],[109.343684,-0.051295],[109.343448,-0.05107],[109.343866,-0.050684]]]}','2026-06-09 06:41:00','2026-06-09 06:41:00'),(13,'D','HP',4256.4,'{\"type\":\"Polygon\",\"coordinates\":[[[109.347342,-0.051134],[109.347911,-0.051553],[109.347632,-0.05196],[109.347053,-0.051478],[109.347342,-0.051134]]]}','2026-06-09 06:42:20','2026-06-09 06:50:49'),(17,'HHH','HP',10239.35,'{\"type\":\"Polygon\",\"coordinates\":[[[109.348925,-0.048736],[109.349638,-0.049192],[109.349086,-0.050018],[109.348388,-0.049503],[109.348925,-0.048736]]]}','2026-06-09 06:44:43','2026-06-09 06:50:38'),(19,'AAAAAA','SHM',25333.04,'{\"type\":\"Polygon\",\"coordinates\":[[[109.344016,-0.043398],[109.348029,-0.046607],[109.347782,-0.046918],[109.343802,-0.043742],[109.344016,-0.043398]]]}','2026-06-09 06:47:57','2026-06-09 06:47:57'),(20,'ZZZZZ','HGB',17154.86,'{\"type\":\"Polygon\",\"coordinates\":[[[109.345808,-0.045458],[109.347707,-0.047036],[109.347321,-0.047454],[109.345433,-0.045866],[109.345808,-0.045458]]]}','2026-06-09 06:48:27','2026-06-09 06:48:46'),(21,'QQQQ','HP',12997.14,'{\"type\":\"Polygon\",\"coordinates\":[[[109.348351,-0.046349],[109.348737,-0.046574],[109.34731,-0.048473],[109.346956,-0.048195],[109.348351,-0.046349]]]}','2026-06-09 06:49:10','2026-06-09 06:49:10'),(22,'RRRR','SHM',26329.53,'{\"type\":\"Polygon\",\"coordinates\":[[[109.351344,-0.047593],[109.352074,-0.048119],[109.352975,-0.048656],[109.352546,-0.049106],[109.351408,-0.048237],[109.350003,-0.050255],[109.349585,-0.050019],[109.351344,-0.047593]]]}','2026-06-09 06:50:32','2026-06-09 06:50:32'),(23,'TTTTT','HP',48479.09,'{\"type\":\"Polygon\",\"coordinates\":[[[109.342546,-0.047486],[109.34422,-0.049375],[109.342697,-0.050555],[109.341602,-0.04798],[109.342546,-0.047486]]]}','2026-06-09 06:51:58','2026-06-09 06:51:58');
/*!40000 ALTER TABLE `parsil_tanah` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `penduduk_kemiskinan`
--
DROP TABLE IF EXISTS `penduduk_kemiskinan`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `penduduk_kemiskinan` (
`id` int NOT NULL AUTO_INCREMENT,
`nama_kepala_keluarga` varchar(150) NOT NULL,
`nik` varchar(20) DEFAULT NULL,
`tanggal_lahir` date DEFAULT NULL,
`pendidikan` enum('Tidak sekolah','SD','SMP','SMA/SMK','Diploma','S1','S2','S3','Tidak diketahui') NOT NULL DEFAULT 'Tidak diketahui',
`riwayat_penyakit` text,
`jumlah_kk` int NOT NULL DEFAULT '1',
`alamat` text NOT NULL,
`kecamatan` varchar(100) DEFAULT NULL,
`kelurahan` varchar(100) DEFAULT NULL,
`status_bantuan` enum('Belum menerima','Sudah menerima','Perlu verifikasi') NOT NULL DEFAULT 'Belum menerima',
`latitude` decimal(12,9) NOT NULL,
`longitude` decimal(12,9) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_penduduk_nik` (`nik`),
KEY `idx_penduduk_nama` (`nama_kepala_keluarga`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `penduduk_kemiskinan`
--
LOCK TABLES `penduduk_kemiskinan` WRITE;
/*!40000 ALTER TABLE `penduduk_kemiskinan` DISABLE KEYS */;
INSERT INTO `penduduk_kemiskinan` VALUES (1,'DUMMY Siti Aminah','6171010101010001','1978-04-12','SD','Diabetes',4,'Jl. Gajah Mada Gg. Mawar No. 12','Pontianak Selatan','Benua Melayu Darat','Sudah menerima',-0.032830444,109.337782975,'2026-06-06 22:34:46','2026-06-09 06:05:01'),(2,'DUMMY Maimunah','6171010606060006','1975-03-22','SD','Lansia dan sakit sendi',3,'Jl. Kom Yos Sudarso Gg. Sejahtera No. 4','Pontianak Barat','Sungai Jawi Luar','Belum menerima',-0.017598041,109.331774712,'2026-06-06 22:52:42','2026-06-06 22:52:42');
/*!40000 ALTER TABLE `penduduk_kemiskinan` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `spbu`
--
DROP TABLE IF EXISTS `spbu`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `spbu` (
`id` int NOT NULL AUTO_INCREMENT,
`nama_spbu` varchar(150) NOT NULL,
`no_telepon` varchar(30) NOT NULL,
`buka_24_jam` tinyint(1) NOT NULL DEFAULT '0',
`latitude` decimal(10,7) NOT NULL,
`longitude` decimal(10,7) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `spbu`
--
LOCK TABLES `spbu` WRITE;
/*!40000 ALTER TABLE `spbu` DISABLE KEYS */;
INSERT INTO `spbu` VALUES (5,'SPBU Paris 2','08123456789',0,-0.0648854,109.3563652,'2026-06-09 06:09:33','2026-06-09 06:51:26'),(6,'SPBU Paris 1','021135',1,-0.0554674,109.3626523,'2026-06-09 06:10:26','2026-06-09 06:10:33'),(7,'SPBU Imam Bonjol','08123456789',1,-0.0463543,109.3542945,'2026-06-09 06:11:48','2026-06-09 06:13:07'),(8,'SPBU Tanjung Raya 2','08123456789',0,-0.0489240,109.3687356,'2026-06-09 06:13:00','2026-06-09 06:13:00'),(9,'SPBU Panglima Aim','08123456789',0,-0.0291771,109.3681294,'2026-06-09 06:13:59','2026-06-09 06:13:59'),(10,'SPBU OSO MT Haryono','08123456789',0,-0.0449221,109.3367153,'2026-06-09 06:14:36','2026-06-09 06:14:36'),(11,'SPBU Bundaran Kota BAru ','08123456789',1,-0.0478097,109.3187499,'2026-06-09 06:15:48','2026-06-09 06:15:48'),(12,'SPBU Kota Baru Ujung ','08123456789',0,-0.0623463,109.3044698,'2026-06-09 06:17:38','2026-06-09 06:17:38'),(13,'SPBU Wahidin','08123456789',0,-0.0388818,109.3107516,'2026-06-09 06:18:48','2026-06-09 06:18:48'),(14,'SPBU Husein Hamzah','08123456789',0,-0.0280991,109.2976248,'2026-06-09 06:19:53','2026-06-09 06:19:53'),(15,'SPBU Tanjungpura','08123456789',0,-0.0312108,109.3451428,'2026-06-09 06:20:53','2026-06-09 06:20:53'),(16,'SPBU KH Ahmad Dahlan','08123456789',0,-0.0345266,109.3318713,'2026-06-09 06:22:14','2026-06-09 06:22:14'),(17,'SPBU Hasanudin','08123456789',0,-0.0179067,109.3313885,'2026-06-09 06:23:10','2026-06-09 06:23:10'),(18,'SPBU Hos Cokroaminoto','08123456789',0,-0.0272142,109.3331909,'2026-06-09 06:23:49','2026-06-09 06:23:49'),(19,'SPBU Gusti Hamzah','08123456789',0,-0.0282396,109.3204129,'2026-06-09 06:24:45','2026-06-09 06:24:45'),(20,'SPBU Martadinata','08123456789',0,-0.0117108,109.3139809,'2026-06-09 06:25:22','2026-06-09 06:25:22'),(21,'SPBU Siantan 1','08123456789',0,-0.0162492,109.3580604,'2026-06-09 06:26:05','2026-06-09 06:26:05'),(22,'SPBU Siantan','0561886446',1,-0.0114167,109.3353796,'2026-06-09 06:27:15','2026-06-09 06:27:15');
/*!40000 ALTER TABLE `spbu` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`nama` varchar(150) NOT NULL,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`role` enum('admin','walikota','dinas') NOT NULL DEFAULT 'admin',
`instansi` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `users`
--
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES (1,'Administrator Sistem','admin','$2y$12$QrkopzCKam0f.9T0zBtesOsIacS.Ra.4BGkG0oO6RVlb8m1dAjHyG','admin','Admin Pengelola','2026-06-06 22:16:21'),(2,'Wali Kota Pontianak','walikota','$2y$12$QrkopzCKam0f.9T0zBtesOsIacS.Ra.4BGkG0oO6RVlb8m1dAjHyG','walikota','Wali Kota','2026-06-06 22:16:21'),(3,'Dinas Sosial Kota Pontianak','dinsos','$2y$12$QrkopzCKam0f.9T0zBtesOsIacS.Ra.4BGkG0oO6RVlb8m1dAjHyG','dinas','Dinas Sosial','2026-06-06 22:16:21'),(4,'Dinas Kesehatan Kota Pontianak','dinkes','$2y$12$QrkopzCKam0f.9T0zBtesOsIacS.Ra.4BGkG0oO6RVlb8m1dAjHyG','dinas','Dinas Kesehatan','2026-06-06 22:16:21');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!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 */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-06-09 14:53:59
+54
View File
@@ -0,0 +1,54 @@
<?php
// =========================================================
// env.php
// File bantu untuk membaca konfigurasi dari file .env
// =========================================================
function load_env_file($path)
{
if (!file_exists($path)) {
return;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '#') === 0) {
continue;
}
if (strpos($line, '=') === false) {
continue;
}
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// Hapus tanda kutip di awal/akhir jika ada
$value = trim($value, '"');
$value = trim($value, "'");
if ($key !== '') {
putenv($key . '=' . $value);
$_ENV[$key] = $value;
}
}
}
function env($key, $default = null)
{
$value = getenv($key);
if ($value === false || $value === '') {
return $default;
}
return $value;
}
load_env_file(__DIR__ . '/.env');
+255
View File
@@ -0,0 +1,255 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Landing Page WebGIS SIG</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f3f4f6;
color: #1f2937;
}
.hero {
background: linear-gradient(135deg, #1e3a8a, #2563eb);
color: white;
padding: 46px 20px;
text-align: center;
}
.hero h1 {
margin: 0 0 10px;
font-size: 34px;
}
.hero p {
margin: 5px 0;
font-size: 16px;
}
.container {
max-width: 1150px;
margin: 30px auto;
padding: 0 20px;
}
.section-title {
margin: 30px 0 16px;
font-size: 24px;
color: #1d4ed8;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px;
}
.card {
background: white;
border-radius: 16px;
padding: 22px;
border: 1px solid #e5e7eb;
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08);
}
.card h3 {
margin: 0 0 10px;
color: #1d4ed8;
font-size: 20px;
}
.card p {
margin: 0;
color: #4b5563;
line-height: 1.5;
font-size: 14px;
}
.btn {
display: inline-block;
margin-top: 14px;
padding: 10px 14px;
background: #2563eb;
color: white;
text-decoration: none;
border-radius: 10px;
font-size: 14px;
font-weight: bold;
}
.btn:hover {
background: #1d4ed8;
}
.info-box {
background: white;
border-left: 5px solid #2563eb;
border-radius: 12px;
padding: 18px;
margin-top: 18px;
line-height: 1.6;
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.table-wrapper {
overflow-x: auto;
background: white;
border-radius: 16px;
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08);
border: 1px solid #e5e7eb;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 720px;
}
th, td {
padding: 13px 14px;
border-bottom: 1px solid #e5e7eb;
text-align: left;
font-size: 14px;
}
th {
background: #1d4ed8;
color: white;
}
tr:last-child td {
border-bottom: none;
}
code {
background: #eff6ff;
color: #1e40af;
padding: 3px 6px;
border-radius: 6px;
}
footer {
text-align: center;
color: #6b7280;
font-size: 14px;
padding: 28px 20px;
}
</style>
</head>
<body>
<section class="hero">
<h1>WebGIS Poverty Mapping</h1>
<p>Tugas Besar Sistem Informasi Geografis</p>
<p><strong>Talitha Syakirah Artanti - D1041231013</strong></p>
<p>Informatika FT Universitas Tanjungpura</p>
</section>
<main class="container">
<h2 class="section-title">Daftar Project SIG</h2>
<div class="grid">
<div class="card">
<h3>1. Pemetaan SPBU</h3>
<p>
Project WebGIS untuk menampilkan lokasi SPBU di Kota Pontianak.
</p>
<a class="btn" href="1_Pemetaan_SPBU/">Buka Project</a>
</div>
<div class="card">
<h3>2. SPBU, Jalan, Parsil Tanah dan Layer</h3>
<p>
Project WebGIS dengan beberapa layer seperti SPBU, jalan, dan parsil tanah.
</p>
<a class="btn" href="2_SPBU_Jalan_ParsilTanah_dan_Layer/">Buka Project</a>
</div>
<div class="card">
<h3>3. Poverty Mapping</h3>
<p>
Project utama untuk pemetaan masyarakat prasejahtera, rumah ibadah,
laporan masyarakat, dan histori bantuan.
</p>
<a class="btn" href="3_PovertyMapping/">Buka Project</a>
</div>
</div>
<h2 class="section-title">Informasi Link</h2>
<div class="info-box">
<strong>URL Landing Page:</strong><br>
<code>isi dengan URL Coolify setelah deploy</code>
<br><br>
<strong>Repository Gitea:</strong><br>
<code>isi dengan link repo Gitea kamu</code>
<br><br>
<strong>Catatan:</strong><br>
Landing page ini digunakan sebagai halaman utama yang mengarahkan pengguna ke seluruh project SIG.
</div>
<h2 class="section-title">Akun Demo Poverty Mapping</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Role</th>
<th>Username</th>
<th>Password</th>
<th>Hak Akses</th>
</tr>
</thead>
<tbody>
<tr>
<td>Admin</td>
<td>admin</td>
<td>password</td>
<td>Mengelola data, akun dinas, laporan masyarakat, dan data WebGIS.</td>
</tr>
<tr>
<td>Walikota</td>
<td>walikota</td>
<td>password</td>
<td>Melihat dan mengunduh seluruh data tanpa melakukan perubahan data.</td>
</tr>
<tr>
<td>Dinas</td>
<td>dinas</td>
<td>password</td>
<td>Melihat dan mengunduh data sesuai kebutuhan instansi.</td>
</tr>
<tr>
<td>Masyarakat</td>
<td>-</td>
<td>-</td>
<td>Mengirim laporan cepat melalui fitur laporan masyarakat.</td>
</tr>
</tbody>
</table>
</div>
<div class="info-box">
<strong>Perhatian:</strong><br>
Jika username di database kamu berbeda, bagian tabel akun demo ini tinggal disesuaikan dengan akun yang benar.
</div>
</main>
<footer>
© 2026 - WebGIS SIG Talitha Syakirah Artanti
</footer>
</body>
</html>