@@ -0,0 +1,349 @@
<! 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 : 12 px ;
padding : 24 px ;
width : 300 px ;
box - shadow : 0 8 px 32 px rgba ( 0 , 0 , 0 , 0.25 );
}
. modal - box h3 {
font - size : 15 px ;
margin - bottom : 14 px ;
color : #222;
border - bottom : 2 px solid #f0f0f0;
padding - bottom : 10 px ;
}
. modal - box label {
display : block ;
font - size : 12 px ;
font - weight : 600 ;
color : #555;
margin : 10 px 0 3 px ;
}
. modal - box input , . modal - box select {
width : 100 % ;
padding : 8 px 10 px ;
border : 1 px solid #ddd;
border - radius : 6 px ;
font - size : 13 px ;
outline : none ;
}
. modal - box input : focus , . modal - box select : focus { border - color : #3498db; }
. modal - actions { display : flex ; gap : 8 px ; margin - top : 16 px ; }
. btn - save {
flex : 1 ; padding : 9 px ;
background : #3498db; color:#fff;
border : none ; border - radius : 6 px ;
font - size : 13 px ; font - weight : 600 ; cursor : pointer ;
}
. btn - save : hover { background : #2980b9; }
. btn - cancel {
flex : 1 ; padding : 9 px ;
background : #f0f0f0; color:#555;
border : none ; border - radius : 6 px ;
font - size : 13 px ; font - weight : 600 ; cursor : pointer ;
}
. btn - cancel : hover { background : #e0e0e0; }
#info-bar {
position : fixed ;
bottom : 16 px ; left : 50 % ;
transform : translateX ( - 50 % );
background : rgba ( 20 , 20 , 20 , 0.8 );
color : #fff;
padding : 8 px 20 px ;
border - radius : 20 px ;
font - size : 12 px ;
z - index : 1000 ;
pointer - events : none ;
white - space : nowrap ;
}
#toast {
position : fixed ;
bottom : 56 px ; left : 50 % ;
transform : translateX ( - 50 % ) translateY ( 10 px );
background : #2ecc71; color: #fff;
padding : 8 px 22 px ;
border - radius : 20 px ;
font - size : 13 px ; font - weight : 600 ;
z - index : 9998 ;
opacity : 0 ;
transition : all . 3 s ;
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 & nbsp ; · & nbsp ; Klik marker untuk Edit / Hapus & nbsp ; · & nbsp ; 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>