1165 lines
45 KiB
PHP
1165 lines
45 KiB
PHP
<?php
|
|
include 'koneksi.php';
|
|
|
|
$result = mysqli_query($conn, "SELECT * FROM spbu");
|
|
$data = [];
|
|
|
|
while ($row = mysqli_fetch_assoc($result)) {
|
|
$data[] = $row;
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>WebGIS SPBU - Modern</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', 'Inter', system-ui, sans-serif;
|
|
background: #111827;
|
|
overflow: hidden;
|
|
height: 100vh;
|
|
}
|
|
|
|
#map {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 1;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
/* SIDEBAR */
|
|
.sidebar {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 340px;
|
|
height: 100%;
|
|
background: rgba(255, 255, 255, 0.98);
|
|
backdrop-filter: blur(2px);
|
|
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.08);
|
|
z-index: 2;
|
|
transform: translateX(0);
|
|
transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.4, 1.1);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
border-right: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.sidebar.closed {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
/* Tombol toggle sidebar (hamburger) */
|
|
.toggle-sidebar-btn {
|
|
position: absolute;
|
|
top: 18px;
|
|
left: 18px;
|
|
z-index: 3;
|
|
background: white;
|
|
border: none;
|
|
border-radius: 40px;
|
|
width: 44px;
|
|
height: 44px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
cursor: pointer;
|
|
font-size: 1.4rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.2s;
|
|
color: #1f2937;
|
|
}
|
|
body.sidebar-open .toggle-sidebar-btn {
|
|
display: none;
|
|
}
|
|
|
|
/* Atur posisi zoom control Leaflet agar tidak tertutup hamburger */
|
|
.leaflet-control-zoom {
|
|
transition: margin-left 0.3s ease;
|
|
margin-left: 60px;
|
|
}
|
|
body.sidebar-open .leaflet-control-zoom {
|
|
margin-left: 340px;
|
|
}
|
|
@media (max-width: 640px) {
|
|
body.sidebar-open .leaflet-control-zoom {
|
|
margin-left: 280px;
|
|
}
|
|
.sidebar { width: 280px; }
|
|
}
|
|
|
|
.sidebar-header {
|
|
padding: 20px 20px 12px 20px;
|
|
border-bottom: 1px solid #eef2f6;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.sidebar-header h2 {
|
|
font-size: 1.35rem;
|
|
font-weight: 600;
|
|
background: linear-gradient(135deg, #1e293b, #2d3a4a);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
.close-sidebar {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
color: #64748b;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.close-sidebar:hover {
|
|
background: #f1f5f9;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.search-area {
|
|
padding: 16px 20px;
|
|
background: #ffffff;
|
|
border-bottom: 1px solid #edf2f7;
|
|
}
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: 10px 16px;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 44px;
|
|
font-size: 0.9rem;
|
|
outline: none;
|
|
transition: 0.2s;
|
|
}
|
|
.search-box input:focus {
|
|
border-color: #6366f1;
|
|
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
|
|
}
|
|
.filter-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
margin-top: 12px;
|
|
}
|
|
.filter-chip {
|
|
background: #f1f5f9;
|
|
border: none;
|
|
padding: 6px 14px;
|
|
border-radius: 40px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
color: #334155;
|
|
}
|
|
.filter-chip.active {
|
|
background: #6366f1;
|
|
color: white;
|
|
}
|
|
|
|
.sidebar-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 12px 16px;
|
|
}
|
|
.result-card {
|
|
background: #ffffff;
|
|
border-radius: 16px;
|
|
padding: 12px 14px;
|
|
margin-bottom: 10px;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
border: 1px solid #ecf3fa;
|
|
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
|
}
|
|
.result-card:hover {
|
|
background: #fafcff;
|
|
transform: translateX(4px);
|
|
border-color: #cbdff2;
|
|
}
|
|
.result-title {
|
|
font-weight: 600;
|
|
color: #0f172a;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.result-sub {
|
|
font-size: 0.7rem;
|
|
color: #5b6e8c;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.floating-toolbar {
|
|
position: absolute;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
z-index: 3;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
backdrop-filter: blur(12px);
|
|
border-radius: 56px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
padding: 8px 16px;
|
|
display: flex;
|
|
gap: 12px;
|
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
transition: all 0.3s ease;
|
|
}
|
|
.mode-icon {
|
|
background: #f1f5f9;
|
|
border: none;
|
|
padding: 8px 18px;
|
|
border-radius: 40px;
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
color: #1e293b;
|
|
}
|
|
.mode-icon.active {
|
|
background: #6366f1;
|
|
color: white;
|
|
}
|
|
.mode-icon.edit-mode {
|
|
background: #1e293b;
|
|
color: white;
|
|
}
|
|
.mode-icon.edit-mode.active {
|
|
background: #10b981;
|
|
}
|
|
.draw-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
body.view-mode .draw-actions {
|
|
display: none;
|
|
}
|
|
|
|
.custom-draw-toolbar {
|
|
position: absolute;
|
|
bottom: 24px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: #1e293b;
|
|
border-radius: 60px;
|
|
padding: 8px 20px;
|
|
display: flex;
|
|
gap: 16px;
|
|
z-index: 10;
|
|
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
}
|
|
.custom-draw-toolbar button {
|
|
background: #334155;
|
|
border: none;
|
|
color: white;
|
|
padding: 8px 20px;
|
|
border-radius: 40px;
|
|
cursor: pointer;
|
|
}
|
|
.custom-draw-toolbar button:hover {
|
|
background: #6366f1;
|
|
}
|
|
|
|
.edit-popup {
|
|
min-width: 220px;
|
|
}
|
|
.edit-popup input, .edit-popup select, .edit-popup textarea {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
margin: 8px 0;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 30px;
|
|
font-size: 0.85rem;
|
|
}
|
|
.edit-popup button {
|
|
padding: 6px 14px;
|
|
border-radius: 30px;
|
|
border: none;
|
|
margin-right: 6px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
}
|
|
.btn-save { background: #10b981; color: white; }
|
|
.btn-cancel { background: #94a3b8; color: white; }
|
|
.btn-edit { background: #f59e0b; color: white; }
|
|
.btn-delete { background: #ef4444; color: white; }
|
|
|
|
.toast {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
background: #1e293b;
|
|
color: white;
|
|
padding: 8px 18px;
|
|
border-radius: 40px;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
transition: 0.2s;
|
|
pointer-events: none;
|
|
}
|
|
.toast.show { opacity: 1; }
|
|
|
|
.drawing-cursor {
|
|
cursor: crosshair !important;
|
|
}
|
|
body.view-mode #map {
|
|
cursor: grab;
|
|
}
|
|
body.view-mode #map:active {
|
|
cursor: grabbing;
|
|
}
|
|
/* Kursor saat drag marker */
|
|
.leaflet-marker-dragging {
|
|
cursor: grabbing !important;
|
|
}
|
|
/* thumbail foto di popup */
|
|
.popup-img {
|
|
max-width: 200px;
|
|
max-height: 150px;
|
|
border-radius: 12px;
|
|
margin-top: 6px;
|
|
border: 1px solid #ddd;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<button class="toggle-sidebar-btn" id="openSidebarBtn">☰</button>
|
|
|
|
<div class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<h2>🗺️ GIS Dashboard</h2>
|
|
<button class="close-sidebar" id="closeSidebarBtn">✕</button>
|
|
</div>
|
|
<div class="search-area">
|
|
<div class="search-box">
|
|
<input type="text" id="searchInput" placeholder="Cari nama, jalan, pemilik, jalan rusak..." autocomplete="off">
|
|
</div>
|
|
<div class="filter-buttons">
|
|
<button class="filter-chip active" data-filter="all">Semua</button>
|
|
<button class="filter-chip" data-filter="marker">📍 SPBU</button>
|
|
<button class="filter-chip" data-filter="polyline">📈 Jalan</button>
|
|
<button class="filter-chip" data-filter="polygon">🔷 Tanah</button>
|
|
<button class="filter-chip" data-filter="road_damage">⚠️ Jalan Rusak</button>
|
|
</div>
|
|
</div>
|
|
<div class="sidebar-content" id="sidebarContent">
|
|
<div style="text-align:center; padding:20px;">Memuat data...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="floating-toolbar" id="floatingToolbar">
|
|
<div class="draw-actions">
|
|
<button class="mode-icon" id="btn-marker" data-mode="marker_add">📍 SPBU</button>
|
|
<button class="mode-icon" id="btn-polyline" data-mode="polyline">📈 Jalan</button>
|
|
<button class="mode-icon" id="btn-polygon" data-mode="polygon">🔷 Tanah</button>
|
|
<button class="mode-icon" id="btn-road-damage" data-mode="road_damage">⚠️ Jalan Rusak</button>
|
|
</div>
|
|
<div style="width:1px; background:#ccc; margin:0 4px;"></div>
|
|
<button class="mode-icon edit-mode" id="btn-view-mode">👁️ View</button>
|
|
<button class="mode-icon edit-mode active" id="btn-edit-mode">✏️ Edit</button>
|
|
</div>
|
|
|
|
<div id="customDrawToolbar" class="custom-draw-toolbar" style="display: none;">
|
|
<button id="undoPointBtn">↩️ Undo</button>
|
|
<button id="finishDrawBtn">✅ Selesai</button>
|
|
<button id="cancelDrawBtn">✖️ Batal</button>
|
|
</div>
|
|
|
|
<div id="toast" class="toast"></div>
|
|
<div id="map"></div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
|
<script src="https://unpkg.com/leaflet-geometryutil"></script>
|
|
|
|
<script>
|
|
// ================= INIT MAP =================
|
|
const map = L.map('map').setView([-0.02, 109.34], 13);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
// ================= GLOBAL STATE =================
|
|
let globalEditMode = true; // true = edit, false = view
|
|
let activeMode = null; // null = tidak ada mode aktif, 'marker_add', 'polyline', 'polygon', 'road_damage'
|
|
let customDrawingActive = false;
|
|
let customDrawingType = null;
|
|
let customPoints = [];
|
|
let customTempLayer = null;
|
|
let customPreviewLine = null;
|
|
|
|
let markers = {};
|
|
let polylines = {};
|
|
let polygons = {};
|
|
let roadDamageMarkers = {}; // simpan marker jalan rusak
|
|
let dataSpbu = <?php echo json_encode($data); ?>;
|
|
let jalanData = [];
|
|
let parsilData = [];
|
|
let roadDamageData = [];
|
|
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 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]});
|
|
// Icon khusus jalan rusak (merah)
|
|
const damageIcon = L.icon({
|
|
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',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
|
|
function postForm(url, data) {
|
|
const params = new URLSearchParams();
|
|
Object.entries(data).forEach(([key, value]) => params.append(key, value));
|
|
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
|
body: params.toString()
|
|
}).then(res => res.text()).then(res => res.trim());
|
|
}
|
|
|
|
// Fungsi upload file dengan FormData
|
|
function uploadForm(url, formData) {
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
body: formData
|
|
}).then(res => res.text()).then(res => res.trim());
|
|
}
|
|
|
|
function toast(msg) {
|
|
const t = document.getElementById('toast');
|
|
t.innerText = msg;
|
|
t.classList.add('show');
|
|
setTimeout(() => t.classList.remove('show'), 2000);
|
|
}
|
|
|
|
// ================= SIDEBAR TOGGLE =================
|
|
const sidebar = document.getElementById('sidebar');
|
|
const openBtn = document.getElementById('openSidebarBtn');
|
|
const closeBtn = document.getElementById('closeSidebarBtn');
|
|
|
|
function updateSidebarState() {
|
|
const isOpen = !sidebar.classList.contains('closed');
|
|
if (isOpen) {
|
|
document.body.classList.add('sidebar-open');
|
|
openBtn.style.display = 'none';
|
|
} else {
|
|
document.body.classList.remove('sidebar-open');
|
|
openBtn.style.display = 'flex';
|
|
}
|
|
setTimeout(() => map.invalidateSize(), 250);
|
|
}
|
|
|
|
openBtn.addEventListener('click', () => {
|
|
sidebar.classList.remove('closed');
|
|
updateSidebarState();
|
|
});
|
|
closeBtn.addEventListener('click', () => {
|
|
sidebar.classList.add('closed');
|
|
updateSidebarState();
|
|
});
|
|
updateSidebarState();
|
|
|
|
// ================= EDIT / VIEW MODE =================
|
|
function setGlobalMode(modeType) {
|
|
globalEditMode = (modeType === 'edit');
|
|
if (globalEditMode) {
|
|
document.body.classList.remove('view-mode');
|
|
document.getElementById('btn-edit-mode').classList.add('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 roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.enable();
|
|
toast("Mode Edit aktif");
|
|
} else {
|
|
document.body.classList.add('view-mode');
|
|
document.getElementById('btn-view-mode').classList.add('active');
|
|
document.getElementById('btn-edit-mode').classList.remove('active');
|
|
// Batalkan drawing jika sedang aktif
|
|
if (customDrawingActive) cancelCustomDrawing();
|
|
// Nonaktifkan mode marker_add dan road_damage
|
|
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 roadDamageMarkers) if (roadDamageMarkers[id].dragging) roadDamageMarkers[id].dragging.disable();
|
|
toast("Mode View aktif");
|
|
}
|
|
refreshAllPopups();
|
|
filterSidebar();
|
|
}
|
|
|
|
document.getElementById('btn-edit-mode').addEventListener('click', () => setGlobalMode('edit'));
|
|
document.getElementById('btn-view-mode').addEventListener('click', () => setGlobalMode('view'));
|
|
|
|
// ================= FUNGSI MENGATUR MODE AKTIF (SPBU, Jalan, Tanah, Jalan Rusak) =================
|
|
function setActiveMode(mode) {
|
|
// Jika mode yang sama dan sedang aktif, nonaktifkan
|
|
if (activeMode === mode && mode !== null) {
|
|
mode = null;
|
|
}
|
|
activeMode = mode;
|
|
// Update tampilan tombol
|
|
document.querySelectorAll('.mode-icon[data-mode]').forEach(btn => btn.classList.remove('active'));
|
|
if (activeMode === 'marker_add') {
|
|
document.getElementById('btn-marker').classList.add('active');
|
|
map.getContainer().style.cursor = 'crosshair';
|
|
// Jika sedang drawing, batalkan
|
|
if (customDrawingActive) cancelCustomDrawing();
|
|
} else if (activeMode === 'polyline') {
|
|
document.getElementById('btn-polyline').classList.add('active');
|
|
startCustomDrawing('polyline');
|
|
} else if (activeMode === 'polygon') {
|
|
document.getElementById('btn-polygon').classList.add('active');
|
|
startCustomDrawing('polygon');
|
|
} else if (activeMode === 'road_damage') {
|
|
document.getElementById('btn-road-damage').classList.add('active');
|
|
map.getContainer().style.cursor = 'crosshair';
|
|
if (customDrawingActive) cancelCustomDrawing();
|
|
} else {
|
|
// Tidak ada mode aktif
|
|
map.getContainer().style.cursor = '';
|
|
if (customDrawingActive) cancelCustomDrawing();
|
|
}
|
|
}
|
|
|
|
// Tombol SPBU: toggle mode marker_add
|
|
document.getElementById('btn-marker').addEventListener('click', () => {
|
|
if (!globalEditMode) return;
|
|
if (activeMode === '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', () => {
|
|
if (!globalEditMode) return;
|
|
if (customDrawingActive && customDrawingType === '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', () => {
|
|
if (!globalEditMode) return;
|
|
if (customDrawingActive && customDrawingType === 'polygon') {
|
|
cancelCustomDrawing();
|
|
setActiveMode(null);
|
|
} else {
|
|
setActiveMode('polygon');
|
|
}
|
|
});
|
|
|
|
// Tombol Jalan Rusak: toggle mode road_damage
|
|
document.getElementById('btn-road-damage').addEventListener('click', () => {
|
|
if (!globalEditMode) return;
|
|
if (activeMode === 'road_damage') {
|
|
setActiveMode(null);
|
|
} else {
|
|
setActiveMode('road_damage');
|
|
}
|
|
});
|
|
|
|
// ================= CUSTOM DRAWING =================
|
|
function startCustomDrawing(type) {
|
|
if (!globalEditMode) return;
|
|
if (customDrawingActive) cancelCustomDrawing();
|
|
customDrawingActive = true;
|
|
customDrawingType = type;
|
|
customPoints = [];
|
|
map.dragging.disable();
|
|
map.getContainer().classList.add('drawing-cursor');
|
|
document.getElementById('customDrawToolbar').style.display = 'flex';
|
|
toast(`Mode ${type === 'polyline' ? 'Jalan' : 'Tanah'} aktif. Klik pada peta untuk titik.`);
|
|
map.on('mousemove', onMouseMovePreview);
|
|
}
|
|
|
|
function onMouseMovePreview(e) {
|
|
if (!customDrawingActive || customPoints.length === 0) return;
|
|
if (customPreviewLine) map.removeLayer(customPreviewLine);
|
|
const last = customPoints[customPoints.length-1];
|
|
customPreviewLine = L.polyline([last, e.latlng], { color: '#6366f1', weight: 3, dashArray: '5,10', opacity: 0.7 });
|
|
customPreviewLine.addTo(map);
|
|
}
|
|
|
|
function addCustomPoint(latlng) {
|
|
if (!customDrawingActive) return;
|
|
customPoints.push(latlng);
|
|
updateCustomLayer();
|
|
if (customPreviewLine) { map.removeLayer(customPreviewLine); customPreviewLine = null; }
|
|
}
|
|
|
|
function updateCustomLayer() {
|
|
if (customTempLayer) map.removeLayer(customTempLayer);
|
|
if (customPoints.length < 2) return;
|
|
if (customDrawingType === 'polyline') {
|
|
customTempLayer = L.polyline(customPoints, { color: '#6366f1', weight: 5 });
|
|
} else {
|
|
if (customPoints.length >= 3)
|
|
customTempLayer = L.polygon(customPoints, { color: '#6366f1', weight: 3, fillOpacity: 0.2 });
|
|
else
|
|
customTempLayer = L.polyline(customPoints, { color: '#6366f1', weight: 4, dashArray: '5,10' });
|
|
}
|
|
customTempLayer.addTo(map);
|
|
}
|
|
|
|
function undoLastPoint() {
|
|
if (!customDrawingActive || customPoints.length === 0) return;
|
|
customPoints.pop();
|
|
updateCustomLayer();
|
|
toast(`Titik dihapus, tersisa ${customPoints.length}`);
|
|
}
|
|
|
|
function finishCustomDrawing() {
|
|
if (!customDrawingActive) return;
|
|
const min = (customDrawingType === 'polyline') ? 2 : 3;
|
|
if (customPoints.length < min) { toast(`Minimal ${min} titik`); return; }
|
|
const panjang = hitungPanjang(customPoints);
|
|
const luas = (customDrawingType === 'polygon') ? hitungLuas([customPoints]) : null;
|
|
const center = customPoints[Math.floor(customPoints.length/2)];
|
|
let formHtml = '';
|
|
if (customDrawingType === 'polyline') {
|
|
formHtml = `<div class="edit-popup">
|
|
<b>📏 Panjang: ${panjang.toFixed(2)} m</b>
|
|
<input id="nama_jalan" placeholder="Nama Jalan" autocomplete="off">
|
|
<select id="status_jalan">
|
|
<option value="" disabled selected>-- Pilih Status Jalan --</option>
|
|
<option value="Nasional">Nasional</option>
|
|
<option value="Provinsi">Provinsi</option>
|
|
<option value="Kabupaten">Kabupaten</option>
|
|
<option value="Kota">Kota</option>
|
|
</select>
|
|
<button class="btn-save" onclick="simpanJalanCustom()">Simpan</button>
|
|
<button class="btn-cancel" onclick="batalCustomDrawing()">Batal</button>
|
|
</div>`;
|
|
} else {
|
|
formHtml = `<div class="edit-popup">
|
|
<b>📐 Luas: ${luas.toFixed(2)} m²</b>
|
|
<input id="pemilik" placeholder="Nama Pemilik" autocomplete="off">
|
|
<select id="status_tanah">
|
|
<option value="" disabled selected>-- Pilih Status Tanah --</option>
|
|
<option value="SHM">SHM</option>
|
|
<option value="HGB">HGB</option>
|
|
<option value="HGU">HGU</option>
|
|
<option value="HP">HP</option>
|
|
</select>
|
|
<button class="btn-save" onclick="simpanParsilCustom()">Simpan</button>
|
|
<button class="btn-cancel" onclick="batalCustomDrawing()">Batal</button>
|
|
</div>`;
|
|
}
|
|
if (customTempLayer) customTempLayer.bindPopup(formHtml).openPopup();
|
|
else {
|
|
let tmp = L.marker(center).addTo(map);
|
|
tmp.bindPopup(formHtml).openPopup();
|
|
window.tempPopupMarker = tmp;
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk menyimpan jalan dari custom drawing dengan validasi lengkap
|
|
window.simpanJalanCustom = function() {
|
|
const namaInput = document.getElementById('nama_jalan');
|
|
const statusSelect = document.getElementById('status_jalan');
|
|
|
|
if (!namaInput || !statusSelect) {
|
|
toast("Error: Form tidak ditemukan, coba ulangi drawing.");
|
|
return;
|
|
}
|
|
|
|
const nama = namaInput.value.trim();
|
|
const status = statusSelect.value;
|
|
|
|
if (nama === "") {
|
|
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 panjang = hitungPanjang(customPoints);
|
|
|
|
postForm('insert_jalan.php', {
|
|
nama,
|
|
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() {
|
|
const pemilikInput = document.getElementById('pemilik');
|
|
const statusSelect = document.getElementById('status_tanah');
|
|
|
|
if (!pemilikInput || !statusSelect) {
|
|
toast("Error: Form tidak ditemukan, coba ulangi drawing.");
|
|
return;
|
|
}
|
|
|
|
const pemilik = pemilikInput.value.trim();
|
|
const status = statusSelect.value;
|
|
|
|
if (pemilik === "") {
|
|
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 luas = hitungLuas([customPoints]);
|
|
|
|
postForm('insert_parsil.php', {
|
|
pemilik,
|
|
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() {
|
|
if (customTempLayer) map.removeLayer(customTempLayer);
|
|
if (customPreviewLine) map.removeLayer(customPreviewLine);
|
|
if (window.tempPopupMarker) map.removeLayer(window.tempPopupMarker);
|
|
map.off('mousemove', onMouseMovePreview);
|
|
map.dragging.enable();
|
|
map.getContainer().classList.remove('drawing-cursor');
|
|
customDrawingActive = false;
|
|
customPoints = [];
|
|
document.getElementById('customDrawToolbar').style.display = 'none';
|
|
// Jangan reset activeMode, biar tombol tetap menunjukkan status
|
|
if (activeMode === 'polyline' || activeMode === 'polygon') {
|
|
setActiveMode(null);
|
|
}
|
|
toast("Drawing dibatalkan");
|
|
}
|
|
window.batalCustomDrawing = cancelCustomDrawing;
|
|
|
|
function hitungPanjang(latlngs) {
|
|
let total = 0;
|
|
for (let i=0; i<latlngs.length-1; i++) total += map.distance(latlngs[i], latlngs[i+1]);
|
|
return total;
|
|
}
|
|
function hitungLuas(latlngs) {
|
|
return L.GeometryUtil.geodesicArea(latlngs[0]);
|
|
}
|
|
|
|
// ================= LOAD DATA JALAN & PARSIL & JALAN RUSAK =================
|
|
function loadJalan() {
|
|
fetch('get_jalan.php').then(res => res.json()).then(data => {
|
|
jalanData = data;
|
|
data.forEach(j => {
|
|
let coords = JSON.parse(j.geojson);
|
|
let colorMap = {
|
|
"Nasional": "#ef4444", // merah
|
|
"Provinsi": "#f97316", // oranye
|
|
"Kabupaten": "#3b82f6", // biru
|
|
"Kota": "#10b981" // hijau
|
|
};
|
|
let color = colorMap[j.status] || "#6b7280";
|
|
let polyline = L.polyline(coords, { color: color, weight: 4, opacity: 0.8 }).addTo(map);
|
|
polyline.on('click', () => polyline.bindPopup(getPopupJalan(j)).openPopup());
|
|
polylines[j.id] = polyline;
|
|
j.layer = polyline;
|
|
});
|
|
dataLoaded.jalan = true;
|
|
if (dataLoaded.jalan && dataLoaded.parsil && dataLoaded.roadDamage) filterSidebar();
|
|
}).catch(err => console.error(err));
|
|
}
|
|
|
|
function loadParsil() {
|
|
fetch('get_parsil.php').then(res => res.json()).then(data => {
|
|
parsilData = data;
|
|
data.forEach(p => {
|
|
let coords = JSON.parse(p.geojson);
|
|
let colorMap = { SHM: "#10b981", HGB: "#3b82f6", HGU: "#f59e0b", HP: "#8b5cf6" };
|
|
let polygon = L.polygon(coords, { color: colorMap[p.status], weight: 2, fillOpacity: 0.3 }).addTo(map);
|
|
polygon.on('click', () => polygon.bindPopup(getPopupParsil(p)).openPopup());
|
|
polygons[p.id] = polygon;
|
|
p.layer = polygon;
|
|
});
|
|
dataLoaded.parsil = true;
|
|
if (dataLoaded.jalan && dataLoaded.parsil && dataLoaded.roadDamage) filterSidebar();
|
|
}).catch(err => console.error(err));
|
|
}
|
|
|
|
function loadRoadDamage() {
|
|
fetch('get_jalan_rusak.php').then(res => res.json()).then(data => {
|
|
roadDamageData = data;
|
|
data.forEach(rd => {
|
|
let m = L.marker([parseFloat(rd.latitude), parseFloat(rd.longitude)], { icon: damageIcon, draggable: globalEditMode }).addTo(map);
|
|
m.bindPopup(getPopupRoadDamage(rd));
|
|
m.on('dragend', function() {
|
|
if (!globalEditMode) return;
|
|
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 })
|
|
.then(res => { if (res !== "success") alert("Gagal update posisi: " + res); else toast("Posisi jalan rusak diperbarui"); })
|
|
.catch(err => alert("Error: " + err));
|
|
rd.latitude = pos.lat;
|
|
rd.longitude = pos.lng;
|
|
});
|
|
roadDamageMarkers[rd.id] = m;
|
|
rd.marker = m;
|
|
});
|
|
dataLoaded.roadDamage = true;
|
|
if (dataLoaded.jalan && dataLoaded.parsil && dataLoaded.roadDamage) filterSidebar();
|
|
}).catch(err => console.error(err));
|
|
}
|
|
|
|
// Load marker SPBU
|
|
dataSpbu.forEach(d => {
|
|
let icon = d.status === "Buka 24 Jam" ? greenIcon : yellowIcon;
|
|
let m = L.marker([d.latitude, d.longitude], { icon: icon, draggable: globalEditMode }).addTo(map);
|
|
markers[d.id] = m;
|
|
m.bindPopup(getPopupMarker(d));
|
|
m.on('dragstart', () => {
|
|
map.getContainer().style.cursor = 'grabbing';
|
|
});
|
|
m.on('dragend', () => {
|
|
map.getContainer().style.cursor = '';
|
|
if (!globalEditMode) return;
|
|
let pos = m.getLatLng();
|
|
postForm('update.php', {
|
|
id: d.id,
|
|
lat: pos.lat,
|
|
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;
|
|
toast("Posisi SPBU diperbarui");
|
|
});
|
|
});
|
|
|
|
loadJalan();
|
|
loadParsil();
|
|
loadRoadDamage();
|
|
|
|
// ================= POPUP GENERATOR =================
|
|
function getPopupMarker(d) {
|
|
if (globalEditMode) {
|
|
return `<div class="edit-popup">
|
|
<b>📍 ${d.nama_spbu}</b><br>${d.no_spbu}<br>${d.status}<br>
|
|
<button class="btn-edit" onclick="editMarker(${d.id})">Edit</button>
|
|
<button class="btn-delete" onclick="deleteMarker(${d.id})">Hapus</button>
|
|
</div>`;
|
|
} else {
|
|
return `<div><b>${d.nama_spbu}</b><br>${d.no_spbu}<br>${d.status}</div>`;
|
|
}
|
|
}
|
|
function getPopupJalan(j) {
|
|
if (globalEditMode) {
|
|
return `<div class="edit-popup">
|
|
<b>🛣️ ${j.nama_jalan}</b><br>Status: ${j.status}<br>Panjang: ${j.panjang} m<br>
|
|
<button class="btn-edit" onclick="editJalan(${j.id})">Edit</button>
|
|
<button class="btn-delete" onclick="deleteJalan(${j.id})">Hapus</button>
|
|
</div>`;
|
|
} else {
|
|
return `<div><b>🛣️ ${j.nama_jalan}</b><br>${j.status}</div>`;
|
|
}
|
|
}
|
|
function getPopupParsil(p) {
|
|
if (globalEditMode) {
|
|
return `<div class="edit-popup">
|
|
<b>🏞️ ${p.pemilik}</b><br>Status: ${p.status}<br>Luas: ${p.luas} m²<br>
|
|
<button class="btn-edit" onclick="editParsil(${p.id})">Edit</button>
|
|
<button class="btn-delete" onclick="deleteParsil(${p.id})">Hapus</button>
|
|
</div>`;
|
|
} else {
|
|
return `<div><b>🏞️ ${p.pemilik}</b><br>${p.status}</div>`;
|
|
}
|
|
}
|
|
function getPopupRoadDamage(rd) {
|
|
const fotoUrl = 'uploads/' + rd.foto;
|
|
if (globalEditMode) {
|
|
return `<div class="edit-popup">
|
|
<b>⚠️ Laporan Jalan Rusak</b><br>
|
|
<img src="${fotoUrl}" class="popup-img" onerror="this.src='https://via.placeholder.com/150?text=No+Image'"><br>
|
|
<textarea id="deskripsi_edit" rows="2" placeholder="Deskripsi">${rd.deskripsi}</textarea>
|
|
<input type="file" id="foto_edit" accept="image/*"><br>
|
|
<button class="btn-save" onclick="saveEditRoadDamage(${rd.id})">Simpan</button>
|
|
<button class="btn-delete" onclick="deleteRoadDamage(${rd.id})">Hapus</button>
|
|
<button class="btn-cancel" onclick="refreshAllPopups()">Batal</button>
|
|
</div>`;
|
|
} else {
|
|
return `<div><b>⚠️ Jalan Rusak</b><br>${rd.deskripsi}<br><img src="${fotoUrl}" class="popup-img" onerror="this.src='https://via.placeholder.com/150?text=No+Image'"></div>`;
|
|
}
|
|
}
|
|
function refreshAllPopups() {
|
|
dataSpbu.forEach(d => { if(markers[d.id]) markers[d.id].setPopupContent(getPopupMarker(d)); });
|
|
jalanData.forEach(j => { if(polylines[j.id]) polylines[j.id].setPopupContent(getPopupJalan(j)); });
|
|
parsilData.forEach(p => { if(polygons[p.id]) polygons[p.id].setPopupContent(getPopupParsil(p)); });
|
|
roadDamageData.forEach(rd => { if(roadDamageMarkers[rd.id]) roadDamageMarkers[rd.id].setPopupContent(getPopupRoadDamage(rd)); });
|
|
}
|
|
|
|
// ================= SIDEBAR SEARCH & FILTER =================
|
|
function filterSidebar() {
|
|
let searchText = document.getElementById('searchInput').value.toLowerCase();
|
|
let activeFilter = document.querySelector('.filter-chip.active')?.dataset.filter || 'all';
|
|
let results = [];
|
|
|
|
if (activeFilter === 'all' || activeFilter === 'marker') {
|
|
dataSpbu.forEach(d => {
|
|
if (d.nama_spbu.toLowerCase().includes(searchText))
|
|
results.push({ type: 'marker', id: d.id, name: d.nama_spbu, desc: d.status, latlng: [d.latitude, d.longitude] });
|
|
});
|
|
}
|
|
if (activeFilter === 'all' || activeFilter === 'polyline') {
|
|
jalanData.forEach(j => {
|
|
if (j.nama_jalan.toLowerCase().includes(searchText))
|
|
results.push({ type: 'polyline', id: j.id, name: j.nama_jalan, desc: `Jalan ${j.status}`, bounds: j.layer.getBounds() });
|
|
});
|
|
}
|
|
if (activeFilter === 'all' || activeFilter === 'polygon') {
|
|
parsilData.forEach(p => {
|
|
if (p.pemilik.toLowerCase().includes(searchText))
|
|
results.push({ type: 'polygon', id: p.id, name: p.pemilik, desc: `Tanah ${p.status}`, bounds: p.layer.getBounds() });
|
|
});
|
|
}
|
|
if (activeFilter === 'all' || activeFilter === 'road_damage') {
|
|
roadDamageData.forEach(rd => {
|
|
if (rd.deskripsi.toLowerCase().includes(searchText))
|
|
results.push({ type: 'road_damage', id: rd.id, name: 'Jalan Rusak', desc: rd.deskripsi.substring(0, 50), latlng: [rd.latitude, rd.longitude] });
|
|
});
|
|
}
|
|
|
|
let html = `<div style="margin-bottom:10px; font-size:0.75rem;">${results.length} ditemukan</div>`;
|
|
results.forEach(r => {
|
|
let icon = r.type === 'marker' ? '📍' : (r.type === 'polyline' ? '📈' : (r.type === 'polygon' ? '🔷' : '⚠️'));
|
|
html += `<div class="result-card" onclick="focusFeature('${r.type}', ${r.id})">
|
|
<div class="result-title">${icon} ${r.name}</div>
|
|
<div class="result-sub">${r.desc}</div>
|
|
</div>`;
|
|
});
|
|
if (results.length === 0) html += '<div style="text-align:center; padding:20px;">❌ Tidak ada data</div>';
|
|
document.getElementById('sidebarContent').innerHTML = html;
|
|
}
|
|
|
|
window.focusFeature = function(type, id) {
|
|
if (type === 'marker' && markers[id]) { map.setView(markers[id].getLatLng(), 17); markers[id].openPopup(); }
|
|
else if (type === 'polyline') { let j = jalanData.find(j => j.id == id); if(j && j.layer) { map.fitBounds(j.layer.getBounds()); j.layer.openPopup(); } }
|
|
else if (type === 'polygon') { let p = parsilData.find(p => p.id == id); if(p && p.layer) { map.fitBounds(p.layer.getBounds()); p.layer.openPopup(); } }
|
|
else if (type === 'road_damage' && roadDamageMarkers[id]) { map.setView(roadDamageMarkers[id].getLatLng(), 17); roadDamageMarkers[id].openPopup(); }
|
|
};
|
|
|
|
document.querySelectorAll('.filter-chip').forEach(chip => {
|
|
chip.addEventListener('click', () => {
|
|
document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
|
chip.classList.add('active');
|
|
filterSidebar();
|
|
});
|
|
});
|
|
document.getElementById('searchInput').addEventListener('input', filterSidebar);
|
|
|
|
// ================= CRUD FUNCTIONS (SPBU, Jalan, Tanah) =================
|
|
window.editMarker = function(id) {
|
|
let d = dataSpbu.find(x => x.id == id);
|
|
let form = `<div class="edit-popup">
|
|
<input id="nama" value="${d.nama_spbu}"><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>
|
|
<button class="btn-save" onclick="saveEditMarker(${id})">Simpan</button>
|
|
<button class="btn-cancel" onclick="refreshAllPopups()">Batal</button>
|
|
</div>`;
|
|
markers[id].setPopupContent(form).openPopup();
|
|
};
|
|
window.saveEditMarker = function(id) {
|
|
let nama = document.getElementById('nama').value;
|
|
let no = document.getElementById('no').value;
|
|
let status = document.getElementById('status').value;
|
|
postForm('update_data.php', { id, nama, no, status })
|
|
.then(res=>{ if(res==="success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
window.deleteMarker = function(id){
|
|
if (!confirm("Hapus SPBU?")) return;
|
|
postForm('delete.php', { id })
|
|
.then(res => { if (res === "success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
window.editJalan = function(id){
|
|
let j = jalanData.find(j=>j.id==id);
|
|
let form = `<div class="edit-popup"><input id="nama_jalan" value="${j.nama_jalan}"><select id="status_jalan"><option ${j.status=="Nasional"?"selected":""}>Nasional</option><option ${j.status=="Provinsi"?"selected":""}>Provinsi</option><option ${j.status=="Kabupaten"?"selected":""}>Kabupaten</option></select><button class="btn-save" onclick="saveEditJalan(${id})">Simpan</button><button class="btn-cancel" onclick="refreshAllPopups()">Batal</button></div>`;
|
|
polylines[id].setPopupContent(form).openPopup();
|
|
};
|
|
window.saveEditJalan = function(id){
|
|
let nama = document.getElementById('nama_jalan').value;
|
|
let status = document.getElementById('status_jalan').value;
|
|
postForm('update_jalan.php', { id, nama, status })
|
|
.then(res=>{ if(res==="success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
window.deleteJalan = function(id){
|
|
if (!confirm("Hapus jalan?")) return;
|
|
postForm('delete_jalan.php', { id })
|
|
.then(res => { if (res === "success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
window.editParsil = function(id){
|
|
let p = parsilData.find(p=>p.id==id);
|
|
let form = `<div class="edit-popup"><input id="pemilik" value="${p.pemilik}"><select id="status_tanah"><option ${p.status=="SHM"?"selected":""}>SHM</option><option ${p.status=="HGB"?"selected":""}>HGB</option><option ${p.status=="HGU"?"selected":""}>HGU</option><option ${p.status=="HP"?"selected":""}>HP</option></select><button class="btn-save" onclick="saveEditParcil(${id})">Simpan</button><button class="btn-cancel" onclick="refreshAllPopups()">Batal</button></div>`;
|
|
polygons[id].setPopupContent(form).openPopup();
|
|
};
|
|
window.saveEditParcil = function(id){
|
|
let pemilik = document.getElementById('pemilik').value;
|
|
let status = document.getElementById('status_tanah').value;
|
|
postForm('update_parsil.php', { id, pemilik, status })
|
|
.then(res=>{ if(res==="success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
window.deleteParsil = function(id){
|
|
if (!confirm("Hapus persil?")) return;
|
|
postForm('delete_parsil.php', { id })
|
|
.then(res => { if (res === "success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
// ================= CRUD JALAN RUSAK =================
|
|
// Variabel untuk marker sementara saat mode road_damage
|
|
let tempRoadMarker = null;
|
|
|
|
// Hapus event click lama dan ganti dengan yang baru (mencakup road_damage)
|
|
map.off('click');
|
|
|
|
let tempMarker = null;
|
|
|
|
map.on('click', function(e) {
|
|
if (!globalEditMode) return;
|
|
if (customDrawingActive) { addCustomPoint(e.latlng); return; }
|
|
if (activeMode === 'marker_add') {
|
|
if (tempMarker) map.removeLayer(tempMarker);
|
|
tempMarker = L.marker(e.latlng, { icon: blueIcon }).addTo(map);
|
|
let form = `<div class="edit-popup">
|
|
<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>
|
|
<button class="btn-save" onclick="simpanMarkerBaru(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
|
|
<button class="btn-cancel" onclick="batalMarkerBaru()">Batal</button>
|
|
</div>`;
|
|
tempMarker.bindPopup(form).openPopup();
|
|
} else if (activeMode === 'road_damage') {
|
|
if (tempRoadMarker) map.removeLayer(tempRoadMarker);
|
|
tempRoadMarker = L.marker(e.latlng, { icon: damageIcon }).addTo(map);
|
|
let form = `<div class="edit-popup">
|
|
<textarea id="deskripsi_lapor" rows="3" placeholder="Deskripsi kerusakan jalan..." autocomplete="off"></textarea>
|
|
<input type="file" id="foto_lapor" accept="image/*" required>
|
|
<button class="btn-save" onclick="simpanRoadDamageBaru(${e.latlng.lat}, ${e.latlng.lng})">Laporkan</button>
|
|
<button class="btn-cancel" onclick="batalRoadDamageBaru()">Batal</button>
|
|
</div>`;
|
|
tempRoadMarker.bindPopup(form).openPopup();
|
|
}
|
|
});
|
|
|
|
window.batalMarkerBaru = function() { if(tempMarker) map.removeLayer(tempMarker); tempMarker=null; };
|
|
window.simpanMarkerBaru = function(lat,lng) {
|
|
let nama = document.getElementById('nama_baru').value.trim();
|
|
let no = document.getElementById('no_baru').value.trim();
|
|
let status = document.getElementById('status_baru').value;
|
|
if(!nama) { alert("❌ Nama SPBU harus diisi!"); return; }
|
|
if(!no) { alert("❌ Nomor SPBU harus diisi!"); return; }
|
|
if(!status) { alert("❌ Status harus dipilih!"); return; }
|
|
postForm('insert_data.php', { nama, no, status, lat, lng })
|
|
.then(res=>{ if(res==="success") location.reload(); else alert("Error:"+res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
window.batalRoadDamageBaru = function() { if(tempRoadMarker) map.removeLayer(tempRoadMarker); tempRoadMarker=null; };
|
|
window.simpanRoadDamageBaru = function(lat, lng) {
|
|
let deskripsi = document.getElementById('deskripsi_lapor').value.trim();
|
|
let fileInput = document.getElementById('foto_lapor');
|
|
if (!deskripsi) { alert("❌ Deskripsi harus diisi!"); return; }
|
|
if (!fileInput.files.length) { alert("❌ Foto harus diupload!"); return; }
|
|
let formData = new FormData();
|
|
formData.append('deskripsi', deskripsi);
|
|
formData.append('foto', fileInput.files[0]);
|
|
formData.append('lat', lat);
|
|
formData.append('lng', lng);
|
|
uploadForm('insert_jalan_rusak.php', formData)
|
|
.then(res => { if (res === "success") location.reload(); else alert("Gagal simpan: " + res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
// Edit & Delete jalan rusak
|
|
window.saveEditRoadDamage = function(id) {
|
|
let rd = roadDamageData.find(r => r.id == id);
|
|
let deskripsiBaru = document.getElementById('deskripsi_edit').value.trim();
|
|
let fotoFile = document.getElementById('foto_edit').files[0];
|
|
if (!deskripsiBaru) { alert("Deskripsi tidak boleh kosong"); return; }
|
|
let formData = new FormData();
|
|
formData.append('id', id);
|
|
formData.append('deskripsi', deskripsiBaru);
|
|
if (fotoFile) formData.append('foto', fotoFile);
|
|
formData.append('foto_lama', rd.foto);
|
|
uploadForm('update_jalan_rusak.php', formData)
|
|
.then(res => { if (res === "success") location.reload(); else alert("Gagal update: " + res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
window.deleteRoadDamage = function(id) {
|
|
if (!confirm("Hapus laporan jalan rusak?")) return;
|
|
let rd = roadDamageData.find(r => r.id == id);
|
|
postForm('delete_jalan_rusak.php', { id, foto: rd.foto })
|
|
.then(res => { if (res === "success") location.reload(); else alert("Gagal hapus: " + res); })
|
|
.catch(err => alert("Error: " + err));
|
|
};
|
|
|
|
// ================= INITIAL =================
|
|
document.getElementById('undoPointBtn').addEventListener('click', undoLastPoint);
|
|
document.getElementById('finishDrawBtn').addEventListener('click', finishCustomDrawing);
|
|
document.getElementById('cancelDrawBtn').addEventListener('click', cancelCustomDrawing);
|
|
// Tidak ada mode aktif saat awal
|
|
setActiveMode(null);
|
|
setGlobalMode('edit'); // default mode edit
|
|
</script>
|
|
</body>
|
|
</html>
|