570 lines
24 KiB
HTML
570 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="id">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="description" content="WebGIS SPBU – Layer Groups & Layers Control">
|
||
<title>WebGIS SPBU – Layer Groups</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
|
||
<body>
|
||
<header id="header-bar">
|
||
<div>
|
||
<h1>⛽ WebGIS SPBU Pontianak</h1>
|
||
<p>Layer Groups & Layers Control – Pertemuan 2</p>
|
||
</div>
|
||
<div id="stats-bar">
|
||
<span class="stat-badge green" id="count-24jam">🟢 24 Jam: 0</span>
|
||
<span class="stat-badge orange" id="count-tidak24jam">🔴 Tidak 24 Jam: 0</span>
|
||
</div>
|
||
</header>
|
||
|
||
<div id="map"></div>
|
||
|
||
<div class="fab-container">
|
||
<button class="fab-btn" id="fab-tambah" title="Tambah SPBU" onclick="aktifkanModeTambah()">➕</button>
|
||
</div>
|
||
|
||
<div id="toast"></div>
|
||
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||
<script>
|
||
// Gunakan path relatif sederhana — lebih stabil dari URL constructor
|
||
const API_SPBU = 'api_spbu.php';
|
||
|
||
// ============================================================
|
||
// INISIALISASI PETA (mengikuti pola Leaflet Layers Control)
|
||
// https://leafletjs.com/examples/layers-control/
|
||
// ============================================================
|
||
const map = L.map('map').setView([-0.0553, 109.3495], 13);
|
||
|
||
const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||
maxZoom: 19
|
||
});
|
||
|
||
const satelliteLayer = L.tileLayer(
|
||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||
{ attribution: 'Tiles © Esri', maxZoom: 19 }
|
||
);
|
||
|
||
osmLayer.addTo(map);
|
||
|
||
// ============================================================
|
||
// LAYER GROUPS – 2 grup SPBU
|
||
// ============================================================
|
||
const layer24Jam = L.layerGroup();
|
||
const layerTidak24Jam = L.layerGroup();
|
||
|
||
// Kedua layer tampil secara default saat app dimuat
|
||
layer24Jam.addTo(map);
|
||
layerTidak24Jam.addTo(map);
|
||
|
||
// ============================================================
|
||
// BASE MAPS & OVERLAY MAPS
|
||
// ============================================================
|
||
const baseMaps = {
|
||
'🗺️ OpenStreetMap': osmLayer,
|
||
'🛰️ Citra Satelit': satelliteLayer
|
||
};
|
||
|
||
const overlayMaps = {
|
||
'🟢 SPBU Buka 24 Jam': layer24Jam,
|
||
'🔴 SPBU Tidak 24 Jam': layerTidak24Jam
|
||
};
|
||
|
||
L.control.layers(baseMaps, overlayMaps, {
|
||
collapsed: false,
|
||
position: 'topright'
|
||
}).addTo(map);
|
||
|
||
// ============================================================
|
||
// IKON MARKER – warna berbeda per grup
|
||
// ============================================================
|
||
function ikonSpbu(buka24Jam) {
|
||
const is24Jam = buka24Jam === 'Ya';
|
||
const color = is24Jam ? '#10b981' : '#f97316';
|
||
const border = is24Jam ? '#065f46' : '#9a3412';
|
||
const emoji = '⛽';
|
||
|
||
return L.divIcon({
|
||
className: '',
|
||
html: `<div style="width:32px;height:32px;border-radius:50% 50% 50% 0;background:${color};transform:rotate(-45deg);border:3px solid ${border};box-shadow:0 3px 10px rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;">
|
||
<span style="transform:rotate(45deg);font-size:14px;line-height:1">${emoji}</span>
|
||
</div>`,
|
||
iconSize: [32, 32],
|
||
iconAnchor: [16, 32],
|
||
popupAnchor: [0, -34]
|
||
});
|
||
}
|
||
|
||
// ============================================================
|
||
// POPUP INFO
|
||
// ============================================================
|
||
function statusLabel(buka24Jam) {
|
||
return buka24Jam === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam';
|
||
}
|
||
|
||
function escHtml(str) {
|
||
return String(str ?? '')
|
||
.replace(/&/g, '&')
|
||
.replace(/"/g, '"')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>');
|
||
}
|
||
|
||
function popupInfoSpbu(d) {
|
||
const is24Jam = d.buka_24_jam === 'Ya';
|
||
const statusClass = is24Jam ? 'status-24jam' : 'status-tidak24jam';
|
||
const statusText = statusLabel(d.buka_24_jam);
|
||
const titleColor = is24Jam ? '#10b981' : '#f97316';
|
||
|
||
return `<div class="gis-popup">
|
||
<div class="popup-title" style="border-bottom-color:${titleColor}">⛽ ${escHtml(d.nama_spbu)}</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Alamat</span>
|
||
<span class="info-value" style="font-size:11px">${escHtml(d.alamat)}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Status</span>
|
||
<span class="info-value"><span class="status-badge ${statusClass}">${statusText}</span></span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">WhatsApp</span>
|
||
<span class="info-value">${escHtml(d.no_wa)}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Koordinat</span>
|
||
<span class="info-value" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span>
|
||
</div>
|
||
<div class="popup-actions">
|
||
<button type="button" class="popup-btn popup-btn-edit btn-edit-spbu" data-id="${d.id}" onclick="event.stopPropagation(); bukaFormEditSpbu(${d.id}); return false;">✏️ Edit</button>
|
||
<button type="button" class="popup-btn popup-btn-delete btn-delete-spbu" data-id="${d.id}" onclick="event.stopPropagation(); konfirmasiHapusSpbu(${d.id}); return false;">🗑️ Hapus</button>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function formSpbuHtml({ title, prefix, lat, lng, d = {} }) {
|
||
const buka24 = d.buka_24_jam === 'Ya' ? 'Ya' : 'Tidak';
|
||
return `<div class="gis-popup">
|
||
<div class="popup-title">${title}</div>
|
||
<div class="popup-form">
|
||
<label>Nama SPBU</label>
|
||
<input type="text" id="${prefix}-nama" placeholder="Contoh: SPBU Jl. Ahmad Yani" value="${escHtml(d.nama_spbu || '')}">
|
||
<label>Alamat</label>
|
||
<input type="text" id="${prefix}-alamat" placeholder="Alamat lengkap" value="${escHtml(d.alamat || '')}">
|
||
<label>WhatsApp</label>
|
||
<input type="text" id="${prefix}-wa" placeholder="08xx-xxxx-xxxx" value="${escHtml(d.no_wa || '')}">
|
||
<label>Status Operasional</label>
|
||
<select id="${prefix}-24jam">
|
||
<option value="Ya"${buka24 === 'Ya' ? ' selected' : ''}>🟢 Buka 24 Jam</option>
|
||
<option value="Tidak"${buka24 === 'Tidak' ? ' selected' : ''}>🔴 Tidak Buka 24 Jam</option>
|
||
</select>
|
||
<label>Koordinat</label>
|
||
<input type="text" id="${prefix}-koord" readonly value="${lat.toFixed(6)}, ${lng.toFixed(6)}" style="font-size:10px">
|
||
</div>
|
||
<div class="popup-actions">
|
||
<button class="popup-btn popup-btn-save" onclick="${prefix === 'add' ? `simpanSpbu(${lat},${lng})` : `updateSpbu(${d.id})`}">💾 Simpan</button>
|
||
<button class="popup-btn popup-btn-cancel" onclick="map.closePopup()">✖ Batal</button>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// ============================================================
|
||
// RENDER MARKER KE LAYER GROUP YANG SESUAI
|
||
// ============================================================
|
||
const spbuMarkers = {};
|
||
const spbuDataCache = {};
|
||
let modeTambah = false;
|
||
let dbReady = false;
|
||
|
||
function getAllSpbuData() {
|
||
return Object.values(spbuDataCache);
|
||
}
|
||
|
||
function normalizeId(id) {
|
||
return Number(id);
|
||
}
|
||
|
||
function parseJsonResponse(text) {
|
||
const clean = (text || '').replace(/^\uFEFF/, '').trim();
|
||
if (!clean) return null;
|
||
try {
|
||
return JSON.parse(clean);
|
||
} catch (e) {
|
||
throw new Error('Respons server bukan JSON valid');
|
||
}
|
||
}
|
||
|
||
async function apiSend(method, id, body) {
|
||
const hasId = id !== null && id !== undefined && id !== '';
|
||
const baseUrl = hasId ? `${API_SPBU}?id=${normalizeId(id)}` : API_SPBU;
|
||
const fetchOpts = {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
};
|
||
if (body !== undefined) {
|
||
fetchOpts.body = JSON.stringify(body);
|
||
}
|
||
|
||
let res = await fetch(baseUrl, fetchOpts);
|
||
|
||
if ((method === 'DELETE' || method === 'PUT') && (res.status === 405 || res.status === 501)) {
|
||
res = await fetch(`${baseUrl}&_method=${method}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ...(body || {}), _method: method, id: normalizeId(id) })
|
||
});
|
||
}
|
||
|
||
const data = parseJsonResponse(await res.text());
|
||
if (!res.ok) {
|
||
throw new Error((data && data.error) ? data.error : `Server error (${res.status})`);
|
||
}
|
||
return data;
|
||
}
|
||
|
||
async function apiGetAll() {
|
||
const res = await fetch(API_SPBU);
|
||
const data = parseJsonResponse(await res.text());
|
||
if (!res.ok) {
|
||
throw new Error((data && data.error) ? data.error : `Server error (${res.status})`);
|
||
}
|
||
if (!Array.isArray(data)) {
|
||
throw new Error('Data SPBU harus berupa array JSON');
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function renderSpbu(d) {
|
||
const id = normalizeId(d.id);
|
||
d.id = id;
|
||
spbuDataCache[id] = d;
|
||
const targetLayer = d.buka_24_jam === 'Ya' ? layer24Jam : layerTidak24Jam;
|
||
|
||
if (spbuMarkers[id]) {
|
||
const oldMarker = spbuMarkers[id];
|
||
layer24Jam.removeLayer(oldMarker);
|
||
layerTidak24Jam.removeLayer(oldMarker);
|
||
}
|
||
|
||
const marker = L.marker([d.latitude, d.longitude], {
|
||
icon: ikonSpbu(d.buka_24_jam),
|
||
draggable: true
|
||
});
|
||
|
||
marker._data = d;
|
||
marker._targetLayer = targetLayer;
|
||
marker._spbuId = id;
|
||
|
||
marker.bindPopup(() => popupInfoSpbu(spbuDataCache[id] || d));
|
||
marker.on('click', () => marker.openPopup());
|
||
marker.on('dragend', function () {
|
||
const pos = marker.getLatLng();
|
||
const data = spbuDataCache[id];
|
||
if (!data) return;
|
||
|
||
data.latitude = pos.lat;
|
||
data.longitude = pos.lng;
|
||
spbuDataCache[id] = data;
|
||
|
||
if (!dbReady) {
|
||
showToast('📍 Posisi diperbarui (mode offline)', 'info');
|
||
marker.getPopup().setContent(popupInfoSpbu(data));
|
||
return;
|
||
}
|
||
|
||
apiSend('PUT', id, { latitude: pos.lat, longitude: pos.lng })
|
||
.then(() => {
|
||
showToast('📍 Posisi SPBU diperbarui', 'success');
|
||
marker.getPopup().setContent(popupInfoSpbu(data));
|
||
})
|
||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||
});
|
||
|
||
targetLayer.addLayer(marker);
|
||
spbuMarkers[id] = marker;
|
||
updateCounts(getAllSpbuData());
|
||
}
|
||
|
||
function removeSpbuFromMap(id) {
|
||
const numId = normalizeId(id);
|
||
const marker = spbuMarkers[numId];
|
||
if (marker) {
|
||
layer24Jam.removeLayer(marker);
|
||
layerTidak24Jam.removeLayer(marker);
|
||
marker.remove();
|
||
delete spbuMarkers[numId];
|
||
}
|
||
delete spbuDataCache[numId];
|
||
updateCounts(getAllSpbuData());
|
||
}
|
||
|
||
function updateCounts(data) {
|
||
const count24 = data.filter(d => d.buka_24_jam === 'Ya').length;
|
||
const countTidak = data.filter(d => d.buka_24_jam === 'Tidak').length;
|
||
document.getElementById('count-24jam').textContent = `🟢 24 Jam: ${count24}`;
|
||
document.getElementById('count-tidak24jam').textContent = `🔴 Tidak 24 Jam: ${countTidak}`;
|
||
}
|
||
|
||
function showToast(msg, type = 'info') {
|
||
const container = document.getElementById('toast');
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast-item toast-${type}`;
|
||
toast.textContent = msg;
|
||
container.appendChild(toast);
|
||
|
||
setTimeout(() => {
|
||
toast.style.opacity = '0';
|
||
toast.style.transform = 'translateX(30px)';
|
||
toast.style.transition = 'all 0.3s ease';
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// ============================================================
|
||
// MODE TAMBAH SPBU (FAB + klik peta)
|
||
// ============================================================
|
||
function aktifkanModeTambah() {
|
||
modeTambah = !modeTambah;
|
||
const fab = document.getElementById('fab-tambah');
|
||
if (modeTambah) {
|
||
fab.classList.add('active');
|
||
fab.textContent = '✖';
|
||
showToast('📍 Klik lokasi di peta untuk menambah SPBU baru', 'info');
|
||
} else {
|
||
fab.classList.remove('active');
|
||
fab.textContent = '➕';
|
||
}
|
||
}
|
||
window.aktifkanModeTambah = aktifkanModeTambah;
|
||
|
||
map.on('click', function (e) {
|
||
if (!modeTambah) return;
|
||
modeTambah = false;
|
||
document.getElementById('fab-tambah').classList.remove('active');
|
||
document.getElementById('fab-tambah').textContent = '➕';
|
||
tampilkanFormTambahSpbu(e.latlng.lat, e.latlng.lng);
|
||
});
|
||
|
||
function tampilkanFormTambahSpbu(lat, lng) {
|
||
L.popup({ closeButton: true, maxWidth: 300 })
|
||
.setLatLng([lat, lng])
|
||
.setContent(formSpbuHtml({ title: '➕ Tambah SPBU Baru', prefix: 'add', lat, lng }))
|
||
.openOn(map);
|
||
}
|
||
|
||
function simpanSpbu(lat, lng) {
|
||
const nama = document.getElementById('add-nama').value.trim();
|
||
const alamat = document.getElementById('add-alamat').value.trim();
|
||
const no_wa = document.getElementById('add-wa').value.trim();
|
||
const buka_24_jam = document.getElementById('add-24jam').value;
|
||
|
||
if (!nama || !alamat || !no_wa) {
|
||
showToast('⚠️ Nama, alamat, dan WhatsApp wajib diisi!', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!dbReady) {
|
||
showToast('❌ Database belum tersedia. Import database.sql terlebih dahulu.', 'error');
|
||
return;
|
||
}
|
||
|
||
apiSend('POST', null, { nama_spbu: nama, alamat, no_wa, buka_24_jam, latitude: lat, longitude: lng })
|
||
.then(res => {
|
||
map.closePopup();
|
||
renderSpbu({
|
||
id: normalizeId(res.id),
|
||
nama_spbu: res.nama_spbu,
|
||
alamat: res.alamat,
|
||
no_wa: res.no_wa,
|
||
buka_24_jam: res.buka_24_jam,
|
||
latitude: res.latitude,
|
||
longitude: res.longitude
|
||
});
|
||
showToast('✅ SPBU berhasil ditambahkan', 'success');
|
||
})
|
||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||
}
|
||
window.simpanSpbu = simpanSpbu;
|
||
|
||
function bukaFormEditSpbu(id) {
|
||
const d = spbuDataCache[normalizeId(id)];
|
||
if (!d) return;
|
||
|
||
map.closePopup();
|
||
L.popup({ closeButton: true, maxWidth: 300 })
|
||
.setLatLng([d.latitude, d.longitude])
|
||
.setContent(formSpbuHtml({
|
||
title: '✏️ Edit SPBU',
|
||
prefix: 'edit',
|
||
lat: d.latitude,
|
||
lng: d.longitude,
|
||
d
|
||
}))
|
||
.openOn(map);
|
||
}
|
||
window.bukaFormEditSpbu = bukaFormEditSpbu;
|
||
|
||
function updateSpbu(id) {
|
||
const numId = normalizeId(id);
|
||
const nama = document.getElementById('edit-nama').value.trim();
|
||
const alamat = document.getElementById('edit-alamat').value.trim();
|
||
const no_wa = document.getElementById('edit-wa').value.trim();
|
||
const buka_24_jam = document.getElementById('edit-24jam').value;
|
||
const old = spbuDataCache[numId];
|
||
if (!old) return;
|
||
|
||
if (!nama || !alamat || !no_wa) {
|
||
showToast('⚠️ Nama, alamat, dan WhatsApp wajib diisi!', 'error');
|
||
return;
|
||
}
|
||
|
||
const payload = {
|
||
nama_spbu: nama,
|
||
alamat,
|
||
no_wa,
|
||
buka_24_jam,
|
||
latitude: old.latitude,
|
||
longitude: old.longitude
|
||
};
|
||
|
||
if (!dbReady) {
|
||
map.closePopup();
|
||
renderSpbu({ ...payload, id: numId });
|
||
showToast('✅ Data diperbarui (mode offline)', 'success');
|
||
return;
|
||
}
|
||
|
||
apiSend('PUT', numId, payload)
|
||
.then(() => {
|
||
map.closePopup();
|
||
renderSpbu({ ...payload, id: numId });
|
||
showToast('✅ SPBU berhasil diperbarui', 'success');
|
||
})
|
||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||
}
|
||
window.updateSpbu = updateSpbu;
|
||
|
||
// Tampilkan konfirmasi inline di dalam popup (confirm() diblokir di Leaflet popup)
|
||
function konfirmasiHapusSpbu(id) {
|
||
const numId = normalizeId(id);
|
||
const d = spbuDataCache[numId];
|
||
if (!d) { showToast('❌ Data SPBU tidak ditemukan', 'error'); return; }
|
||
|
||
const nama = d.nama_spbu;
|
||
const konfirmHtml = `
|
||
<div class="gis-popup">
|
||
<div class="popup-title" style="color:#ef4444">🗑️ Hapus SPBU?</div>
|
||
<p style="margin:8px 0 16px;font-size:12px;color:#94a3b8">"${escHtml(nama)}" akan dihapus permanen.</p>
|
||
<div class="popup-actions">
|
||
<button class="popup-btn" onclick="map.closePopup()" style="background:rgba(255,255,255,0.08);color:#94a3b8">Batal</button>
|
||
<button class="popup-btn popup-btn-delete" onclick="hapusSpbu(${numId})">🗑️ Ya, Hapus</button>
|
||
</div>
|
||
</div>`;
|
||
|
||
const lyr = spbuMarkers[numId];
|
||
if (lyr) {
|
||
lyr.getPopup().setContent(konfirmHtml);
|
||
} else {
|
||
// fallback: langsung hapus tanpa konfirmasi popup
|
||
hapusSpbu(numId);
|
||
}
|
||
}
|
||
window.konfirmasiHapusSpbu = konfirmasiHapusSpbu;
|
||
|
||
async function hapusSpbu(id) {
|
||
const numId = normalizeId(id);
|
||
const d = spbuDataCache[numId];
|
||
if (!d) {
|
||
showToast('❌ Data SPBU tidak ditemukan', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!dbReady) {
|
||
map.closePopup();
|
||
removeSpbuFromMap(numId);
|
||
showToast('🗑️ Data dihapus (mode offline)', 'success');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await apiSend('DELETE', numId);
|
||
map.closePopup();
|
||
removeSpbuFromMap(numId);
|
||
showToast('🗑️ SPBU berhasil dihapus', 'success');
|
||
} catch (err) {
|
||
showToast('❌ ' + err.message, 'error');
|
||
}
|
||
}
|
||
window.hapusSpbu = hapusSpbu;
|
||
|
||
// Data fallback jika database belum diimport
|
||
const SAMPLE_DATA = [
|
||
{ id: 1, nama_spbu: 'SPBU Jl. Ahmad Yani', alamat: 'Jl. Ahmad Yani No. 12, Pontianak', no_wa: '0812-1111-0001', buka_24_jam: 'Ya', latitude: -0.0400, longitude: 109.3200 },
|
||
{ id: 2, nama_spbu: 'SPBU Jl. Gajah Mada', alamat: 'Jl. Gajah Mada No. 45, Pontianak', no_wa: '0812-1111-0002', buka_24_jam: 'Ya', latitude: -0.0520, longitude: 109.3450 },
|
||
{ id: 3, nama_spbu: 'SPBU Jl. Diponegoro', alamat: 'Jl. Diponegoro No. 88, Pontianak', no_wa: '0812-1111-0003', buka_24_jam: 'Tidak', latitude: -0.0610, longitude: 109.3600 },
|
||
{ id: 4, nama_spbu: 'SPBU Jl. Sutan Syahrir', alamat: 'Jl. Sutan Syahrir No. 5, Pontianak', no_wa: '0812-1111-0004', buka_24_jam: 'Tidak', latitude: -0.0480, longitude: 109.3700 },
|
||
{ id: 5, nama_spbu: 'SPBU Jl. Kom. Yos Sudarso', alamat: 'Jl. Kom. Yos Sudarso No. 22, Pontianak', no_wa: '0812-1111-0005', buka_24_jam: 'Ya', latitude: -0.0350, longitude: 109.3550 }
|
||
];
|
||
|
||
function loadSpbuData(data, fitBounds = true) {
|
||
layer24Jam.clearLayers();
|
||
layerTidak24Jam.clearLayers();
|
||
Object.keys(spbuMarkers).forEach(k => delete spbuMarkers[k]);
|
||
Object.keys(spbuDataCache).forEach(k => delete spbuDataCache[k]);
|
||
|
||
data.forEach(d => renderSpbu(d));
|
||
|
||
if (fitBounds && data.length > 0) {
|
||
const bounds = L.latLngBounds(data.map(d => [d.latitude, d.longitude]));
|
||
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 14 });
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// MUAT DATA DARI API (dengan retry jika gagal)
|
||
// ============================================================
|
||
async function muatDataSpbu() {
|
||
const MAX_RETRY = 3;
|
||
for (let attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||
try {
|
||
const data = await apiGetAll();
|
||
dbReady = true;
|
||
loadSpbuData(data);
|
||
showToast(data.length > 0
|
||
? `✅ ${data.length} SPBU dimuat dari database`
|
||
: '📭 Database kosong — klik ➕ untuk tambah SPBU', 'success');
|
||
return; // sukses, keluar
|
||
} catch (err) {
|
||
console.warn(`API SPBU gagal (percobaan ${attempt}/${MAX_RETRY}):`, err);
|
||
if (attempt < MAX_RETRY) {
|
||
await new Promise(r => setTimeout(r, 800 * attempt)); // tunggu sebentar
|
||
} else {
|
||
// Semua retry gagal — mode offline
|
||
dbReady = false;
|
||
loadSpbuData(SAMPLE_DATA);
|
||
const pesan = err.message || 'Tidak bisa terhubung ke server';
|
||
showToast(`⚠️ Mode offline: ${pesan}`, 'error');
|
||
console.error('API SPBU gagal setelah semua retry:', err);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Tunggu page load penuh sebelum fetch ke API
|
||
if (document.readyState === 'complete') {
|
||
muatDataSpbu();
|
||
} else {
|
||
window.addEventListener('load', muatDataSpbu);
|
||
}
|
||
</script>
|
||
</body>
|
||
|
||
</html> |