Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-06-11 19:53:31 +07:00
parent 51ea70b6ce
commit 6cc14e087e
2 changed files with 4527 additions and 245 deletions
+141 -245
View File
@@ -8,15 +8,10 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS SPBU - Layer Control</title> <title>WebGIS SPBU - Layer Control</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <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> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.css"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-500-normal.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.css">
<style> <style>
body { font-family: 'Inter', sans-serif; } body { font-family: 'Inter', sans-serif; }
#map { height: 100%; width: 100%; z-index: 1; } #map { height: 100%; width: 100%; z-index: 1; }
@@ -26,144 +21,36 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
.custom-scrollbar::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 10px; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #94a3b8; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.glass-panel { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); }
.fade-in { animation: fadeIn 0.3s ease-in-out; } .fade-in { animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
.admin-mode .leaflet-marker-draggable { cursor: grab; } .admin-mode .leaflet-marker-draggable { cursor: grab; }
.admin-mode .leaflet-marker-draggable:active { cursor: grabbing; } .admin-mode .leaflet-marker-draggable:active { cursor: grabbing; }
@keyframes pulse { @keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(13, 148, 136, 0.5); } 0% { box-shadow: 0 0 0 0 rgba(13, 148, 136, 0.5); }
70% { box-shadow: 0 0 0 10px rgba(13, 148, 136, 0); } 70% { box-shadow: 0 0 0 10px rgba(13, 148, 136, 0); }
100% { box-shadow: 0 0 0 0 rgba(13, 148, 136, 0); } 100% { box-shadow: 0 0 0 0 rgba(13, 148, 136, 0); }
} }
.pulse-ring { animation: pulse 2s infinite; } .pulse-ring { animation: pulse 2s infinite; }
.gm-panel-wrapper { display: flex; align-items: stretch; z-index: 20; flex-shrink: 0; }
/* Google Maps Style Panel + Handle */ .gm-panel-wrapper-left { flex-direction: row; }
.gm-panel-wrapper { .gm-panel-wrapper-right { flex-direction: row-reverse; }
display: flex; .gm-panel-content { background: white; display: flex; flex-direction: column; overflow: hidden; transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
align-items: stretch; .gm-panel-content-left { width: 384px; min-width: 384px; border-right: 1px solid #e2e8f0; }
z-index: 20; .gm-panel-content-right { width: 320px; min-width: 320px; border-left: 1px solid #e2e8f0; }
flex-shrink: 0; .gm-panel-content.collapsed { width: 0 !important; min-width: 0 !important; border: none !important; }
} .gm-handle { width: 23px; background: white; border: 1px solid #e2e8f0; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s; box-shadow: 0 1px 2px rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15); flex-shrink: 0; margin-top: auto; margin-bottom: auto; height: 48px; z-index: 30; }
.gm-handle:hover { background: #f1f3f4; }
.gm-panel-wrapper-left { .gm-handle-left { border-radius: 0 8px 8px 0; border-left: none; }
flex-direction: row; .gm-handle-right { border-radius: 8px 0 0 8px; border-right: none; }
} .gm-handle svg { width: 20px; height: 20px; color: #5f6368; transition: transform 0.3s; }
.layer-card { cursor: pointer; transition: all 0.2s ease; border: 2px solid transparent; }
.gm-panel-wrapper-right {
flex-direction: row-reverse;
}
.gm-panel-content {
background: white;
display: flex;
flex-direction: column;
overflow: hidden;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.gm-panel-content-left {
width: 384px;
min-width: 384px;
border-right: 1px solid #e2e8f0;
}
.gm-panel-content-right {
width: 320px;
min-width: 320px;
border-left: 1px solid #e2e8f0;
}
.gm-panel-content.collapsed {
width: 0 !important;
min-width: 0 !important;
border: none !important;
}
.gm-handle {
width: 23px;
background: white;
border: 1px solid #e2e8f0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
box-shadow: 0 1px 2px rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);
flex-shrink: 0;
margin-top: auto;
margin-bottom: auto;
height: 48px;
z-index: 30;
}
.gm-handle:hover {
background: #f1f3f4;
}
.gm-handle-left {
border-radius: 0 8px 8px 0;
border-left: none;
}
.gm-handle-right {
border-radius: 8px 0 0 8px;
border-right: none;
}
.gm-handle svg {
width: 20px;
height: 20px;
color: #5f6368;
transition: transform 0.3s;
}
.layer-card {
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.layer-card:hover { border-color: #cbd5e1; } .layer-card:hover { border-color: #cbd5e1; }
.layer-card.active { .layer-card.active { border-color: #0d9488; background: #f0fdfa; }
border-color: #0d9488; .layer-preview { width: 100%; height: 45px; border-radius: 8px; overflow: hidden; border: 2px solid #e2e8f0; transition: all 0.2s; }
background: #f0fdfa;
}
.layer-preview {
width: 100%;
height: 45px;
border-radius: 8px;
overflow: hidden;
border: 2px solid #e2e8f0;
transition: all 0.2s;
}
.layer-card.active .layer-preview { border-color: #0d9488; } .layer-card.active .layer-preview { border-color: #0d9488; }
.toggle-switch { width: 40px; height: 22px; background: #cbd5e1; border-radius: 11px; position: relative; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; flex-shrink: 0; }
.toggle-switch {
width: 40px;
height: 22px;
background: #cbd5e1;
border-radius: 11px;
position: relative;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
flex-shrink: 0;
}
.toggle-switch.active { background: #0d9488; } .toggle-switch.active { background: #0d9488; }
.toggle-switch::after { .toggle-switch::after { content: ''; width: 18px; height: 18px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
content: '';
width: 18px;
height: 18px;
background: white;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.toggle-switch.active::after { left: 20px; } .toggle-switch.active::after { left: 20px; }
</style> </style>
</head> </head>
@@ -183,7 +70,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<p class="text-xs text-teal-200">Sistem Informasi Lokasi Stasiun Pengisian Bahan Bakar Umum</p> <p class="text-xs text-teal-200">Sistem Informasi Lokasi Stasiun Pengisian Bahan Bakar Umum</p>
</div> </div>
</div> </div>
<div class="relative z-10 flex items-center gap-4"> <div class="relative z-10 flex items-center gap-4">
<div class="relative hidden md:block"> <div class="relative hidden md:block">
<input type="text" id="searchInput" placeholder="Cari nama SPBU..." class="w-64 pl-10 pr-4 py-2 rounded-full bg-white/20 border border-white/30 text-white placeholder-teal-200 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:bg-white/30 transition-all text-sm"> <input type="text" id="searchInput" placeholder="Cari nama SPBU..." class="w-64 pl-10 pr-4 py-2 rounded-full bg-white/20 border border-white/30 text-white placeholder-teal-200 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:bg-white/30 transition-all text-sm">
@@ -191,13 +77,11 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> </svg>
</div> </div>
<div id="authContainer"></div> <div id="authContainer"></div>
</div> </div>
</header> </header>
<main class="flex flex-1 overflow-hidden relative"> <main class="flex flex-1 overflow-hidden relative">
<div class="gm-panel-wrapper gm-panel-wrapper-left" id="panelWrapperLeft"> <div class="gm-panel-wrapper gm-panel-wrapper-left" id="panelWrapperLeft">
<aside class="gm-panel-content gm-panel-content-left shadow-xl" id="sidebarLeft"> <aside class="gm-panel-content gm-panel-content-left shadow-xl" id="sidebarLeft">
<div class="p-4 border-b border-slate-100 bg-slate-50"> <div class="p-4 border-b border-slate-100 bg-slate-50">
@@ -212,7 +96,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
</div> </div>
</div> </div>
<div class="p-4 border-b border-slate-100 md:hidden"> <div class="p-4 border-b border-slate-100 md:hidden">
<div class="relative"> <div class="relative">
<input type="text" id="searchInputMobile" placeholder="Cari nama SPBU..." class="w-full pl-10 pr-4 py-2 rounded-lg bg-slate-100 text-slate-800 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 text-sm"> <input type="text" id="searchInputMobile" placeholder="Cari nama SPBU..." class="w-full pl-10 pr-4 py-2 rounded-lg bg-slate-100 text-slate-800 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 text-sm">
@@ -221,7 +104,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</svg> </svg>
</div> </div>
</div> </div>
<div class="px-4 py-3 flex items-center justify-between bg-white border-b border-slate-100"> <div class="px-4 py-3 flex items-center justify-between bg-white border-b border-slate-100">
<h2 class="font-semibold text-slate-700 text-sm uppercase tracking-wider">Daftar SPBU</h2> <h2 class="font-semibold text-slate-700 text-sm uppercase tracking-wider">Daftar SPBU</h2>
<button id="btnAddSpbu" class="bg-teal-600 hover:bg-teal-700 text-white text-xs font-medium px-3 py-1.5 rounded-lg shadow-sm transition-colors flex items-center gap-1" style="display: <?php echo $isAdmin ? 'flex' : 'none'; ?>"> <button id="btnAddSpbu" class="bg-teal-600 hover:bg-teal-700 text-white text-xs font-medium px-3 py-1.5 rounded-lg shadow-sm transition-colors flex items-center gap-1" style="display: <?php echo $isAdmin ? 'flex' : 'none'; ?>">
@@ -231,10 +113,8 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
Tambah Tambah
</button> </button>
</div> </div>
<div id="spbuList" class="flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2"></div> <div id="spbuList" class="flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2"></div>
</aside> </aside>
<button class="gm-handle gm-handle-left" id="handleLeft" onclick="toggleLeftPanel()" title="Ciutkan panel samping"> <button class="gm-handle gm-handle-left" id="handleLeft" onclick="toggleLeftPanel()" title="Ciutkan panel samping">
<svg id="handleLeftIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg id="handleLeftIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
@@ -244,11 +124,9 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<div class="flex-1 relative" id="mapContainer"> <div class="flex-1 relative" id="mapContainer">
<div id="map"></div> <div id="map"></div>
<div id="mapHint" class="absolute bottom-4 left-1/2 -translate-x-1/2 z-[1000] bg-white/90 backdrop-blur px-4 py-2 rounded-lg shadow-md text-xs text-slate-600 border border-slate-200 pointer-events-none" style="display: none;"> <div id="mapHint" class="absolute bottom-4 left-1/2 -translate-x-1/2 z-[1000] bg-white/90 backdrop-blur px-4 py-2 rounded-lg shadow-md text-xs text-slate-600 border border-slate-200 pointer-events-none" style="display: none;">
💡 Klik pada peta untuk memilih lokasi SPBU baru 💡 Klik pada peta untuk memilih lokasi SPBU baru
</div> </div>
<div id="modeIndicator" class="absolute top-20 left-4 z-[1000] bg-teal-600 text-white px-4 py-2 rounded-lg shadow-md text-xs font-medium hidden pulse-ring"> <div id="modeIndicator" class="absolute top-20 left-4 z-[1000] bg-teal-600 text-white px-4 py-2 rounded-lg shadow-md text-xs font-medium hidden pulse-ring">
🎯 Mode Pilih Titik - Klik pada peta 🎯 Mode Pilih Titik - Klik pada peta
</div> </div>
@@ -259,7 +137,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<div class="p-4 border-b border-slate-100 bg-slate-50"> <div class="p-4 border-b border-slate-100 bg-slate-50">
<h3 class="text-sm font-semibold text-slate-700">Pengaturan Peta</h3> <h3 class="text-sm font-semibold text-slate-700">Pengaturan Peta</h3>
</div> </div>
<div class="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-5"> <div class="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-5">
<div> <div>
<label class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3 block">Tipe Peta Dasar</label> <label class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3 block">Tipe Peta Dasar</label>
@@ -272,7 +149,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
<span class="text-[11px] font-semibold text-slate-700">Ringan</span> <span class="text-[11px] font-semibold text-slate-700">Ringan</span>
</div> </div>
<div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('satellite')" id="layerSatellite"> <div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('satellite')" id="layerSatellite">
<div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #064e3b 0%, #065f46 30%, #047857 60%, #059669 100%); position: relative; overflow: hidden;"> <div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #064e3b 0%, #065f46 30%, #047857 60%, #059669 100%); position: relative; overflow: hidden;">
<div style="position: absolute; top: 20%; left: 30%; width: 40%; height: 15%; background: rgba(255,255,255,0.3); border-radius: 2px;"></div> <div style="position: absolute; top: 20%; left: 30%; width: 40%; height: 15%; background: rgba(255,255,255,0.3); border-radius: 2px;"></div>
@@ -280,14 +156,12 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
<span class="text-[11px] font-semibold text-slate-700">Satelit</span> <span class="text-[11px] font-semibold text-slate-700">Satelit</span>
</div> </div>
<div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('terrain')" id="layerTerrain"> <div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('terrain')" id="layerTerrain">
<div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #fef3c7 0%, #d4a574 40%, #8b6914 70%, #4a7c59 100%); position: relative; overflow: hidden;"> <div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #fef3c7 0%, #d4a574 40%, #8b6914 70%, #4a7c59 100%); position: relative; overflow: hidden;">
<div style="position: absolute; bottom: 0; left: 0; right: 0; height: 40%; background: linear-gradient(to top, #4a7c59, transparent);"></div> <div style="position: absolute; bottom: 0; left: 0; right: 0; height: 40%; background: linear-gradient(to top, #4a7c59, transparent);"></div>
</div> </div>
<span class="text-[11px] font-semibold text-slate-700">Topografi</span> <span class="text-[11px] font-semibold text-slate-700">Topografi</span>
</div> </div>
<div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('dark')" id="layerDark"> <div class="layer-card p-2 rounded-lg" onclick="switchBaseMap('dark')" id="layerDark">
<div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #1e293b 0%, #334155 30%, #1e293b 60%, #0f172a 100%); position: relative; overflow: hidden;"> <div class="layer-preview mb-1.5" style="background: linear-gradient(135deg, #1e293b 0%, #334155 30%, #1e293b 60%, #0f172a 100%); position: relative; overflow: hidden;">
<div style="position: absolute; top: 25%; left: 15%; width: 35%; height: 1.5px; background: #64748b;"></div> <div style="position: absolute; top: 25%; left: 15%; width: 35%; height: 1.5px; background: #64748b;"></div>
@@ -298,7 +172,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<label class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3 block">Layer SPBU</label> <label class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3 block">Layer SPBU</label>
<div class="space-y-2"> <div class="space-y-2">
@@ -316,7 +189,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
<div class="toggle-switch active" onclick="toggleLayer('24jam', event)" id="toggle24Jam"></div> <div class="toggle-switch active" onclick="toggleLayer('24jam', event)" id="toggle24Jam"></div>
</div> </div>
<div class="layer-card active p-3 rounded-lg flex items-center justify-between" id="layerTerbatasCard"> <div class="layer-card active p-3 rounded-lg flex items-center justify-between" id="layerTerbatasCard">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center shadow-md"> <div class="w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center shadow-md">
@@ -333,16 +205,33 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
</div> </div>
</div> </div>
<div>
<label class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3 block">Choropleth</label>
<div class="space-y-2">
<div class="layer-card active p-3 rounded-lg flex items-center justify-between" id="layerChoroplethCard">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-purple-500 flex items-center justify-center shadow-md">
<svg class="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
<div>
<p class="text-sm font-semibold text-slate-700">Kecamatan</p>
<p class="text-[10px] text-slate-400" id="countChoropleth">-</p>
</div>
</div>
<div class="toggle-switch active" onclick="toggleChoropleth(event)" id="toggleChoropleth"></div>
</div>
</div>
</div>
</div> </div>
</aside> </aside>
<button class="gm-handle gm-handle-right" id="handleRight" onclick="toggleRightPanel()" title="Ciutkan panel lapisan"> <button class="gm-handle gm-handle-right" id="handleRight" onclick="toggleRightPanel()" title="Ciutkan panel lapisan">
<svg id="handleRightIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg id="handleRightIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg> </svg>
</button> </button>
</div> </div>
</main> </main>
<div id="loginOverlay" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4"> <div id="loginOverlay" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
@@ -353,19 +242,10 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div> </div>
<div class="p-6"> <div class="p-6">
<form id="loginForm" class="space-y-4"> <form id="loginForm" class="space-y-4">
<div> <div><label for="loginUsername" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Username</label><input type="text" id="loginUsername" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="admin"></div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Username</label> <div><label for="loginPassword" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Password</label><input type="password" id="loginPassword" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="••••••••"></div>
<input type="text" id="loginUsername" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="admin">
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Password</label>
<input type="password" id="loginPassword" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="••••••••">
</div>
<div id="loginError" class="text-red-500 text-xs hidden">Username atau password salah!</div> <div id="loginError" class="text-red-500 text-xs hidden">Username atau password salah!</div>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2"><button type="button" id="btnCancelLogin" class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button><button type="submit" class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Masuk</button></div>
<button type="button" id="btnCancelLogin" class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button>
<button type="submit" class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Masuk</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -373,50 +253,14 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
<div id="modalOverlay" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4"> <div id="modalOverlay" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden fade-in"> <div class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden fade-in">
<div class="bg-gradient-to-r from-teal-600 to-teal-500 px-6 py-4 flex items-center justify-between"> <div class="bg-gradient-to-r from-teal-600 to-teal-500 px-6 py-4 flex items-center justify-between"><h3 class="text-lg font-bold text-white" id="modalTitle">Tambah SPBU Baru</h3><button id="btnCloseModal" class="text-white/80 hover:text-white transition-colors"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg></button></div>
<h3 class="text-lg font-bold text-white" id="modalTitle">Tambah SPBU Baru</h3>
<button id="btnCloseModal" class="text-white/80 hover:text-white transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<form id="spbuForm" class="p-6 space-y-4"> <form id="spbuForm" class="p-6 space-y-4">
<input type="hidden" id="spbuId"> <input type="hidden" id="spbuId">
<div><label for="inputNama" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Nama SPBU</label><input type="text" id="inputNama" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="Contoh: SPBU Pertamina Tebet"></div>
<div> <div><label for="inputNo" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">No. SPBU</label><input type="text" id="inputNo" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="Contoh: 34.101.01"></div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Nama SPBU</label> <div><label for="inputStatus" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Status Operasional</label><select id="inputStatus" class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm bg-white"><option value="24jam">Buka 24 Jam</option><option value="terbatas">Jam Terbatas</option></select></div>
<input type="text" id="inputNama" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="Contoh: SPBU Pertamina Tebet"> <div class="grid grid-cols-2 gap-3"><div><label for="inputLat" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Latitude</label><input type="number" step="any" id="inputLat" required readonly class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed"></div><div><label for="inputLng" class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Longitude</label><input type="number" step="any" id="inputLng" required readonly class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed"></div></div>
</div> <div class="pt-2 flex gap-3"><button type="button" id="btnCancelModal" class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button><button type="submit" class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Simpan</button></div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">No. SPBU</label>
<input type="text" id="inputNo" required class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm" placeholder="Contoh: 34.101.01">
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Status Operasional</label>
<select id="inputStatus" class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm bg-white">
<option value="24jam">Buka 24 Jam</option>
<option value="terbatas">Jam Terbatas</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Latitude</label>
<input type="number" step="any" id="inputLat" required readonly class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed">
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Longitude</label>
<input type="number" step="any" id="inputLng" required readonly class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed">
</div>
</div>
<div class="pt-2 flex gap-3">
<button type="button" id="btnCancelModal" class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button>
<button type="submit" class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Simpan</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -434,6 +278,82 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
let layer24Jam, layerTerbatas; let layer24Jam, layerTerbatas;
let isLeftPanelOpen = true; let isLeftPanelOpen = true;
let isRightPanelOpen = true; let isRightPanelOpen = true;
let choroplethLayer = L.layerGroup();
let choroplethGeoJsonLayer = null;
function pointInPolygon(lng, lat, polygonCoords) {
if (lng == null || lat == null) return false;
let inside = false;
for (let i = 0, j = polygonCoords.length - 1; i < polygonCoords.length; j = i++) {
const xi = polygonCoords[i][0], yi = polygonCoords[i][1];
const xj = polygonCoords[j][0], yj = polygonCoords[j][1];
const intersect = ((yi > lat) !== (yj > lat)) && (lng < (xj - xi) * (lat - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
function getChoroplethColor(jumlah) {
return jumlah > 8 ? '#4a1486' :
jumlah > 5 ? '#6a51a3' :
jumlah > 2 ? '#9e9ac8' :
jumlah > 0 ? '#cbc9e2' :
'#f2f0f7';
}
async function loadChoropleth() {
try {
const response = await fetch('./json/batas.geojson');
const geoJsonData = await response.json();
if (choroplethGeoJsonLayer) {
choroplethLayer.removeLayer(choroplethGeoJsonLayer);
}
choroplethGeoJsonLayer = L.geoJSON(geoJsonData, {
style: function(feature) {
const coords = feature.geometry.type === 'Polygon' ? feature.geometry.coordinates[0] : feature.geometry.coordinates[0][0];
const count = spbuData.filter(spbu => {
const lat = spbu.latitude || spbu.lat;
const lng = spbu.longitude || spbu.lng;
return pointInPolygon(lng, lat, coords);
}).length;
return {
fillColor: getChoroplethColor(count),
weight: 1.5,
opacity: 1,
color: '#666',
fillOpacity: 0.6
};
},
onEachFeature: function(feature, layer) {
const coords = feature.geometry.type === 'Polygon' ? feature.geometry.coordinates[0] : feature.geometry.coordinates[0][0];
const count = spbuData.filter(spbu => {
const lat = spbu.latitude || spbu.lat;
const lng = spbu.longitude || spbu.lng;
return pointInPolygon(lng, lat, coords);
}).length;
layer.bindTooltip(`<strong>${feature.properties.name}</strong><br>Jumlah SPBU: ${count}`, { sticky: true });
layer.on('mouseover', function(e) {
layer.setStyle({ weight: 3, fillOpacity: 0.85, color: '#000' });
layer.bringToFront();
});
layer.on('mouseout', function(e) {
layer.setStyle({ weight: 1.5, fillOpacity: 0.6, color: '#666' });
});
layer.on('click', function(e) {
map.fitBounds(layer.getBounds());
});
}
});
choroplethLayer.addLayer(choroplethGeoJsonLayer);
if (document.getElementById('toggleChoropleth').classList.contains('active')) {
choroplethLayer.addTo(map);
}
document.getElementById('countChoropleth').textContent = geoJsonData.features.length + ' kecamatan';
} catch (error) {
console.error('Gagal memuat choropleth:', error);
document.getElementById('countChoropleth').textContent = 'gagal';
}
}
async function fetchSpbuData() { async function fetchSpbuData() {
try { try {
@@ -594,13 +514,13 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
renderList(); renderList();
updateStats(); updateStats();
updateLayerCounts(); updateLayerCounts();
loadChoropleth();
} }
function toggleLeftPanel() { function toggleLeftPanel() {
isLeftPanelOpen = !isLeftPanelOpen; isLeftPanelOpen = !isLeftPanelOpen;
const sidebar = document.getElementById('sidebarLeft'); const sidebar = document.getElementById('sidebarLeft');
const icon = document.getElementById('handleLeftIcon'); const icon = document.getElementById('handleLeftIcon');
if (isLeftPanelOpen) { if (isLeftPanelOpen) {
sidebar.classList.remove('collapsed'); sidebar.classList.remove('collapsed');
icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />'; icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />';
@@ -616,7 +536,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
isRightPanelOpen = !isRightPanelOpen; isRightPanelOpen = !isRightPanelOpen;
const sidebar = document.getElementById('sidebarRight'); const sidebar = document.getElementById('sidebarRight');
const icon = document.getElementById('handleRightIcon'); const icon = document.getElementById('handleRightIcon');
if (isRightPanelOpen) { if (isRightPanelOpen) {
sidebar.classList.remove('collapsed'); sidebar.classList.remove('collapsed');
icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />'; icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />';
@@ -630,7 +549,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
function initMap() { function initMap() {
map = L.map('map', { zoomControl: true }).setView([-0.0263, 109.3425], 13); map = L.map('map', { zoomControl: true }).setView([-0.0263, 109.3425], 13);
baseMaps = { baseMaps = {
light: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { light: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>',
@@ -651,41 +569,34 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
maxZoom: 19 maxZoom: 19
}) })
}; };
baseMaps.light.addTo(map); baseMaps.light.addTo(map);
layer24Jam = L.layerGroup(); layer24Jam = L.layerGroup();
layerTerbatas = L.layerGroup(); layerTerbatas = L.layerGroup();
map.on('click', function(e) { map.on('click', function(e) {
if (isSelectingLocation && isAdminMode) { if (isSelectingLocation && isAdminMode) {
selectLocation(e.latlng); selectLocation(e.latlng);
} }
}); });
refreshData().then(() => { refreshData().then(() => {
renderAuthContainer(); renderAuthContainer();
updateAdminUI(); updateAdminUI();
updateLayerCounts(); updateLayerCounts();
loadChoropleth();
}); });
} }
function switchBaseMap(type) { function switchBaseMap(type) {
map.removeLayer(baseMaps[currentBaseMap]); map.removeLayer(baseMaps[currentBaseMap]);
baseMaps[type].addTo(map); baseMaps[type].addTo(map);
document.querySelectorAll('#sidebarRight .layer-card').forEach(card => card.classList.remove('active')); document.querySelectorAll('#sidebarRight .layer-card').forEach(card => card.classList.remove('active'));
document.getElementById(`layer${type.charAt(0).toUpperCase() + type.slice(1)}`).classList.add('active'); document.getElementById(`layer${type.charAt(0).toUpperCase() + type.slice(1)}`).classList.add('active');
currentBaseMap = type; currentBaseMap = type;
} }
function toggleLayer(type, event) { function toggleLayer(type, event) {
event.stopPropagation(); event.stopPropagation();
const toggle = document.getElementById(`toggle${type === '24jam' ? '24Jam' : 'Terbatas'}`); const toggle = document.getElementById(`toggle${type === '24jam' ? '24Jam' : 'Terbatas'}`);
const card = document.getElementById(`layer${type === '24jam' ? '24Jam' : 'Terbatas'}Card`); const card = document.getElementById(`layer${type === '24jam' ? '24Jam' : 'Terbatas'}Card`);
if (type === '24jam') { if (type === '24jam') {
if (map.hasLayer(layer24Jam)) { if (map.hasLayer(layer24Jam)) {
map.removeLayer(layer24Jam); map.removeLayer(layer24Jam);
@@ -709,6 +620,21 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
} }
} }
function toggleChoropleth(event) {
event.stopPropagation();
const toggle = document.getElementById('toggleChoropleth');
const card = document.getElementById('layerChoroplethCard');
if (map.hasLayer(choroplethLayer)) {
map.removeLayer(choroplethLayer);
toggle.classList.remove('active');
card.classList.remove('active');
} else {
choroplethLayer.addTo(map);
toggle.classList.add('active');
card.classList.add('active');
}
}
function getMarkerIcon(status) { function getMarkerIcon(status) {
const color = status === '24jam' ? '#10b981' : '#f59e0b'; const color = status === '24jam' ? '#10b981' : '#f59e0b';
return L.divIcon({ return L.divIcon({
@@ -727,33 +653,28 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
function refreshLayers() { function refreshLayers() {
layer24Jam.clearLayers(); layer24Jam.clearLayers();
layerTerbatas.clearLayers(); layerTerbatas.clearLayers();
spbuData.forEach(spbu => { spbuData.forEach(spbu => {
const lat = spbu.latitude || spbu.lat; const lat = spbu.latitude || spbu.lat;
const lng = spbu.longitude || spbu.lng; const lng = spbu.longitude || spbu.lng;
const marker = L.marker([lat, lng], { const marker = L.marker([lat, lng], {
icon: getMarkerIcon(spbu.status), icon: getMarkerIcon(spbu.status),
draggable: isAdminMode draggable: isAdminMode
}); });
marker.on('dragend', function(e) { marker.on('dragend', function(e) {
const newPos = e.target.getLatLng(); const newPos = e.target.getLatLng();
updatePosition(spbu.id, newPos.lat, newPos.lng); updatePosition(spbu.id, newPos.lat, newPos.lng);
spbu.latitude = newPos.lat; spbu.latitude = newPos.lat;
spbu.longitude = newPos.lng; spbu.longitude = newPos.lng;
marker.setPopupContent(createPopupContent(spbu)); marker.setPopupContent(createPopupContent(spbu));
loadChoropleth();
}); });
marker.bindPopup(createPopupContent(spbu), { maxWidth: 300 }); marker.bindPopup(createPopupContent(spbu), { maxWidth: 300 });
if (spbu.status === '24jam') { if (spbu.status === '24jam') {
layer24Jam.addLayer(marker); layer24Jam.addLayer(marker);
} else { } else {
layerTerbatas.addLayer(marker); layerTerbatas.addLayer(marker);
} }
}); });
if (document.getElementById('toggle24Jam') && document.getElementById('toggle24Jam').classList.contains('active')) { if (document.getElementById('toggle24Jam') && document.getElementById('toggle24Jam').classList.contains('active')) {
layer24Jam.addTo(map); layer24Jam.addTo(map);
} }
@@ -767,7 +688,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
const no = spbu.no_spbu || spbu.no || ''; const no = spbu.no_spbu || spbu.no || '';
const lat = spbu.latitude || spbu.lat || 0; const lat = spbu.latitude || spbu.lat || 0;
const lng = spbu.longitude || spbu.lng || 0; const lng = spbu.longitude || spbu.lng || 0;
return ` return `
<div class="min-w-[200px]"> <div class="min-w-[200px]">
<div class="flex items-start justify-between mb-2"> <div class="flex items-start justify-between mb-2">
@@ -800,7 +720,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
function selectLocation(latlng) { function selectLocation(latlng) {
selectedLatLng = latlng; selectedLatLng = latlng;
removeTempMarker(); removeTempMarker();
tempMarker = L.marker([latlng.lat, latlng.lng], { tempMarker = L.marker([latlng.lat, latlng.lng], {
icon: L.divIcon({ icon: L.divIcon({
className: 'custom-temp-icon', className: 'custom-temp-icon',
@@ -813,7 +732,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
iconAnchor: [16, 16] iconAnchor: [16, 16]
}) })
}).addTo(map); }).addTo(map);
openModalForNewLocation(latlng); openModalForNewLocation(latlng);
} }
@@ -828,13 +746,11 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
const modal = document.getElementById('modalOverlay'); const modal = document.getElementById('modalOverlay');
const title = document.getElementById('modalTitle'); const title = document.getElementById('modalTitle');
const form = document.getElementById('spbuForm'); const form = document.getElementById('spbuForm');
form.reset(); form.reset();
document.getElementById('spbuId').value = ''; document.getElementById('spbuId').value = '';
title.textContent = 'Tambah SPBU Baru'; title.textContent = 'Tambah SPBU Baru';
document.getElementById('inputLat').value = latlng.lat.toFixed(7); document.getElementById('inputLat').value = latlng.lat.toFixed(7);
document.getElementById('inputLng').value = latlng.lng.toFixed(7); document.getElementById('inputLng').value = latlng.lng.toFixed(7);
modal.classList.remove('hidden'); modal.classList.remove('hidden');
} }
@@ -844,7 +760,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
(spbu.nama && spbu.nama.toLowerCase().includes(searchQuery.toLowerCase())) || (spbu.nama && spbu.nama.toLowerCase().includes(searchQuery.toLowerCase())) ||
((spbu.no_spbu || spbu.no) && (spbu.no_spbu || spbu.no).toLowerCase().includes(searchQuery.toLowerCase())) ((spbu.no_spbu || spbu.no) && (spbu.no_spbu || spbu.no).toLowerCase().includes(searchQuery.toLowerCase()))
); );
if (filteredData.length === 0) { if (filteredData.length === 0) {
listContainer.innerHTML = ` listContainer.innerHTML = `
<div class="flex flex-col items-center justify-center py-10 text-slate-400"> <div class="flex flex-col items-center justify-center py-10 text-slate-400">
@@ -855,7 +770,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
</div>`; </div>`;
return; return;
} }
listContainer.innerHTML = filteredData.map(spbu => ` listContainer.innerHTML = filteredData.map(spbu => `
<div class="bg-white border border-slate-200 rounded-xl p-3 hover:border-teal-400 hover:shadow-md transition-all cursor-pointer group" onclick="zoomToSpbu(${spbu.id})"> <div class="bg-white border border-slate-200 rounded-xl p-3 hover:border-teal-400 hover:shadow-md transition-all cursor-pointer group" onclick="zoomToSpbu(${spbu.id})">
<div class="flex items-start justify-between mb-1"> <div class="flex items-start justify-between mb-1">
@@ -915,7 +829,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
const btnAddSpbu = document.getElementById('btnAddSpbu'); const btnAddSpbu = document.getElementById('btnAddSpbu');
const mapHint = document.getElementById('mapHint'); const mapHint = document.getElementById('mapHint');
const mapContainer = document.getElementById('mapContainer'); const mapContainer = document.getElementById('mapContainer');
if (isAdminMode) { if (isAdminMode) {
btnAddSpbu.style.display = 'flex'; btnAddSpbu.style.display = 'flex';
mapHint.style.display = 'block'; mapHint.style.display = 'block';
@@ -928,7 +841,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
removeTempMarker(); removeTempMarker();
document.getElementById('modeIndicator').classList.add('hidden'); document.getElementById('modeIndicator').classList.add('hidden');
} }
refreshLayers(); refreshLayers();
renderList(); renderList();
updateStats(); updateStats();
@@ -941,7 +853,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
const lat = spbu.latitude || spbu.lat; const lat = spbu.latitude || spbu.lat;
const lng = spbu.longitude || spbu.lng; const lng = spbu.longitude || spbu.lng;
map.flyTo([lat, lng], 16, { duration: 1.5 }); map.flyTo([lat, lng], 16, { duration: 1.5 });
setTimeout(() => { setTimeout(() => {
[layer24Jam, layerTerbatas].forEach(layer => { [layer24Jam, layerTerbatas].forEach(layer => {
layer.eachLayer(marker => { layer.eachLayer(marker => {
@@ -961,7 +872,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
const modal = document.getElementById('modalOverlay'); const modal = document.getElementById('modalOverlay');
const title = document.getElementById('modalTitle'); const title = document.getElementById('modalTitle');
const form = document.getElementById('spbuForm'); const form = document.getElementById('spbuForm');
form.reset(); form.reset();
document.getElementById('spbuId').value = spbu.id; document.getElementById('spbuId').value = spbu.id;
title.textContent = 'Edit Data SPBU'; title.textContent = 'Edit Data SPBU';
@@ -970,7 +880,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
document.getElementById('inputStatus').value = spbu.status; document.getElementById('inputStatus').value = spbu.status;
document.getElementById('inputLat').value = spbu.latitude || spbu.lat; document.getElementById('inputLat').value = spbu.latitude || spbu.lat;
document.getElementById('inputLng').value = spbu.longitude || spbu.lng; document.getElementById('inputLng').value = spbu.longitude || spbu.lng;
modal.classList.remove('hidden'); modal.classList.remove('hidden');
} }
} }
@@ -989,19 +898,15 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
notification.className = 'fixed bottom-4 left-1/2 -translate-x-1/2 bg-teal-600 text-white px-4 py-2 rounded-lg shadow-lg z-[9999] text-sm font-medium fade-in'; notification.className = 'fixed bottom-4 left-1/2 -translate-x-1/2 bg-teal-600 text-white px-4 py-2 rounded-lg shadow-lg z-[9999] text-sm font-medium fade-in';
notification.textContent = message; notification.textContent = message;
document.body.appendChild(notification); document.body.appendChild(notification);
setTimeout(() => { setTimeout(() => { notification.remove(); }, 3000);
notification.remove();
}, 3000);
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initMap(); initMap();
document.getElementById('searchInput').addEventListener('input', (e) => { document.getElementById('searchInput').addEventListener('input', (e) => {
searchQuery = e.target.value; searchQuery = e.target.value;
renderList(); renderList();
}); });
document.getElementById('authContainer').addEventListener('click', (e) => { document.getElementById('authContainer').addEventListener('click', (e) => {
if (e.target.closest('#btnShowLogin')) { if (e.target.closest('#btnShowLogin')) {
document.getElementById('loginOverlay').classList.remove('hidden'); document.getElementById('loginOverlay').classList.remove('hidden');
@@ -1013,7 +918,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
logout(); logout();
} }
}); });
document.getElementById('loginForm').addEventListener('submit', (e) => { document.getElementById('loginForm').addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
const username = document.getElementById('loginUsername').value; const username = document.getElementById('loginUsername').value;
@@ -1028,7 +932,6 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
document.getElementById('loginOverlay').classList.add('hidden'); document.getElementById('loginOverlay').classList.add('hidden');
} }
}); });
document.getElementById('btnAddSpbu').addEventListener('click', () => { document.getElementById('btnAddSpbu').addEventListener('click', () => {
if (isAdminMode) { if (isAdminMode) {
isSelectingLocation = true; isSelectingLocation = true;
@@ -1036,38 +939,31 @@ $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
showNotification('Silakan klik pada peta untuk memilih lokasi SPBU'); showNotification('Silakan klik pada peta untuk memilih lokasi SPBU');
} }
}); });
document.getElementById('btnCloseModal').addEventListener('click', closeModal); document.getElementById('btnCloseModal').addEventListener('click', closeModal);
document.getElementById('btnCancelModal').addEventListener('click', closeModal); document.getElementById('btnCancelModal').addEventListener('click', closeModal);
document.getElementById('modalOverlay').addEventListener('click', (e) => { document.getElementById('modalOverlay').addEventListener('click', (e) => {
if (e.target === document.getElementById('modalOverlay')) closeModal(); if (e.target === document.getElementById('modalOverlay')) closeModal();
}); });
document.getElementById('spbuForm').addEventListener('submit', async (e) => { document.getElementById('spbuForm').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
if (!isAdminMode) return; if (!isAdminMode) return;
const id = document.getElementById('spbuId').value; const id = document.getElementById('spbuId').value;
const nama = document.getElementById('inputNama').value; const nama = document.getElementById('inputNama').value;
const no_spbu = document.getElementById('inputNo').value; const no_spbu = document.getElementById('inputNo').value;
const status = document.getElementById('inputStatus').value; const status = document.getElementById('inputStatus').value;
const latitude = parseFloat(document.getElementById('inputLat').value); const latitude = parseFloat(document.getElementById('inputLat').value);
const longitude = parseFloat(document.getElementById('inputLng').value); const longitude = parseFloat(document.getElementById('inputLng').value);
const data = { nama, no_spbu, status, latitude, longitude }; const data = { nama, no_spbu, status, latitude, longitude };
if (id) { if (id) {
data.id = parseInt(id); data.id = parseInt(id);
await updateSpbu(data); await updateSpbu(data);
} else { } else {
await addSpbu(data); await addSpbu(data);
} }
closeModal(); closeModal();
isSelectingLocation = false; isSelectingLocation = false;
removeTempMarker(); removeTempMarker();
document.getElementById('modeIndicator').classList.add('hidden'); document.getElementById('modeIndicator').classList.add('hidden');
map.flyTo([latitude, longitude], 16, { duration: 1 }); map.flyTo([latitude, longitude], 16, { duration: 1 });
}); });
}); });
File diff suppressed because it is too large Load Diff