This commit is contained in:
2026-06-08 17:07:17 +07:00
parent a8349a43af
commit afd656779f
14 changed files with 7510 additions and 1142 deletions
-21
View File
@@ -1,21 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
if ($id <= 0) {
echo json_encode(["status" => "error", "message" => "ID tidak valid"]);
exit;
}
$sql = "DELETE FROM spbu WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
-21
View File
@@ -1,21 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
if ($id <= 0) {
echo json_encode(["status" => "error", "message" => "ID tidak valid"]);
exit;
}
$sql = "DELETE FROM jalan WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
-21
View File
@@ -1,21 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
if ($id <= 0) {
echo json_encode(["status" => "error", "message" => "ID tidak valid"]);
exit;
}
$sql = "DELETE FROM parsil WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
-25
View File
@@ -1,25 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$sql = "SELECT * FROM spbu";
$result = $conn->query($sql);
if (!$result) {
echo json_encode([
"status" => "error",
"message" => "Gagal membaca tabel spbu",
"detail" => $conn->error
]);
exit;
}
$data = [];
while($row = $result->fetch_assoc()){
$data[] = $row;
}
echo json_encode($data);
?>
-25
View File
@@ -1,25 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$sql = "SELECT * FROM jalan";
$result = $conn->query($sql);
if (!$result) {
echo json_encode([
"status" => "error",
"message" => "Gagal membaca tabel jalan",
"detail" => $conn->error
]);
exit;
}
$data = [];
while($row = $result->fetch_assoc()){
$data[] = $row;
}
echo json_encode($data);
?>
-25
View File
@@ -1,25 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$sql = "SELECT * FROM parsil";
$result = $conn->query($sql);
if (!$result) {
echo json_encode([
"status" => "error",
"message" => "Gagal membaca tabel parsil",
"detail" => $conn->error
]);
exit;
}
$data = [];
while($row = $result->fetch_assoc()){
$data[] = $row;
}
echo json_encode($data);
?>
-53
View File
@@ -1,53 +0,0 @@
<?php
ini_set('display_errors', 0);
error_reporting(0);
ob_start();
include "db_config.php";
header('Content-Type: application/json');
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
$nama = $data['nama'] ?? '';
$no = $data['no'] ?? '';
$status = $data['status'] ?? 'Tidak';
$lat = floatval($data['lat'] ?? 0);
$lng = floatval($data['lng'] ?? 0);
if(!$nama || !$no) {
ob_clean();
echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]);
exit;
}
try {
$stmt = $conn->prepare("INSERT INTO spbu (nama,no,status,lat,lng) VALUES (?,?,?,?,?)");
if(!$stmt) {
$err = $conn->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU PREPARE ERR: $err\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]);
exit;
}
$stmt->bind_param("sssdd", $nama, $no, $status, $lat, $lng);
if($stmt->execute()){
$id = $stmt->insert_id;
ob_clean();
echo json_encode(["status"=>"success","id"=>$id]);
} else {
$err = $stmt->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU EXEC ERR: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]);
}
$stmt->close();
} catch (Exception $e) {
$err = $e->getMessage();
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU EXCEPTION: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]);
}
?>
-59
View File
@@ -1,59 +0,0 @@
<?php
ini_set('display_errors', 0);
error_reporting(0);
ob_start();
include "db_config.php";
header('Content-Type: application/json');
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
$nama = $data['nama'] ?? '';
$status = $data['status'] ?? 'Kabupaten';
$panjang = floatval($data['panjang'] ?? 0);
$koordinatInput = $data['koordinat'] ?? [];
if (is_string($koordinatInput)) {
$decoded = json_decode($koordinatInput, true);
$koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]';
} else {
$koordinat = json_encode($koordinatInput);
}
if(!$nama) {
ob_clean();
echo json_encode(["status" => "error", "message" => "Nama tidak boleh kosong"]);
exit;
}
try {
$stmt = $conn->prepare("INSERT INTO jalan (nama,status,panjang,koordinat) VALUES (?,?,?,?)");
if(!$stmt) {
$err = $conn->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN PREPARE ERR: $err\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]);
exit;
}
$stmt->bind_param("ssds", $nama, $status, $panjang, $koordinat);
if($stmt->execute()){
$id = $stmt->insert_id;
ob_clean();
echo json_encode(["status"=>"success","id"=>$id]);
} else {
$err = $stmt->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN EXEC ERR: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]);
}
$stmt->close();
} catch (Exception $e) {
$err = $e->getMessage();
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN EXCEPTION: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]);
}
?>
-60
View File
@@ -1,60 +0,0 @@
<?php
ini_set('display_errors', 0);
error_reporting(0);
ob_start();
include "db_config.php";
header('Content-Type: application/json');
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
$nama = $data['nama'] ?? '';
$pemilik = $data['pemilik'] ?? '';
$status = $data['status'] ?? 'SHM';
$luas = floatval($data['luas'] ?? 0);
$koordinatInput = $data['koordinat'] ?? [];
if (is_string($koordinatInput)) {
$decoded = json_decode($koordinatInput, true);
$koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]';
} else {
$koordinat = json_encode($koordinatInput);
}
if(!$nama) {
ob_clean();
echo json_encode(["status" => "error", "message" => "Nama tidak boleh kosong"]);
exit;
}
try {
$stmt = $conn->prepare("INSERT INTO parsil (nama, pemilik, status, luas, koordinat) VALUES (?,?,?,?,?)");
if(!$stmt) {
$err = $conn->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL PREPARE ERR: $err\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]);
exit;
}
$stmt->bind_param("sssds", $nama, $pemilik, $status, $luas, $koordinat);
if($stmt->execute()){
$id = $stmt->insert_id;
ob_clean();
echo json_encode(["status"=>"success","id"=>$id]);
} else {
$err = $stmt->error;
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL EXEC ERR: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]);
}
$stmt->close();
} catch (Exception $e) {
$err = $e->getMessage();
file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL EXCEPTION: $err | Payload: $raw\n", FILE_APPEND);
ob_clean();
echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]);
}
?>
-27
View File
@@ -1,27 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
$nama = $conn->real_escape_string($data['nama'] ?? '');
$no = $conn->real_escape_string($data['no'] ?? '');
$status = $conn->real_escape_string($data['status'] ?? 'Tidak');
$lat = isset($data['lat']) ? floatval($data['lat']) : 0;
$lng = isset($data['lng']) ? floatval($data['lng']) : 0;
if ($id <= 0 || $nama === '' || $no === '') {
echo json_encode(["status" => "error", "message" => "Data tidak valid"]);
exit;
}
$sql = "UPDATE spbu SET nama='$nama', no='$no', status='$status', lat='$lat', lng='$lng' WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
-35
View File
@@ -1,35 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
$nama = $conn->real_escape_string($data['nama'] ?? '');
$status = $conn->real_escape_string($data['status'] ?? 'Kabupaten');
$panjang = isset($data['panjang']) ? floatval($data['panjang']) : 0;
$koordinatInput = $data['koordinat'] ?? [];
if ($id <= 0 || $nama === '') {
echo json_encode(["status" => "error", "message" => "Data tidak valid"]);
exit;
}
if (is_string($koordinatInput)) {
$decoded = json_decode($koordinatInput, true);
$koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]';
} else {
$koordinat = json_encode($koordinatInput);
}
$koordinat = $conn->real_escape_string($koordinat);
$sql = "UPDATE jalan SET nama='$nama', status='$status', panjang='$panjang', koordinat='$koordinat' WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
-36
View File
@@ -1,36 +0,0 @@
<?php
include "db_config.php";
header('Content-Type: application/json');
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? intval($data['id']) : 0;
$nama = $conn->real_escape_string($data['nama'] ?? '');
$pemilik = $conn->real_escape_string($data['pemilik'] ?? '');
$status = $conn->real_escape_string($data['status'] ?? 'SHM');
$luas = isset($data['luas']) ? floatval($data['luas']) : 0;
$koordinatInput = $data['koordinat'] ?? [];
if ($id <= 0 || $nama === '') {
echo json_encode(["status" => "error", "message" => "Data tidak valid"]);
exit;
}
if (is_string($koordinatInput)) {
$decoded = json_decode($koordinatInput, true);
$koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]';
} else {
$koordinat = json_encode($koordinatInput);
}
$koordinat = $conn->real_escape_string($koordinat);
$sql = "UPDATE parsil SET nama='$nama', pemilik='$pemilik', status='$status', luas='$luas', koordinat='$koordinat' WHERE id=$id";
if ($conn->query($sql)) {
echo json_encode(["status" => "success"]);
} else {
echo json_encode(["status" => "error", "message" => $conn->error]);
}
?>
+28 -734
View File
@@ -2950,10 +2950,7 @@
}
.pf h4 i { font-size: 15px !important; opacity: .9; }
/* Color the h4 based on icon color */
.pf h4:has(.fa-gas-pump) { background: linear-gradient(135deg,#064e3b,#059669); }
.pf h4:has(.fa-road) { background: linear-gradient(135deg,#1e3a8a,#2563eb); }
.pf h4:has(.fa-vector-square){ background: linear-gradient(135deg,#78350f,#d97706); }
.pf label {
display: block;
font-size: 10px !important;
@@ -3284,10 +3281,7 @@
</div>
<div id="list-masjid" style="display:none;"><span id="cnt-masjid">0</span></div>
<div id="list-kemiskinan" style="display:none;"><span id="cnt-kemiskinan">0</span></div>
<div id="list-spbu" style="display:none;"><span id="cnt-spbu">0</span></div>
<div id="list-jalan" style="display:none;"><span id="cnt-jalan">0</span></div>
<div id="list-parsil" style="display:none;"><span id="cnt-parsil">0</span></div>
</div>
</div>
<div class="leg-sec">
<div class="sec-lbl" style="margin-bottom:10px">Legenda Peta</div>
@@ -3341,20 +3335,7 @@
<span>Rumah Ibadah <small>Tambah lokasi ibadah</small></span>
</div>
<div style="height:1px;background:#e2e8f0;margin:4px 4px"></div>
<div style="padding:6px 10px 2px;font-size:10px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.6px">Opsional</div>
<div class="add-item" onclick="setMode('spbu');closeAddMenu()">
<span class="ai-icon" style="background:#dcfce7;color:#16a34a"><i class="fas fa-gas-pump"></i></span>
<span>SPBU <small>Tambah titik SPBU</small></span>
</div>
<div class="add-item" onclick="setMode('jalan');closeAddMenu()">
<span class="ai-icon" style="background:#dbeafe;color:#2563eb"><i class="fas fa-road"></i></span>
<span>Jalan <small>Gambar ruas jalan</small></span>
</div>
<div class="add-item" onclick="setMode('parsil');closeAddMenu()">
<span class="ai-icon" style="background:#fef3c7;color:#d97706"><i class="fas fa-vector-square"></i></span>
<span>Parsil / Lahan <small>Gambar area persil</small></span>
</div>
</div>
</div>
<!-- Icon buttons -->
@@ -3523,10 +3504,10 @@
/* STATE */
const state = {
mode: null, spbu: [], jalan: [], parsil: [], kemiskinan: [], masjid: [],
featureRefs: { spbu: {}, jalan: {}, parsil: {}, kemiskinan: {}, masjid: {} },
mode: null, kemiskinan: [], masjid: [],
featureRefs: { kemiskinan: {}, masjid: {} },
kkLayerRefs: {}, blankRefs: {}, useSatellite: false, geometryEdit: null, activeCard: null,
spbuFilterVis: { open24: true, non24: true }, drawingLine: false, drawingPoly: false,
drawingLine: false, drawingPoly: false,
tempPoints: [], tempMarkers: [], tempPolyline: null, tempPolygon: null
};
@@ -3542,22 +3523,12 @@
const layers = {
kemiskinan: L.layerGroup().addTo(map),
masjid: L.layerGroup().addTo(map),
blankspot: L.layerGroup().addTo(map),
// Layer opsional — OFF by default (tidak .addTo(map))
spbu24: L.layerGroup(),
spbuNon24: L.layerGroup(),
jalan: L.layerGroup(),
parsil: L.layerGroup()
};
blankspot: L.layerGroup().addTo(map),};
const layersControl = L.control.layers(null, {
'<span class="lc-item"><span class="lc-dot lc-dot-kemiskinan"></span><span class="lc-text"><strong>Kemiskinan</strong><small>Data KK miskin</small></span></span>': layers.kemiskinan,
'<span class="lc-item"><span class="lc-dot lc-dot-masjid"></span><span class="lc-text"><strong>Rumah Ibadah</strong><small>Fasilitas ibadah</small></span></span>': layers.masjid,
'<span class="lc-item"><span class="lc-dot" style="background:#64748b"></span><span class="lc-text"><strong>Blank Spot</strong><small>Di luar radius rumah ibadah</small></span></span>': layers.blankspot,
'<span class="lc-item"><span class="lc-dot" style="background:#16a34a"></span><span class="lc-text"><strong>SPBU 24 Jam</strong><small>Buka 24 jam</small></span></span>': layers.spbu24,
'<span class="lc-item"><span class="lc-dot" style="background:#dc2626"></span><span class="lc-text"><strong>SPBU Non-24</strong><small>Tidak 24 jam</small></span></span>': layers.spbuNon24,
'<span class="lc-item"><span class="lc-dot" style="background:#2563eb"></span><span class="lc-text"><strong>Jalan</strong><small>Jaringan jalan</small></span></span>': layers.jalan,
'<span class="lc-item"><span class="lc-dot" style="background:#d97706"></span><span class="lc-text"><strong>Parsil / Lahan</strong><small>Data persil tanah</small></span></span>': layers.parsil,
}, { collapsed: false, position: 'topright' }).addTo(map);
}, { collapsed: false, position: 'topright' }).addTo(map);
decorateLayersControl();
function decorateLayersControl() {
const container = layersControl.getContainer();
@@ -3565,7 +3536,7 @@
const overlays = container.querySelector('.leaflet-control-layers-overlays');
if (!overlays) return;
const labels = Array.from(overlays.querySelectorAll('label'));
if (labels.length < 6) return;
if (labels.length < 3) return;
overlays.innerHTML = '';
const addGroup = (title, items, color) => {
const head = document.createElement('div');
@@ -3613,7 +3584,7 @@
});
};
addGroup('Tematik Utama', [labels[0], labels[1], labels[2]]);
addGroup('Opsional', [labels[3], labels[4], labels[5], labels[6]], '#64748b');
}
const statusStoreKey = 'gis-kemiskinan-status';
@@ -3897,18 +3868,6 @@
const fWarga = state.kemiskinan.find(d => (`${d.nama_kk} ${d.alamat}`).toLowerCase().includes(q));
if (fWarga) return focusFeature('kemiskinan', fWarga.id);
// 3. Cari SPBU
const fSpbu = state.spbu.find(d => (d.nama || '').toLowerCase().includes(q));
if (fSpbu) return focusFeature('spbu', fSpbu.id);
// 4. Cari Jalan
const fJalan = state.jalan.find(d => (d.nama || '').toLowerCase().includes(q));
if (fJalan) return focusFeature('jalan', fJalan.id);
// 5. Cari Parsil
const fParsil = state.parsil.find(d => (`${d.nama} ${d.pemilik}`).toLowerCase().includes(q));
if (fParsil) return focusFeature('parsil', fParsil.id);
// Jika tidak ketemu
alert('Data "' + q + '" tidak ditemukan.');
}
@@ -4124,7 +4083,7 @@
function setMode(m) {
cancelDrawing();
state.mode = state.mode === m ? null : m;
['kemiskinan', 'masjid', 'spbu', 'jalan', 'parsil'].forEach(x => {
['kemiskinan', 'masjid'].forEach(x => {
const el = document.getElementById('btn-' + x);
if (el) el.className = 'mbtn';
});
@@ -4134,11 +4093,6 @@
const msgs = {
kemiskinan: 'Klik peta untuk menambah data Kemiskinan',
masjid: 'Klik peta untuk menambah titik Rumah Ibadah',
spbu: 'Klik peta untuk menambah titik SPBU',
jalan: 'Klik untuk gambar jalan — klik ganda untuk selesai',
parsil: 'Klik untuk gambar parsil — klik titik awal atau dbl-klik selesai',
line: 'Klik untuk gambar jalan — klik ganda untuk selesai',
poly: 'Klik untuk gambar parsil — klik titik awal atau dbl-klik selesai',
};
hint.textContent = msgs[m] || 'Klik peta';
hint.classList.add('show');
@@ -4157,14 +4111,9 @@
if (!state.mode) return;
if (state.mode === 'kemiskinan') handleKemiskinanClick(e);
else if (state.mode === 'masjid') handleMasjidClick(e);
else if (state.mode === 'spbu') handlePointClick(e);
else if (state.mode === 'jalan') handleLineClick(e);
else if (state.mode === 'parsil') handlePolyClick(e);
});
map.on('dblclick', function (e) {
if ((state.mode === 'jalan' || state.mode === 'line') && state.drawingLine) finishLine(e);
if ((state.mode === 'parsil' || state.mode === 'poly') && state.drawingPoly) finishPoly(e);
if (['jalan','parsil','line','poly'].includes(state.mode)) L.DomEvent.stopPropagation(e);
});
/* ── MODERN MARKER ICON ── */
@@ -4229,102 +4178,9 @@
window._tmpM = tmpM;
}
function savePoint() {
const nama = document.getElementById('p-nama').value.trim();
const no = document.getElementById('p-no').value.trim();
const status = document.getElementById('p-status').value;
const lat = parseFloat(document.getElementById('p-lat').value);
const lng = parseFloat(document.getElementById('p-lng').value);
if (!nama || !no) {
alert('Lengkapi nama dan nomor SPBU!');
return;
}
fetch("api/save_data.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
nama: nama,
no: no,
status: status,
lat: lat,
lng: lng
})
})
.then(res => res.json())
.then(response => {
if (response.status == "success") {
const d = {
id: response.id || Date.now(),
nama,
no,
status,
lat,
lng
};
state.spbu.push(d);
renderSPBU(d);
updateCount('spbu', state.spbu.length);
addCard(
'spbu',
d.id,
nama,
'No: ' + no,
status === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam',
status === 'Ya' ? '#1a6b3c' : '#b93328'
);
if (window._tmpM) {
map.closePopup();
map.removeLayer(window._tmpM);
window._tmpM = null;
}
setMode(null);
ensureLayerVisible('spbu', d.id);
focusFeature('spbu', d.id);
} else {
alert("Gagal menyimpan: " + (response.message || "Periksa database"));
}
})
.catch(err => {
console.error(err);
alert("Terjadi error jaringan");
});
}
function renderSPBU(d) {
const color = d.status === 'Ya' ? '#10b981' : '#ef4444';
const icon = createModernMarker(color, 'fas fa-gas-pump', d.nama);
const m = L.marker([d.lat, d.lng], { icon, draggable: true });
m.bindPopup(`<div class="pi">
<h4><i class="fas fa-gas-pump" style="color:${color}"></i>${d.nama}<button class="pi-close-alt" onclick="map.closePopup()"><i class="fas fa-times"></i></button></h4>
<div class="pi-body">
<div class="pi-row"><span class="pi-k">No. SPBU</span><span class="pi-v">${d.no}</span></div>
<div class="pi-row"><span class="pi-k">Status</span><span class="pi-v" style="color:${color}">${d.status === 'Ya' ? '&#10004; Buka 24 Jam' : '&#10006; Tidak Buka 24 Jam'}</span></div>
<div class="pi-row"><span class="pi-k">Koordinat</span><span class="pi-v" style="font-size:11px;font-family:monospace">${d.lat.toFixed(5)}, ${d.lng.toFixed(5)}</span></div>
</div>
<div class="act-row">
<button class="pbtn pbtn-y" onclick="openEditSpbu(${d.id})">Edit</button>
<button class="pbtn pbtn-r" onclick="deleteFeature('spbu',${d.id})">Hapus</button>
</div>
</div>`, { autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] });
m.addTo(d.status === 'Ya' ? layers.spbu24 : layers.spbuNon24);
state.featureRefs.spbu[String(d.id)] = m;
}
/* ── JALAN ── */
function handleLineClick(e) {
@@ -4337,231 +4193,14 @@
document.getElementById('hint').textContent = `📍 ${state.tempPoints.length} titik — klik ganda untuk selesai`;
}
function finishLine(e) {
if (state.tempPoints.length < 2) { alert('Minimal 2 titik!'); return; }
const pts = [...state.tempPoints];
const len = calcLen(pts);
window._tempLinePts = pts;
window._tempLineLen = len;
clearTemp();
L.popup({ closeOnClick: false, keepInView: true, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] })
.setLatLng(pts[Math.floor(pts.length / 2)])
.setContent(`<div class="pf">
<h4><i class="fas fa-road"></i> Tambah Jalan</h4>
<label>Nama Jalan</label>
<input id="j-nama" placeholder="Jl. Ahmad Yani">
<label>Status Jalan</label>
<select id="j-status">
<option value="Nasional">Jalan Nasional</option>
<option value="Provinsi">Jalan Provinsi</option>
<option value="Kabupaten" selected>Jalan Kabupaten</option>
</select>
<div style="display:flex;align-items:center;gap:10px;margin:10px 16px 4px;padding:9px 12px;background:#eff6ff;border-radius:9px;border:1px solid #dbeafe">
<i class="fas fa-ruler-horizontal" style="color:#2563eb;font-size:13px"></i>
<span style="font-size:11px;color:#64748b;font-weight:600">Panjang terukur:</span>
<span style="font-size:14px;font-weight:800;color:#1d4ed8;margin-left:auto">${fmtLen(len)}</span>
</div>
<button class="pbtn pbtn-b" onclick="saveLine()">Simpan Jalan</button>
</div>`).addTo(map);
}
function saveLine() {
const pts = window._tempLinePts || [];
const len = window._tempLineLen || 0;
const nama = document.getElementById('j-nama').value.trim();
const status = document.getElementById('j-status').value;
if (!nama) {
alert('Isi nama jalan!');
return;
}
fetch("api/save_jalan.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
nama: nama,
status: status,
panjang: len,
koordinat: pts
})
})
.then(res => res.json())
.then(response => {
if (response.status == "success") {
const d = {
id: response.id || Date.now(),
nama,
status,
pts,
len
};
state.jalan.push(d);
renderJalan(d);
updateCount('jalan', state.jalan.length);
addCard(
'jalan',
d.id,
nama,
status + ' · ' + fmtLen(len),
status,
jColor[status]
);
map.closePopup();
setMode(null);
ensureLayerVisible('jalan', d.id);
focusFeature('jalan', d.id);
} else {
// log removed
alert("Gagal simpan jalan");
}
})
.catch(err => {
// log removed
alert("Error simpan jalan");
});
}
function renderJalan(d) {
const pts = normalizeLinePoints(d.pts);
if (pts.length < 2) {
// log removed
return;
}
const c = jColor[d.status] || '#ef4444';
const w =
d.status === 'Nasional' ? 6 :
d.status === 'Provinsi' ? 5 : 4;
const line = L.polyline(pts, {
color: c,
weight: w,
opacity: 0.95,
lineCap: 'round',
lineJoin: 'round',
className: 'path-glow'
})
.bindPopup(`
<div class="pi">
<h4><i class="fas fa-road" style="color:${c}"></i>${d.nama}<button class="pi-close-alt" onclick="map.closePopup()"><i class="fas fa-times"></i></button></h4>
<div class="pi-body">
<div class="pi-row">
<span class="pi-k">Status</span>
<span class="pi-v">${d.status}</span>
</div>
<div class="pi-row">
<span class="pi-k">Panjang</span>
<span class="pi-v">${fmtLen(d.len)}</span>
</div>
</div>
<div class="act-row">
<button class="pbtn pbtn-y" onclick="openEditJalan(${d.id})">Edit</button>
<button class="pbtn pbtn-r" onclick="deleteFeature('jalan',${d.id})">Hapus</button>
</div>
</div>
`, { autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] })
.addTo(layers.jalan);
state.featureRefs.jalan[String(d.id)] = line;
}
function normalizeLinePoints(raw) {
let pts = raw;
if (typeof pts === 'string') {
try {
pts = JSON.parse(pts);
} catch (_) {
return [];
}
}
// Kompatibilitas data lama: koordinat sempat tersimpan sebagai JSON string di dalam JSON string.
if (typeof pts === 'string') {
try {
pts = JSON.parse(pts);
} catch (_) {
return [];
}
}
if (!Array.isArray(pts) || pts.length === 0) return [];
if (Array.isArray(pts[0])) {
return pts
.map(p => [parseFloat(p[0]), parseFloat(p[1])])
.filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1]));
}
if (pts[0] && typeof pts[0] === 'object' && pts[0].lat !== undefined) {
return pts
.map(p => [parseFloat(p.lat), parseFloat(p.lng)])
.filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1]));
}
return [];
}
function normalizePolyPoints(raw) {
let pts = raw;
if (typeof pts === 'string') {
try {
pts = JSON.parse(pts);
} catch (_) {
return [];
}
}
if (typeof pts === 'string') {
try {
pts = JSON.parse(pts);
} catch (_) {
return [];
}
}
if (!Array.isArray(pts) || pts.length === 0) return [];
if (Array.isArray(pts[0])) {
return pts
.map(p => [parseFloat(p[0]), parseFloat(p[1])])
.filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1]));
}
if (pts[0] && typeof pts[0] === 'object' && pts[0].lat !== undefined) {
return pts
.map(p => [parseFloat(p.lat), parseFloat(p.lng)])
.filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1]));
}
return [];
}
/* ── PARSIL ── */
function handlePolyClick(e) {
@@ -4585,160 +4224,11 @@
`🗺️ ${state.tempPoints.length} titik — klik titik awal atau dbl-klik selesai`;
}
function finishPoly(e) {
if (state.tempPoints.length < 3) { alert('Minimal 3 titik!'); return; }
const pts = [...state.tempPoints];
const area = calcArea(pts);
window._tempPolyPts = pts;
window._tempPolyArea = area;
clearTemp();
const center = L.polygon(pts).getBounds().getCenter();
L.popup({ closeOnClick: false, keepInView: true, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] })
.setLatLng(center)
.setContent(`<div class="pf">
<h4><i class="fas fa-vector-square"></i> Tambah Parsil / Lahan</h4>
<label>Nama / ID Parsil</label>
<input id="ps-nama" placeholder="Parsil A-01">
<label>Pemilik</label>
<input id="ps-pemilik" placeholder="Nama pemilik">
<label>Status Sertifikat</label>
<select id="ps-status">
<option value="SHM">SHM &mdash; Hak Milik</option>
<option value="HGB">HGB &mdash; Hak Guna Bangunan</option>
<option value="Sewa">Sewa / Kontrak</option>
<option value="Lainnya">Lainnya</option>
</select>
<div style="display:flex;align-items:center;gap:10px;margin:10px 16px 4px;padding:9px 12px;background:#fffbeb;border-radius:9px;border:1px solid #fde68a">
<i class="fas fa-ruler-combined" style="color:#d97706;font-size:13px"></i>
<span style="font-size:11px;color:#64748b;font-weight:600">Luas terukur:</span>
<span style="font-size:14px;font-weight:800;color:#d97706;margin-left:auto">${fmtArea(area)}</span>
</div>
<button class="pbtn pbtn-y" onclick="saveParsil()">Simpan Parsil</button>
</div>`).addTo(map);
}
function saveParsil() {
const pts = window._tempPolyPts || [];
const area = window._tempPolyArea || 0;
const nama = document.getElementById('ps-nama').value.trim();
const pemilik = document.getElementById('ps-pemilik').value.trim();
const status = document.getElementById('ps-status').value;
if (!nama) {
alert('Isi nama parsil!');
return;
}
fetch("api/save_parsil.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
nama: nama,
pemilik: pemilik,
status: status,
luas: area,
koordinat: pts
})
})
.then(res => res.json())
.then(response => {
if (response.status == "success") {
const parsil = {
id: response.id || Date.now(),
nama,
pemilik,
status,
pts,
area
};
state.parsil.push(parsil);
renderParsil(parsil);
updateCount('parsil', state.parsil.length);
addCard(
'parsil',
parsil.id,
parsil.nama,
parsil.status,
parsil.status,
'#1a5276'
);
map.closePopup();
setMode(null);
ensureLayerVisible('parsil', parsil.id);
focusFeature('parsil', parsil.id);
}
else {
// log removed
alert("Gagal simpan parsil");
}
})
.catch(err => {});
}
function renderParsil(d) {
const pts = normalizePolyPoints(d.pts);
if (pts.length < 3) {
// log removed
return;
}
const c = pColor[d.status];
const poly = L.polygon(pts, {
color: c,
fillColor: c,
fillOpacity: 0.25,
weight: 3,
dashArray: '8, 6',
lineCap: 'round',
lineJoin: 'round',
className: 'parsil-glow'
})
.bindPopup(`
<div class="pi">
<h4><i class="fas fa-draw-polygon" style="color:${c}"></i>${d.nama}<button class="pi-close-alt" onclick="map.closePopup()"><i class="fas fa-times"></i></button></h4>
<div class="pi-body">
<div class="pi-row">
<span class="pi-k">Pemilik</span>
<span class="pi-v">${d.pemilik || '-'}</span>
</div>
<div class="pi-row">
<span class="pi-k">Status</span>
<span class="pi-v">${d.status}</span>
</div>
<div class="pi-row">
<span class="pi-k">Luas</span>
<span class="pi-v">${fmtArea(d.area)}</span>
</div>
</div>
<div class="act-row">
<button class="pbtn pbtn-y" onclick="openEditParsil(${d.id})">Edit</button>
<button class="pbtn pbtn-r" onclick="deleteFeature('parsil',${d.id})">Hapus</button>
</div>
</div>
`)
.addTo(layers.parsil);
state.featureRefs.parsil[String(d.id)] = poly;
}
function getById(type, id) {
return state[type].find(x => String(x.id) === String(id));
@@ -4911,11 +4401,7 @@
}
function refreshType(type) {
if (state.geometryEdit && state.geometryEdit.type === type) stopGeometryEdit();
if (type === 'spbu') {
layers.spbu24.clearLayers();
layers.spbuNon24.clearLayers();
} else {
if (state.geometryEdit && state.geometryEdit.type === type) stopGeometryEdit(); else {
layers[type].clearLayers();
}
state.featureRefs[type] = {};
@@ -4952,31 +4438,7 @@
renderDashboard();
}
function openEditSpbu(id) {
const d = getById('spbu', id);
if (!d) return;
const ref = state.featureRefs.spbu[String(id)];
const ll = ref && ref.getLatLng ? ref.getLatLng() : { lat: d.lat, lng: d.lng };
const html = `<div class="pf">
<h4><i class="fas fa-edit" style="color:var(--amber)"></i> Edit SPBU</h4>
<label>Nama SPBU</label>
<input id="e-spbu-nama" value="${(d.nama || '').replace(/"/g, '&quot;')}">
<label>Nomor SPBU</label>
<input id="e-spbu-no" value="${(d.no || '').replace(/"/g, '&quot;')}">
<label>Status Operasional</label>
<select id="e-spbu-status">
<option value="Ya" ${d.status === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
<option value="Tidak" ${d.status === 'Tidak' ? 'selected' : ''}>Tidak Buka 24 Jam</option>
</select>
<label>Latitude</label>
<input id="e-spbu-lat" value="${Number(ll.lat).toFixed(6)}">
<label>Longitude</label>
<input id="e-spbu-lng" value="${Number(ll.lng).toFixed(6)}">
<button class="pbtn pbtn-y" onclick="saveEditSpbu(${id})">Simpan Perubahan</button>
</div>`;
map.closePopup();
L.popup({ maxWidth: 290, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng([ll.lat, ll.lng]).setContent(html).openOn(map);
}
function saveEditSpbu(id) {
const nama = document.getElementById('e-spbu-nama').value.trim();
@@ -5011,33 +4473,7 @@
.catch(() => alert('Error update SPBU'));
}
function openEditJalan(id) {
if (state.geometryEdit && (state.geometryEdit.type !== 'jalan' || String(state.geometryEdit.id) !== String(id))) {
stopGeometryEdit();
}
const d = getById('jalan', id);
if (!d) return;
const pts = normalizeLinePoints(d.pts);
const ref = state.featureRefs.jalan[String(id)];
const center = ref && ref.getBounds ? ref.getBounds().getCenter() : map.getCenter();
const html = `<div class="pf" style="width:290px">
<h4><i class="fas fa-edit" style="color:var(--amber)"></i> Edit Jalan</h4>
<label>Nama Jalan</label>
<input id="e-jalan-nama" value="${(d.nama || '').replace(/"/g, '&quot;')}">
<label>Status Jalan</label>
<select id="e-jalan-status">
<option value="Nasional" ${d.status === 'Nasional' ? 'selected' : ''}>Jalan Nasional</option>
<option value="Provinsi" ${d.status === 'Provinsi' ? 'selected' : ''}>Jalan Provinsi</option>
<option value="Kabupaten" ${d.status === 'Kabupaten' ? 'selected' : ''}>Jalan Kabupaten</option>
</select>
<label>Koordinat Lokasi (satu baris: lat,lng)</label>
<textarea id="e-jalan-pts">${pointsToText(pts)}</textarea>
<button class="pbtn pbtn-b" onclick="startGeometryEdit('jalan',${id})">Edit Vertex / Drag di Peta</button>
<button class="pbtn pbtn-y" onclick="saveEditJalan(${id})">Simpan Perubahan</button>
</div>`;
map.closePopup();
L.popup({ maxWidth: 330, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng(center).setContent(html).openOn(map);
}
function saveEditJalan(id) {
if (state.geometryEdit && state.geometryEdit.type === 'jalan' && String(state.geometryEdit.id) === String(id)) {
@@ -5075,36 +4511,7 @@
.catch(() => alert('Error update jalan'));
}
function openEditParsil(id) {
if (state.geometryEdit && (state.geometryEdit.type !== 'parsil' || String(state.geometryEdit.id) !== String(id))) {
stopGeometryEdit();
}
const d = getById('parsil', id);
if (!d) return;
const pts = normalizePolyPoints(d.pts);
const ref = state.featureRefs.parsil[String(id)];
const center = ref && ref.getBounds ? ref.getBounds().getCenter() : map.getCenter();
const html = `<div class="pf" style="width:290px">
<h4><i class="fas fa-edit" style="color:var(--amber)"></i> Edit Parsil</h4>
<label>Nama / ID Parsil</label>
<input id="e-parsil-nama" value="${(d.nama || '').replace(/"/g, '&quot;')}">
<label>Pemilik</label>
<input id="e-parsil-pemilik" value="${(d.pemilik || '').replace(/"/g, '&quot;')}">
<label>Status Sertifikat</label>
<select id="e-parsil-status">
<option value="SHM" ${d.status === 'SHM' ? 'selected' : ''}>SHM – Hak Milik</option>
<option value="HGB" ${d.status === 'HGB' ? 'selected' : ''}>HGB – Hak Guna Bangunan</option>
<option value="HGU" ${d.status === 'HGU' ? 'selected' : ''}>HGU – Hak Guna Usaha</option>
<option value="HP" ${d.status === 'HP' ? 'selected' : ''}>HP – Hak Pakai</option>
</select>
<label>Koordinat Lokasi (satu baris: lat,lng)</label>
<textarea id="e-parsil-pts">${pointsToText(pts)}</textarea>
<button class="pbtn pbtn-b" onclick="startGeometryEdit('parsil',${id})">Edit Vertex / Drag di Peta</button>
<button class="pbtn pbtn-y" onclick="saveEditParsil(${id})">Simpan Perubahan</button>
</div>`;
map.closePopup();
L.popup({ maxWidth: 330, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng(center).setContent(html).openOn(map);
}
function saveEditParsil(id) {
if (state.geometryEdit && state.geometryEdit.type === 'parsil' && String(state.geometryEdit.id) === String(id)) {
@@ -5290,10 +4697,6 @@
document.addEventListener("DOMContentLoaded", function () {
loadSPBU();
loadJalan();
loadParsil();
// Kemiskinan butuh data Masjid untuk hitung Blank Spot (merah/hijau)
// Jadi pastikan loadMasjid selesai dulu baru loadKemiskinan
loadMasjid().then(() => {
@@ -5302,120 +4705,11 @@
});
function loadSPBU() {
fetch("api/read_data.php")
.then(res => res.json())
.then(data => {
data.forEach(d => {
const spbu = {
id: d.id,
nama: d.nama,
no: d.no,
status: d.status,
lat: parseFloat(d.lat),
lng: parseFloat(d.lng)
};
state.spbu.push(spbu);
renderSPBU(spbu);
updateCount('spbu', state.spbu.length);
addCard(
'spbu',
spbu.id,
spbu.nama,
'No: ' + spbu.no,
spbu.status === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam',
spbu.status === 'Ya' ? '#1a6b3c' : '#b93328'
);
});
})
.catch(err => {});
}
function loadJalan() {
fetch("api/read_jalan.php")
.then(res => res.json())
.then(data => {
data.forEach(d => {
const jalan = {
id: d.id,
nama: d.nama,
status: d.status,
len: parseFloat(d.panjang),
pts: d.koordinat
};
state.jalan.push(jalan);
renderJalan(jalan);
updateCount('jalan', state.jalan.length);
addCard(
'jalan',
jalan.id,
jalan.nama,
jalan.status,
jalan.status,
'#e74c3c'
);
});
})
.catch(err => {});
}
function loadParsil() {
fetch("api/read_parsil.php")
.then(res => res.json())
.then(data => {
data.forEach(d => {
const parsil = {
id: d.id,
nama: d.nama,
pemilik: d.pemilik,
status: d.status,
area: parseFloat(d.luas),
pts: d.koordinat
};
state.parsil.push(parsil);
renderParsil(parsil);
updateCount('parsil', state.parsil.length);
addCard(
'parsil',
parsil.id,
parsil.nama,
parsil.status,
parsil.status,
'#1a5276'
);
});
})
.catch(err => {});
}
/* ── KEMISKINAN ── */
function getKemiskinanDraft() {
+7482
View File
File diff suppressed because it is too large Load Diff