Initial commit
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
include 'db.php';
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
|
||||
$id = (int) $_GET['id'];
|
||||
$conn->query("DELETE FROM tempat WHERE id = $id");
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'edit' &&
|
||||
isset($_GET['id'], $_GET['nama'], $_GET['nomor'], $_GET['status'])) {
|
||||
$id = (int) $_GET['id'];
|
||||
$nama = $conn->real_escape_string($_GET['nama']);
|
||||
$nomor = $conn->real_escape_string($_GET['nomor']);
|
||||
$status = $conn->real_escape_string($_GET['status']);
|
||||
$conn->query("UPDATE tempat SET nama_tempat='$nama', nomor_tempat='$nomor', status='$status' WHERE id=$id");
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'update_pos' &&
|
||||
isset($_GET['id'], $_GET['lat'], $_GET['lng'])) {
|
||||
$id = (int) $_GET['id'];
|
||||
$lat = (float) $_GET['lat'];
|
||||
$lng = (float) $_GET['lng'];
|
||||
$result = $conn->query("UPDATE tempat SET latitude='$lat', longitude='$lng' WHERE id=$id");
|
||||
header('Content-Type: text/plain');
|
||||
echo ($result) ? 'ok' : 'error: ' . $conn->error;
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save'])) {
|
||||
$nama = $conn->real_escape_string($_POST['nama']);
|
||||
$nomor = $conn->real_escape_string($_POST['nomor']);
|
||||
$status = $conn->real_escape_string($_POST['status']);
|
||||
$lat = (float) $_POST['lat'];
|
||||
$lng = (float) $_POST['lng'];
|
||||
$sql = "INSERT INTO tempat (nama_tempat, nomor_tempat, status, latitude, longitude)
|
||||
VALUES ('$nama','$nomor','$status','$lat','$lng')";
|
||||
if ($conn->query($sql)) {
|
||||
header('Location: index.php?saved=1');
|
||||
exit;
|
||||
}
|
||||
header('Location: tambah.php?err=' . urlencode($conn->error));
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
function initBaseLayers(map) {
|
||||
var baseMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap', maxZoom: 19
|
||||
});
|
||||
var baseSat = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||
attribution: '© Esri', maxZoom: 19
|
||||
});
|
||||
baseMap.addTo(map);
|
||||
|
||||
document.getElementById('base-map').addEventListener('click', function() {
|
||||
if (map.hasLayer(baseSat)) map.removeLayer(baseSat);
|
||||
if (!map.hasLayer(baseMap)) baseMap.addTo(map);
|
||||
this.classList.add('active');
|
||||
document.getElementById('base-sat').classList.remove('active');
|
||||
});
|
||||
document.getElementById('base-sat').addEventListener('click', function() {
|
||||
if (map.hasLayer(baseMap)) map.removeLayer(baseMap);
|
||||
if (!map.hasLayer(baseSat)) baseSat.addTo(map);
|
||||
this.classList.add('active');
|
||||
document.getElementById('base-map').classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
function makeSpbuIcon(color, size) {
|
||||
size = size || 18;
|
||||
var h = size / 2;
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: '<div style="width:'+size+'px;height:'+size+'px;border-radius:50%;background:'+color+';border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.35);"></div>',
|
||||
iconSize: [size, size], iconAnchor: [h, h], popupAnchor: [0, -12]
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
var el = document.getElementById('toast');
|
||||
if (!el) return;
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
setTimeout(function() { el.classList.remove('show'); }, 3000);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
:root {
|
||||
--bg: #f1f5f9; --white: #fff; --border: #e2e8f0;
|
||||
--text: #0f172a; --text2: #475569; --text3: #94a3b8;
|
||||
--blue: #1d4ed8; --blue-lt: #eff6ff;
|
||||
--green: #15803d; --green-lt: #f0fdf4;
|
||||
--red: #b91c1c; --red-lt: #fef2f2;
|
||||
--sidebar-w: 320px;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { height: 100%; font-family: 'DM Sans', sans-serif; color: var(--text); background: var(--bg); overflow: hidden; }
|
||||
body { display: flex; }
|
||||
|
||||
#sidebar {
|
||||
width: var(--sidebar-w); min-width: var(--sidebar-w);
|
||||
background: var(--white); border-right: 1px solid var(--border);
|
||||
display: flex; flex-direction: column; z-index: 200;
|
||||
}
|
||||
.sb-head {
|
||||
padding: 18px 16px 14px; border-bottom: 1px solid var(--border);
|
||||
background: var(--white); color: var(--text);
|
||||
}
|
||||
.sb-head-top { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; }
|
||||
.sb-logo {
|
||||
width: 38px; height: 38px; border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--blue), #60a5fa);
|
||||
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
||||
box-shadow: 0 2px 8px rgba(29,78,216,.25);
|
||||
}
|
||||
.sb-logo svg { width: 18px; height: 18px; fill: #fff; }
|
||||
.sb-title h1 { font-size: 14px; font-weight: 700; color: var(--text); }
|
||||
.sb-title p { font-size: 10.5px; color: var(--text3); margin-top: 2px; }
|
||||
.sb-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
|
||||
.sb-stat {
|
||||
background: var(--bg); border: 1px solid var(--border);
|
||||
border-radius: 8px; padding: 8px 6px; text-align: center;
|
||||
}
|
||||
.sb-stat b { display: block; font-size: 16px; font-weight: 800; font-family: 'DM Mono', monospace; line-height: 1; }
|
||||
.sb-stat span { font-size: 8.5px; color: var(--text3); margin-top: 4px; display: block; text-transform: uppercase; letter-spacing: .3px; font-weight: 600; }
|
||||
.sb-stat.total b { color: var(--blue); }
|
||||
.sb-stat.open b { color: var(--green); }
|
||||
.sb-stat.closed b{ color: var(--red); }
|
||||
|
||||
.sb-section-lbl {
|
||||
font-size: 9.5px; font-weight: 700; color: var(--text3);
|
||||
text-transform: uppercase; letter-spacing: .8px; padding: 10px 8px 4px;
|
||||
}
|
||||
.sb-item {
|
||||
display: flex; align-items: center; gap: 9px; padding: 8px 10px;
|
||||
border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 500;
|
||||
color: var(--text2); transition: all .15s; border: 1px solid transparent; text-decoration: none;
|
||||
}
|
||||
.sb-item:hover { background: var(--bg); color: var(--text); }
|
||||
.sb-item.active { background: var(--blue-lt); color: var(--blue); border-color: #bfdbfe; font-weight: 600; }
|
||||
.sb-item .ico { font-size: 16px; width: 20px; text-align: center; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
.sb-body { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.sb-search { padding: 12px; border-bottom: 1px solid var(--border); }
|
||||
.sb-search input {
|
||||
width: 100%; padding: 9px 12px 9px 34px;
|
||||
border: 1px solid var(--border); border-radius: 8px; font-size: 13px;
|
||||
outline: none; font-family: inherit; background: var(--bg);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.099zM6.5 12a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat; background-position: 11px center;
|
||||
}
|
||||
.sb-search input:focus { border-color: var(--blue); background-color: #fff; }
|
||||
|
||||
.sb-filter { display: flex; padding: 0 12px 10px; gap: 5px; }
|
||||
.sb-filter button {
|
||||
flex: 1; padding: 6px 4px; font-size: 11px; font-weight: 600;
|
||||
border: 1px solid var(--border); border-radius: 6px; background: var(--white);
|
||||
color: var(--text2); cursor: pointer; font-family: inherit; transition: all .15s;
|
||||
}
|
||||
.sb-filter button.active { background: var(--blue); color: #fff; border-color: var(--blue); }
|
||||
|
||||
.sb-list-head {
|
||||
padding: 0 14px 8px; display: flex; justify-content: space-between; align-items: center;
|
||||
font-size: 11px; color: var(--text3); font-weight: 600; text-transform: uppercase; letter-spacing: .4px;
|
||||
}
|
||||
.sb-list { flex: 1; overflow-y: auto; }
|
||||
.sb-list::-webkit-scrollbar { width: 4px; }
|
||||
.sb-list::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
||||
|
||||
.spbu-item {
|
||||
padding: 11px 14px; border-bottom: 1px solid #f1f5f9; cursor: pointer;
|
||||
display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: start;
|
||||
transition: background .12s;
|
||||
}
|
||||
.spbu-item:hover { background: #f8fafc; }
|
||||
.spbu-item.active { background: var(--blue-lt); border-left: 3px solid var(--blue); padding-left: 11px; }
|
||||
.spbu-dot {
|
||||
width: 10px; height: 10px; border-radius: 50%; margin-top: 4px;
|
||||
border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.1);
|
||||
}
|
||||
.spbu-nama { font-size: 13px; font-weight: 600; line-height: 1.3; }
|
||||
.spbu-meta { font-size: 11px; color: var(--text3); margin-top: 3px; font-family: 'DM Mono', monospace; }
|
||||
.spbu-badge { font-size: 10px; font-weight: 700; padding: 3px 7px; border-radius: 5px; white-space: nowrap; }
|
||||
.badge-green { background: var(--green-lt); color: var(--green); }
|
||||
.badge-red { background: var(--red-lt); color: var(--red); }
|
||||
.no-result { padding: 32px 16px; text-align: center; color: var(--text3); font-size: 13px; }
|
||||
|
||||
.sb-foot {
|
||||
padding: 10px 14px; border-top: 1px solid var(--border);
|
||||
font-size: 11px; color: var(--text3); display: flex; justify-content: space-between;
|
||||
}
|
||||
.sb-foot b { color: var(--text2); }
|
||||
|
||||
/* Add form in sidebar */
|
||||
.form-panel { padding: 16px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0; }
|
||||
.form-panel h3 { font-size: 14px; font-weight: 700; margin-bottom: 4px; }
|
||||
.form-panel .form-desc { font-size: 12px; color: var(--text2); line-height: 1.5; margin-bottom: 16px; }
|
||||
.form-group { margin-bottom: 14px; }
|
||||
.form-group label {
|
||||
display: block; font-size: 11px; font-weight: 700; color: var(--text3);
|
||||
text-transform: uppercase; letter-spacing: .4px; margin-bottom: 5px;
|
||||
}
|
||||
.form-group input, .form-group select {
|
||||
width: 100%; padding: 10px 12px; border: 1px solid var(--border);
|
||||
border-radius: 8px; font-size: 14px; font-family: inherit; outline: none; background: #fff;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.form-group input:focus, .form-group select:focus {
|
||||
border-color: var(--blue); box-shadow: 0 0 0 3px rgba(29,78,216,.1);
|
||||
}
|
||||
.form-group input[readonly] { background: var(--bg); color: var(--text2); font-family: 'DM Mono', monospace; font-size: 12px; }
|
||||
.coord-box {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
|
||||
}
|
||||
.coord-box input { text-align: center; }
|
||||
.form-alert {
|
||||
padding: 10px 12px; border-radius: 8px; font-size: 12px; margin-bottom: 14px;
|
||||
background: var(--red-lt); color: var(--red); border: 1px solid #fecaca;
|
||||
}
|
||||
.form-hint {
|
||||
font-size: 11px; color: var(--text3); padding: 10px 12px;
|
||||
background: var(--bg); border-radius: 8px; border: 1px solid var(--border);
|
||||
line-height: 1.5; margin-top: auto;
|
||||
}
|
||||
.form-hint.active { border-color: #93c5fd; background: var(--blue-lt); color: var(--blue); }
|
||||
.form-actions { display: flex; gap: 8px; margin-top: 16px; }
|
||||
.btn-primary {
|
||||
flex: 1; padding: 11px; background: var(--blue); color: #fff; border: none;
|
||||
border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.btn-primary:hover { background: #1e40af; }
|
||||
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
|
||||
.btn-secondary {
|
||||
padding: 11px 14px; background: #f1f5f9; color: var(--text2); border: 1px solid var(--border);
|
||||
border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
|
||||
#map-area { flex: 1; position: relative; min-width: 0; display: flex; flex-direction: column; height: 100%; }
|
||||
#map { flex: 1; min-height: 0; width: 100%; position: relative; }
|
||||
|
||||
#map-controls {
|
||||
position: absolute; top: 14px; right: 14px; z-index: 800;
|
||||
background: var(--white); border: 1px solid var(--border);
|
||||
border-radius: 10px; box-shadow: 0 2px 12px rgba(0,0,0,.1);
|
||||
width: 190px; overflow: hidden; font-size: 12px;
|
||||
}
|
||||
.mc-section { padding: 10px 12px; border-bottom: 1px solid var(--border); }
|
||||
.mc-section:last-child { border-bottom: none; }
|
||||
.mc-label {
|
||||
font-size: 10px; font-weight: 700; color: var(--text3);
|
||||
text-transform: uppercase; letter-spacing: .5px; margin-bottom: 8px;
|
||||
}
|
||||
.mc-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer; color: var(--text2); }
|
||||
.mc-row:last-child { margin-bottom: 0; }
|
||||
.mc-row input { accent-color: var(--blue); }
|
||||
.mc-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
|
||||
.mc-base { display: flex; gap: 5px; }
|
||||
.mc-base-btn {
|
||||
flex: 1; padding: 7px 4px; border: 1px solid var(--border); border-radius: 6px;
|
||||
background: var(--white); font-size: 11px; font-weight: 600; color: var(--text2);
|
||||
cursor: pointer; font-family: inherit; transition: all .15s;
|
||||
}
|
||||
.mc-base-btn.active { background: var(--blue); color: #fff; border-color: var(--blue); }
|
||||
|
||||
#map-hint {
|
||||
position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%);
|
||||
z-index: 800; background: rgba(15,23,42,.88); color: #fff;
|
||||
padding: 8px 16px; border-radius: 8px; font-size: 12px; pointer-events: none;
|
||||
}
|
||||
#toast {
|
||||
position: fixed; bottom: 20px; right: 20px; z-index: 9000;
|
||||
background: #0f172a; color: #fff; padding: 11px 16px; border-radius: 8px;
|
||||
font-size: 13px; display: none; box-shadow: 0 4px 16px rgba(0,0,0,.2);
|
||||
}
|
||||
#toast.show { display: block; }
|
||||
|
||||
#edit-modal {
|
||||
display: none; position: fixed; inset: 0; z-index: 9999;
|
||||
background: rgba(15,23,42,.4); align-items: center; justify-content: center; padding: 20px;
|
||||
}
|
||||
#edit-modal.active { display: flex; }
|
||||
#edit-box {
|
||||
background: #fff; border-radius: 12px; padding: 24px;
|
||||
width: 100%; max-width: 380px; box-shadow: 0 16px 48px rgba(0,0,0,.18);
|
||||
}
|
||||
#edit-box h3 { font-size: 16px; font-weight: 700; margin-bottom: 16px; }
|
||||
#edit-box label {
|
||||
display: block; font-size: 11px; font-weight: 700; color: var(--text3);
|
||||
text-transform: uppercase; letter-spacing: .4px; margin: 12px 0 4px;
|
||||
}
|
||||
#edit-box input, #edit-box select {
|
||||
width: 100%; padding: 9px 12px; border: 1px solid var(--border);
|
||||
border-radius: 8px; font-size: 14px; font-family: inherit; outline: none;
|
||||
}
|
||||
#edit-box input:focus, #edit-box select:focus { border-color: var(--blue); }
|
||||
.edit-btns { display: flex; gap: 8px; margin-top: 20px; }
|
||||
.edit-btns button {
|
||||
flex: 1; padding: 10px; border-radius: 8px; font-size: 13px;
|
||||
font-weight: 600; cursor: pointer; font-family: inherit; border: none;
|
||||
}
|
||||
.btn-save { background: var(--blue); color: #fff; }
|
||||
.btn-cancel { background: #f1f5f9; color: var(--text2); }
|
||||
|
||||
.popup-wrap { font-family: 'DM Sans', sans-serif; min-width: 190px; }
|
||||
.popup-title { font-size: 14px; font-weight: 700; margin-bottom: 6px; }
|
||||
.popup-row { font-size: 12px; color: var(--text2); margin-bottom: 3px; }
|
||||
.popup-coord { font-size: 11px; color: var(--text3); font-family: 'DM Mono', monospace; margin: 4px 0 8px; }
|
||||
.popup-badge { display: inline-block; padding: 2px 8px; border-radius: 5px; font-size: 10px; font-weight: 700; }
|
||||
.popup-actions { display: flex; gap: 6px; margin-top: 10px; }
|
||||
.btn-edit, .btn-delete {
|
||||
flex: 1; border: none; padding: 7px; border-radius: 6px;
|
||||
font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.btn-edit { background: var(--blue); color: #fff; }
|
||||
.btn-delete { background: var(--red); color: #fff; }
|
||||
.btn-yes, .btn-no {
|
||||
border: none; padding: 7px 16px; border-radius: 6px;
|
||||
font-size: 12px; font-weight: 600; cursor: pointer; margin: 3px; font-family: inherit;
|
||||
}
|
||||
.btn-yes { background: var(--green); color: #fff; }
|
||||
.btn-no { background: #e2e8f0; color: var(--text2); }
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "webgis");
|
||||
|
||||
if ($conn->connect_error) {
|
||||
die("Koneksi gagal: " . $conn->connect_error);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,115 @@
|
||||
<style>
|
||||
#confirm-overlay {
|
||||
display: none; position: fixed; inset: 0; z-index: 99999;
|
||||
background: rgba(15,23,42,.4); backdrop-filter: blur(2px);
|
||||
align-items: center; justify-content: center; padding: 20px;
|
||||
}
|
||||
#confirm-overlay.show { display: flex; }
|
||||
#confirm-overlay.confirm-top {
|
||||
align-items: flex-start; justify-content: center;
|
||||
padding-top: 72px; background: transparent; backdrop-filter: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
#confirm-box {
|
||||
background: #fff; border: 1px solid #e2e8f0; border-radius: 12px;
|
||||
width: 100%; max-width: 400px;
|
||||
box-shadow: 0 16px 48px rgba(15,23,42,.14);
|
||||
animation: confirmIn .18s ease; overflow: hidden;
|
||||
}
|
||||
#confirm-overlay.confirm-top #confirm-box {
|
||||
pointer-events: all; max-width: 460px;
|
||||
box-shadow: 0 8px 28px rgba(15,23,42,.12);
|
||||
}
|
||||
@keyframes confirmIn { from { opacity:0; transform: translateY(-6px); } to { opacity:1; transform: none; } }
|
||||
#confirm-box .cf-head {
|
||||
display: flex; gap: 14px; padding: 20px 22px 0; align-items: flex-start;
|
||||
}
|
||||
#confirm-box .cf-icon {
|
||||
width: 40px; height: 40px; border-radius: 10px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
#confirm-box .cf-icon.warn { background: #fffbeb; border: 1px solid #fde68a; }
|
||||
#confirm-box .cf-icon.danger { background: #fef2f2; border: 1px solid #fecaca; }
|
||||
#confirm-box .cf-icon.info { background: #eff6ff; border: 1px solid #bfdbfe; }
|
||||
#confirm-box .cf-icon.move { background: #eff6ff; border: 1px solid #bfdbfe; }
|
||||
#confirm-box .cf-text { flex: 1; min-width: 0; }
|
||||
#confirm-box .cf-title { font-size: 15px; font-weight: 700; color: #0f172a; margin-bottom: 4px; }
|
||||
#confirm-box .cf-msg { font-size: 13px; color: #64748b; line-height: 1.55; }
|
||||
#confirm-box .cf-actions {
|
||||
display: flex; gap: 8px; padding: 18px 22px 20px; justify-content: flex-end;
|
||||
}
|
||||
#confirm-box .cf-btn {
|
||||
padding: 9px 16px; border-radius: 8px; font-size: 13px;
|
||||
font-weight: 600; cursor: pointer; border: 1px solid transparent;
|
||||
font-family: inherit; transition: background .15s;
|
||||
}
|
||||
#confirm-box .cf-cancel { background: #fff; color: #475569; border-color: #e2e8f0; }
|
||||
#confirm-box .cf-cancel:hover { background: #f8fafc; }
|
||||
#confirm-box .cf-ok { background: #1d4ed8; color: #fff; }
|
||||
#confirm-box .cf-ok:hover { background: #1e40af; }
|
||||
#confirm-box .cf-ok.danger { background: #dc2626; }
|
||||
#confirm-box .cf-ok.danger:hover { background: #b91c1c; }
|
||||
</style>
|
||||
<div id="confirm-overlay" role="dialog" aria-modal="true">
|
||||
<div id="confirm-box">
|
||||
<div class="cf-head">
|
||||
<div class="cf-icon warn" id="cf-icon"></div>
|
||||
<div class="cf-text">
|
||||
<div class="cf-title" id="cf-title">Konfirmasi</div>
|
||||
<div class="cf-msg" id="cf-msg"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cf-actions">
|
||||
<button type="button" class="cf-btn cf-cancel" id="cf-cancel">Batal</button>
|
||||
<button type="button" class="cf-btn cf-ok" id="cf-ok">Lanjutkan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var overlay = document.getElementById('confirm-overlay');
|
||||
var iconEl = document.getElementById('cf-icon');
|
||||
var titleEl = document.getElementById('cf-title');
|
||||
var msgEl = document.getElementById('cf-msg');
|
||||
var btnOk = document.getElementById('cf-ok');
|
||||
var btnCancel = document.getElementById('cf-cancel');
|
||||
var resolver = null;
|
||||
|
||||
var icons = {
|
||||
warn: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" stroke-linecap="round"><path d="M12 9v4m0 4h.01"/><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>',
|
||||
danger: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg>',
|
||||
move: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round"><path d="M12 2v20M2 12h20"/><circle cx="12" cy="12" r="3"/></svg>'
|
||||
};
|
||||
|
||||
function closeConfirm(result) {
|
||||
overlay.classList.remove('show', 'confirm-top');
|
||||
document.body.style.overflow = '';
|
||||
if (resolver) { var r = resolver; resolver = null; r(result); }
|
||||
}
|
||||
|
||||
btnCancel.addEventListener('click', function(){ closeConfirm(false); });
|
||||
btnOk.addEventListener('click', function(){ closeConfirm(true); });
|
||||
overlay.addEventListener('click', function(e){ if (e.target === overlay && !overlay.classList.contains('confirm-top')) closeConfirm(false); });
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (!overlay.classList.contains('show')) return;
|
||||
if (e.key === 'Escape') closeConfirm(false);
|
||||
});
|
||||
|
||||
window.showConfirm = function(opts) {
|
||||
opts = opts || {};
|
||||
var danger = !!opts.danger;
|
||||
var kind = danger ? 'danger' : (opts.icon || 'warn');
|
||||
iconEl.className = 'cf-icon ' + kind;
|
||||
iconEl.innerHTML = icons[kind] || icons.warn;
|
||||
titleEl.textContent = opts.title || 'Konfirmasi';
|
||||
msgEl.innerHTML = opts.message || 'Apakah Anda yakin?';
|
||||
btnOk.textContent = opts.confirmText || (danger ? 'Ya, Hapus' : 'Lanjutkan');
|
||||
btnCancel.textContent = opts.cancelText || 'Batal';
|
||||
btnOk.classList.toggle('danger', danger);
|
||||
overlay.classList.toggle('confirm-top', opts.position === 'top');
|
||||
if (opts.position !== 'top') document.body.style.overflow = 'hidden';
|
||||
overlay.classList.add('show');
|
||||
return new Promise(function(resolve){ resolver = resolve; });
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<div id="map-controls" style="top: 70px;">
|
||||
<div class="mc-section">
|
||||
<div class="mc-label">Layer SPBU</div>
|
||||
<label class="mc-row"><input type="checkbox" id="ly-24" checked><span class="mc-dot" style="background:#22c55e"></span> Buka 24 Jam</label>
|
||||
<label class="mc-row"><input type="checkbox" id="ly-tdk" checked><span class="mc-dot" style="background:#ef4444"></span> Tidak 24 Jam</label>
|
||||
</div>
|
||||
<div class="mc-section">
|
||||
<div class="mc-label">Tampilan Peta</div>
|
||||
<div class="mc-base">
|
||||
<button class="mc-base-btn active" id="base-map" type="button">Peta</button>
|
||||
<button class="mc-base-btn" id="base-sat" type="button">Satelit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/** @var array $stats @var string $activePage 'index'|'tambah' */
|
||||
$activePage = $activePage ?? 'index';
|
||||
?>
|
||||
<aside id="sidebar">
|
||||
<div class="sb-head">
|
||||
<a href="index.php" style="display:flex; align-items:center; gap:10px; text-decoration:none; color:inherit">
|
||||
<div class="sb-logo" style="width:38px; height:38px; border-radius:10px; background:linear-gradient(135deg, #1d4ed8, #60a5fa); display:flex; align-items:center; justify-content:center; flex-shrink:0; box-shadow:0 2px 8px rgba(29,78,216,.25)">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#fff" stroke-width="2" style="flex-shrink:0"><path d="M6 3h12l-1 7H7L6 3zm0 9h12v9H6v-9zm3 2v5h6v-5H9z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
<div class="sb-title"><h1>Geografis SPBU</h1><p>Sistem Informasi Geografis</p></div>
|
||||
</a>
|
||||
|
||||
<!-- 2x2 Stats Grid -->
|
||||
<div class="sb-stats" style="display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:12px">
|
||||
<div class="sb-stat" style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:9px 10px; text-align:center">
|
||||
<div class="sb-stat-n" style="font-size:20px; font-weight:800; font-family:'DM Mono',monospace; line-height:1; color:#1d4ed8" id="s-total"><?= $stats['total'] ?></div>
|
||||
<div class="sb-stat-l" style="font-size:9.5px; color:#94a3b8; margin-top:3px; font-weight:600; text-transform:uppercase; letter-spacing:.4px">Total SPBU</div>
|
||||
</div>
|
||||
<div class="sb-stat" style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:9px 10px; text-align:center">
|
||||
<div class="sb-stat-n" style="font-size:20px; font-weight:800; font-family:'DM Mono',monospace; line-height:1; color:#16a34a" id="s-jam24"><?= $stats['jam24'] ?></div>
|
||||
<div class="sb-stat-l" style="font-size:9.5px; color:#94a3b8; margin-top:3px; font-weight:600; text-transform:uppercase; letter-spacing:.4px">24 Jam</div>
|
||||
</div>
|
||||
<div class="sb-stat" style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:9px 10px; text-align:center">
|
||||
<div class="sb-stat-n" style="font-size:20px; font-weight:800; font-family:'DM Mono',monospace; line-height:1; color:#dc2626" id="s-tidak24"><?= $stats['tidak24'] ?></div>
|
||||
<div class="sb-stat-l" style="font-size:9.5px; color:#94a3b8; margin-top:3px; font-weight:600; text-transform:uppercase; letter-spacing:.4px">Tdk 24 Jam</div>
|
||||
</div>
|
||||
<div class="sb-stat" style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:9px 10px; text-align:center">
|
||||
<div class="sb-stat-n" style="font-size:20px; font-weight:800; font-family:'DM Mono',monospace; line-height:1; color:#ca8a04" id="s-pct"><?= $stats['pct24'] ?>%</div>
|
||||
<div class="sb-stat-l" style="font-size:9.5px; color:#94a3b8; margin-top:3px; font-weight:600; text-transform:uppercase; letter-spacing:.4px">Ratio 24h</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Sections -->
|
||||
<div class="sb-nav-container" style="padding:8px; display:flex; flex-direction:column; gap:2px; border-bottom:1px solid #e2e8f0">
|
||||
<div class="sb-section-lbl" style="font-size:9.5px; font-weight:700; color:#94a3b8; text-transform:uppercase; letter-spacing:.8px; padding:10px 8px 4px;">Aksi Admin</div>
|
||||
<a href="tambah.php" class="sb-item <?= $activePage==='tambah'?'active':'' ?>" style="display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:8px; cursor:pointer; font-size:13px; font-weight:500; color:#475569; text-decoration:none; transition:all .15s; border:1px solid transparent;">
|
||||
<span class="ico" style="font-size:16px; width:20px; text-align:center; flex-shrink:0; display:flex; align-items:center; justify-content:center">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
</span>
|
||||
<span>Tambah SPBU Baru</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
function spbu_stats($conn) {
|
||||
$total = (int) $conn->query("SELECT COUNT(*) c FROM tempat")->fetch_assoc()['c'];
|
||||
$jam24 = (int) $conn->query("SELECT COUNT(*) c FROM tempat WHERE status='24 Jam'")->fetch_assoc()['c'];
|
||||
$tidak24 = $total - $jam24;
|
||||
$pct24 = $total > 0 ? round($jam24 / $total * 100) : 0;
|
||||
return compact('total', 'jam24', 'tidak24', 'pct24');
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
if (isset($_GET['action'])) { include 'api.php'; exit; }
|
||||
include 'db.php';
|
||||
require_once 'includes/stats.php';
|
||||
$stats = spbu_stats($conn);
|
||||
$activePage = 'index';
|
||||
$saved = isset($_GET['saved']);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Sistem Informasi Geografis SPBU — Pontianak</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||
<link rel="stylesheet" href="assets/spbu.css?v=1.2"/>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php include 'includes/sidebar.php'; ?>
|
||||
<div class="sb-body">
|
||||
<div class="sb-search"><input type="text" id="search-input" placeholder="Cari nama atau nomor SPBU..."></div>
|
||||
<div class="sb-filter">
|
||||
<button class="active" data-filter="semua">Semua (<?= $stats['total'] ?>)</button>
|
||||
<button data-filter="24 Jam">24 Jam (<?= $stats['jam24'] ?>)</button>
|
||||
<button data-filter="Tidak 24 Jam">Lainnya (<?= $stats['tidak24'] ?>)</button>
|
||||
</div>
|
||||
<div class="sb-list-head">
|
||||
<span>Daftar SPBU</span>
|
||||
<span id="list-count"><?= $stats['total'] ?> lokasi</span>
|
||||
</div>
|
||||
<div class="sb-list" id="spbu-list"></div>
|
||||
</div>
|
||||
<div class="sb-foot" style="flex-direction:column; gap:10px">
|
||||
<div style="display:flex; justify-content:space-between; width:100%">
|
||||
<span>Operasional 24 jam: <b><?= $stats['pct24'] ?>%</b></span>
|
||||
<span><?= date('d M Y') ?></span>
|
||||
</div>
|
||||
<a href="../index.php" class="btn-portal" style="display:flex; align-items:center; justify-content:center; gap:6px; padding:10px; background:#eff6ff; color:#1d4ed8; border:1px solid #bfdbfe; border-radius:8px; font-weight:600; text-decoration:none; font-size:12px; transition:all 0.15s; width:100%">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
Kembali ke Portal
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="map-area">
|
||||
<!-- Topbar -->
|
||||
<div id="topbar" style="position:relative; z-index:1000; height:56px; background:#fff; border-bottom:1px solid #e2e8f0; display:flex; align-items:center; justify-content:space-between; padding:0 16px; flex-shrink:0">
|
||||
<div style="display:flex; align-items:center; gap:8px">
|
||||
<button type="button" onclick="document.getElementById('sidebar').style.display=document.getElementById('sidebar').style.display==='none'?'flex':'none';setTimeout(function(){map.invalidateSize()},300)" style="background:none;border:none;cursor:pointer;color:#64748b;padding:4px;display:flex;align-items:center"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
|
||||
<div style="font-size:15px; font-weight:700; color:#1e293b">Peta GIS <span style="font-size:13px; font-weight:500; color:#64748b; margin-left:4px">Kanalisasi SPBU</span></div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px">
|
||||
<a href="tambah.php" style="display:flex; align-items:center; gap:6px; padding:8px 14px; background:#1d4ed8; color:#fff; border-radius:8px; font-size:12px; font-weight:600; text-decoration:none; box-shadow:0 2px 4px rgba(29,78,216,0.15)">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
Tambah Baru
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
<?php include 'includes/map-controls.php'; ?>
|
||||
</div>
|
||||
|
||||
<div id="edit-modal">
|
||||
<div id="edit-box">
|
||||
<h3>Edit Data SPBU</h3>
|
||||
<input type="hidden" id="edit-id">
|
||||
<label>Nama SPBU</label>
|
||||
<input type="text" id="edit-nama">
|
||||
<label>Nomor Depot</label>
|
||||
<input type="text" id="edit-nomor">
|
||||
<label>Jam Operasional</label>
|
||||
<select id="edit-status">
|
||||
<option value="24 Jam">24 Jam</option>
|
||||
<option value="Tidak 24 Jam">Tidak 24 Jam</option>
|
||||
</select>
|
||||
<div class="edit-btns">
|
||||
<button class="btn-cancel" type="button" onclick="closeModal()">Batal</button>
|
||||
<button class="btn-save" type="button" onclick="submitEdit()">Simpan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast"></div>
|
||||
<?php include 'includes/confirm-modal.php'; ?>
|
||||
<script src="assets/map-base.js"></script>
|
||||
<script>
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
initBaseLayers(map);
|
||||
<?php if ($saved): ?>showToast('Data SPBU berhasil disimpan.');<?php endif ?>
|
||||
|
||||
var spbuData = [];
|
||||
var layer24Jam = L.layerGroup().addTo(map);
|
||||
var layerTidak24 = L.layerGroup().addTo(map);
|
||||
document.getElementById('ly-24').addEventListener('change', function() {
|
||||
this.checked ? map.addLayer(layer24Jam) : map.removeLayer(layer24Jam);
|
||||
});
|
||||
document.getElementById('ly-tdk').addEventListener('change', function() {
|
||||
this.checked ? map.addLayer(layerTidak24) : map.removeLayer(layerTidak24);
|
||||
});
|
||||
|
||||
async function deleteData(id) {
|
||||
var ok = await showConfirm({
|
||||
title: 'Hapus Data SPBU',
|
||||
message: 'Data ini akan dihapus permanen dari database.',
|
||||
confirmText: 'Ya, Hapus', danger: true
|
||||
});
|
||||
if (ok) window.location.href = 'index.php?action=delete&id=' + id;
|
||||
}
|
||||
function openEdit(id, nama, nomor, status) {
|
||||
document.getElementById('edit-id').value = id;
|
||||
document.getElementById('edit-nama').value = nama;
|
||||
document.getElementById('edit-nomor').value = nomor;
|
||||
document.getElementById('edit-status').value = status;
|
||||
document.getElementById('edit-modal').classList.add('active');
|
||||
}
|
||||
function closeModal() { document.getElementById('edit-modal').classList.remove('active'); }
|
||||
function submitEdit() {
|
||||
var id = document.getElementById('edit-id').value;
|
||||
var nama = document.getElementById('edit-nama').value.trim();
|
||||
var nomor = document.getElementById('edit-nomor').value.trim();
|
||||
var status = document.getElementById('edit-status').value;
|
||||
if (!nama || !nomor) { showToast('Nama dan nomor wajib diisi.'); return; }
|
||||
window.location.href = 'index.php?action=edit&id=' + id
|
||||
+ '&nama=' + encodeURIComponent(nama)
|
||||
+ '&nomor=' + encodeURIComponent(nomor)
|
||||
+ '&status=' + encodeURIComponent(status);
|
||||
}
|
||||
document.getElementById('edit-modal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeModal();
|
||||
});
|
||||
|
||||
function flyToSpbu(id) {
|
||||
var s = spbuData.find(function(x) { return x.id === id; });
|
||||
if (!s) return;
|
||||
document.querySelectorAll('.spbu-item').forEach(function(el) { el.classList.remove('active'); });
|
||||
var el = document.querySelector('.spbu-item[data-id="' + id + '"]');
|
||||
if (el) el.classList.add('active');
|
||||
map.flyTo([s.lat, s.lng], 17, { duration: 1 });
|
||||
setTimeout(function() { s.marker.openPopup(); }, 1100);
|
||||
}
|
||||
|
||||
<?php
|
||||
$data = $conn->query("SELECT * FROM tempat ORDER BY nama_tempat ASC");
|
||||
while ($row = $data->fetch_assoc()):
|
||||
$id = $row['id']; $lat = $row['latitude']; $lng = $row['longitude'];
|
||||
$nama = addslashes($row['nama_tempat']); $nomor = addslashes($row['nomor_tempat']);
|
||||
$status = addslashes($row['status']);
|
||||
$color = ($row['status'] === '24 Jam') ? '#22c55e' : '#ef4444';
|
||||
$badge = ($row['status'] === '24 Jam') ? 'badge-green' : 'badge-red';
|
||||
$layerVar = ($row['status'] === '24 Jam') ? 'layer24Jam' : 'layerTidak24';
|
||||
$latFmt = number_format($lat, 5); $lngFmt = number_format($lng, 5);
|
||||
?>
|
||||
(function() {
|
||||
var savedLat = <?= $lat ?>, savedLng = <?= $lng ?>, markerId = <?= $id ?>, color = '<?= $color ?>';
|
||||
var marker = L.marker([savedLat, savedLng], { icon: makeSpbuIcon(color), draggable: true }).addTo(<?= $layerVar ?>);
|
||||
spbuData.push({ id: markerId, lat: savedLat, lng: savedLng, nama: '<?= $nama ?>', nomor: '<?= $nomor ?>', status: '<?= $status ?>', color: color, badge: '<?= $badge ?>', marker: marker });
|
||||
function showInfoPopup() {
|
||||
marker.unbindPopup();
|
||||
marker.bindPopup(
|
||||
'<div class="popup-wrap"><div class="popup-title"><?= $nama ?></div>' +
|
||||
'<div class="popup-row">Depot No. <?= $nomor ?></div>' +
|
||||
'<div class="popup-coord"><?= $latFmt ?>, <?= $lngFmt ?></div>' +
|
||||
'<span class="popup-badge <?= $badge ?>"><?= $status ?></span>' +
|
||||
'<div class="popup-actions">' +
|
||||
'<button class="btn-edit" onclick="openEdit(<?= $id ?>,\'<?= $nama ?>\',\'<?= $nomor ?>\',\'<?= $status ?>\')">Edit</button>' +
|
||||
'<button class="btn-delete" onclick="deleteData(<?= $id ?>)">Hapus</button></div></div>'
|
||||
);
|
||||
}
|
||||
showInfoPopup();
|
||||
var dragStartLat, dragStartLng;
|
||||
marker.on('dragstart', function() {
|
||||
dragStartLat = marker.getLatLng().lat; dragStartLng = marker.getLatLng().lng;
|
||||
marker.closePopup(); marker.unbindPopup();
|
||||
});
|
||||
marker.on('dragend', function() {
|
||||
var np = marker.getLatLng();
|
||||
var msgBase = '<div style="text-align:left;line-height:1.6">' +
|
||||
'<b>' + '<?= $nama ?>' + '</b><br>' +
|
||||
'<span style="color:var(--text3);font-size:12px">Koordinat: ' + np.lat.toFixed(6) + ', ' + np.lng.toFixed(6) + '</span><br>';
|
||||
|
||||
fetch('https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat='+np.lat+'&lon='+np.lng+'&accept-language=id&zoom=18')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
var alamat = d.display_name || 'Alamat tidak ditemukan';
|
||||
showConfirm({
|
||||
title: 'Simpan posisi baru?',
|
||||
message: msgBase + '<span style="font-size:13px;color:var(--text2)"><b>Alamat Baru:</b> ' + alamat + '</span></div>',
|
||||
confirmText: 'Ya, Simpan',
|
||||
icon: 'move',
|
||||
position: 'top'
|
||||
}).then(function(ok) {
|
||||
if (ok) {
|
||||
fetch('index.php?action=update_pos&id=' + markerId + '&lat=' + np.lat + '&lng=' + np.lng)
|
||||
.then(function(r) { return r.text(); }).then(function(t) {
|
||||
if (t.trim() === 'ok') {
|
||||
savedLat = np.lat; savedLng = np.lng;
|
||||
var s = spbuData.find(function(x) { return x.id === markerId; });
|
||||
if (s) { s.lat = savedLat; s.lng = savedLng; }
|
||||
showToast('Posisi diperbarui.');
|
||||
} else {
|
||||
showToast('Gagal menyimpan.');
|
||||
marker.setLatLng([dragStartLat, dragStartLng]);
|
||||
}
|
||||
showInfoPopup();
|
||||
});
|
||||
} else {
|
||||
marker.setLatLng([dragStartLat, dragStartLng]);
|
||||
showInfoPopup();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
showConfirm({
|
||||
title: 'Simpan posisi baru?',
|
||||
message: msgBase + '<span style="font-size:13px;color:var(--text2)"><b>Alamat Baru:</b> Gagal memuat alamat</span></div>',
|
||||
confirmText: 'Ya, Simpan',
|
||||
icon: 'move',
|
||||
position: 'top'
|
||||
}).then(function(ok) {
|
||||
if (ok) {
|
||||
fetch('index.php?action=update_pos&id=' + markerId + '&lat=' + np.lat + '&lng=' + np.lng)
|
||||
.then(function(r) { return r.text(); }).then(function(t) {
|
||||
if (t.trim() === 'ok') {
|
||||
savedLat = np.lat; savedLng = np.lng;
|
||||
var s = spbuData.find(function(x) { return x.id === markerId; });
|
||||
if (s) { s.lat = savedLat; s.lng = savedLng; }
|
||||
showToast('Posisi diperbarui.');
|
||||
} else {
|
||||
showToast('Gagal menyimpan.');
|
||||
marker.setLatLng([dragStartLat, dragStartLng]);
|
||||
}
|
||||
showInfoPopup();
|
||||
});
|
||||
} else {
|
||||
marker.setLatLng([dragStartLat, dragStartLng]);
|
||||
showInfoPopup();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
marker.on('click', function() {
|
||||
document.querySelectorAll('.spbu-item').forEach(function(el) { el.classList.remove('active'); });
|
||||
var el = document.querySelector('.spbu-item[data-id="' + markerId + '"]');
|
||||
if (el) { el.classList.add('active'); el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
|
||||
});
|
||||
})();
|
||||
<?php endwhile; ?>
|
||||
|
||||
var currentFilter = 'semua', currentSearch = '';
|
||||
function renderList() {
|
||||
var list = document.getElementById('spbu-list');
|
||||
var kw = currentSearch.toLowerCase();
|
||||
var filtered = spbuData.filter(function(s) {
|
||||
var f = (currentFilter === 'semua') || (s.status === currentFilter);
|
||||
var q = s.nama.toLowerCase().indexOf(kw) !== -1 || s.nomor.toLowerCase().indexOf(kw) !== -1;
|
||||
return f && q;
|
||||
});
|
||||
document.getElementById('list-count').textContent = filtered.length + ' lokasi';
|
||||
if (!filtered.length) { list.innerHTML = '<div class="no-result">Tidak ada SPBU ditemukan</div>'; return; }
|
||||
list.innerHTML = filtered.map(function(s) {
|
||||
return '<div class="spbu-item" data-id="' + s.id + '" onclick="flyToSpbu(' + s.id + ')">' +
|
||||
'<div class="spbu-dot" style="background:' + s.color + '"></div>' +
|
||||
'<div><div class="spbu-nama">' + s.nama + '</div>' +
|
||||
'<div class="spbu-meta">No. ' + s.nomor + ' · ' + s.lat.toFixed(5) + ', ' + s.lng.toFixed(5) + '</div></div>' +
|
||||
'<span class="spbu-badge ' + s.badge + '">' + (s.status === '24 Jam' ? '24 Jam' : 'Tdk 24 Jam') + '</span></div>';
|
||||
}).join('');
|
||||
}
|
||||
document.getElementById('search-input').addEventListener('input', function() { currentSearch = this.value; renderList(); });
|
||||
document.querySelectorAll('.sb-filter button').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.sb-filter button').forEach(function(b) { b.classList.remove('active'); });
|
||||
this.classList.add('active'); currentFilter = this.dataset.filter; renderList();
|
||||
});
|
||||
});
|
||||
renderList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
include 'db.php';
|
||||
require_once 'includes/stats.php';
|
||||
$stats = spbu_stats($conn);
|
||||
$activePage = 'tambah';
|
||||
$err = isset($_GET['err']) ? $_GET['err'] : '';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Tambah SPBU — Sistem Informasi Geografis SPBU</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||
<link rel="stylesheet" href="assets/spbu.css?v=1.2"/>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php include 'includes/sidebar.php'; ?>
|
||||
<div class="sb-body">
|
||||
<form class="form-panel" id="form-spbu" method="POST" action="api.php">
|
||||
<h3>Input Data SPBU</h3>
|
||||
<p class="form-desc">Klik titik pada peta untuk menentukan koordinat, lalu lengkapi formulir di bawah.</p>
|
||||
<?php if ($err): ?><div class="form-alert"><?= htmlspecialchars($err) ?></div><?php endif ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nama">Nama SPBU</label>
|
||||
<input type="text" id="nama" name="nama" placeholder="Contoh: SPBU Kota Baru" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="nomor">Nomor Depot</label>
|
||||
<input type="text" id="nomor" name="nomor" placeholder="Contoh: 34.123.45" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Jam Operasional</label>
|
||||
<select id="status" name="status">
|
||||
<option value="24 Jam">Buka 24 Jam</option>
|
||||
<option value="Tidak 24 Jam">Tidak 24 Jam</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Koordinat Lokasi</label>
|
||||
<div class="coord-box">
|
||||
<input type="text" id="lat-display" placeholder="Latitude" readonly>
|
||||
<input type="text" id="lng-display" placeholder="Longitude" readonly>
|
||||
</div>
|
||||
<input type="hidden" name="lat" id="lat" required>
|
||||
<input type="hidden" name="lng" id="lng" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn-secondary" onclick="resetForm()">Reset</button>
|
||||
<button type="submit" name="save" class="btn-primary" id="btn-save" disabled>Simpan Data</button>
|
||||
</div>
|
||||
|
||||
<div class="form-hint" id="form-hint">Belum ada lokasi dipilih — klik peta di sebelah kanan</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="sb-foot" style="flex-direction:column; gap:10px">
|
||||
<div style="display:flex; justify-content:space-between; width:100%">
|
||||
<span>Operasional 24 jam: <b><?= $stats['pct24'] ?>%</b></span>
|
||||
<span><?= date('d M Y') ?></span>
|
||||
</div>
|
||||
<a href="../index.php" class="btn-portal" style="display:flex; align-items:center; justify-content:center; gap:6px; padding:10px; background:#eff6ff; color:#1d4ed8; border:1px solid #bfdbfe; border-radius:8px; font-weight:600; text-decoration:none; font-size:12px; transition:all 0.15s; width:100%">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
Kembali ke Portal
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="map-area">
|
||||
<!-- Topbar -->
|
||||
<div id="topbar" style="position:relative; z-index:1000; height:56px; background:#fff; border-bottom:1px solid #e2e8f0; display:flex; align-items:center; justify-content:space-between; padding:0 16px; flex-shrink:0">
|
||||
<div style="display:flex; align-items:center; gap:8px">
|
||||
<div style="font-size:15px; font-weight:700; color:#1e293b">Tambah Baru <span style="font-size:13px; font-weight:500; color:#64748b; margin-left:4px">Lokasi Depot SPBU</span></div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px">
|
||||
<a href="index.php" style="display:flex; align-items:center; gap:6px; padding:8px 14px; border:1px solid #cbd5e1; color:#475569; border-radius:8px; font-size:12px; font-weight:600; text-decoration:none; background:#fff">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
|
||||
Lihat Peta
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
<?php include 'includes/map-controls.php'; ?>
|
||||
<div id="map-hint" style="display:block">Klik peta untuk menandai lokasi SPBU</div>
|
||||
</div>
|
||||
|
||||
<div id="toast"></div>
|
||||
<script src="assets/map-base.js"></script>
|
||||
<script>
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
initBaseLayers(map);
|
||||
|
||||
var newMarker = null;
|
||||
var newIcon = makeSpbuIcon('#3b82f6', 22);
|
||||
var hintEl = document.getElementById('form-hint');
|
||||
var btnSave = document.getElementById('btn-save');
|
||||
|
||||
<?php
|
||||
$existing = $conn->query("SELECT * FROM tempat");
|
||||
while ($row = $existing->fetch_assoc()):
|
||||
$nama = addslashes($row['nama_tempat']); $nomor = addslashes($row['nomor_tempat']);
|
||||
$status = addslashes($row['status']);
|
||||
$color = ($row['status'] === '24 Jam') ? '#22c55e' : '#ef4444';
|
||||
$badge = ($row['status'] === '24 Jam') ? 'badge-green' : 'badge-red';
|
||||
?>
|
||||
L.marker([<?= $row['latitude'] ?>, <?= $row['longitude'] ?>], { icon: makeSpbuIcon('<?= $color ?>') })
|
||||
.addTo(map).bindPopup(
|
||||
'<div class="popup-wrap"><div class="popup-title"><?= $nama ?></div>' +
|
||||
'<div class="popup-row">Depot No. <?= $nomor ?></div>' +
|
||||
'<span class="popup-badge <?= $badge ?>"><?= $status ?></span></div>'
|
||||
);
|
||||
<?php endwhile; ?>
|
||||
|
||||
function setCoords(lat, lng) {
|
||||
document.getElementById('lat').value = lat.toFixed(6);
|
||||
document.getElementById('lng').value = lng.toFixed(6);
|
||||
document.getElementById('lat-display').value = lat.toFixed(6);
|
||||
document.getElementById('lng-display').value = lng.toFixed(6);
|
||||
btnSave.disabled = false;
|
||||
hintEl.textContent = 'Lokasi terpilih — lengkapi data lalu simpan';
|
||||
hintEl.classList.add('active');
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('form-spbu').reset();
|
||||
document.getElementById('lat').value = '';
|
||||
document.getElementById('lng').value = '';
|
||||
document.getElementById('lat-display').value = '';
|
||||
document.getElementById('lng-display').value = '';
|
||||
btnSave.disabled = true;
|
||||
hintEl.textContent = 'Belum ada lokasi dipilih — klik peta di sebelah kanan';
|
||||
hintEl.classList.remove('active');
|
||||
if (newMarker) { map.removeLayer(newMarker); newMarker = null; }
|
||||
}
|
||||
|
||||
map.on('click', function(e) {
|
||||
if (newMarker) map.removeLayer(newMarker);
|
||||
newMarker = L.marker(e.latlng, { icon: newIcon }).addTo(map);
|
||||
setCoords(e.latlng.lat, e.latlng.lng);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user