add files

This commit is contained in:
luthfihadinugroho79
2026-06-10 19:42:59 +07:00
parent 7bf3c98e59
commit e950468760
3541 changed files with 0 additions and 0 deletions
+790
View File
@@ -0,0 +1,790 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS SPBU Pontianak</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
<!-- ✅ FIX 1: Leaflet CSS & JS dipindah ke <head> agar sudah siap sebelum script dijalankan -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
:root {
--bg: #0d0f14;
--surface: #161923;
--surface2: #1e2330;
--border: rgba(255,255,255,0.07);
--accent-green: #00e5a0;
--accent-amber: #ffb830;
--accent-blue: #3d8bff;
--text: #e8ecf4;
--text-muted: #6b7385;
--danger: #ff4d6a;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'DM Sans', sans-serif;
background: var(--bg);
color: var(--text);
display: flex;
height: 100vh;
overflow: hidden;
}
/* ─── SIDEBAR ─── */
#sidebar {
width: 340px;
min-width: 340px;
background: var(--surface);
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
z-index: 1000;
overflow: hidden;
}
.sidebar-header {
padding: 28px 24px 20px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, rgba(61,139,255,0.12), rgba(0,229,160,0.06));
position: relative;
overflow: hidden;
}
.sidebar-header::before {
content: '';
position: absolute;
top: -40px; right: -40px;
width: 130px; height: 130px;
border-radius: 50%;
background: radial-gradient(circle, rgba(0,229,160,0.15), transparent 70%);
}
.logo-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.logo-icon {
width: 40px; height: 40px;
background: linear-gradient(135deg, var(--accent-green), var(--accent-blue));
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
}
.sidebar-header h1 {
font-family: 'Syne', sans-serif;
font-size: 20px;
font-weight: 800;
letter-spacing: -0.5px;
}
.sidebar-header p {
font-size: 12px;
color: var(--text-muted);
margin-left: 52px;
}
/* Stats */
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
padding: 18px 20px;
border-bottom: 1px solid var(--border);
}
.stat-box {
background: var(--surface2);
border-radius: 10px;
padding: 12px 10px;
text-align: center;
border: 1px solid var(--border);
transition: transform 0.2s;
}
.stat-box:hover { transform: translateY(-2px); }
.stat-num {
font-family: 'Syne', sans-serif;
font-size: 26px;
font-weight: 800;
line-height: 1;
}
.stat-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
.stat-box.total .stat-num { color: var(--accent-blue); }
.stat-box.jam24 .stat-num { color: var(--accent-green); }
.stat-box.reguler .stat-num { color: var(--accent-amber); }
/* Layer Control Panel */
.layer-panel {
padding: 16px 20px;
border-bottom: 1px solid var(--border);
}
.panel-title {
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-muted);
margin-bottom: 12px;
}
.layer-toggle {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
margin-bottom: 6px;
border: 1px solid var(--border);
}
.layer-toggle:hover { background: var(--surface2); }
.toggle-dot {
width: 12px; height: 12px;
border-radius: 3px;
flex-shrink: 0;
}
.toggle-dot.green { background: var(--accent-green); box-shadow: 0 0 8px rgba(0,229,160,0.5); }
.toggle-dot.amber { background: var(--accent-amber); box-shadow: 0 0 8px rgba(255,184,48,0.5); }
.layer-toggle span { font-size: 13px; flex-grow: 1; }
.toggle-count {
font-size: 11px;
background: var(--surface2);
padding: 2px 7px;
border-radius: 20px;
color: var(--text-muted);
}
/* Custom Checkbox */
.layer-toggle input[type="checkbox"] {
appearance: none;
width: 18px; height: 18px;
border: 2px solid var(--border);
border-radius: 5px;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
}
.layer-toggle input[type="checkbox"]:checked {
background: var(--accent-blue);
border-color: var(--accent-blue);
}
.layer-toggle input[type="checkbox"]:checked::after {
content: '✓';
position: absolute;
color: white;
font-size: 11px;
top: 0px; left: 2px;
font-weight: bold;
}
/* SPBU List */
.list-header {
padding: 14px 20px 8px;
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-muted);
}
#spbu-list {
flex-grow: 1;
overflow-y: auto;
padding: 0 12px 12px;
scrollbar-width: thin;
scrollbar-color: var(--surface2) transparent;
}
.spbu-item {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 12px;
border-radius: 8px;
margin-bottom: 5px;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.2s;
}
.spbu-item:hover {
background: var(--surface2);
border-color: var(--border);
}
.spbu-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.spbu-dot.green { background: var(--accent-green); }
.spbu-dot.amber { background: var(--accent-amber); }
.spbu-item-name { font-size: 13px; font-weight: 500; flex-grow: 1; }
.badge-pill {
font-size: 9px;
padding: 3px 7px;
border-radius: 20px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.badge-pill.green { background: rgba(0,229,160,0.15); color: var(--accent-green); }
.badge-pill.amber { background: rgba(255,184,48,0.15); color: var(--accent-amber); }
/* ─── MAP ─── */
#map-container {
flex-grow: 1;
position: relative;
/* ✅ FIX 2: Pastikan tinggi eksplisit agar peta punya dimensi */
height: 100vh;
min-height: 0;
}
#map {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
}
/* Leaflet overrides for dark theme */
.leaflet-popup-content-wrapper {
background: var(--surface) !important;
color: var(--text) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
box-shadow: 0 20px 60px rgba(0,0,0,0.6) !important;
padding: 0 !important;
overflow: hidden;
}
.leaflet-popup-tip { background: var(--surface) !important; }
.leaflet-popup-content { margin: 0 !important; }
.leaflet-control-zoom {
border: none !important;
border-radius: 10px !important;
overflow: hidden;
}
.leaflet-control-zoom a {
background: var(--surface) !important;
color: var(--text) !important;
border-bottom: 1px solid var(--border) !important;
}
.leaflet-control-zoom a:hover { background: var(--surface2) !important; }
/* Popup Form */
.popup-wrap { padding: 18px; min-width: 220px; }
.popup-type {
font-size: 9px;
font-family: 'Syne', sans-serif;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 4px;
}
.popup-name {
font-family: 'Syne', sans-serif;
font-size: 16px;
font-weight: 700;
margin-bottom: 12px;
}
.popup-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--text-muted);
margin-bottom: 6px;
}
.popup-row strong { color: var(--text); }
.popup-divider { height: 1px; background: var(--border); margin: 14px 0; }
.popup-input {
width: 100%;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 7px;
padding: 9px 12px;
color: var(--text);
font-family: 'DM Sans', sans-serif;
font-size: 13px;
margin-bottom: 8px;
outline: none;
transition: border-color 0.2s;
}
.popup-input:focus { border-color: var(--accent-blue); }
.popup-actions { display: flex; gap: 7px; margin-top: 12px; }
.pop-btn {
flex: 1;
padding: 9px;
border: none;
border-radius: 7px;
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
cursor: pointer;
transition: all 0.2s;
}
.pop-btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
.pop-btn.blue { background: var(--accent-blue); color: white; }
.pop-btn.amber { background: var(--accent-amber); color: #1a1000; }
.pop-btn.red { background: var(--danger); color: white; }
.pop-btn.gray { background: var(--surface2); color: var(--text-muted); border: 1px solid var(--border); }
/* Map hint badge */
.map-hint {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(13,15,20,0.85);
backdrop-filter: blur(10px);
border: 1px solid var(--border);
border-radius: 30px;
padding: 8px 18px;
font-size: 12px;
color: var(--text-muted);
z-index: 999;
pointer-events: none;
}
.map-hint span { color: var(--accent-blue); font-weight: 600; }
/* ✅ FIX 3: Loading overlay HTML ditambahkan */
.loading-overlay {
position: absolute;
inset: 0;
background: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
transition: opacity 0.5s;
}
.loading-overlay.hidden { opacity: 0; pointer-events: none; }
.loader-ring {
width: 50px; height: 50px;
border: 3px solid var(--surface2);
border-top-color: var(--accent-green);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 16px;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Animations */
.spbu-item { animation: fadeUp 0.3s ease backwards; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div id="sidebar">
<div class="sidebar-header">
<div class="logo-row">
<div class="logo-icon"></div>
<h1>SPBU Pontianak</h1>
</div>
<p>WebGIS — Sistem Informasi Geografis</p>
</div>
<div class="stats-grid">
<div class="stat-box total">
<div class="stat-num" id="stat-total"></div>
<div class="stat-label">Total</div>
</div>
<div class="stat-box jam24">
<div class="stat-num" id="stat-24"></div>
<div class="stat-label">24 Jam</div>
</div>
<div class="stat-box reguler">
<div class="stat-num" id="stat-reg"></div>
<div class="stat-label">Reguler</div>
</div>
</div>
<div class="layer-panel">
<div class="panel-title">Layer Control</div>
<label class="layer-toggle">
<div class="toggle-dot green"></div>
<span>SPBU Buka 24 Jam</span>
<span class="toggle-count" id="cnt-24">0</span>
<input type="checkbox" id="chk-24" checked>
</label>
<label class="layer-toggle">
<div class="toggle-dot amber"></div>
<span>SPBU Buka Reguler</span>
<span class="toggle-count" id="cnt-reg">0</span>
<input type="checkbox" id="chk-reg" checked>
</label>
</div>
<div class="list-header">Daftar SPBU</div>
<div id="spbu-list">
<p style="text-align:center;color:var(--text-muted);font-size:13px;padding:20px;">Memuat data...</p>
</div>
</div>
<div id="map-container">
<!-- ✅ FIX 3: Loading overlay ditambahkan ke HTML -->
<div class="loading-overlay" id="loading-overlay">
<div class="loader-ring"></div>
<p style="color:var(--text-muted);font-size:13px;">Memuat peta...</p>
</div>
<div id="map"></div>
<div class="map-hint">Klik <span>di mana saja</span> pada peta untuk menambah SPBU baru</div>
</div>
<!-- ✅ FIX 1: Script Leaflet DIHAPUS dari sini karena sudah dipindah ke <head> -->
<script>
// ── MAP INIT ──
// ✅ FIX 4: Inisialisasi map dibungkus dalam DOMContentLoaded agar aman
document.addEventListener('DOMContentLoaded', function() {
const map = L.map('map', { zoomControl: false }).setView([-0.026330, 109.342504], 13);
L.control.zoom({ position: 'topright' }).addTo(map);
// ✅ FIX 5: Tile layer dengan 2 pilihan — CartoDB dark (utama) + OSM (fallback)
const tileCartoDB = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_matter/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/">CartoDB</a>',
maxZoom: 19,
subdomains: 'abcd'
});
const tileOSM = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19
});
// Coba CartoDB dulu, jika tile gagal load pakai OSM
tileCartoDB.addTo(map);
tileCartoDB.on('tileerror', function() {
map.removeLayer(tileCartoDB);
tileOSM.addTo(map);
});
// ── LAYER GROUPS ──
// Dibuat dulu, belum ditambah ke map — ditambah sesuai state checkbox
const layer24 = L.layerGroup();
const layerReg = L.layerGroup();
// ── EVENT LAYER CONTROL (FIX UTAMA) ──
document.getElementById('chk-24').addEventListener('change', function() {
if (this.checked) {
layer24.addTo(map);
} else {
map.removeLayer(layer24);
}
});
document.getElementById('chk-reg').addEventListener('change', function() {
if (this.checked) {
layerReg.addTo(map);
} else {
map.removeLayer(layerReg);
}
});
// ── ICONS ──
function makeIcon(color) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="42" viewBox="0 0 32 42">
<defs>
<filter id="s${color.replace('#','')}"><feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="${color}" flood-opacity="0.4"/></filter>
</defs>
<path fill="${color}" filter="url(#s${color.replace('#','')})" d="M16 0C7.163 0 0 7.163 0 16c0 12 16 26 16 26s16-14 16-26C32 7.163 24.837 0 16 0z"/>
<circle cx="16" cy="16" r="8" fill="white" opacity="0.9"/>
<circle cx="16" cy="16" r="5" fill="${color}"/>
</svg>`;
return L.divIcon({
html: svg,
className: '',
iconSize: [32, 42],
iconAnchor: [16, 42],
popupAnchor: [0, -42]
});
}
const iconGreen = makeIcon('#00e5a0');
const iconAmber = makeIcon('#ffb830');
const iconTemp = makeIcon('#3d8bff');
// ── DATA ──
let allData = [];
let tempMarker = null;
function muatData() {
fetch('ambil_data.php')
.then(r => r.json())
.then(data => {
allData = data;
renderAll(data);
})
.catch(() => {
// Demo mode jika tidak ada server
renderAll(demoData());
});
}
function demoData() {
return [
{id:'1', nama_spbu:'SPBU Jl. Ahmad Yani', no_wa:'08123456789', status_24_jam:'yes', lat:-0.0202, lng:109.3423},
{id:'2', nama_spbu:'SPBU Jl. Gajah Mada', no_wa:'08234567890', status_24_jam:'no', lat:-0.0280, lng:109.3310},
{id:'3', nama_spbu:'SPBU Sui Raya', no_wa:'08345678901', status_24_jam:'yes', lat:-0.0450, lng:109.3580},
{id:'4', nama_spbu:'SPBU Jl. Tanjungpura', no_wa:'08456789012', status_24_jam:'no', lat:-0.0120, lng:109.3390},
{id:'5', nama_spbu:'SPBU Jl. Imam Bonjol', no_wa:'08567890123', status_24_jam:'yes', lat:-0.0330, lng:109.3500},
{id:'6', nama_spbu:'SPBU Siantan', no_wa:'08678901234', status_24_jam:'no', lat:0.0050, lng:109.3350},
];
}
function renderAll(data) {
layer24.clearLayers();
layerReg.clearLayers();
let c24 = 0, cReg = 0;
let listHTML = '';
data.forEach((item, i) => {
const is24 = item.status_24_jam === 'yes';
const icon = is24 ? iconGreen : iconAmber;
const layer = is24 ? layer24 : layerReg;
if (is24) c24++; else cReg++;
// Tambah marker ke layerGroup (bukan langsung ke map)
const marker = L.marker([parseFloat(item.lat), parseFloat(item.lng)], { icon, draggable: true });
layer.addLayer(marker);
marker.bindPopup(buildInfoPopup(item), { maxWidth: 280 });
marker.on('dragend', function() {
const pos = marker.getLatLng();
if (confirm('Simpan koordinat baru?')) updateLokasi(item.id, pos.lat, pos.lng);
else muatData();
});
const delay = i * 40;
listHTML += `
<div class="spbu-item" style="animation-delay:${delay}ms" onclick="flyTo(${item.lat},${item.lng})">
<div class="spbu-dot ${is24 ? 'green' : 'amber'}"></div>
<span class="spbu-item-name">${item.nama_spbu}</span>
<span class="badge-pill ${is24 ? 'green' : 'amber'}">${is24 ? '24 Jam' : 'Reguler'}</span>
</div>
`;
});
document.getElementById('stat-total').textContent = data.length;
document.getElementById('stat-24').textContent = c24;
document.getElementById('stat-reg').textContent = cReg;
document.getElementById('cnt-24').textContent = c24;
document.getElementById('cnt-reg').textContent = cReg;
document.getElementById('spbu-list').innerHTML = listHTML || '<p style="text-align:center;color:var(--text-muted);font-size:13px;padding:20px;">Belum ada data.</p>';
// Terapkan state checkbox saat ini ke masing-masing layer
const show24 = document.getElementById('chk-24').checked;
const showReg = document.getElementById('chk-reg').checked;
show24 ? layer24.addTo(map) : map.removeLayer(layer24);
showReg ? layerReg.addTo(map) : map.removeLayer(layerReg);
// Sembunyikan loading overlay setelah data selesai dirender
const overlay = document.getElementById('loading-overlay');
if (overlay) overlay.classList.add('hidden');
}
window.flyTo = function flyTo(lat, lng) {
map.flyTo([lat, lng], 16, { duration: 1 });
};
// ── POPUP BUILDERS ──
function buildInfoPopup(item) {
const is24 = item.status_24_jam === 'yes';
return `
<div class="popup-wrap">
<div class="popup-type">Stasiun Bahan Bakar</div>
<div class="popup-name">${item.nama_spbu}</div>
<div class="popup-row">📞 <strong>${item.no_wa}</strong></div>
<div class="popup-row">🕐 Status: <span class="badge-pill ${is24?'green':'amber'}" style="margin-left:4px">${is24?'Buka 24 Jam':'Buka Reguler'}</span></div>
<div class="popup-divider"></div>
<div class="popup-actions">
<button class="pop-btn amber" onclick="bukaEdit('${item.id}','${item.nama_spbu}','${item.no_wa}','${item.status_24_jam}',${item.lat},${item.lng})">✏ Edit</button>
<button class="pop-btn red" onclick="hapusData('${item.id}')">🗑 Hapus</button>
</div>
</div>
`;
}
function buildAddPopup(lat, lng) {
return `
<div class="popup-wrap">
<div class="popup-type">Tambah Lokasi Baru</div>
<div class="popup-name" style="font-size:14px;margin-bottom:14px;">Data SPBU</div>
<input class="popup-input" id="add_nama" placeholder="Nama SPBU" />
<input class="popup-input" id="add_wa" placeholder="Nomor WhatsApp" />
<select class="popup-input" id="add_status">
<option value="no">Buka Reguler</option>
<option value="yes">Buka 24 Jam</option>
</select>
<div class="popup-actions">
<button class="pop-btn blue" onclick="prosesSimpan(${lat},${lng},'tambah')">💾 Simpan</button>
<button class="pop-btn gray" onclick="batalTambah()">✕</button>
</div>
</div>
`;
}
function buildEditPopup(id, nama, wa, status, lat, lng) {
const sY = status==='yes'?'selected':'';
const sN = status==='no'?'selected':'';
return `
<div class="popup-wrap">
<div class="popup-type">Edit Data</div>
<input type="hidden" id="edit_id" value="${id}">
<input class="popup-input" id="edit_nama" value="${nama}">
<input class="popup-input" id="edit_wa" value="${wa}">
<select class="popup-input" id="edit_status">
<option value="no" ${sN}>Buka Reguler</option>
<option value="yes" ${sY}>Buka 24 Jam</option>
</select>
<div class="popup-actions">
<button class="pop-btn blue" onclick="prosesSimpan(${lat},${lng},'edit')">✓ Update</button>
<button class="pop-btn gray" onclick="map.closePopup()">✕ Batal</button>
</div>
</div>
`;
}
// ── MAP CLICK ADD ──
map.on('click', function(e) {
if (tempMarker) map.removeLayer(tempMarker);
tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: iconTemp }).addTo(map);
tempMarker.bindPopup(buildAddPopup(e.latlng.lat, e.latlng.lng), { maxWidth: 280 }).openPopup();
});
window.batalTambah = function() {
if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; }
map.closePopup();
};
// ── EDIT ──
window.bukaEdit = function(id, nama, wa, status, lat, lng) {
L.popup({ maxWidth: 280 })
.setLatLng([lat, lng])
.setContent(buildEditPopup(id, nama, wa, status, lat, lng))
.openOn(map);
};
// ── SIMPAN (POST) ──
window.prosesSimpan = function prosesSimpan(lat, lng, mode) {
const pfx = mode === 'edit' ? 'edit_' : 'add_';
const id = mode === 'edit' ? document.getElementById('edit_id').value : '';
const nama = document.getElementById(pfx + 'nama').value.trim();
const wa = document.getElementById(pfx + 'wa').value.trim();
const stat = document.getElementById(pfx + 'status').value;
if (!nama || !wa) return alert('Nama dan nomor WA wajib diisi!');
const fd = new FormData();
if (mode === 'edit') fd.append('id', id);
fd.append('nama_spbu', nama);
fd.append('no_wa', wa);
fd.append('status_24_jam', stat);
fd.append('lat', lat);
fd.append('lng', lng);
const url = mode === 'edit' ? 'edit_data.php' : 'simpan_data.php';
fetch(url, { method: 'POST', body: fd })
.then(r => r.text())
.then(msg => {
showToast(msg, 'success');
map.closePopup();
if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; }
muatData();
})
.catch(() => {
// Demo mode: update state lokal agar data langsung tampil tanpa server
if (mode === 'tambah') {
const newItem = {
id: Date.now().toString(),
nama_spbu: nama,
no_wa: wa,
status_24_jam: stat,
lat: lat,
lng: lng
};
allData.push(newItem);
} else {
const idx = allData.findIndex(d => d.id === id);
if (idx !== -1) {
allData[idx].nama_spbu = nama;
allData[idx].no_wa = wa;
allData[idx].status_24_jam = stat;
}
}
renderAll(allData);
showToast(mode === 'tambah' ? 'Data berhasil disimpan! (demo mode)' : 'Data berhasil diperbarui! (demo mode)', 'success');
map.closePopup();
if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; }
});
}
// ── HAPUS ──
window.hapusData = function hapusData(id) {
if (!confirm('Hapus data SPBU ini?')) return;
const fd = new FormData();
fd.append('id', id);
fetch('hapus_data.php', { method: 'POST', body: fd })
.then(r => r.text())
.then(msg => { showToast(msg, 'danger'); muatData(); })
.catch(() => {
allData = allData.filter(d => d.id !== id);
renderAll(allData);
showToast('Data dihapus! (demo mode)', 'danger');
});
}
// ── UPDATE LOKASI ──
window.updateLokasi = function updateLokasi(id, lat, lng) {
const fd = new FormData();
fd.append('id', id); fd.append('lat', lat); fd.append('lng', lng);
fetch('update_lokasi.php', { method: 'POST', body: fd })
.then(() => { showToast('Lokasi diperbarui!', 'success'); muatData(); })
.catch(() => { showToast('Lokasi diperbarui! (demo mode)', 'success'); muatData(); });
}
// ── TOAST ──
function showToast(msg, type) {
const t = document.createElement('div');
t.style.cssText = `
position:fixed; bottom:30px; right:30px; z-index:9999;
background:${type==='success'?'#00e5a0':'#ff4d6a'};
color:${type==='success'?'#001a0d':'#fff'};
padding:12px 20px; border-radius:10px; font-size:13px; font-weight:600;
font-family:'Syne',sans-serif; box-shadow:0 8px 30px rgba(0,0,0,0.4);
animation: slideIn 0.3s ease;
`;
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), 3000);
}
// ── START ──
muatData();
// ✅ FIX 6: invalidateSize dipanggil beberapa kali untuk memastikan peta terrender sempurna
setTimeout(() => map.invalidateSize(), 100);
setTimeout(() => map.invalidateSize(), 500);
}); // tutup DOMContentLoaded
</script>
</body>
</html>