Refactor database interactions and schema updates

- Updated column names in insert_data.php and update_data.php to match the database schema.
- Added new tables for 'jalan', 'parsil', and 'jalan_rusak' in spbu_db.sql to enhance data structure.
- Changed admin password hash in spbu_db.sql for security improvements.
This commit is contained in:
2026-06-12 11:02:31 +07:00
parent 6cc14e087e
commit 6901d07126
5 changed files with 698 additions and 726 deletions
+19 -8
View File
@@ -1,16 +1,27 @@
CREATE DATABASE db_spbu; CREATE DATABASE IF NOT EXISTS spbu_db;
USE db_spbu; USE spbu_db;
CREATE TABLE spbu ( CREATE TABLE IF NOT EXISTS admin (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
nama_spbu VARCHAR(100), username VARCHAR(50) NOT NULL UNIQUE,
no_spbu VARCHAR(50), password VARCHAR(255) NOT NULL,
status ENUM('Buka 24 Jam', 'Tidak 24 Jam'),
latitude DOUBLE,
longitude DOUBLE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
INSERT INTO admin (username, password) VALUES
('admin', '$2y$10$A9m/u6sAxEwgIO4dlmIvNO2WJioIya4TwMvSe06hgFrDsq1xg7x16');
CREATE TABLE IF NOT EXISTS spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(200) NOT NULL,
no_spbu VARCHAR(50) NOT NULL,
status ENUM('24jam', 'terbatas') NOT NULL DEFAULT '24jam',
latitude DECIMAL(10, 7) NOT NULL,
longitude DECIMAL(10, 7) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE jalan ( CREATE TABLE jalan (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
nama_jalan VARCHAR(100), nama_jalan VARCHAR(100),
+105 -169
View File
@@ -11,6 +11,7 @@ while ($row = mysqli_fetch_assoc($result)) {
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>WebGIS SPBU - Modern</title> <title>WebGIS SPBU - Modern</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -41,7 +42,6 @@ while ($row = mysqli_fetch_assoc($result)) {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* SIDEBAR */
.sidebar { .sidebar {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -64,7 +64,6 @@ while ($row = mysqli_fetch_assoc($result)) {
transform: translateX(-100%); transform: translateX(-100%);
} }
/* Tombol toggle sidebar (hamburger) */
.toggle-sidebar-btn { .toggle-sidebar-btn {
position: absolute; position: absolute;
top: 18px; top: 18px;
@@ -84,23 +83,28 @@ while ($row = mysqli_fetch_assoc($result)) {
transition: all 0.2s; transition: all 0.2s;
color: #1f2937; color: #1f2937;
} }
body.sidebar-open .toggle-sidebar-btn { body.sidebar-open .toggle-sidebar-btn {
display: none; display: none;
} }
/* Atur posisi zoom control Leaflet agar tidak tertutup hamburger */
.leaflet-control-zoom { .leaflet-control-zoom {
transition: margin-left 0.3s ease; transition: margin-left 0.3s ease;
margin-left: 60px; margin-left: 60px;
} }
body.sidebar-open .leaflet-control-zoom { body.sidebar-open .leaflet-control-zoom {
margin-left: 340px; margin-left: 340px;
} }
@media (max-width: 640px) { @media (max-width: 640px) {
body.sidebar-open .leaflet-control-zoom { body.sidebar-open .leaflet-control-zoom {
margin-left: 280px; margin-left: 280px;
} }
.sidebar { width: 280px; }
.sidebar {
width: 280px;
}
} }
.sidebar-header { .sidebar-header {
@@ -110,6 +114,7 @@ while ($row = mysqli_fetch_assoc($result)) {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.sidebar-header h2 { .sidebar-header h2 {
font-size: 1.35rem; font-size: 1.35rem;
font-weight: 600; font-weight: 600;
@@ -118,6 +123,7 @@ while ($row = mysqli_fetch_assoc($result)) {
background-clip: text; background-clip: text;
color: transparent; color: transparent;
} }
.close-sidebar { .close-sidebar {
background: none; background: none;
border: none; border: none;
@@ -131,6 +137,7 @@ while ($row = mysqli_fetch_assoc($result)) {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.close-sidebar:hover { .close-sidebar:hover {
background: #f1f5f9; background: #f1f5f9;
color: #0f172a; color: #0f172a;
@@ -141,6 +148,7 @@ while ($row = mysqli_fetch_assoc($result)) {
background: #ffffff; background: #ffffff;
border-bottom: 1px solid #edf2f7; border-bottom: 1px solid #edf2f7;
} }
.search-box input { .search-box input {
width: 100%; width: 100%;
padding: 10px 16px; padding: 10px 16px;
@@ -150,16 +158,19 @@ while ($row = mysqli_fetch_assoc($result)) {
outline: none; outline: none;
transition: 0.2s; transition: 0.2s;
} }
.search-box input:focus { .search-box input:focus {
border-color: #6366f1; border-color: #6366f1;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
} }
.filter-buttons { .filter-buttons {
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 12px; margin-top: 12px;
} }
.filter-chip { .filter-chip {
background: #f1f5f9; background: #f1f5f9;
border: none; border: none;
@@ -171,6 +182,7 @@ while ($row = mysqli_fetch_assoc($result)) {
transition: 0.2s; transition: 0.2s;
color: #334155; color: #334155;
} }
.filter-chip.active { .filter-chip.active {
background: #6366f1; background: #6366f1;
color: white; color: white;
@@ -181,6 +193,7 @@ while ($row = mysqli_fetch_assoc($result)) {
overflow-y: auto; overflow-y: auto;
padding: 12px 16px; padding: 12px 16px;
} }
.result-card { .result-card {
background: #ffffff; background: #ffffff;
border-radius: 16px; border-radius: 16px;
@@ -191,11 +204,13 @@ while ($row = mysqli_fetch_assoc($result)) {
border: 1px solid #ecf3fa; border: 1px solid #ecf3fa;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
} }
.result-card:hover { .result-card:hover {
background: #fafcff; background: #fafcff;
transform: translateX(4px); transform: translateX(4px);
border-color: #cbdff2; border-color: #cbdff2;
} }
.result-title { .result-title {
font-weight: 600; font-weight: 600;
color: #0f172a; color: #0f172a;
@@ -203,6 +218,7 @@ while ($row = mysqli_fetch_assoc($result)) {
align-items: center; align-items: center;
gap: 6px; gap: 6px;
} }
.result-sub { .result-sub {
font-size: 0.7rem; font-size: 0.7rem;
color: #5b6e8c; color: #5b6e8c;
@@ -222,8 +238,8 @@ while ($row = mysqli_fetch_assoc($result)) {
display: flex; display: flex;
gap: 12px; gap: 12px;
border: 1px solid rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.5);
transition: all 0.3s ease;
} }
.mode-icon { .mode-icon {
background: #f1f5f9; background: #f1f5f9;
border: none; border: none;
@@ -238,22 +254,27 @@ while ($row = mysqli_fetch_assoc($result)) {
transition: 0.2s; transition: 0.2s;
color: #1e293b; color: #1e293b;
} }
.mode-icon.active { .mode-icon.active {
background: #6366f1; background: #6366f1;
color: white; color: white;
} }
.mode-icon.edit-mode { .mode-icon.edit-mode {
background: #1e293b; background: #1e293b;
color: white; color: white;
} }
.mode-icon.edit-mode.active { .mode-icon.edit-mode.active {
background: #10b981; background: #10b981;
} }
.draw-actions { .draw-actions {
display: flex; display: flex;
gap: 12px; gap: 12px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
body.view-mode .draw-actions { body.view-mode .draw-actions {
display: none; display: none;
} }
@@ -271,6 +292,7 @@ while ($row = mysqli_fetch_assoc($result)) {
z-index: 10; z-index: 10;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
} }
.custom-draw-toolbar button { .custom-draw-toolbar button {
background: #334155; background: #334155;
border: none; border: none;
@@ -279,6 +301,7 @@ while ($row = mysqli_fetch_assoc($result)) {
border-radius: 40px; border-radius: 40px;
cursor: pointer; cursor: pointer;
} }
.custom-draw-toolbar button:hover { .custom-draw-toolbar button:hover {
background: #6366f1; background: #6366f1;
} }
@@ -286,7 +309,10 @@ while ($row = mysqli_fetch_assoc($result)) {
.edit-popup { .edit-popup {
min-width: 220px; min-width: 220px;
} }
.edit-popup input, .edit-popup select, .edit-popup textarea {
.edit-popup input,
.edit-popup select,
.edit-popup textarea {
width: 100%; width: 100%;
padding: 8px 12px; padding: 8px 12px;
margin: 8px 0; margin: 8px 0;
@@ -294,6 +320,7 @@ while ($row = mysqli_fetch_assoc($result)) {
border-radius: 30px; border-radius: 30px;
font-size: 0.85rem; font-size: 0.85rem;
} }
.edit-popup button { .edit-popup button {
padding: 6px 14px; padding: 6px 14px;
border-radius: 30px; border-radius: 30px;
@@ -302,10 +329,26 @@ while ($row = mysqli_fetch_assoc($result)) {
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
} }
.btn-save { background: #10b981; color: white; }
.btn-cancel { background: #94a3b8; color: white; } .btn-save {
.btn-edit { background: #f59e0b; color: white; } background: #10b981;
.btn-delete { background: #ef4444; color: white; } color: white;
}
.btn-cancel {
background: #94a3b8;
color: white;
}
.btn-edit {
background: #f59e0b;
color: white;
}
.btn-delete {
background: #ef4444;
color: white;
}
.toast { .toast {
position: fixed; position: fixed;
@@ -320,22 +363,27 @@ while ($row = mysqli_fetch_assoc($result)) {
transition: 0.2s; transition: 0.2s;
pointer-events: none; pointer-events: none;
} }
.toast.show { opacity: 1; }
.toast.show {
opacity: 1;
}
.drawing-cursor { .drawing-cursor {
cursor: crosshair !important; cursor: crosshair !important;
} }
body.view-mode #map { body.view-mode #map {
cursor: grab; cursor: grab;
} }
body.view-mode #map:active { body.view-mode #map:active {
cursor: grabbing; cursor: grabbing;
} }
/* Kursor saat drag marker */
.leaflet-marker-dragging { .leaflet-marker-dragging {
cursor: grabbing !important; cursor: grabbing !important;
} }
/* thumbail foto di popup */
.popup-img { .popup-img {
max-width: 200px; max-width: 200px;
max-height: 150px; max-height: 150px;
@@ -345,6 +393,7 @@ while ($row = mysqli_fetch_assoc($result)) {
} }
</style> </style>
</head> </head>
<body> <body>
<button class="toggle-sidebar-btn" id="openSidebarBtn">☰</button> <button class="toggle-sidebar-btn" id="openSidebarBtn">☰</button>
@@ -356,7 +405,8 @@ while ($row = mysqli_fetch_assoc($result)) {
</div> </div>
<div class="search-area"> <div class="search-area">
<div class="search-box"> <div class="search-box">
<input type="text" id="searchInput" placeholder="Cari nama, jalan, pemilik, jalan rusak..." autocomplete="off"> <input type="text" id="searchInput" placeholder="Cari nama, jalan, pemilik, jalan rusak..."
autocomplete="off">
</div> </div>
<div class="filter-buttons"> <div class="filter-buttons">
<button class="filter-chip active" data-filter="all">Semua</button> <button class="filter-chip active" data-filter="all">Semua</button>
@@ -404,8 +454,8 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
}).addTo(map); }).addTo(map);
// ================= GLOBAL STATE ================= // ================= GLOBAL STATE =================
let globalEditMode = true; // true = edit, false = view let globalEditMode = true;
let activeMode = null; // null = tidak ada mode aktif, 'marker_add', 'polyline', 'polygon', 'road_damage' let activeMode = null;
let customDrawingActive = false; let customDrawingActive = false;
let customDrawingType = null; let customDrawingType = null;
let customPoints = []; let customPoints = [];
@@ -415,7 +465,7 @@ let customPreviewLine = null;
let markers = {}; let markers = {};
let polylines = {}; let polylines = {};
let polygons = {}; let polygons = {};
let roadDamageMarkers = {}; // simpan marker jalan rusak let roadDamageMarkers = {};
let dataSpbu = <?php echo json_encode($data); ?>; let dataSpbu = <?php echo json_encode($data); ?>;
let jalanData = []; let jalanData = [];
let parsilData = []; let parsilData = [];
@@ -425,7 +475,6 @@ let dataLoaded = { jalan: false, parsil: false, roadDamage: false };
const greenIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png', iconSize: [32, 32] }); const greenIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png', iconSize: [32, 32] });
const yellowIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png', iconSize: [32, 32] }); const yellowIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png', iconSize: [32, 32] });
const blueIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png', iconSize: [32, 32] }); const blueIcon = L.icon({ iconUrl: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png', iconSize: [32, 32] });
// Icon khusus jalan rusak (merah)
const damageIcon = L.icon({ const damageIcon = L.icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png', iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
@@ -438,7 +487,6 @@ const damageIcon = L.icon({
function postForm(url, data) { function postForm(url, data) {
const params = new URLSearchParams(); const params = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => params.append(key, value)); Object.entries(data).forEach(([key, value]) => params.append(key, value));
return fetch(url, { return fetch(url, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
@@ -446,7 +494,6 @@ function postForm(url, data) {
}).then(res => res.text()).then(res => res.trim()); }).then(res => res.text()).then(res => res.trim());
} }
// Fungsi upload file dengan FormData
function uploadForm(url, formData) { function uploadForm(url, formData) {
return fetch(url, { return fetch(url, {
method: 'POST', method: 'POST',
@@ -478,14 +525,8 @@ function updateSidebarState() {
setTimeout(() => map.invalidateSize(), 250); setTimeout(() => map.invalidateSize(), 250);
} }
openBtn.addEventListener('click', () => { openBtn.addEventListener('click', () => { sidebar.classList.remove('closed'); updateSidebarState(); });
sidebar.classList.remove('closed'); closeBtn.addEventListener('click', () => { sidebar.classList.add('closed'); updateSidebarState(); });
updateSidebarState();
});
closeBtn.addEventListener('click', () => {
sidebar.classList.add('closed');
updateSidebarState();
});
updateSidebarState(); updateSidebarState();
// ================= EDIT / VIEW MODE ================= // ================= EDIT / VIEW MODE =================
@@ -495,7 +536,6 @@ function setGlobalMode(modeType) {
document.body.classList.remove('view-mode'); document.body.classList.remove('view-mode');
document.getElementById('btn-edit-mode').classList.add('active'); document.getElementById('btn-edit-mode').classList.add('active');
document.getElementById('btn-view-mode').classList.remove('active'); document.getElementById('btn-view-mode').classList.remove('active');
// Aktifkan drag marker untuk SPBU dan Jalan Rusak
for (let id in markers) if (markers[id].dragging) markers[id].dragging.enable(); for (let id in markers) if (markers[id].dragging) markers[id].dragging.enable();
for (let id in roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.enable(); for (let id in roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.enable();
toast("Mode Edit aktif"); toast("Mode Edit aktif");
@@ -503,9 +543,7 @@ function setGlobalMode(modeType) {
document.body.classList.add('view-mode'); document.body.classList.add('view-mode');
document.getElementById('btn-view-mode').classList.add('active'); document.getElementById('btn-view-mode').classList.add('active');
document.getElementById('btn-edit-mode').classList.remove('active'); document.getElementById('btn-edit-mode').classList.remove('active');
// Batalkan drawing jika sedang aktif
if (customDrawingActive) cancelCustomDrawing(); if (customDrawingActive) cancelCustomDrawing();
// Nonaktifkan mode marker_add dan road_damage
if (activeMode === 'marker_add' || activeMode === 'road_damage') setActiveMode(null); if (activeMode === 'marker_add' || activeMode === 'road_damage') setActiveMode(null);
for (let id in markers) if (markers[id].dragging) markers[id].dragging.disable(); for (let id in markers) if (markers[id].dragging) markers[id].dragging.disable();
for (let id in roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.disable(); for (let id in roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.disable();
@@ -518,19 +556,13 @@ function setGlobalMode(modeType) {
document.getElementById('btn-edit-mode').addEventListener('click', () => setGlobalMode('edit')); document.getElementById('btn-edit-mode').addEventListener('click', () => setGlobalMode('edit'));
document.getElementById('btn-view-mode').addEventListener('click', () => setGlobalMode('view')); document.getElementById('btn-view-mode').addEventListener('click', () => setGlobalMode('view'));
// ================= FUNGSI MENGATUR MODE AKTIF (SPBU, Jalan, Tanah, Jalan Rusak) =================
function setActiveMode(mode) { function setActiveMode(mode) {
// Jika mode yang sama dan sedang aktif, nonaktifkan if (activeMode === mode && mode !== null) { mode = null; }
if (activeMode === mode && mode !== null) {
mode = null;
}
activeMode = mode; activeMode = mode;
// Update tampilan tombol
document.querySelectorAll('.mode-icon[data-mode]').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.mode-icon[data-mode]').forEach(btn => btn.classList.remove('active'));
if (activeMode === 'marker_add') { if (activeMode === 'marker_add') {
document.getElementById('btn-marker').classList.add('active'); document.getElementById('btn-marker').classList.add('active');
map.getContainer().style.cursor = 'crosshair'; map.getContainer().style.cursor = 'crosshair';
// Jika sedang drawing, batalkan
if (customDrawingActive) cancelCustomDrawing(); if (customDrawingActive) cancelCustomDrawing();
} else if (activeMode === 'polyline') { } else if (activeMode === 'polyline') {
document.getElementById('btn-polyline').classList.add('active'); document.getElementById('btn-polyline').classList.add('active');
@@ -543,52 +575,26 @@ function setActiveMode(mode) {
map.getContainer().style.cursor = 'crosshair'; map.getContainer().style.cursor = 'crosshair';
if (customDrawingActive) cancelCustomDrawing(); if (customDrawingActive) cancelCustomDrawing();
} else { } else {
// Tidak ada mode aktif
map.getContainer().style.cursor = ''; map.getContainer().style.cursor = '';
if (customDrawingActive) cancelCustomDrawing(); if (customDrawingActive) cancelCustomDrawing();
} }
} }
// Tombol SPBU: toggle mode marker_add
document.getElementById('btn-marker').addEventListener('click', () => { document.getElementById('btn-marker').addEventListener('click', () => {
if (!globalEditMode) return; if (!globalEditMode) return;
if (activeMode === 'marker_add') { if (activeMode === 'marker_add') { setActiveMode(null); } else { setActiveMode('marker_add'); }
setActiveMode(null);
} else {
setActiveMode('marker_add');
}
}); });
// Tombol Jalan: jika sedang drawing polyline, batalkan; jika tidak, mulai drawing polyline
document.getElementById('btn-polyline').addEventListener('click', () => { document.getElementById('btn-polyline').addEventListener('click', () => {
if (!globalEditMode) return; if (!globalEditMode) return;
if (customDrawingActive && customDrawingType === 'polyline') { if (customDrawingActive && customDrawingType === 'polyline') { cancelCustomDrawing(); setActiveMode(null); } else { setActiveMode('polyline'); }
cancelCustomDrawing();
setActiveMode(null);
} else {
setActiveMode('polyline');
}
}); });
// Tombol Tanah: jika sedang drawing polygon, batalkan; jika tidak, mulai drawing polygon
document.getElementById('btn-polygon').addEventListener('click', () => { document.getElementById('btn-polygon').addEventListener('click', () => {
if (!globalEditMode) return; if (!globalEditMode) return;
if (customDrawingActive && customDrawingType === 'polygon') { if (customDrawingActive && customDrawingType === 'polygon') { cancelCustomDrawing(); setActiveMode(null); } else { setActiveMode('polygon'); }
cancelCustomDrawing();
setActiveMode(null);
} else {
setActiveMode('polygon');
}
}); });
// Tombol Jalan Rusak: toggle mode road_damage
document.getElementById('btn-road-damage').addEventListener('click', () => { document.getElementById('btn-road-damage').addEventListener('click', () => {
if (!globalEditMode) return; if (!globalEditMode) return;
if (activeMode === 'road_damage') { if (activeMode === 'road_damage') { setActiveMode(null); } else { setActiveMode('road_damage'); }
setActiveMode(null);
} else {
setActiveMode('road_damage');
}
}); });
// ================= CUSTOM DRAWING ================= // ================= CUSTOM DRAWING =================
@@ -686,84 +692,34 @@ function finishCustomDrawing() {
} }
} }
// Fungsi untuk menyimpan jalan dari custom drawing dengan validasi lengkap
window.simpanJalanCustom = function () { window.simpanJalanCustom = function () {
const namaInput = document.getElementById('nama_jalan'); const namaInput = document.getElementById('nama_jalan');
const statusSelect = document.getElementById('status_jalan'); const statusSelect = document.getElementById('status_jalan');
if (!namaInput || !statusSelect) { toast("Error: Form tidak ditemukan"); return; }
if (!namaInput || !statusSelect) {
toast("Error: Form tidak ditemukan, coba ulangi drawing.");
return;
}
const nama = namaInput.value.trim(); const nama = namaInput.value.trim();
const status = statusSelect.value; const status = statusSelect.value;
if (nama === "") { alert("❌ Nama jalan harus diisi!"); return; }
if (nama === "") { if (status === "") { alert("❌ Silakan pilih status jalan!"); return; }
alert("❌ Nama jalan harus diisi!");
return;
}
if (status === "") {
alert("❌ Silakan pilih status jalan!");
return;
}
const geo = JSON.stringify(customPoints.map(p => [p.lat, p.lng])); const geo = JSON.stringify(customPoints.map(p => [p.lat, p.lng]));
const panjang = hitungPanjang(customPoints); const panjang = hitungPanjang(customPoints);
postForm('insert_jalan.php', { nama, status, panjang, geojson: geo })
postForm('insert_jalan.php', { .then(res => { if (res === "success") { toast("Jalan tersimpan"); location.reload(); } else { alert("Gagal menyimpan: " + res); } })
nama, .catch(err => alert("Error: " + err));
status,
panjang,
geojson: geo
}).then(res => {
if (res === "success") {
toast("Jalan tersimpan");
location.reload();
} else {
alert("Gagal menyimpan: " + res);
}
}).catch(err => alert("Error: " + err));
}; };
// Fungsi untuk menyimpan persil dari custom drawing dengan validasi lengkap
window.simpanParsilCustom = function () { window.simpanParsilCustom = function () {
const pemilikInput = document.getElementById('pemilik'); const pemilikInput = document.getElementById('pemilik');
const statusSelect = document.getElementById('status_tanah'); const statusSelect = document.getElementById('status_tanah');
if (!pemilikInput || !statusSelect) { toast("Error: Form tidak ditemukan"); return; }
if (!pemilikInput || !statusSelect) {
toast("Error: Form tidak ditemukan, coba ulangi drawing.");
return;
}
const pemilik = pemilikInput.value.trim(); const pemilik = pemilikInput.value.trim();
const status = statusSelect.value; const status = statusSelect.value;
if (pemilik === "") { alert("❌ Nama pemilik harus diisi!"); return; }
if (pemilik === "") { if (status === "") { alert("❌ Silakan pilih status tanah!"); return; }
alert("❌ Nama pemilik harus diisi!");
return;
}
if (status === "") {
alert("❌ Silakan pilih status tanah!");
return;
}
const geo = JSON.stringify([customPoints.map(p => [p.lat, p.lng])]); const geo = JSON.stringify([customPoints.map(p => [p.lat, p.lng])]);
const luas = hitungLuas([customPoints]); const luas = hitungLuas([customPoints]);
postForm('insert_parsil.php', { pemilik, status, luas, geojson: geo })
postForm('insert_parsil.php', { .then(res => { if (res === "success") { toast("Persil tersimpan"); location.reload(); } else { alert("Gagal menyimpan: " + res); } })
pemilik, .catch(err => alert("Error: " + err));
status,
luas,
geojson: geo
}).then(res => {
if (res === "success") {
toast("Persil tersimpan");
location.reload();
} else {
alert("Gagal menyimpan: " + res);
}
}).catch(err => alert("Error: " + err));
}; };
function cancelCustomDrawing() { function cancelCustomDrawing() {
@@ -776,10 +732,7 @@ function cancelCustomDrawing() {
customDrawingActive = false; customDrawingActive = false;
customPoints = []; customPoints = [];
document.getElementById('customDrawToolbar').style.display = 'none'; document.getElementById('customDrawToolbar').style.display = 'none';
// Jangan reset activeMode, biar tombol tetap menunjukkan status if (activeMode === 'polyline' || activeMode === 'polygon') { setActiveMode(null); }
if (activeMode === 'polyline' || activeMode === 'polygon') {
setActiveMode(null);
}
toast("Drawing dibatalkan"); toast("Drawing dibatalkan");
} }
window.batalCustomDrawing = cancelCustomDrawing; window.batalCustomDrawing = cancelCustomDrawing;
@@ -793,18 +746,13 @@ function hitungLuas(latlngs) {
return L.GeometryUtil.geodesicArea(latlngs[0]); return L.GeometryUtil.geodesicArea(latlngs[0]);
} }
// ================= LOAD DATA JALAN & PARSIL & JALAN RUSAK ================= // ================= LOAD DATA =================
function loadJalan() { function loadJalan() {
fetch('get_jalan.php').then(res => res.json()).then(data => { fetch('get_jalan.php').then(res => res.json()).then(data => {
jalanData = data; jalanData = data;
data.forEach(j => { data.forEach(j => {
let coords = JSON.parse(j.geojson); let coords = JSON.parse(j.geojson);
let colorMap = { let colorMap = { "Nasional": "#ef4444", "Provinsi": "#f97316", "Kabupaten": "#3b82f6", "Kota": "#10b981" };
"Nasional": "#ef4444", // merah
"Provinsi": "#f97316", // oranye
"Kabupaten": "#3b82f6", // biru
"Kota": "#10b981" // hijau
};
let color = colorMap[j.status] || "#6b7280"; let color = colorMap[j.status] || "#6b7280";
let polyline = L.polyline(coords, { color: color, weight: 4, opacity: 0.8 }).addTo(map); let polyline = L.polyline(coords, { color: color, weight: 4, opacity: 0.8 }).addTo(map);
polyline.on('click', () => polyline.bindPopup(getPopupJalan(j)).openPopup()); polyline.on('click', () => polyline.bindPopup(getPopupJalan(j)).openPopup());
@@ -841,7 +789,6 @@ function loadRoadDamage() {
m.on('dragend', function () { m.on('dragend', function () {
if (!globalEditMode) return; if (!globalEditMode) return;
let pos = m.getLatLng(); let pos = m.getLatLng();
// Update posisi via AJAX
postForm('update_jalan_rusak.php', { id: rd.id, lat: pos.lat, lng: pos.lng, deskripsi: rd.deskripsi, foto_lama: rd.foto }) postForm('update_jalan_rusak.php', { id: rd.id, lat: pos.lat, lng: pos.lng, deskripsi: rd.deskripsi, foto_lama: rd.foto })
.then(res => { if (res !== "success") alert("Gagal update posisi: " + res); else toast("Posisi jalan rusak diperbarui"); }) .then(res => { if (res !== "success") alert("Gagal update posisi: " + res); else toast("Posisi jalan rusak diperbarui"); })
.catch(err => alert("Error: " + err)); .catch(err => alert("Error: " + err));
@@ -858,24 +805,18 @@ function loadRoadDamage() {
// Load marker SPBU // Load marker SPBU
dataSpbu.forEach(d => { dataSpbu.forEach(d => {
let icon = d.status === "Buka 24 Jam" ? greenIcon : yellowIcon; let icon = d.status === '24jam' ? greenIcon : yellowIcon;
let m = L.marker([d.latitude, d.longitude], { icon: icon, draggable: globalEditMode }).addTo(map); let m = L.marker([d.latitude, d.longitude], { icon: icon, draggable: globalEditMode }).addTo(map);
markers[d.id] = m; markers[d.id] = m;
m.bindPopup(getPopupMarker(d)); m.bindPopup(getPopupMarker(d));
m.on('dragstart', () => { m.on('dragstart', () => { map.getContainer().style.cursor = 'grabbing'; });
map.getContainer().style.cursor = 'grabbing';
});
m.on('dragend', () => { m.on('dragend', () => {
map.getContainer().style.cursor = ''; map.getContainer().style.cursor = '';
if (!globalEditMode) return; if (!globalEditMode) return;
let pos = m.getLatLng(); let pos = m.getLatLng();
postForm('update.php', { postForm('update.php', { id: d.id, lat: pos.lat, lng: pos.lng })
id: d.id, .then(res => { if (res !== "OK") alert("Gagal memperbarui posisi SPBU: " + res); })
lat: pos.lat, .catch(err => alert("Error: " + err));
lng: pos.lng
}).then(res => {
if (res !== "OK") alert("Gagal memperbarui posisi SPBU: " + res);
}).catch(err => alert("Error: " + err));
d.latitude = pos.lat; d.longitude = pos.lng; d.latitude = pos.lat; d.longitude = pos.lng;
toast("Posisi SPBU diperbarui"); toast("Posisi SPBU diperbarui");
}); });
@@ -889,12 +830,12 @@ loadRoadDamage();
function getPopupMarker(d) { function getPopupMarker(d) {
if (globalEditMode) { if (globalEditMode) {
return `<div class="edit-popup"> return `<div class="edit-popup">
<b>📍 ${d.nama_spbu}</b><br>${d.no_spbu}<br>${d.status}<br> <b>📍 ${d.nama}</b><br>${d.no_spbu}<br>${d.status === '24jam' ? '24 Jam' : 'Terbatas'}<br>
<button class="btn-edit" onclick="editMarker(${d.id})">Edit</button> <button class="btn-edit" onclick="editMarker(${d.id})">Edit</button>
<button class="btn-delete" onclick="deleteMarker(${d.id})">Hapus</button> <button class="btn-delete" onclick="deleteMarker(${d.id})">Hapus</button>
</div>`; </div>`;
} else { } else {
return `<div><b>${d.nama_spbu}</b><br>${d.no_spbu}<br>${d.status}</div>`; return `<div><b>${d.nama}</b><br>${d.no_spbu}<br>${d.status === '24jam' ? '24 Jam' : 'Terbatas'}</div>`;
} }
} }
function getPopupJalan(j) { function getPopupJalan(j) {
@@ -950,8 +891,8 @@ function filterSidebar() {
if (activeFilter === 'all' || activeFilter === 'marker') { if (activeFilter === 'all' || activeFilter === 'marker') {
dataSpbu.forEach(d => { dataSpbu.forEach(d => {
if (d.nama_spbu.toLowerCase().includes(searchText)) if (d.nama.toLowerCase().includes(searchText))
results.push({ type: 'marker', id: d.id, name: d.nama_spbu, desc: d.status, latlng: [d.latitude, d.longitude] }); results.push({ type: 'marker', id: d.id, name: d.nama, desc: d.status === '24jam' ? '24 Jam' : 'Terbatas', latlng: [d.latitude, d.longitude] });
}); });
} }
if (activeFilter === 'all' || activeFilter === 'polyline') { if (activeFilter === 'all' || activeFilter === 'polyline') {
@@ -1001,12 +942,12 @@ document.querySelectorAll('.filter-chip').forEach(chip => {
}); });
document.getElementById('searchInput').addEventListener('input', filterSidebar); document.getElementById('searchInput').addEventListener('input', filterSidebar);
// ================= CRUD FUNCTIONS (SPBU, Jalan, Tanah) ================= // ================= CRUD FUNCTIONS =================
window.editMarker = function (id) { window.editMarker = function (id) {
let d = dataSpbu.find(x => x.id == id); let d = dataSpbu.find(x => x.id == id);
let form = `<div class="edit-popup"> let form = `<div class="edit-popup">
<input id="nama" value="${d.nama_spbu}"><input id="no" value="${d.no_spbu}"> <input id="nama" value="${d.nama}"><input id="no" value="${d.no_spbu}">
<select id="status"><option ${d.status=="Buka 24 Jam"?"selected":""}>Buka 24 Jam</option><option ${d.status=="Tidak 24 Jam"?"selected":""}>Tidak 24 Jam</option></select> <select id="status"><option ${d.status == "24jam" ? "selected" : ""} value="24jam">24 Jam</option><option ${d.status == "terbatas" ? "selected" : ""} value="terbatas">Terbatas</option></select>
<button class="btn-save" onclick="saveEditMarker(${id})">Simpan</button> <button class="btn-save" onclick="saveEditMarker(${id})">Simpan</button>
<button class="btn-cancel" onclick="refreshAllPopups()">Batal</button> <button class="btn-cancel" onclick="refreshAllPopups()">Batal</button>
</div>`; </div>`;
@@ -1065,13 +1006,9 @@ window.deleteParsil = function(id){
.catch(err => alert("Error: " + err)); .catch(err => alert("Error: " + err));
}; };
// ================= CRUD JALAN RUSAK ================= // ================= JALAN RUSAK =================
// Variabel untuk marker sementara saat mode road_damage
let tempRoadMarker = null; let tempRoadMarker = null;
// Hapus event click lama dan ganti dengan yang baru (mencakup road_damage)
map.off('click'); map.off('click');
let tempMarker = null; let tempMarker = null;
map.on('click', function (e) { map.on('click', function (e) {
@@ -1082,7 +1019,7 @@ map.on('click', function(e) {
tempMarker = L.marker(e.latlng, { icon: blueIcon }).addTo(map); tempMarker = L.marker(e.latlng, { icon: blueIcon }).addTo(map);
let form = `<div class="edit-popup"> let form = `<div class="edit-popup">
<input id="nama_baru" placeholder="Nama SPBU" autocomplete="off"><input id="no_baru" placeholder="No SPBU" autocomplete="off"> <input id="nama_baru" placeholder="Nama SPBU" autocomplete="off"><input id="no_baru" placeholder="No SPBU" autocomplete="off">
<select id="status_baru"><option>Buka 24 Jam</option><option>Tidak 24 Jam</option></select> <select id="status_baru"><option value="24jam">24 Jam</option><option value="terbatas">Terbatas</option></select>
<button class="btn-save" onclick="simpanMarkerBaru(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button> <button class="btn-save" onclick="simpanMarkerBaru(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
<button class="btn-cancel" onclick="batalMarkerBaru()">Batal</button> <button class="btn-cancel" onclick="batalMarkerBaru()">Batal</button>
</div>`; </div>`;
@@ -1129,7 +1066,6 @@ window.simpanRoadDamageBaru = function(lat, lng) {
.catch(err => alert("Error: " + err)); .catch(err => alert("Error: " + err));
}; };
// Edit & Delete jalan rusak
window.saveEditRoadDamage = function (id) { window.saveEditRoadDamage = function (id) {
let rd = roadDamageData.find(r => r.id == id); let rd = roadDamageData.find(r => r.id == id);
let deskripsiBaru = document.getElementById('deskripsi_edit').value.trim(); let deskripsiBaru = document.getElementById('deskripsi_edit').value.trim();
@@ -1157,9 +1093,9 @@ window.deleteRoadDamage = function(id) {
document.getElementById('undoPointBtn').addEventListener('click', undoLastPoint); document.getElementById('undoPointBtn').addEventListener('click', undoLastPoint);
document.getElementById('finishDrawBtn').addEventListener('click', finishCustomDrawing); document.getElementById('finishDrawBtn').addEventListener('click', finishCustomDrawing);
document.getElementById('cancelDrawBtn').addEventListener('click', cancelCustomDrawing); document.getElementById('cancelDrawBtn').addEventListener('click', cancelCustomDrawing);
// Tidak ada mode aktif saat awal
setActiveMode(null); setActiveMode(null);
setGlobalMode('edit'); // default mode edit setGlobalMode('edit');
</script> </script>
</body> </body>
</html> </html>
+1 -3
View File
@@ -7,14 +7,12 @@ $status = $_POST['status'] ?? '';
$lat = $_POST['lat'] ?? ''; $lat = $_POST['lat'] ?? '';
$lng = $_POST['lng'] ?? ''; $lng = $_POST['lng'] ?? '';
// VALIDASI
if($nama == "" || $no == "" || $status == "" || $lat == "" || $lng == ""){ if($nama == "" || $no == "" || $status == "" || $lat == "" || $lng == ""){
echo "DATA TIDAK LENGKAP"; echo "DATA TIDAK LENGKAP";
exit; exit;
} }
// PREPARED STATEMENT $stmt = $conn->prepare("INSERT INTO spbu (nama, no_spbu, status, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
$stmt = $conn->prepare("INSERT INTO spbu (nama_spbu, no_spbu, status, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssdd", $nama, $no, $status, $lat, $lng); $stmt->bind_param("sssdd", $nama, $no, $status, $lat, $lng);
if($stmt->execute()){ if($stmt->execute()){
+1 -1
View File
@@ -11,7 +11,7 @@ if($id == "" || $nama == "" || $no == "" || $status == ""){
exit; exit;
} }
$stmt = $conn->prepare("UPDATE spbu SET nama_spbu=?, no_spbu=?, status=? WHERE id=?"); $stmt = $conn->prepare("UPDATE spbu SET nama=?, no_spbu=?, status=? WHERE id=?");
$stmt->bind_param("sssi", $nama, $no, $status, $id); $stmt->bind_param("sssi", $nama, $no, $status, $id);
if($stmt->execute()){ if($stmt->execute()){
+28 -1
View File
@@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS admin (
); );
INSERT INTO admin (username, password) VALUES INSERT INTO admin (username, password) VALUES
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'); ('admin', '$2y$10$A9m/u6sAxEwgIO4dlmIvNO2WJioIya4TwMvSe06hgFrDsq1xg7x16');
CREATE TABLE IF NOT EXISTS spbu ( CREATE TABLE IF NOT EXISTS spbu (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
@@ -21,3 +21,30 @@ CREATE TABLE IF NOT EXISTS spbu (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
); );
CREATE TABLE jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_jalan VARCHAR(100),
status ENUM('Nasional','Provinsi','Kabupaten'),
panjang DOUBLE,
geojson TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE parsil (
id INT AUTO_INCREMENT PRIMARY KEY,
pemilik VARCHAR(100),
status ENUM('SHM','HGB','HGU','HP'),
luas DOUBLE,
geojson TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `jalan_rusak` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`deskripsi` TEXT NOT NULL,
`foto` VARCHAR(255) NOT NULL,
`latitude` DOUBLE NOT NULL,
`longitude` DOUBLE NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;