350 lines
13 KiB
PHP
350 lines
13 KiB
PHP
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Peta SPBU</title>
|
||
<meta charset="UTF-8">
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
|
||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||
<style>
|
||
* { margin:0; padding:0; box-sizing:border-box; }
|
||
body { font-family: 'Segoe UI', sans-serif; }
|
||
#map { height: 100vh; width: 100%; }
|
||
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 9999;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.modal-overlay.show { display: flex; }
|
||
|
||
.modal-box {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
width: 300px;
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
|
||
}
|
||
.modal-box h3 {
|
||
font-size: 15px;
|
||
margin-bottom: 14px;
|
||
color: #222;
|
||
border-bottom: 2px solid #f0f0f0;
|
||
padding-bottom: 10px;
|
||
}
|
||
.modal-box label {
|
||
display: block;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin: 10px 0 3px;
|
||
}
|
||
.modal-box input, .modal-box select {
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
outline: none;
|
||
}
|
||
.modal-box input:focus, .modal-box select:focus { border-color: #3498db; }
|
||
|
||
.modal-actions { display:flex; gap:8px; margin-top:16px; }
|
||
.btn-save {
|
||
flex:1; padding:9px;
|
||
background:#3498db; color:#fff;
|
||
border:none; border-radius:6px;
|
||
font-size:13px; font-weight:600; cursor:pointer;
|
||
}
|
||
.btn-save:hover { background:#2980b9; }
|
||
.btn-cancel {
|
||
flex:1; padding:9px;
|
||
background:#f0f0f0; color:#555;
|
||
border:none; border-radius:6px;
|
||
font-size:13px; font-weight:600; cursor:pointer;
|
||
}
|
||
.btn-cancel:hover { background:#e0e0e0; }
|
||
|
||
#info-bar {
|
||
position: fixed;
|
||
bottom: 16px; left: 50%;
|
||
transform: translateX(-50%);
|
||
background: rgba(20,20,20,0.8);
|
||
color: #fff;
|
||
padding: 8px 20px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
z-index: 1000;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
#toast {
|
||
position: fixed;
|
||
bottom: 56px; left: 50%;
|
||
transform: translateX(-50%) translateY(10px);
|
||
background: #2ecc71; color: #fff;
|
||
padding: 8px 22px;
|
||
border-radius: 20px;
|
||
font-size: 13px; font-weight:600;
|
||
z-index: 9998;
|
||
opacity: 0;
|
||
transition: all .3s;
|
||
pointer-events: none;
|
||
}
|
||
#toast.show { opacity:1; transform: translateX(-50%) translateY(0); }
|
||
#toast.error { background: #e74c3c; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div id="map"></div>
|
||
<div id="info-bar">🖱️ Klik peta untuk tambah SPBU · Klik marker untuk Edit/Hapus · Seret marker untuk pindah lokasi</div>
|
||
<div id="toast"></div>
|
||
|
||
<!-- ===== MODAL TAMBAH ===== -->
|
||
<div class="modal-overlay" id="modalTambah">
|
||
<div class="modal-box">
|
||
<h3>➕ Tambah SPBU Baru</h3>
|
||
<input type="hidden" id="tLat">
|
||
<input type="hidden" id="tLng">
|
||
|
||
<label>Nama SPBU</label>
|
||
<input type="text" id="tNama" placeholder="Contoh: SPBU 64.751.01">
|
||
|
||
<label>No. WhatsApp</label>
|
||
<input type="text" id="tWa" placeholder="08xxxxxxxxxx">
|
||
|
||
<label>Status</label>
|
||
<select id="tStatus">
|
||
<option value="yes">🟢 Buka 24 Jam</option>
|
||
<option value="no">🔴 Tidak 24 Jam</option>
|
||
</select>
|
||
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="btnBatalTambah">Batal</button>
|
||
<button class="btn-save" id="btnSimpanTambah">💾 Simpan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== MODAL EDIT ===== -->
|
||
<div class="modal-overlay" id="modalEdit">
|
||
<div class="modal-box">
|
||
<h3>✏️ Edit Data SPBU</h3>
|
||
<input type="hidden" id="eId">
|
||
<input type="hidden" id="eLat">
|
||
<input type="hidden" id="eLng">
|
||
|
||
<label>Nama SPBU</label>
|
||
<input type="text" id="eNama" placeholder="Nama SPBU...">
|
||
|
||
<label>No. WhatsApp</label>
|
||
<input type="text" id="eWa" placeholder="08xxxxxxxxxx">
|
||
|
||
<label>Status</label>
|
||
<select id="eStatus">
|
||
<option value="yes">🟢 Buka 24 Jam</option>
|
||
<option value="no">🔴 Tidak 24 Jam</option>
|
||
</select>
|
||
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="btnBatalEdit">Batal</button>
|
||
<button class="btn-save" id="btnSimpanEdit">💾 Simpan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ── PETA ──────────────────────────────────────────────────
|
||
const map = L.map('map').setView([-0.02, 109.34], 13);
|
||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
maxZoom: 19,
|
||
attribution: '© OpenStreetMap contributors'
|
||
}).addTo(map);
|
||
|
||
// ── ICON ──────────────────────────────────────────────────
|
||
function buatIcon(warna) {
|
||
return L.icon({
|
||
iconUrl: `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-${warna}.png`,
|
||
shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png',
|
||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34]
|
||
});
|
||
}
|
||
const ikonHijau = buatIcon('green');
|
||
const ikonMerah = buatIcon('red');
|
||
|
||
// ── TOAST ─────────────────────────────────────────────────
|
||
function toast(msg, tipe) {
|
||
const el = document.getElementById('toast');
|
||
el.textContent = msg;
|
||
el.className = 'show' + (tipe === 'err' ? ' error' : '');
|
||
setTimeout(() => el.className = '', 2800);
|
||
}
|
||
|
||
// ── LOAD DATA ─────────────────────────────────────────────
|
||
function loadData() {
|
||
fetch('ambil_data.php')
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
map.eachLayer(l => { if (l instanceof L.Marker) map.removeLayer(l); });
|
||
|
||
data.forEach(d => {
|
||
const jam24 = d.status === 'yes';
|
||
const mk = L.marker([parseFloat(d.latitude), parseFloat(d.longitude)], {
|
||
icon: jam24 ? ikonHijau : ikonMerah,
|
||
draggable: true
|
||
}).addTo(map);
|
||
|
||
mk.bindPopup(popupHTML(d, jam24));
|
||
|
||
// drag → pindah lokasi
|
||
mk.on('dragend', function(e) {
|
||
const p = e.target.getLatLng();
|
||
kirim('update.php', {
|
||
id: d.id, nama: d.nama_spbu, wa: d.no_wa,
|
||
status: d.status, lat: p.lat, lng: p.lng
|
||
}).then(r => {
|
||
if (r === 'success') { toast('📍 Lokasi dipindahkan'); loadData(); }
|
||
else toast('Gagal pindah: ' + r, 'err');
|
||
});
|
||
});
|
||
});
|
||
})
|
||
.catch(err => toast('Gagal memuat data: ' + err, 'err'));
|
||
}
|
||
|
||
// ── POPUP HTML ────────────────────────────────────────────
|
||
function popupHTML(d, jam24) {
|
||
const bgBadge = jam24 ? '#d4edda' : '#f8d7da';
|
||
const clBadge = jam24 ? '#155724' : '#721c24';
|
||
const label = jam24 ? 'Buka 24 Jam' : 'Tidak 24 Jam';
|
||
return `
|
||
<div style="min-width:180px;font-family:'Segoe UI',sans-serif">
|
||
<b style="font-size:14px">${d.nama_spbu}</b><br>
|
||
<span style="color:#666;font-size:12px">📞 ${d.no_wa}</span><br>
|
||
<span style="display:inline-block;margin-top:5px;padding:2px 10px;
|
||
border-radius:12px;font-size:11px;font-weight:600;
|
||
background:${bgBadge};color:${clBadge}">⏰ ${label}</span>
|
||
<hr style="margin:8px 0;border:none;border-top:1px solid #eee">
|
||
<div style="display:flex;gap:6px">
|
||
<button onclick="bukaEdit(${d.id},'${esc(d.nama_spbu)}','${esc(d.no_wa)}','${d.status}',${d.latitude},${d.longitude})"
|
||
style="flex:1;padding:6px 0;background:#3498db;color:#fff;border:none;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600">
|
||
✏️ Edit
|
||
</button>
|
||
<button onclick="hapus(${d.id})"
|
||
style="flex:1;padding:6px 0;background:#e74c3c;color:#fff;border:none;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600">
|
||
🗑️ Hapus
|
||
</button>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function esc(s) { return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'"); }
|
||
|
||
// ── FETCH HELPER ──────────────────────────────────────────
|
||
function kirim(url, data) {
|
||
return fetch(url, { method:'POST', body: new URLSearchParams(data) })
|
||
.then(r => r.text());
|
||
}
|
||
|
||
// ── KLIK PETA → TAMBAH ────────────────────────────────────
|
||
map.on('click', function(e) {
|
||
document.getElementById('tLat').value = e.latlng.lat;
|
||
document.getElementById('tLng').value = e.latlng.lng;
|
||
document.getElementById('tNama').value = '';
|
||
document.getElementById('tWa').value = '';
|
||
document.getElementById('tStatus').value = 'yes';
|
||
document.getElementById('modalTambah').classList.add('show');
|
||
});
|
||
|
||
document.getElementById('btnBatalTambah').onclick = function() {
|
||
document.getElementById('modalTambah').classList.remove('show');
|
||
};
|
||
document.getElementById('modalTambah').onclick = function(e) {
|
||
if (e.target === this) this.classList.remove('show');
|
||
};
|
||
|
||
document.getElementById('btnSimpanTambah').onclick = function() {
|
||
const nama = document.getElementById('tNama').value.trim();
|
||
const wa = document.getElementById('tWa').value.trim();
|
||
const status = document.getElementById('tStatus').value;
|
||
const lat = document.getElementById('tLat').value;
|
||
const lng = document.getElementById('tLng').value;
|
||
|
||
if (!nama || !wa) { toast('Nama & No. WA wajib diisi', 'err'); return; }
|
||
|
||
kirim('simpan.php', { nama, wa, status, lat, lng }).then(r => {
|
||
if (r === 'success') {
|
||
document.getElementById('modalTambah').classList.remove('show');
|
||
toast('✅ SPBU berhasil ditambahkan');
|
||
loadData();
|
||
} else {
|
||
toast('Gagal simpan: ' + r, 'err');
|
||
}
|
||
});
|
||
};
|
||
|
||
// ── EDIT ──────────────────────────────────────────────────
|
||
window.bukaEdit = function(id, nama, wa, status, lat, lng) {
|
||
document.getElementById('eId').value = id;
|
||
document.getElementById('eNama').value = nama;
|
||
document.getElementById('eWa').value = wa;
|
||
document.getElementById('eStatus').value = status;
|
||
document.getElementById('eLat').value = lat;
|
||
document.getElementById('eLng').value = lng;
|
||
document.getElementById('modalEdit').classList.add('show');
|
||
map.closePopup();
|
||
};
|
||
|
||
document.getElementById('btnBatalEdit').onclick = function() {
|
||
document.getElementById('modalEdit').classList.remove('show');
|
||
};
|
||
document.getElementById('modalEdit').onclick = function(e) {
|
||
if (e.target === this) this.classList.remove('show');
|
||
};
|
||
|
||
document.getElementById('btnSimpanEdit').onclick = function() {
|
||
const id = document.getElementById('eId').value;
|
||
const nama = document.getElementById('eNama').value.trim();
|
||
const wa = document.getElementById('eWa').value.trim();
|
||
const status = document.getElementById('eStatus').value;
|
||
const lat = document.getElementById('eLat').value;
|
||
const lng = document.getElementById('eLng').value;
|
||
|
||
if (!nama || !wa) { toast('Nama & No. WA wajib diisi', 'err'); return; }
|
||
|
||
kirim('update.php', { id, nama, wa, status, lat, lng }).then(r => {
|
||
if (r === 'success') {
|
||
document.getElementById('modalEdit').classList.remove('show');
|
||
toast('✅ Data berhasil diperbarui');
|
||
loadData();
|
||
} else {
|
||
toast('Gagal update: ' + r, 'err');
|
||
}
|
||
});
|
||
};
|
||
|
||
// ── HAPUS ─────────────────────────────────────────────────
|
||
window.hapus = function(id) {
|
||
if (!confirm('Yakin hapus SPBU ini?')) return;
|
||
kirim('hapus.php', { id }).then(r => {
|
||
if (r === 'success') {
|
||
map.closePopup();
|
||
toast('🗑️ Data dihapus');
|
||
loadData();
|
||
} else {
|
||
toast('Gagal hapus: ' + r, 'err');
|
||
}
|
||
});
|
||
};
|
||
|
||
// ── MULAI ─────────────────────────────────────────────────
|
||
loadData();
|
||
</script>
|
||
</body>
|
||
</html>
|