601 lines
30 KiB
HTML
601 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Sistem GIS — Lokasi SPBU</title>
|
|
|
|
<!-- Google Fonts -->
|
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<!-- Leaflet CSS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<!-- Tailwind CSS -->
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
sans: ['Outfit', 'sans-serif'],
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
html, body { height: 100%; font-family: 'Outfit', sans-serif; overflow: hidden; }
|
|
#map {
|
|
height: 100vh;
|
|
width: 100%;
|
|
z-index: 1;
|
|
}
|
|
.custom-popup .leaflet-popup-content-wrapper {
|
|
border-radius: 16px;
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
|
padding: 4px;
|
|
}
|
|
.custom-popup .leaflet-popup-content {
|
|
margin: 12px;
|
|
font-family: 'Outfit', sans-serif;
|
|
}
|
|
.spbu-marker-icon {
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-slate-50 text-slate-800">
|
|
|
|
<!-- App Shell -->
|
|
<div class="relative w-full h-screen flex overflow-hidden">
|
|
|
|
<!-- Sidebar -->
|
|
<div class="w-[380px] h-full bg-white border-r border-slate-200/80 flex flex-col z-10 shadow-lg relative">
|
|
<!-- Header -->
|
|
<div class="p-6 border-b border-slate-100 flex flex-col gap-1.5">
|
|
<div class="flex items-center gap-2">
|
|
<a href="../index.html" class="flex items-center justify-center w-8 h-8 rounded-lg bg-slate-100 text-slate-600 hover:bg-slate-200 transition">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
|
</a>
|
|
<div class="flex items-center gap-1.5 text-xs font-semibold text-emerald-600 bg-emerald-50 px-2.5 py-1 rounded-full uppercase tracking-wider">
|
|
<span>Informatika UNTAN</span>
|
|
</div>
|
|
</div>
|
|
<h1 class="text-xl font-bold text-slate-800 mt-2 font-black">Lokasi SPBU</h1>
|
|
<p class="text-xs text-slate-500">Pemetaan Stasiun Pengisian Bahan Bakar Umum (SPBU) beserta jam operasional.</p>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="px-6 py-4 bg-slate-50 border-b border-slate-100 grid grid-cols-2 gap-4">
|
|
<div class="flex flex-col">
|
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Total SPBU</span>
|
|
<span id="spbuCount" class="text-lg font-black text-indigo-600">0</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Buka 24 Jam</span>
|
|
<span id="spbu24Count" class="text-lg font-black text-emerald-600">0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List & Search -->
|
|
<div class="flex-1 flex flex-col min-h-0">
|
|
<div class="p-4 border-b border-slate-100">
|
|
<div class="relative">
|
|
<input id="searchQuery" type="text" placeholder="Cari SPBU berdasarkan nama/nomor..." class="w-full bg-slate-50 border border-slate-200 rounded-xl pl-10 pr-4 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
<svg class="absolute left-3.5 top-3.5 text-slate-400" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="spbuList" class="flex-1 overflow-y-auto p-4 space-y-3">
|
|
<!-- Loading Placeholder -->
|
|
<div class="text-center py-8 text-slate-400 text-sm">
|
|
<svg class="animate-spin h-6 w-6 mx-auto mb-2 text-indigo-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
</svg>
|
|
Memuat data SPBU...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Instructions Panel -->
|
|
<div class="p-4 bg-emerald-50/50 border-t border-emerald-100/50 text-xs text-emerald-800 flex gap-2.5">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0 mt-0.5"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
|
<div>
|
|
<span class="font-bold">Cara Menambah SPBU:</span> Klik tombol <span class="bg-indigo-600 text-white px-1.5 py-0.5 rounded font-semibold text-[10px]">Tambah Titik</span> di atas peta, lalu klik lokasi SPBU pada peta.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Map Container -->
|
|
<div class="flex-1 h-full relative">
|
|
<div id="map"></div>
|
|
|
|
<!-- Custom floating action toolbar -->
|
|
<div class="absolute top-4 left-4 z-[2000] flex gap-2.5">
|
|
<button id="addPointBtn" onclick="toggleAddMode()" class="flex items-center gap-2 px-4 py-2.5 bg-white border border-slate-200/80 rounded-xl shadow-lg hover:border-indigo-500 hover:shadow-indigo-500/10 font-semibold text-sm transition">
|
|
<svg id="addIcon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="text-indigo-600"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
|
<span id="addText" class="text-slate-700">Tambah Titik SPBU</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Toast notification -->
|
|
<div id="toast" class="absolute top-4 right-4 z-[2000] hidden items-center gap-3 px-4 py-3 bg-slate-900/90 text-white rounded-xl shadow-xl backdrop-blur-md transition transform scale-95 opacity-0">
|
|
<span id="toastMessage" class="text-sm font-medium">Notifikasi</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Input SPBU Baru -->
|
|
<div id="newSpbuModal" class="fixed inset-0 z-[10000] hidden items-center justify-center bg-slate-900/40 backdrop-blur-sm px-4">
|
|
<div class="w-full max-w-md bg-white rounded-2xl shadow-2xl border border-slate-100 overflow-hidden transform scale-95 opacity-0 transition-all duration-300">
|
|
<div class="p-5 border-b border-slate-100 flex items-center justify-between bg-slate-50/50">
|
|
<h3 class="font-bold text-slate-800">Tambah SPBU Baru</h3>
|
|
<button onclick="closeNewSpbuModal()" class="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-slate-200 transition text-slate-500">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-5 space-y-4">
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Nama SPBU</label>
|
|
<input id="newSpbuName" type="text" placeholder="Contoh: SPBU Pertamina 61.781.01" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Nomor SPBU / Kode</label>
|
|
<input id="newSpbuNumber" type="text" placeholder="Contoh: 61.781.01" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Buka 24 Jam?</label>
|
|
<select id="newSpbuStatus" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-3 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
<option value="ya">Ya (24 Jam)</option>
|
|
<option value="tidak">Tidak</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-[10px] text-slate-400 bg-slate-50 border border-slate-100 p-2.5 rounded-lg">
|
|
📍 Koordinat terpilih: <span id="newSpbuCoords" class="font-mono font-semibold">0, 0</span>
|
|
</div>
|
|
</div>
|
|
<div class="p-5 border-t border-slate-100 bg-slate-50/50 flex gap-3">
|
|
<button onclick="saveNewSpbu()" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-sm py-2.5 px-4 rounded-xl shadow-lg transition">Simpan</button>
|
|
<button onclick="closeNewSpbuModal()" class="flex-1 bg-slate-200 hover:bg-slate-300 text-slate-700 font-semibold text-sm py-2.5 px-4 rounded-xl transition">Batal</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Edit SPBU -->
|
|
<div id="editSpbuModal" class="fixed inset-0 z-[10000] hidden items-center justify-center bg-slate-900/40 backdrop-blur-sm px-4">
|
|
<div class="w-full max-w-md bg-white rounded-2xl shadow-2xl border border-slate-100 overflow-hidden transform scale-95 opacity-0 transition-all duration-300">
|
|
<div class="p-5 border-b border-slate-100 flex items-center justify-between bg-slate-50/50">
|
|
<h3 class="font-bold text-slate-800">Edit Data SPBU</h3>
|
|
<button onclick="closeEditSpbuModal()" class="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-slate-200 transition text-slate-500">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-5 space-y-4">
|
|
<input type="hidden" id="editSpbuId">
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Nama SPBU</label>
|
|
<input id="editSpbuName" type="text" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Nomor SPBU</label>
|
|
<input id="editSpbuNumber" type="text" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wider text-slate-400 mb-1.5">Buka 24 Jam?</label>
|
|
<select id="editSpbuStatus" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-3 py-2.5 text-sm outline-none focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-100 transition shadow-sm">
|
|
<option value="ya">Ya (24 Jam)</option>
|
|
<option value="tidak">Tidak</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-5 border-t border-slate-100 bg-slate-50/50 flex gap-2">
|
|
<button onclick="updateSpbuDetails()" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-sm py-2.5 px-4 rounded-xl shadow-lg transition">Simpan Detail</button>
|
|
<button onclick="deleteSpbuDirect()" class="bg-red-50 hover:bg-red-100 text-red-600 font-semibold text-sm py-2.5 px-4 rounded-xl transition">Hapus</button>
|
|
<button onclick="closeEditSpbuModal()" class="bg-slate-200 hover:bg-slate-300 text-slate-700 font-semibold text-sm py-2.5 px-4 rounded-xl transition">Batal</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Leaflet JS -->
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
|
|
<script>
|
|
// Initialize Map
|
|
const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
|
L.control.zoom({ position: 'topright' }).addTo(map);
|
|
|
|
// Tile layer
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
let records = [];
|
|
let markersGroup = L.featureGroup().addTo(map);
|
|
let mapLayers = {};
|
|
let isAddMode = false;
|
|
let activePoint = null;
|
|
|
|
// Custom DivIcon for SPBU based on status
|
|
function createSpbuIcon(isOpen24) {
|
|
const color = isOpen24 ? '#10b981' : '#ef4444'; // Green if 24 Hours, Red if not
|
|
const iconHtml = `
|
|
<div class="relative w-7 h-7 flex items-center justify-center rounded-full bg-white shadow-lg border border-slate-200 transition transform hover:scale-110">
|
|
<div class="w-4.5 h-4.5 rounded-full flex items-center justify-center" style="background-color: ${color}">
|
|
<svg class="text-white w-2.5 h-2.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M4 10h12"/>
|
|
<path d="M12 4v12"/>
|
|
</svg>
|
|
</div>
|
|
</div>`;
|
|
return L.divIcon({
|
|
html: iconHtml,
|
|
className: 'spbu-marker-icon',
|
|
iconSize: [28, 28],
|
|
iconAnchor: [14, 14]
|
|
});
|
|
}
|
|
|
|
// Load SPBU data
|
|
async function loadSPBU() {
|
|
try {
|
|
const response = await fetch('api/get_location.php');
|
|
if (!response.ok) throw new Error('Query error');
|
|
records = await response.json();
|
|
renderSpbuList(records);
|
|
displaySpbuOnMap(records);
|
|
} catch (err) {
|
|
console.error(err);
|
|
showToast('Gagal memuat lokasi SPBU.', 'error');
|
|
}
|
|
}
|
|
|
|
// Render Sidebar List
|
|
function renderSpbuList(items) {
|
|
const list = document.getElementById('spbuList');
|
|
document.getElementById('spbuCount').textContent = items.length;
|
|
document.getElementById('spbu24Count').textContent = items.filter(i => (i.buka_24jam || '').toLowerCase() === 'ya').length;
|
|
|
|
if (items.length === 0) {
|
|
list.innerHTML = `
|
|
<div class="text-center py-12 text-slate-400 text-sm border-2 border-dashed border-slate-200 rounded-2xl">
|
|
<svg class="h-8 w-8 mx-auto mb-2 text-slate-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
Belum ada data lokasi SPBU.
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = items.map(item => {
|
|
const isOpen24 = (item.buka_24jam || '').toLowerCase() === 'ya';
|
|
return `
|
|
<div onclick="focusSpbu(${item.id})" class="p-4 bg-white border border-slate-200 rounded-2xl hover:border-indigo-500 hover:shadow-md cursor-pointer transition flex flex-col gap-2.5">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<span class="font-bold text-slate-800 text-sm leading-tight line-clamp-1">${escapeHtml(item.nama)}</span>
|
|
<span class="text-[9px] font-extrabold uppercase tracking-wider px-2 py-0.5 rounded-full ${isOpen24 ? 'bg-emerald-100 text-emerald-700' : 'bg-rose-100 text-rose-700'}">
|
|
${isOpen24 ? '24 Jam' : 'Reguler'}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center justify-between text-xs text-slate-500 border-t border-slate-50 pt-2">
|
|
<span>No. SPBU: <strong>${escapeHtml(item.nomor)}</strong></span>
|
|
<span class="font-mono text-[10px] text-slate-400">${Number(item.latitude || 0).toFixed(4)}, ${Number(item.longitude || 0).toFixed(4)}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Display markers on Leaflet
|
|
function displaySpbuOnMap(items) {
|
|
markersGroup.clearLayers();
|
|
mapLayers = {};
|
|
|
|
items.forEach(item => {
|
|
const lat = parseFloat(item.latitude);
|
|
const lng = parseFloat(item.longitude);
|
|
if (isNaN(lat) || isNaN(lng)) return;
|
|
|
|
const isOpen24 = (item.buka_24jam || '').toLowerCase() === 'ya';
|
|
const marker = L.marker([lat, lng], {
|
|
icon: createSpbuIcon(isOpen24),
|
|
draggable: true
|
|
}).addTo(markersGroup);
|
|
|
|
// Open detail on click
|
|
marker.on('click', (e) => {
|
|
L.DomEvent.stopPropagation(e);
|
|
openEditModal(item);
|
|
});
|
|
|
|
// Update coords on dragend
|
|
marker.on('dragend', async (e) => {
|
|
const newLatLng = marker.getLatLng();
|
|
const formData = new FormData();
|
|
formData.append('id', item.id);
|
|
formData.append('lat', newLatLng.lat);
|
|
formData.append('lng', newLatLng.lng);
|
|
|
|
try {
|
|
const res = await fetch('api/update_coords.php', { method: 'POST', body: formData });
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
showToast('Koordinat SPBU berhasil dipindahkan.', 'success');
|
|
loadSPBU();
|
|
} else {
|
|
throw new Error(data.message);
|
|
}
|
|
} catch (err) {
|
|
showToast(err.message || 'Gagal mengubah koordinat SPBU.', 'error');
|
|
loadSPBU();
|
|
}
|
|
});
|
|
|
|
mapLayers[item.id] = marker;
|
|
});
|
|
}
|
|
|
|
// Focus and Zoom to SPBU marker
|
|
function focusSpbu(id) {
|
|
const marker = mapLayers[id];
|
|
if (marker) {
|
|
map.setView(marker.getLatLng(), 16);
|
|
marker.bounce();
|
|
}
|
|
}
|
|
|
|
// Add Mode Toggle
|
|
function toggleAddMode() {
|
|
isAddMode = !isAddMode;
|
|
const btn = document.getElementById('addPointBtn');
|
|
const addIcon = document.getElementById('addIcon');
|
|
const addText = document.getElementById('addText');
|
|
|
|
if (isAddMode) {
|
|
btn.classList.add('bg-indigo-600', 'border-indigo-600', 'text-white');
|
|
addIcon.classList.remove('text-indigo-600');
|
|
addIcon.classList.add('text-white');
|
|
addIcon.innerHTML = `<path d="M18 6 6 18"/><path d="m6 6 12 12"/>`;
|
|
addText.textContent = 'Batal Tambah';
|
|
addText.classList.remove('text-slate-700');
|
|
addText.classList.add('text-white');
|
|
map.getContainer().style.cursor = 'crosshair';
|
|
showToast('Klik lokasi pada peta untuk meletakkan titik SPBU.', 'info');
|
|
} else {
|
|
btn.classList.remove('bg-indigo-600', 'border-indigo-600', 'text-white');
|
|
addIcon.classList.remove('text-white');
|
|
addIcon.classList.add('text-indigo-600');
|
|
addIcon.innerHTML = `<path d="M12 5v14"/><path d="M5 12h14"/>`;
|
|
addText.textContent = 'Tambah Titik SPBU';
|
|
addText.classList.remove('text-white');
|
|
addText.classList.add('text-slate-700');
|
|
map.getContainer().style.cursor = '';
|
|
}
|
|
}
|
|
|
|
// Map Click Handler for Adding Point
|
|
map.on('click', function (e) {
|
|
if (!isAddMode) return;
|
|
|
|
activePoint = e.latlng;
|
|
document.getElementById('newSpbuName').value = '';
|
|
document.getElementById('newSpbuNumber').value = '';
|
|
document.getElementById('newSpbuStatus').value = 'ya';
|
|
document.getElementById('newSpbuCoords').textContent = `${activePoint.lat.toFixed(6)}, ${activePoint.lng.toFixed(6)}`;
|
|
|
|
openNewSpbuModal();
|
|
});
|
|
|
|
// Save new SPBU
|
|
async function saveNewSpbu() {
|
|
const name = document.getElementById('newSpbuName').value.trim();
|
|
const number = document.getElementById('newSpbuNumber').value.trim();
|
|
const status = document.getElementById('newSpbuStatus').value;
|
|
|
|
if (!name || !number) {
|
|
showToast('Harap isi nama dan nomor SPBU.', 'error');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('nama', name);
|
|
formData.append('nomor', number);
|
|
formData.append('status', status);
|
|
formData.append('lat', activePoint.lat);
|
|
formData.append('lng', activePoint.lng);
|
|
|
|
try {
|
|
const res = await fetch('api/save_location.php', { method: 'POST', body: formData });
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
showToast('SPBU berhasil ditambahkan!', 'success');
|
|
closeNewSpbuModal();
|
|
toggleAddMode(); // disable add mode
|
|
loadSPBU();
|
|
} else {
|
|
throw new Error(data.message);
|
|
}
|
|
} catch (err) {
|
|
showToast(err.message || 'Gagal menyimpan lokasi SPBU.', 'error');
|
|
}
|
|
}
|
|
|
|
// Open edit modal
|
|
function openEditModal(record) {
|
|
document.getElementById('editSpbuId').value = record.id;
|
|
document.getElementById('editSpbuName').value = record.nama;
|
|
document.getElementById('editSpbuNumber').value = record.nomor;
|
|
document.getElementById('editSpbuStatus').value = (record.buka_24jam || '').toLowerCase();
|
|
|
|
openEditSpbuModal();
|
|
}
|
|
|
|
// Update details
|
|
async function updateSpbuDetails() {
|
|
const id = document.getElementById('editSpbuId').value;
|
|
const name = document.getElementById('editSpbuName').value.trim();
|
|
const number = document.getElementById('editSpbuNumber').value.trim();
|
|
const status = document.getElementById('editSpbuStatus').value;
|
|
|
|
if (!name || !number) {
|
|
showToast('Nama dan Nomor SPBU tidak boleh kosong.', 'error');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('id', id);
|
|
formData.append('nama', name);
|
|
formData.append('nomor', number);
|
|
formData.append('status', status);
|
|
|
|
try {
|
|
const res = await fetch('api/update_details.php', { method: 'POST', body: formData });
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
showToast('Data SPBU berhasil disimpan.', 'success');
|
|
closeEditSpbuModal();
|
|
loadSPBU();
|
|
} else {
|
|
throw new Error(data.message);
|
|
}
|
|
} catch (err) {
|
|
showToast(err.message || 'Gagal memperbarui SPBU.', 'error');
|
|
}
|
|
}
|
|
|
|
// Delete SPBU
|
|
async function deleteSpbuDirect() {
|
|
const id = document.getElementById('editSpbuId').value;
|
|
if (!confirm('Apakah Anda yakin ingin menghapus SPBU ini?')) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append('id', id);
|
|
|
|
try {
|
|
const res = await fetch('api/delete_location.php', { method: 'POST', body: formData });
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
showToast('SPBU berhasil dihapus.', 'success');
|
|
closeEditSpbuModal();
|
|
loadSPBU();
|
|
} else {
|
|
throw new Error(data.message);
|
|
}
|
|
} catch (err) {
|
|
showToast(err.message || 'Gagal menghapus SPBU.', 'error');
|
|
}
|
|
}
|
|
|
|
// Search Filter
|
|
document.getElementById('searchQuery').addEventListener('input', function (e) {
|
|
const query = e.target.value.toLowerCase();
|
|
const filtered = records.filter(rec =>
|
|
rec.nama.toLowerCase().includes(query) ||
|
|
rec.nomor.toLowerCase().includes(query)
|
|
);
|
|
renderSpbuList(filtered);
|
|
});
|
|
|
|
// Modals management
|
|
function openNewSpbuModal() {
|
|
const modal = document.getElementById('newSpbuModal');
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
setTimeout(() => {
|
|
modal.children[0].classList.remove('scale-95', 'opacity-0');
|
|
modal.children[0].classList.add('scale-100', 'opacity-100');
|
|
}, 10);
|
|
}
|
|
|
|
function closeNewSpbuModal() {
|
|
const modal = document.getElementById('newSpbuModal');
|
|
modal.children[0].classList.remove('scale-100', 'opacity-100');
|
|
modal.children[0].classList.add('scale-95', 'opacity-0');
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
modal.classList.remove('flex');
|
|
}, 150);
|
|
}
|
|
|
|
function openEditSpbuModal() {
|
|
const modal = document.getElementById('editSpbuModal');
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
setTimeout(() => {
|
|
modal.children[0].classList.remove('scale-95', 'opacity-0');
|
|
modal.children[0].classList.add('scale-100', 'opacity-100');
|
|
}, 10);
|
|
}
|
|
|
|
function closeEditSpbuModal() {
|
|
const modal = document.getElementById('editSpbuModal');
|
|
modal.children[0].classList.remove('scale-100', 'opacity-100');
|
|
modal.children[0].classList.add('scale-95', 'opacity-0');
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
modal.classList.remove('flex');
|
|
}, 150);
|
|
}
|
|
|
|
// Toast Helper
|
|
function showToast(message, type = 'info') {
|
|
const toast = document.getElementById('toast');
|
|
const msg = document.getElementById('toastMessage');
|
|
msg.textContent = message;
|
|
|
|
toast.classList.remove('hidden', 'bg-red-500', 'bg-emerald-500', 'bg-indigo-500');
|
|
toast.classList.add('flex');
|
|
|
|
if (type === 'error') {
|
|
toast.classList.add('bg-red-500');
|
|
} else if (type === 'success') {
|
|
toast.classList.add('bg-emerald-500');
|
|
} else {
|
|
toast.classList.add('bg-indigo-500');
|
|
}
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('scale-95', 'opacity-0');
|
|
toast.classList.add('scale-100', 'opacity-100');
|
|
}, 10);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('scale-100', 'opacity-100');
|
|
toast.classList.add('scale-95', 'opacity-0');
|
|
setTimeout(() => {
|
|
toast.classList.add('hidden');
|
|
}, 150);
|
|
}, 3000);
|
|
}
|
|
|
|
// Helper escape HTML
|
|
function escapeHtml(str) {
|
|
return String(str || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
// Simple bounce animation for markers
|
|
L.Marker.prototype.bounce = function() {
|
|
const marker = this;
|
|
const element = marker._icon;
|
|
if (!element) return;
|
|
element.style.transition = 'transform 0.15s ease-out';
|
|
element.style.transform = 'translate3d(0px, -14px, 0px) scale(1.15)';
|
|
setTimeout(() => {
|
|
element.style.transform = 'translate3d(0px, 0px, 0px) scale(1)';
|
|
}, 150);
|
|
};
|
|
|
|
// Startup Load
|
|
loadSPBU();
|
|
</script>
|
|
</body>
|
|
</html>
|