634 lines
30 KiB
PHP
634 lines
30 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="id" data-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Data SPBU - WebGIS</title>
|
|
|
|
<!-- Leaflet -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
|
<!-- DaisyUI + Tailwind -->
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<!-- Font Awesome -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
|
<!-- Plus Jakarta Sans Font -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
* {
|
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
}
|
|
body {
|
|
font-weight: 500;
|
|
}
|
|
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value {
|
|
font-weight: 700 !important;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
#previewMap { height: 380px; width: 100%; }
|
|
|
|
/* Custom Leaflet Control Layers Styling */
|
|
.leaflet-control-layers {
|
|
border-radius: 12px !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15) !important;
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
background: rgba(255, 255, 255, 0.85) !important;
|
|
padding: 8px 12px !important;
|
|
font-family: inherit !important;
|
|
font-weight: 700 !important;
|
|
font-size: 0.85rem !important;
|
|
transition: all 0.3s ease !important;
|
|
}
|
|
.leaflet-control-layers:hover {
|
|
background: rgba(255, 255, 255, 0.95) !important;
|
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.25) !important;
|
|
}
|
|
.leaflet-control-layers-list label {
|
|
margin-bottom: 4px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 4px 6px;
|
|
border-radius: 6px;
|
|
font-weight: 600 !important;
|
|
}
|
|
.leaflet-control-layers-list label:hover {
|
|
background: rgba(0, 0, 0, 0.05);
|
|
}
|
|
.leaflet-control-layers-separator {
|
|
margin: 8px 0 !important;
|
|
border-top: 1px solid rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
.leaflet-control-layers-selector {
|
|
accent-color: var(--lg-primary, #EA580C);
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-top: 0 !important;
|
|
}
|
|
</style>
|
|
@include('partials.localguide-theme')
|
|
</head>
|
|
|
|
<body class="localguide-shell min-h-screen bg-base-200">
|
|
|
|
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
|
<div class="navbar localguide-navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
|
<div class="flex-1">
|
|
<a href="{{ route('home') }}" class="btn btn-ghost text-lg font-bold gap-2 localguide-brand">
|
|
<i class="fa-solid fa-gas-pump"></i>
|
|
WebGIS SPBU
|
|
</a>
|
|
</div>
|
|
<div class="flex-none">
|
|
<ul class="menu menu-horizontal px-1 gap-1">
|
|
<li>
|
|
<a href="{{ route('spbu.input') }}" class="font-medium localguide-link">
|
|
<i class="fa-solid fa-map-location-dot"></i> Input Data
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ route('spbu.data') }}"
|
|
class="font-semibold localguide-active-link">
|
|
<i class="fa-solid fa-table-list"></i> Lihat Data
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ MAIN ══════════════════════════════════════════════════════════════ -->
|
|
<main class="max-w-screen-xl mx-auto px-4 py-6">
|
|
|
|
<!-- Page header -->
|
|
<div class="flex flex-wrap items-center justify-between gap-3 mb-6">
|
|
<div>
|
|
<div class="localguide-kicker mb-1">
|
|
<i class="fa-solid fa-compass"></i>
|
|
Local discovery dashboard
|
|
</div>
|
|
<h1 class="text-2xl font-bold">Data <span class="localguide-highlight">SPBU</span></h1>
|
|
<p class="text-sm text-base-content/50 mt-1">
|
|
Daftar seluruh titik SPBU yang telah terdata dalam sistem
|
|
</p>
|
|
</div>
|
|
<a href="{{ route('spbu.input') }}" class="btn btn-primary gap-2">
|
|
<i class="fa-solid fa-plus"></i> Tambah SPBU
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats stats-horizontal shadow bg-base-100 w-full mb-6 overflow-x-auto">
|
|
<div class="stat">
|
|
<div class="stat-figure text-primary">
|
|
<i class="fa-solid fa-gas-pump text-3xl"></i>
|
|
</div>
|
|
<div class="stat-title">Total SPBU</div>
|
|
<div class="stat-value text-primary" id="statTotal">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-figure text-success">
|
|
<i class="fa-solid fa-clock-rotate-left text-3xl"></i>
|
|
</div>
|
|
<div class="stat-title">Buka 24 Jam</div>
|
|
<div class="stat-value text-success" id="stat24j">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-figure text-error">
|
|
<i class="fa-solid fa-circle-xmark text-3xl"></i>
|
|
</div>
|
|
<div class="stat-title">Tidak 24 Jam</div>
|
|
<div class="stat-value text-error" id="statNon24">—</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="localguide-filter-bar mb-5" aria-label="Filter kategori SPBU">
|
|
<button type="button" class="localguide-filter-chip is-active" data-filter="all">
|
|
<i class="fa-solid fa-layer-group"></i>
|
|
Semua
|
|
</button>
|
|
<button type="button" class="localguide-filter-chip" data-filter="24h">
|
|
<i class="fa-solid fa-clock"></i>
|
|
Buka 24 Jam
|
|
</button>
|
|
<button type="button" class="localguide-filter-chip" data-filter="regular">
|
|
<i class="fa-solid fa-compass"></i>
|
|
Tidak 24 Jam
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-[1fr_380px] gap-5 items-start">
|
|
|
|
<!-- ── TABEL ─────────────────────────────────────────────────── -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body p-0">
|
|
|
|
<!-- Card header -->
|
|
<div class="flex items-center justify-between px-5 pt-5 pb-3 border-b border-base-200">
|
|
<h2 class="card-title text-base">
|
|
<i class="fa-solid fa-list text-primary"></i>
|
|
Daftar SPBU
|
|
</h2>
|
|
<span class="text-xs text-base-content/50" id="totalBadge">Memuat...</span>
|
|
</div>
|
|
|
|
<!-- Search -->
|
|
<div class="px-5 py-3 border-b border-base-200">
|
|
<label class="input input-bordered input-sm flex items-center gap-2 w-full">
|
|
<i class="fa-solid fa-magnifying-glass text-base-content/40"></i>
|
|
<input type="text" id="searchInput" class="grow"
|
|
placeholder="Cari nama atau nomor SPBU...">
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div id="loadingTable" class="flex items-center justify-center gap-3 py-16
|
|
text-sm text-base-content/50">
|
|
<span class="loading loading-spinner loading-md"></span>
|
|
Memuat data...
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra table-sm" id="dataTable" style="display:none">
|
|
<thead>
|
|
<tr class="text-xs text-base-content/60">
|
|
<th>#</th>
|
|
<th>Nama SPBU</th>
|
|
<th>Nomor</th>
|
|
<th>Status</th>
|
|
<th>Koordinat</th>
|
|
<th class="text-center">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Empty state -->
|
|
<div id="emptyState" class="text-center py-16 hidden">
|
|
<div class="text-4xl mb-3">📭</div>
|
|
<p class="font-semibold text-base-content/60">Belum ada data SPBU</p>
|
|
<p class="text-sm text-base-content/40 mt-1">
|
|
Tambahkan SPBU melalui halaman Input Data
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── SIDEBAR ───────────────────────────────────────────────── -->
|
|
<div class="flex flex-col gap-5">
|
|
|
|
<!-- Mini Map -->
|
|
<div class="card bg-base-100 shadow overflow-hidden">
|
|
<div class="card-body p-0">
|
|
<div class="flex items-center gap-2 px-4 py-3 border-b border-base-200">
|
|
<i class="fa-solid fa-map text-primary"></i>
|
|
<span class="font-semibold text-sm">Peta Sebaran</span>
|
|
</div>
|
|
<div id="previewMap"></div>
|
|
<div class="flex gap-4 px-4 py-2 border-t border-base-200 text-xs text-base-content/60">
|
|
<span class="flex items-center gap-1">
|
|
<span class="inline-block w-2.5 h-2.5 rounded-full localguide-map-legend-dot-primary"></span>
|
|
Buka 24 Jam
|
|
</span>
|
|
<span class="flex items-center gap-1">
|
|
<span class="inline-block w-2.5 h-2.5 rounded-full localguide-map-legend-dot-secondary"></span>
|
|
Tidak 24 Jam
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick List -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body p-0">
|
|
<div class="flex items-center gap-2 px-4 py-3 border-b border-base-200">
|
|
<i class="fa-solid fa-list-ul text-primary"></i>
|
|
<span class="font-semibold text-sm">Daftar Cepat</span>
|
|
</div>
|
|
<div class="max-h-80 overflow-y-auto" id="quickList">
|
|
<div class="flex items-center justify-center gap-2 py-10
|
|
text-sm text-base-content/40">
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
Memuat...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- ═══ MODAL: KONFIRMASI HAPUS ══════════════════════════════════════════ -->
|
|
<dialog id="confirmModal" class="modal">
|
|
<div class="modal-box max-w-sm">
|
|
<!-- Header merah -->
|
|
<div class="flex items-center gap-3 mb-5">
|
|
<div class="bg-error/10 text-error rounded-full w-11 h-11 flex items-center justify-center flex-shrink-0">
|
|
<i class="fa-solid fa-triangle-exclamation text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold">Hapus Data SPBU</h3>
|
|
<p class="text-xs text-base-content/50">Tindakan ini tidak dapat dibatalkan</p>
|
|
</div>
|
|
<button class="btn btn-sm btn-circle btn-ghost ml-auto" id="btnTutupKonfirmasi">✕</button>
|
|
</div>
|
|
|
|
<p class="text-sm text-base-content/70 mb-2">
|
|
Apakah kamu yakin ingin menghapus data SPBU berikut?
|
|
</p>
|
|
<div class="bg-base-200 border-l-4 border-error rounded px-3 py-2 font-semibold mb-4"
|
|
id="confirmName">—</div>
|
|
<div class="alert alert-error alert-sm py-2 text-xs mb-4">
|
|
<i class="fa-solid fa-circle-info"></i>
|
|
Data yang dihapus tidak bisa dikembalikan.
|
|
</div>
|
|
|
|
<div class="modal-action mt-0">
|
|
<button class="btn btn-sm" id="btnBatal">
|
|
<i class="fa-solid fa-xmark"></i> Batal
|
|
</button>
|
|
<button class="btn btn-sm btn-error" id="btnYaHapus">
|
|
<i class="fa-solid fa-trash"></i> Ya, Hapus
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button>close</button>
|
|
</form>
|
|
</dialog>
|
|
|
|
<!-- ═══ TOAST ══════════════════════════════════════════════════════════════ -->
|
|
<div class="toast toast-end toast-bottom z-[99999]" id="toastContainer"></div>
|
|
|
|
|
|
<!-- ═══ JAVASCRIPT ════════════════════════════════════════════════════════ -->
|
|
<script>
|
|
const API = '/api/spbu';
|
|
const LG_PRIMARY = '#EA580C';
|
|
const LG_SECONDARY = '#0D9488';
|
|
|
|
// ── MAP ─────────────────────────────────────────────────────────────────
|
|
const previewMap = L.map('previewMap', {
|
|
center: [-0.0606, 109.345],
|
|
zoom: 14
|
|
});
|
|
|
|
// 1. Base Maps
|
|
const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 19,
|
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
});
|
|
|
|
const esriSatellite = L.layerGroup([
|
|
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
maxZoom: 19,
|
|
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
|
|
}),
|
|
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', {
|
|
maxZoom: 19
|
|
}),
|
|
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}', {
|
|
maxZoom: 19
|
|
})
|
|
]).addTo(previewMap);
|
|
|
|
// 2. Layer Groups (Overlays)
|
|
const layer24h = L.layerGroup().addTo(previewMap);
|
|
const layerRegular = L.layerGroup().addTo(previewMap);
|
|
|
|
const baseMaps = {
|
|
"Satelit (Hybrid)": esriSatellite,
|
|
"Standard Map": osm
|
|
};
|
|
|
|
const overlays = {
|
|
"SPBU Buka 24 Jam": layer24h,
|
|
"SPBU Tidak Buka 24 Jam": layerRegular
|
|
};
|
|
|
|
L.control.layers(baseMaps, overlays, { collapsed: true }).addTo(previewMap);
|
|
|
|
function makeIcon(color) {
|
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 42" width="32" height="42">
|
|
<path d="M16 0C7.163 0 0 7.163 0 16c0 12 16 26 16 26S32 28 32 16C32 7.163 24.837 0 16 0z"
|
|
fill="${color}" stroke="white" stroke-width="2"/>
|
|
<text x="16" y="21" text-anchor="middle" fill="white"
|
|
font-size="13" font-family="Arial" font-weight="bold">⛽</text>
|
|
</svg>`;
|
|
return L.divIcon({ html: svg, className: '', iconSize:[32,42], iconAnchor:[16,42], popupAnchor:[0,-44] });
|
|
}
|
|
|
|
// ── STATE ────────────────────────────────────────────────────────────────
|
|
let allData = [];
|
|
let mapMarkers = {};
|
|
let activeFilter = 'all';
|
|
|
|
// ── MODAL ────────────────────────────────────────────────────────────────
|
|
const confirmModal = document.getElementById('confirmModal');
|
|
|
|
// ── HELPERS ──────────────────────────────────────────────────────────────
|
|
function esc(str) {
|
|
const d = document.createElement('div');
|
|
d.appendChild(document.createTextNode(str));
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function showToast(msg, type = 'info') {
|
|
const t = document.createElement('div');
|
|
const cls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
|
t.className = `alert ${cls} shadow-md`;
|
|
t.innerHTML = `<span>${msg}</span>`;
|
|
document.getElementById('toastContainer').appendChild(t);
|
|
setTimeout(() => t.remove(), 3500);
|
|
}
|
|
|
|
// ── LOAD ─────────────────────────────────────────────────────────────────
|
|
function loadData() {
|
|
document.getElementById('loadingTable').style.display = 'flex';
|
|
document.getElementById('dataTable').style.display = 'none';
|
|
document.getElementById('emptyState').classList.add('hidden');
|
|
|
|
// Clear previous layer groups
|
|
layer24h.clearLayers();
|
|
layerRegular.clearLayers();
|
|
mapMarkers = {};
|
|
|
|
fetch(API)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
allData = data;
|
|
renderStats(data);
|
|
renderMarkers(data);
|
|
applyFilters();
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('loadingTable').style.display = 'none';
|
|
document.getElementById('emptyState').classList.remove('hidden');
|
|
document.getElementById('totalBadge').textContent = 'Gagal memuat data';
|
|
renderStats([]);
|
|
});
|
|
}
|
|
|
|
// ── STATS ────────────────────────────────────────────────────────────────
|
|
function renderStats(data) {
|
|
const total = data.length;
|
|
const buka = data.filter(d => d.status === 'Buka 24 Jam').length;
|
|
document.getElementById('statTotal').textContent = total;
|
|
document.getElementById('stat24j').textContent = buka;
|
|
document.getElementById('statNon24').textContent = total - buka;
|
|
document.getElementById('totalBadge').textContent = total + ' data';
|
|
}
|
|
|
|
function matchesActiveFilter(spbu) {
|
|
if (activeFilter === '24h') {
|
|
return spbu.status === 'Buka 24 Jam';
|
|
}
|
|
|
|
if (activeFilter === 'regular') {
|
|
return spbu.status !== 'Buka 24 Jam';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getFilteredData() {
|
|
const q = document.getElementById('searchInput').value.toLowerCase();
|
|
|
|
return allData.filter(spbu => matchesActiveFilter(spbu))
|
|
.filter(spbu =>
|
|
spbu.nama_spbu.toLowerCase().includes(q) ||
|
|
spbu.nomor_spbu.toLowerCase().includes(q)
|
|
);
|
|
}
|
|
|
|
function applyFilters() {
|
|
const filtered = getFilteredData();
|
|
|
|
renderTable(filtered);
|
|
renderQuickList(filtered);
|
|
document.getElementById('totalBadge').textContent = filtered.length + ' data';
|
|
}
|
|
|
|
// ── TABLE ────────────────────────────────────────────────────────────────
|
|
function renderTable(data) {
|
|
document.getElementById('loadingTable').style.display = 'none';
|
|
|
|
if (!data.length) {
|
|
document.getElementById('emptyState').classList.remove('hidden');
|
|
document.getElementById('dataTable').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
document.getElementById('emptyState').classList.add('hidden');
|
|
document.getElementById('dataTable').style.display = 'table';
|
|
|
|
document.getElementById('tableBody').innerHTML = data.map((spbu, i) => {
|
|
const is24 = spbu.status === 'Buka 24 Jam';
|
|
const badgeCls = is24 ? 'localguide-status-24' : 'localguide-status-regular';
|
|
const badgeIco = is24 ? '●' : '◆';
|
|
const lat = parseFloat(spbu.latitude).toFixed(5);
|
|
const lng = parseFloat(spbu.longitude).toFixed(5);
|
|
const namaJS = spbu.nama_spbu.replace(/'/g, "\\'");
|
|
|
|
return `
|
|
<tr class="hover cursor-pointer">
|
|
<td class="text-base-content/50 text-xs font-semibold"
|
|
onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">${i+1}</td>
|
|
<td onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">
|
|
<span class="font-semibold">${esc(spbu.nama_spbu)}</span>
|
|
</td>
|
|
<td class="text-xs text-base-content/60"
|
|
onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">
|
|
${esc(spbu.nomor_spbu)}
|
|
</td>
|
|
<td onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">
|
|
<span class="badge ${badgeCls} badge-sm gap-1">${badgeIco} ${esc(spbu.status)}</span>
|
|
</td>
|
|
<td class="font-mono text-xs text-base-content/50"
|
|
onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">
|
|
${lat}<br>${lng}
|
|
</td>
|
|
<td class="text-center">
|
|
<button class="btn btn-xs btn-outline btn-error gap-1"
|
|
onclick="bukaKonfirmasi(${spbu.id}, '${namaJS}')">
|
|
<i class="fa-solid fa-trash"></i> Hapus
|
|
</button>
|
|
</td>
|
|
</tr>`;
|
|
}).join('');
|
|
}
|
|
|
|
// ── MARKERS ───────────────────────────────────────────────────────────────
|
|
function renderMarkers(data) {
|
|
const bounds = [];
|
|
data.forEach(spbu => {
|
|
const is24 = spbu.status === 'Buka 24 Jam';
|
|
const color = is24 ? LG_PRIMARY : LG_SECONDARY;
|
|
const group = is24 ? layer24h : layerRegular;
|
|
|
|
const m = L.marker([+spbu.latitude, +spbu.longitude], { icon: makeIcon(color) })
|
|
.addTo(group)
|
|
.bindPopup(`
|
|
<div style="font-family:ui-sans-serif,system-ui,sans-serif;min-width:150px">
|
|
<b>⛽ ${esc(spbu.nama_spbu)}</b><br>
|
|
<span style="font-size:0.78rem;color:#6b7280">No: ${esc(spbu.nomor_spbu)}</span><br>
|
|
<span class="badge ${spbu.status === 'Buka 24 Jam' ? 'localguide-status-24' : 'localguide-status-regular'}"
|
|
style="font-size:0.7rem;margin-top:5px">${esc(spbu.status)}</span>
|
|
</div>`);
|
|
mapMarkers[spbu.id] = m;
|
|
bounds.push([+spbu.latitude, +spbu.longitude]);
|
|
});
|
|
if (bounds.length) previewMap.fitBounds(bounds, { padding: [28, 28] });
|
|
}
|
|
|
|
function focusMap(lat, lng, id) {
|
|
previewMap.flyTo([lat, lng], 17, { animate: true, duration: 1 });
|
|
if (mapMarkers[id]) mapMarkers[id].openPopup();
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
|
|
// ── QUICK LIST ─────────────────────────────────────────────────────────
|
|
function renderQuickList(data) {
|
|
const el = document.getElementById('quickList');
|
|
if (!data.length) {
|
|
el.innerHTML = `<div class="text-center py-10 text-sm text-base-content/40">
|
|
📭 Belum ada data</div>`;
|
|
return;
|
|
}
|
|
el.innerHTML = data.map(spbu => {
|
|
const is24 = spbu.status === 'Buka 24 Jam';
|
|
return `
|
|
<div class="flex gap-3 items-start px-4 py-3 border-b border-base-200
|
|
hover:bg-base-200 cursor-pointer transition-colors"
|
|
onclick="focusMap(${spbu.latitude},${spbu.longitude},${spbu.id})">
|
|
<span class="text-xl mt-0.5">⛽</span>
|
|
<div>
|
|
<div class="font-semibold text-sm">${esc(spbu.nama_spbu)}</div>
|
|
<div class="text-xs text-base-content/50">${esc(spbu.nomor_spbu)}</div>
|
|
<span class="badge ${is24 ? 'localguide-status-24' : 'localguide-status-regular'} badge-sm mt-1">
|
|
${is24 ? '●' : '◆'} ${esc(spbu.status)}
|
|
</span>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
// ── SEARCH ────────────────────────────────────────────────────────────
|
|
document.getElementById('searchInput').addEventListener('input', function () {
|
|
applyFilters();
|
|
});
|
|
|
|
document.querySelectorAll('[data-filter]').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
activeFilter = this.dataset.filter;
|
|
|
|
document.querySelectorAll('[data-filter]').forEach(item => {
|
|
item.classList.toggle('is-active', item === this);
|
|
});
|
|
|
|
applyFilters();
|
|
});
|
|
});
|
|
|
|
// ── HAPUS ─────────────────────────────────────────────────────────────
|
|
let pendingId = null;
|
|
|
|
function bukaKonfirmasi(id, nama) {
|
|
pendingId = id;
|
|
document.getElementById('confirmName').textContent = '⛽ ' + nama;
|
|
confirmModal.showModal();
|
|
}
|
|
|
|
function tutupKonfirmasi() { confirmModal.close(); pendingId = null; }
|
|
|
|
document.getElementById('btnBatal').addEventListener('click', tutupKonfirmasi);
|
|
document.getElementById('btnTutupKonfirmasi').addEventListener('click', tutupKonfirmasi);
|
|
confirmModal.addEventListener('close', () => { pendingId = null; });
|
|
|
|
document.getElementById('btnYaHapus').addEventListener('click', function () {
|
|
if (!pendingId) return;
|
|
const btn = this;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="loading loading-spinner loading-xs"></span> Menghapus...';
|
|
|
|
fetch(`${API}/${pendingId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: pendingId })
|
|
})
|
|
.then(r => r.json())
|
|
.then(res => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa-solid fa-trash"></i> Ya, Hapus';
|
|
if (res.success) {
|
|
tutupKonfirmasi();
|
|
showToast('Data SPBU berhasil dihapus! 🗑️', 'success');
|
|
loadData();
|
|
} else {
|
|
showToast('Gagal: ' + (res.message || 'Error server'), 'error');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa-solid fa-trash"></i> Ya, Hapus';
|
|
showToast('Tidak dapat terhubung ke server!', 'error');
|
|
});
|
|
});
|
|
|
|
// ── INIT ─────────────────────────────────────────────────────────────
|
|
loadData();
|
|
</script>
|
|
</body>
|
|
</html>
|