2048 lines
61 KiB
HTML
2048 lines
61 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="id">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<meta name="description" content="WebGIS SPBU — Sistem pendataan Stasiun Pengisian Bahan Bakar Umum berbasis peta interaktif." />
|
||
<title>WebGIS SPBU — Admin Dashboard</title>
|
||
|
||
<!-- Auth Check -->
|
||
<script>
|
||
fetch('backend/check_auth.php')
|
||
.then(response => {
|
||
if (!response.ok) throw new Error('Not logged in');
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
if (data.username !== 'admin') {
|
||
// Normal user trying to access admin panel -> kick them out
|
||
throw new Error('Not admin');
|
||
}
|
||
})
|
||
.catch(() => {
|
||
window.location.href = 'login.html';
|
||
});
|
||
</script>
|
||
<!-- Leaflet CSS -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin="" />
|
||
<link rel="stylesheet" href="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.16.0/dist/leaflet-geoman.css" />
|
||
|
||
<!-- Google Fonts -->
|
||
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||
|
||
<style>
|
||
/* ==========================================
|
||
CSS Reset & Base
|
||
========================================== */
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
:root {
|
||
--clr-bg: #0f172a;
|
||
--clr-surface: #1e293b;
|
||
--clr-surface-light: #334155;
|
||
--clr-border: #334155;
|
||
--clr-text: #f1f5f9;
|
||
--clr-text-muted: #cbd5e1;
|
||
--clr-muted: #94a3b8;
|
||
--clr-primary: #3b82f6;
|
||
--clr-primary-dark: #2563eb;
|
||
--clr-primary-light: #60a5fa;
|
||
--clr-success: #22c55e;
|
||
--clr-warning: #f59e0b;
|
||
--clr-danger: #ef4444;
|
||
--clr-info: #06b6d4;
|
||
--radius: 10px;
|
||
--shadow: 0 4px 24px rgba(0,0,0,.45);
|
||
--shadow-sm: 0 2px 8px rgba(0,0,0,.15);
|
||
--transition: .2s ease;
|
||
--sidebar-width: 280px;
|
||
--sidebar-collapsed: 70px;
|
||
}
|
||
|
||
html, body {
|
||
height: 100%;
|
||
font-family: 'Inter', sans-serif;
|
||
background: var(--clr-bg);
|
||
color: var(--clr-text);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ==========================================
|
||
Layout Structure
|
||
========================================== */
|
||
.app-wrapper {
|
||
display: flex;
|
||
height: 100vh;
|
||
}
|
||
|
||
/* ---- Sidebar ---- */
|
||
.sidebar {
|
||
width: var(--sidebar-width);
|
||
background: linear-gradient(135deg, #1a2a42 0%, #0f172a 100%);
|
||
border-right: 1px solid var(--clr-border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
transition: all var(--transition);
|
||
z-index: 100;
|
||
box-shadow: 2px 0 8px rgba(0,0,0,.3);
|
||
}
|
||
|
||
.sidebar.collapsed {
|
||
width: var(--sidebar-collapsed);
|
||
}
|
||
|
||
.sidebar-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid var(--clr-border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-logo {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 1;
|
||
}
|
||
|
||
.sidebar-logo-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: linear-gradient(135deg, var(--clr-primary), #8b5cf6);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
box-shadow: 0 0 12px rgba(59,130,246,.4);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-logo-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.sidebar-logo-text h2 {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.sidebar-logo-text p {
|
||
font-size: 11px;
|
||
color: var(--clr-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.sidebar.collapsed .sidebar-logo-text {
|
||
display: none;
|
||
}
|
||
|
||
.sidebar-toggle {
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--clr-muted);
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
padding: 4px;
|
||
transition: color var(--transition);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-toggle:hover {
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.sidebar-nav {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.sidebar-section {
|
||
padding: 16px 0;
|
||
border-bottom: 1px solid rgba(255,255,255,.05);
|
||
}
|
||
|
||
.sidebar-section:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.sidebar-section-title {
|
||
padding: 12px 16px;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: var(--clr-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.sidebar.collapsed .sidebar-section-title {
|
||
display: none;
|
||
}
|
||
|
||
.sidebar-menu {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sidebar-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 14px 16px;
|
||
color: var(--clr-muted);
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
border-left: 3px solid transparent;
|
||
position: relative;
|
||
}
|
||
|
||
.sidebar-item:hover {
|
||
color: var(--clr-text);
|
||
background: rgba(255,255,255,.05);
|
||
border-left-color: var(--clr-primary);
|
||
}
|
||
|
||
.sidebar-item.active {
|
||
color: var(--clr-primary);
|
||
background: rgba(59,130,246,.1);
|
||
border-left-color: var(--clr-primary);
|
||
}
|
||
|
||
.sidebar-item-icon {
|
||
width: 20px;
|
||
height: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-item-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.sidebar.collapsed .sidebar-item-text {
|
||
display: none;
|
||
}
|
||
|
||
.sidebar-item-badge {
|
||
background: var(--clr-danger);
|
||
color: #fff;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
padding: 2px 6px;
|
||
border-radius: 999px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar.collapsed .sidebar-item-badge {
|
||
display: none;
|
||
}
|
||
|
||
/* ---- Main Content ---- */
|
||
.main-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ---- Header ---- */
|
||
header {
|
||
flex-shrink: 0;
|
||
background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);
|
||
border-bottom: 1px solid var(--clr-border);
|
||
padding: 0 20px;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,.5);
|
||
z-index: 50;
|
||
}
|
||
|
||
.header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.header-title h1 {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.3px;
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.header-breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
color: var(--clr-muted);
|
||
}
|
||
|
||
.header-breadcrumb span {
|
||
color: var(--clr-primary);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.header-stats {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: rgba(255,255,255,.06);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 8px;
|
||
padding: 8px 14px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.header-stat-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
background: var(--clr-success);
|
||
border-radius: 50%;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { box-shadow: 0 0 0 0 rgba(34,197,94,.5); }
|
||
50% { box-shadow: 0 0 0 5px rgba(34,197,94,0); }
|
||
}
|
||
|
||
/* ---- Content Area ---- */
|
||
.content-container {
|
||
flex: 1;
|
||
display: flex;
|
||
overflow: hidden;
|
||
gap: 0;
|
||
}
|
||
|
||
.content-panel {
|
||
display: none;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.content-panel.active {
|
||
display: flex;
|
||
}
|
||
|
||
.content-panel-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid var(--clr-border);
|
||
background: rgba(15,23,42,.4);
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.content-panel-header h2 {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.content-panel-header .btn-group {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.content-panel-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
/* ---- Map Container (Inside content-panel) ---- */
|
||
.panel-map {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#map {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: relative;
|
||
z-index: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ==========================================
|
||
Leaflet Map Overrides
|
||
========================================== */
|
||
.leaflet-top,
|
||
.leaflet-bottom {
|
||
z-index: 999 !important;
|
||
}
|
||
|
||
.leaflet-control-zoom {
|
||
z-index: 1000 !important;
|
||
}
|
||
|
||
.leaflet-control-layers {
|
||
z-index: 1000 !important;
|
||
}
|
||
|
||
.leaflet-control {
|
||
z-index: 1000 !important;
|
||
}
|
||
|
||
/* ==========================================
|
||
Buttons & Controls
|
||
========================================== */
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--clr-primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: var(--clr-primary-dark);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--clr-surface-light);
|
||
color: var(--clr-text);
|
||
border: 1px solid var(--clr-border);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: rgba(255,255,255,.08);
|
||
}
|
||
|
||
.btn-success {
|
||
background: var(--clr-success);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background: #16a34a;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--clr-danger);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #dc2626;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* ---- Map Toolbar Buttons ---- */
|
||
.map-tool-btn {
|
||
display: block; width: 100%; padding: 8px 10px; margin-bottom: 5px;
|
||
background: rgba(51, 65, 85, 0.7); color: #cbd5e1;
|
||
border: 1px solid rgba(100, 116, 139, 0.4); border-radius: 7px;
|
||
font-family: 'Inter', sans-serif; font-size: 12px; font-weight: 500;
|
||
cursor: pointer; text-align: left; transition: all 0.18s ease;
|
||
}
|
||
.map-tool-btn:hover {
|
||
background: rgba(59, 130, 246, 0.25); color: #93c5fd;
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
}
|
||
.map-tool-btn.active {
|
||
background: rgba(59, 130, 246, 0.35); color: #60a5fa;
|
||
border-color: #3b82f6;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* ==========================================
|
||
Forms & Inputs
|
||
========================================== */
|
||
.form-group {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--clr-muted);
|
||
margin-bottom: 6px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.4px;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group textarea,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 6px;
|
||
color: var(--clr-text);
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
outline: none;
|
||
transition: border-color var(--transition), box-shadow var(--transition);
|
||
}
|
||
|
||
.form-group input:focus,
|
||
.form-group textarea:focus,
|
||
.form-group select:focus {
|
||
border-color: var(--clr-primary);
|
||
box-shadow: 0 0 0 3px rgba(59,130,246,.15);
|
||
}
|
||
|
||
.form-group textarea {
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
|
||
.form-error {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--clr-danger);
|
||
margin-top: 4px;
|
||
min-height: 1em;
|
||
}
|
||
|
||
.form-help {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--clr-muted);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* ==========================================
|
||
Modal / Dialog
|
||
========================================== */
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,.6);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
|
||
.modal-overlay.show {
|
||
display: flex;
|
||
}
|
||
|
||
.modal {
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
width: 90%;
|
||
max-width: 500px;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
animation: slideIn .3s ease;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.modal-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid var(--clr-border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.modal-header h2 {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.modal-close-btn {
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--clr-muted);
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
transition: color var(--transition);
|
||
}
|
||
|
||
.modal-close-btn:hover {
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.modal-footer {
|
||
padding: 16px 20px;
|
||
border-top: 1px solid var(--clr-border);
|
||
display: flex;
|
||
gap: 10px;
|
||
justify-content: flex-end;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ==========================================
|
||
Cards & Containers
|
||
========================================== */
|
||
.card {
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: var(--radius);
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.card-header {
|
||
margin: -16px -16px 12px -16px;
|
||
padding: 12px 16px;
|
||
border-bottom: 1px solid var(--clr-border);
|
||
background: rgba(15,23,42,.5);
|
||
border-radius: var(--radius) var(--radius) 0 0;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.card-subtitle {
|
||
font-size: 12px;
|
||
color: var(--clr-muted);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* ==========================================
|
||
Stats Grid
|
||
========================================== */
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.stat-card-value {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--clr-primary);
|
||
}
|
||
|
||
.stat-card-label {
|
||
font-size: 12px;
|
||
color: var(--clr-muted);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.stat-card-trend {
|
||
font-size: 11px;
|
||
color: var(--clr-success);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* ==========================================
|
||
Table
|
||
========================================== */
|
||
.table-wrapper {
|
||
overflow-x: auto;
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.table thead {
|
||
background: rgba(15,23,42,.5);
|
||
border-bottom: 1px solid var(--clr-border);
|
||
}
|
||
|
||
.table th {
|
||
padding: 12px;
|
||
text-align: left;
|
||
font-weight: 700;
|
||
color: var(--clr-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.3px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.table td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid rgba(255,255,255,.05);
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.table tbody tr:hover {
|
||
background: rgba(59,130,246,.05);
|
||
}
|
||
|
||
.table-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
|
||
/* ==========================================
|
||
Search & Filter
|
||
========================================== */
|
||
.search-box {
|
||
position: relative;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.search-box input {
|
||
width: 100%;
|
||
padding: 10px 12px 10px 36px;
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 6px;
|
||
color: var(--clr-text);
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
outline: none;
|
||
transition: border-color var(--transition);
|
||
}
|
||
|
||
.search-box input:focus {
|
||
border-color: var(--clr-primary);
|
||
}
|
||
|
||
.search-icon {
|
||
position: absolute;
|
||
left: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 16px;
|
||
color: var(--clr-muted);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.filter-btn {
|
||
background: var(--clr-surface-light);
|
||
border: 1px solid var(--clr-border);
|
||
color: var(--clr-muted);
|
||
padding: 6px 12px;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
}
|
||
|
||
.filter-btn:hover {
|
||
background: rgba(255,255,255,.08);
|
||
color: var(--clr-text);
|
||
}
|
||
|
||
.filter-btn.active {
|
||
background: var(--clr-primary);
|
||
color: #fff;
|
||
border-color: var(--clr-primary);
|
||
}
|
||
|
||
/* ==========================================
|
||
Badges & Tags
|
||
========================================== */
|
||
.badge {
|
||
display: inline-block;
|
||
padding: 3px 8px;
|
||
border-radius: 4px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.badge-success {
|
||
background: rgba(34,197,94,.15);
|
||
color: var(--clr-success);
|
||
}
|
||
|
||
.badge-warning {
|
||
background: rgba(245,158,11,.15);
|
||
color: var(--clr-warning);
|
||
}
|
||
|
||
.badge-danger {
|
||
background: rgba(239,68,68,.15);
|
||
color: var(--clr-danger);
|
||
}
|
||
|
||
.badge-info {
|
||
background: rgba(6,182,212,.15);
|
||
color: var(--clr-info);
|
||
}
|
||
|
||
/* ==========================================
|
||
Toast Notifications
|
||
========================================== */
|
||
#toast-container {
|
||
position: fixed;
|
||
bottom: 28px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 9999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.toast {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
background: var(--clr-surface);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: 8px;
|
||
padding: 12px 16px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
box-shadow: var(--shadow);
|
||
opacity: 0;
|
||
transform: translateY(12px);
|
||
transition: opacity .3s ease, transform .3s ease;
|
||
white-space: nowrap;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.toast.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.toast-icon {
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.toast-success {
|
||
border-color: rgba(34,197,94,.4);
|
||
}
|
||
|
||
.toast-success .toast-icon {
|
||
color: var(--clr-success);
|
||
}
|
||
|
||
.toast-error {
|
||
border-color: rgba(239,68,68,.4);
|
||
}
|
||
|
||
.toast-error .toast-icon {
|
||
color: var(--clr-danger);
|
||
}
|
||
|
||
.toast-info {
|
||
border-color: rgba(6,182,212,.4);
|
||
}
|
||
|
||
.toast-info .toast-icon {
|
||
color: var(--clr-info);
|
||
}
|
||
|
||
/* ==========================================
|
||
Scrollbar
|
||
========================================== */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--clr-border);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--clr-surface-light);
|
||
}
|
||
|
||
/* ==========================================
|
||
Legend
|
||
========================================== */
|
||
.legend {
|
||
position: absolute;
|
||
bottom: 24px;
|
||
right: 12px;
|
||
z-index: 999;
|
||
background: rgba(15,23,42,.88);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid var(--clr-border);
|
||
border-radius: var(--radius);
|
||
padding: 12px 16px;
|
||
font-size: 12px;
|
||
color: var(--clr-text);
|
||
box-shadow: var(--shadow);
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.legend-title {
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
color: var(--clr-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
font-size: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.legend-title:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.legend-line {
|
||
width: 16px;
|
||
height: 4px;
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.legend-poly {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 3px;
|
||
opacity: 0.6;
|
||
border: 2px solid;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ==========================================
|
||
Responsive Design
|
||
========================================== */
|
||
@media (max-width: 1024px) {
|
||
:root {
|
||
--sidebar-width: 240px;
|
||
}
|
||
|
||
.stats-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
:root {
|
||
--sidebar-width: 70px;
|
||
}
|
||
|
||
.sidebar {
|
||
width: var(--sidebar-collapsed);
|
||
}
|
||
|
||
.sidebar.collapsed .sidebar-section-title {
|
||
display: block;
|
||
transform: rotate(-90deg);
|
||
transform-origin: center;
|
||
font-size: 10px;
|
||
padding: 8px 2px;
|
||
position: absolute;
|
||
left: 8px;
|
||
}
|
||
|
||
.stats-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.form-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.modal {
|
||
width: 95%;
|
||
max-height: 95vh;
|
||
}
|
||
|
||
.content-panel-header h2 {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.table {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.table th, .table td {
|
||
padding: 8px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.header-breadcrumb {
|
||
display: none;
|
||
}
|
||
|
||
.table-wrapper {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.filter-group {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.filter-btn {
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
/* ---- Custom Marker ---- */
|
||
.custom-marker {
|
||
display: flex; justify-content: center; align-items: center;
|
||
}
|
||
.marker-halo {
|
||
position: absolute;
|
||
width: 40px; height: 40px;
|
||
border-radius: 50%;
|
||
animation: pulse 2s infinite ease-out;
|
||
}
|
||
.marker-halo.spbu { background: rgba(34, 197, 94, 0.5); }
|
||
.marker-halo.spbu-off { background: rgba(245, 158, 11, 0.5); }
|
||
.marker-halo.ibadah { background: rgba(14, 165, 233, 0.5); }
|
||
.marker-halo.miskin { background: rgba(239, 68, 68, 0.5); }
|
||
|
||
.marker-icon-inner {
|
||
position: relative; z-index: 2;
|
||
width: 28px; height: 28px;
|
||
background: white; border-radius: 50%;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
|
||
display: flex; justify-content: center; align-items: center;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(0.5); opacity: 1; }
|
||
100% { transform: scale(1.5); opacity: 0; }
|
||
}
|
||
|
||
/* ---- Floating Panels ---- */
|
||
.search-panel {
|
||
position: absolute; top: 16px; left: 50%; transform: translateX(-50%);
|
||
background: rgba(30, 41, 59, 0.95); backdrop-filter: blur(8px);
|
||
border: 1px solid var(--clr-border); border-radius: var(--radius);
|
||
padding: 12px; z-index: 1000; width: 350px; margin-left: 125px; /* offset for sidebar */
|
||
box-shadow: var(--shadow); display: flex; flex-direction: column;
|
||
}
|
||
.search-panel.collapsed-sidebar { margin-left: 35px; } /* will be handled via JS if needed */
|
||
.search-panel h3 { font-size: 13px; font-weight: 700; color: var(--clr-text); margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.5px; display: flex; align-items: center; gap: 8px; margin-top:0; }
|
||
.search-input-wrap { position: relative; margin-bottom: 8px; }
|
||
.search-input-wrap .ctrl-input { margin-bottom: 0; padding-right: 30px; width: 100%; padding: 9px 12px; background: var(--clr-bg); border: 1px solid var(--clr-border); border-radius: 6px; color: var(--clr-text); font-family: inherit; font-size: 13px; outline: none; box-sizing: border-box; }
|
||
.search-input-wrap .ctrl-input:focus { border-color: var(--clr-primary); }
|
||
.search-results {
|
||
max-height: 250px; overflow-y: auto; margin-top: 4px;
|
||
display: none; flex-direction: column; gap: 4px;
|
||
}
|
||
.search-results.active { display: flex; margin-bottom: 8px; }
|
||
.search-result-item {
|
||
padding: 8px 10px; background: rgba(255,255,255,0.05); border-radius: 6px;
|
||
cursor: pointer; transition: 0.2s; border: 1px solid transparent;
|
||
}
|
||
.search-result-item:hover { background: rgba(59,130,246,0.1); border-color: rgba(59,130,246,0.3); }
|
||
.search-result-name { font-size: 13px; font-weight: 600; color: #f1f5f9; display: block; margin-bottom: 2px; }
|
||
.search-result-desc { font-size: 11px; color: #94a3b8; display: block; }
|
||
|
||
.routing-panel {
|
||
position: absolute; top: 80px; right: 16px;
|
||
background: rgba(30, 41, 59, 0.95); backdrop-filter: blur(8px);
|
||
border: 1px solid var(--clr-border); border-radius: var(--radius);
|
||
padding: 14px; z-index: 1000; width: 260px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.routing-panel h3 { font-size: 12px; font-weight: 700; color: #f1f5f9; margin-bottom: 12px; border-bottom: 1px solid #334155; padding-bottom: 8px; text-transform: uppercase; margin-top:0; }
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="app-wrapper">
|
||
<!-- ======= Sidebar ======= -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-logo">
|
||
<div class="sidebar-logo-icon">WG</div>
|
||
<div class="sidebar-logo-text">
|
||
<h2>WebGIS</h2>
|
||
<p>SPBU Systems</p>
|
||
</div>
|
||
</div>
|
||
<button class="sidebar-toggle" id="sidebar-toggle" title="Toggle Sidebar">≡</button>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav">
|
||
<!-- Main Navigation -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title">Main</div>
|
||
<div class="sidebar-menu">
|
||
<div class="sidebar-item active" data-panel="panel-map">
|
||
<div class="sidebar-item-icon">▦</div>
|
||
<div class="sidebar-item-text">Dashboard Map</div>
|
||
</div>
|
||
<div class="sidebar-item" data-panel="panel-dashboard">
|
||
<div class="sidebar-item-icon">◈</div>
|
||
<div class="sidebar-item-text">Analytics</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Data Management -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title">Data</div>
|
||
<div class="sidebar-menu">
|
||
<div class="sidebar-item" data-panel="panel-spbu">
|
||
<div class="sidebar-item-icon">S</div>
|
||
<div class="sidebar-item-text">SPBU</div>
|
||
<div class="sidebar-item-badge" id="badge-spbu">0</div>
|
||
</div>
|
||
<div class="sidebar-item" data-panel="panel-jalan">
|
||
<div class="sidebar-item-icon">J</div>
|
||
<div class="sidebar-item-text">Jalan</div>
|
||
<div class="sidebar-item-badge" id="badge-jalan">0</div>
|
||
</div>
|
||
<div class="sidebar-item" data-panel="panel-parsil">
|
||
<div class="sidebar-item-icon">P</div>
|
||
<div class="sidebar-item-text">Parsil</div>
|
||
<div class="sidebar-item-badge" id="badge-parsil">0</div>
|
||
</div>
|
||
<div class="sidebar-item" data-panel="panel-ibadah">
|
||
<div class="sidebar-item-icon">I</div>
|
||
<div class="sidebar-item-text">Ibadah</div>
|
||
<div class="sidebar-item-badge" id="badge-ibadah">0</div>
|
||
</div>
|
||
<div class="sidebar-item" data-panel="panel-miskin">
|
||
<div class="sidebar-item-icon">M</div>
|
||
<div class="sidebar-item-text">Pend. Miskin</div>
|
||
<div class="sidebar-item-badge" id="badge-miskin">0</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- User Settings -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title">Settings</div>
|
||
<div class="sidebar-menu">
|
||
<div class="sidebar-item" data-panel="panel-auth">
|
||
<div class="sidebar-item-icon">⚙</div>
|
||
<div class="sidebar-item-text">Account</div>
|
||
</div>
|
||
<div class="sidebar-item" id="logout-btn">
|
||
<div class="sidebar-item-icon">⎋</div>
|
||
<div class="sidebar-item-text">Logout</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
</aside>
|
||
|
||
<!-- ======= Main Content Area ======= -->
|
||
<div class="main-wrapper">
|
||
<!-- Header -->
|
||
<header>
|
||
<div class="header-title">
|
||
<h1 id="page-title">Dashboard Map</h1>
|
||
<div class="header-breadcrumb">
|
||
<span id="breadcrumb-text">Map</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-actions">
|
||
<div class="header-stats">
|
||
<div class="header-stat-dot"></div>
|
||
<span id="total-data-count">0 Data</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Content Panels Container -->
|
||
<div class="content-container">
|
||
|
||
<!-- Panel: Map (Dashboard) -->
|
||
<div id="panel-map" class="content-panel active">
|
||
<div class="panel-map" id="map" role="main" aria-label="Peta persebaran WebGIS"></div>
|
||
|
||
<!-- Search Panel (Top Center) -->
|
||
<div class="search-panel" id="admin-search-panel">
|
||
<h3>🔍 Pencarian Global</h3>
|
||
<div class="search-input-wrap">
|
||
<input type="text" id="global-search" class="ctrl-input" placeholder="Cari SPBU, Jalan, Ibadah..." autocomplete="off">
|
||
</div>
|
||
<div id="search-results" class="search-results"></div>
|
||
</div>
|
||
|
||
<!-- Routing & Radius Panel (Right) -->
|
||
<!-- <div class="routing-panel">
|
||
<h3>📍 ROUTING & RADIUS</h3>
|
||
<button class="btn btn-sm" id="btn-nearest-spbu" style="width:100%; background:#2563eb; color:white; border:none; padding:8px; border-radius:6px; cursor:pointer; font-weight:600; font-size:12px; margin-bottom:6px;">📍 Cari SPBU Terdekat</button>
|
||
<div id="nearest-spbu-result" style="font-size:11px; color:#94a3b8; margin-bottom:10px;"></div>
|
||
|
||
<div style="font-size:11px; font-weight:600; color:#94a3b8; margin-bottom:6px; margin-top:6px;">RADIUS SEARCH</div>
|
||
<input type="range" id="radius-slider" min="100" max="5000" value="500" step="100" style="width:100%">
|
||
<div style="display:flex; justify-content:space-between; font-size:11px; color:#cbd5e1; margin-top:4px;">
|
||
<span id="radius-value">500 m</span>
|
||
<span style="color:#64748b;">(Klik peta)</span>
|
||
</div>
|
||
</div> -->
|
||
|
||
<!-- Floating "Tambah Data" Toolbar -->
|
||
<div id="map-add-toolbar" style="
|
||
position:absolute; top:80px; left:16px; z-index:1000;
|
||
background:rgba(15,23,42,0.96); backdrop-filter:blur(10px);
|
||
border:1px solid #334155; border-radius:12px;
|
||
padding:14px 12px; box-shadow:0 4px 24px rgba(0,0,0,0.6);
|
||
width:158px; font-family:'Inter',sans-serif;
|
||
">
|
||
<div style="font-size:10px;font-weight:700;color:#60a5fa;text-transform:uppercase;letter-spacing:0.6px;margin-bottom:10px;">⊕ Tambah Data</div>
|
||
|
||
<div style="font-size:10px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;">📍 Titik</div>
|
||
<button class="map-tool-btn" id="map-add-spbu"
|
||
onclick="window.mapToolActivatePoint('spbu','SPBU')">⛽ SPBU</button>
|
||
<button class="map-tool-btn" id="map-add-ibadah"
|
||
onclick="window.mapToolActivatePoint('ibadah','Rumah Ibadah')">🕌 Rumah Ibadah</button>
|
||
<button class="map-tool-btn" id="map-add-miskin"
|
||
onclick="window.mapToolActivatePoint('miskin','Pend. Miskin')">🏠 Pend. Miskin</button>
|
||
|
||
<div style="font-size:10px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:0.4px;margin:10px 0 5px;">✏️ Gambar</div>
|
||
<button class="map-tool-btn" id="map-add-jalan"
|
||
onclick="window.mapToolActivateDraw('jalan')">🛣 Jalan (Garis)</button>
|
||
<button class="map-tool-btn" id="map-add-parsil"
|
||
onclick="window.mapToolActivateDraw('parsil')">📐 Parsil (Area)</button>
|
||
|
||
<div id="map-tool-status" style="
|
||
display:none; margin-top:10px; padding:7px 8px;
|
||
background:rgba(59,130,246,.15); border:1px solid rgba(59,130,246,.3);
|
||
border-radius:6px; font-size:11px; color:#93c5fd; text-align:center; line-height:1.4;
|
||
"></div>
|
||
<button id="map-tool-cancel" style="
|
||
display:none; margin-top:6px; width:100%; padding:5px;
|
||
background:rgba(239,68,68,.15); border:1px solid rgba(239,68,68,.3);
|
||
border-radius:5px; color:#fca5a5; font-size:11px; cursor:pointer; font-family:inherit;
|
||
" onclick="window.mapToolCancel()">✕ Batal</button>
|
||
</div>
|
||
|
||
<div class="legend" id="legend-peta">
|
||
<div class="legend-title">SPBU</div>
|
||
<div class="legend-item">
|
||
<div class="legend-dot" style="background:#22c55e;"></div>
|
||
<span>Beroperasi 24 Jam</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-dot" style="background:#f59e0b;"></div>
|
||
<span>Tidak 24 Jam</span>
|
||
</div>
|
||
<div class="legend-title">Jalan</div>
|
||
<div class="legend-item">
|
||
<div class="legend-line" style="background:#ef4444;"></div>
|
||
<span>Nasional</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-line" style="background:#eab308;"></div>
|
||
<span>Provinsi</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-line" style="background:#10b981;"></div>
|
||
<span>Kabupaten</span>
|
||
</div>
|
||
<div class="legend-title">Parsil Tanah</div>
|
||
<div class="legend-item">
|
||
<div class="legend-poly" style="background:#3b82f6; border-color:#2563eb;"></div>
|
||
<span>SHM</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-poly" style="background:#8b5cf6; border-color:#7c3aed;"></div>
|
||
<span>HGB</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-poly" style="background:#ec4899; border-color:#db2777;"></div>
|
||
<span>HGU</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-poly" style="background:#14b8a6; border-color:#0d9488;"></div>
|
||
<span>HP</span>
|
||
</div>
|
||
<div class="legend-title">Sosial Religi</div>
|
||
<div class="legend-item">
|
||
<div class="legend-dot" style="background:#8b5cf6;"></div>
|
||
<span>Rumah Ibadah</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-dot" style="background:#000000;"></div>
|
||
<span>Penduduk Miskin</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: Analytics Dashboard -->
|
||
<div id="panel-dashboard" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>Analytics Dashboard</h2>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="stats-grid" id="analytics-stats">
|
||
<div class="stat-card">
|
||
<div class="stat-card-label">Total SPBU</div>
|
||
<div class="stat-card-value" id="stat-spbu">0</div>
|
||
<div class="stat-card-trend" id="stat-spbu-trend">Data aktif</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-card-label">Total Jalan</div>
|
||
<div class="stat-card-value" id="stat-jalan">0</div>
|
||
<div class="stat-card-trend" id="stat-jalan-trend">Data tercatat</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-card-label">Total Parsil</div>
|
||
<div class="stat-card-value" id="stat-parsil">0</div>
|
||
<div class="stat-card-trend" id="stat-parsil-trend">Data tercatat</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-card-label">Rumah Ibadah</div>
|
||
<div class="stat-card-value" id="stat-ibadah">0</div>
|
||
<div class="stat-card-trend" id="stat-ibadah-trend">Data tercatat</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-card-label">Pend. Miskin</div>
|
||
<div class="stat-card-value" id="stat-miskin">0</div>
|
||
<div class="stat-card-trend" id="stat-miskin-trend">Data tercatat</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">SPBU Status Distribution</h3>
|
||
</div>
|
||
<div id="spbu-distribution-chart" style="height: 200px; display: flex; align-items: center; justify-content: center; color: var(--clr-muted);">
|
||
Chart akan muncul di sini
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Jalan Type Distribution</h3>
|
||
</div>
|
||
<div id="jalan-distribution-chart" style="height: 200px; display: flex; align-items: center; justify-content: center; color: var(--clr-muted);">
|
||
Chart akan muncul di sini
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: SPBU Management -->
|
||
<div id="panel-spbu" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>SPBU Data Management</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-spbu">Add SPBU</button>
|
||
</div>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="search-box">
|
||
<input type="text" id="search-spbu" placeholder="Cari SPBU..." />
|
||
<span class="search-icon">⌕</span>
|
||
</div>
|
||
<div class="filter-group">
|
||
<button class="filter-btn active" data-filter="all">All</button>
|
||
<button class="filter-btn" data-filter="24jam">24 Jam</button>
|
||
<button class="filter-btn" data-filter="tidak">Tidak 24 Jam</button>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="table" id="table-spbu">
|
||
<thead>
|
||
<tr>
|
||
<th>Nama</th>
|
||
<th>No SPBU</th>
|
||
<th>Status</th>
|
||
<th>Lokasi</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-spbu-body">
|
||
<tr>
|
||
<td colspan="5" style="text-align: center; color: var(--clr-muted); padding: 40px;">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: JALAN Management -->
|
||
<div id="panel-jalan" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>JALAN Data Management</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-jalan">Add Jalan</button>
|
||
</div>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="search-box">
|
||
<input type="text" id="search-jalan" placeholder="Cari jalan..." />
|
||
<span class="search-icon">⌕</span>
|
||
</div>
|
||
<div class="filter-group">
|
||
<button class="filter-btn active" data-filter="all">All</button>
|
||
<button class="filter-btn" data-filter="Nasional">Nasional</button>
|
||
<button class="filter-btn" data-filter="Provinsi">Provinsi</button>
|
||
<button class="filter-btn" data-filter="Kabupaten">Kabupaten</button>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="table" id="table-jalan">
|
||
<thead>
|
||
<tr>
|
||
<th>Nama Jalan</th>
|
||
<th>Status</th>
|
||
<th>Panjang</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-jalan-body">
|
||
<tr>
|
||
<td colspan="4" style="text-align: center; color: var(--clr-muted); padding: 40px;">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: PARSIL Management -->
|
||
<div id="panel-parsil" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>PARSIL Data Management</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-parsil">Add Parsil</button>
|
||
</div>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="search-box">
|
||
<input type="text" id="search-parsil" placeholder="Cari parsil..." />
|
||
<span class="search-icon">⌕</span>
|
||
</div>
|
||
<div class="filter-group">
|
||
<button class="filter-btn active" data-filter="all">All</button>
|
||
<button class="filter-btn" data-filter="SHM">SHM</button>
|
||
<button class="filter-btn" data-filter="HGB">HGB</button>
|
||
<button class="filter-btn" data-filter="HGU">HGU</button>
|
||
<button class="filter-btn" data-filter="HP">HP</button>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="table" id="table-parsil">
|
||
<thead>
|
||
<tr>
|
||
<th>Nama Pemilik</th>
|
||
<th>Status</th>
|
||
<th>Luas</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-parsil-body">
|
||
<tr>
|
||
<td colspan="4" style="text-align: center; color: var(--clr-muted); padding: 40px;">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: IBADAH Management -->
|
||
<div id="panel-ibadah" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>IBADAH Data Management</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-ibadah">Add Ibadah</button>
|
||
</div>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="search-box">
|
||
<input type="text" id="search-ibadah" placeholder="Cari rumah ibadah..." />
|
||
<span class="search-icon">⌕</span>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="table" id="table-ibadah">
|
||
<thead>
|
||
<tr>
|
||
<th>Nama</th>
|
||
<th>Alamat</th>
|
||
<th>Lokasi</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-ibadah-body">
|
||
<tr>
|
||
<td colspan="4" style="text-align: center; color: var(--clr-muted); padding: 40px;">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel: MISKIN Management -->
|
||
<div id="panel-miskin" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>PENDUDUK MISKIN Data Management</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-miskin">Add Penduduk Miskin</button>
|
||
</div>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="search-box">
|
||
<input type="text" id="search-miskin" placeholder="Cari penduduk miskin..." />
|
||
<span class="search-icon">⌕</span>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="table" id="table-miskin">
|
||
<thead>
|
||
<tr>
|
||
<th>Nama KK</th>
|
||
<th>Keterangan</th>
|
||
<th>Lokasi</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-miskin-body">
|
||
<tr>
|
||
<td colspan="4" style="text-align: center; color: var(--clr-muted); padding: 40px;">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Panel: Account/Auth -->
|
||
<div id="panel-auth" class="content-panel">
|
||
<div class="content-panel-header">
|
||
<h2>Account Settings</h2>
|
||
</div>
|
||
<div class="content-panel-body">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">User Profile</h3>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Username</label>
|
||
<input type="text" id="username" placeholder="Username" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Email</label>
|
||
<input type="email" id="user-email" placeholder="Email" />
|
||
</div>
|
||
<button class="btn btn-primary" style="width: 100%;">Update Profile</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Change Password</h3>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Current Password</label>
|
||
<input type="password" placeholder="Current password" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>New Password</label>
|
||
<input type="password" placeholder="New password" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Confirm Password</label>
|
||
<input type="password" placeholder="Confirm password" />
|
||
</div>
|
||
<button class="btn btn-primary" style="width: 100%;">Change Password</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /content-container -->
|
||
|
||
</div><!-- /main-wrapper -->
|
||
</div><!-- /app-wrapper -->
|
||
|
||
<!-- ======= Modal Add/Edit SPBU ======= -->
|
||
<div class="modal-overlay" id="modal-spbu">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h2 id="modal-spbu-title">Add SPBU</h2>
|
||
<button class="modal-close-btn" data-modal="modal-spbu">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-spbu">
|
||
<input type="hidden" id="form-spbu-id" />
|
||
<div class="form-group">
|
||
<label>Nama SPBU</label>
|
||
<input type="text" id="form-spbu-nama" placeholder="Nama SPBU" required />
|
||
<span class="form-error" id="error-spbu-nama"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>No SPBU</label>
|
||
<input type="text" id="form-spbu-no" placeholder="No SPBU" required />
|
||
<span class="form-error" id="error-spbu-no"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Status Operasional</label>
|
||
<select id="form-spbu-status" required>
|
||
<option value="">-- Pilih Status --</option>
|
||
<option value="24jam">Buka 24 Jam</option>
|
||
<option value="tidak">Tidak 24 Jam</option>
|
||
</select>
|
||
<span class="form-error" id="error-spbu-status"></span>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Latitude</label>
|
||
<input type="number" id="form-spbu-lat" placeholder="Latitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-spbu-lat"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Longitude</label>
|
||
<input type="number" id="form-spbu-lng" placeholder="Longitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-spbu-lng"></span>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-spbu">Cancel</button>
|
||
<button class="btn btn-primary" id="btn-save-spbu">Save SPBU</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Modal Add/Edit JALAN ======= -->
|
||
<div class="modal-overlay" id="modal-jalan">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h2 id="modal-jalan-title">Add Jalan</h2>
|
||
<button class="modal-close-btn" data-modal="modal-jalan">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-jalan">
|
||
<input type="hidden" id="form-jalan-id" />
|
||
<div class="form-group">
|
||
<label>Nama Jalan</label>
|
||
<input type="text" id="form-jalan-nama" placeholder="Nama Jalan" required />
|
||
<span class="form-error" id="error-jalan-nama"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Status Jalan</label>
|
||
<select id="form-jalan-status" required>
|
||
<option value="">-- Pilih Status --</option>
|
||
<option value="Nasional">Nasional</option>
|
||
<option value="Provinsi">Provinsi</option>
|
||
<option value="Kabupaten">Kabupaten</option>
|
||
</select>
|
||
<span class="form-error" id="error-jalan-status"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Panjang (meter)</label>
|
||
<input type="number" id="form-jalan-panjang" placeholder="Panjang jalan" step="0.01" required />
|
||
<span class="form-error" id="error-jalan-panjang"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Geometry (GeoJSON)</label>
|
||
<textarea id="form-jalan-geom" placeholder='{"type": "LineString", "coordinates": [...]}' required></textarea>
|
||
<span class="form-help">Paste LineString GeoJSON geometry</span>
|
||
<span class="form-error" id="error-jalan-geom"></span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-jalan">Cancel</button>
|
||
<button class="btn btn-primary" id="btn-save-jalan">Save Jalan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Modal Add/Edit PARSIL ======= -->
|
||
<div class="modal-overlay" id="modal-parsil">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h2 id="modal-parsil-title">Add Parsil</h2>
|
||
<button class="modal-close-btn" data-modal="modal-parsil">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-parsil">
|
||
<input type="hidden" id="form-parsil-id" />
|
||
<div class="form-group">
|
||
<label>Nama Pemilik</label>
|
||
<input type="text" id="form-parsil-nama" placeholder="Nama Pemilik" required />
|
||
<span class="form-error" id="error-parsil-nama"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Status Kepemilikan</label>
|
||
<select id="form-parsil-status" required>
|
||
<option value="">-- Pilih Status --</option>
|
||
<option value="SHM">SHM (Sertifikat Hak Milik)</option>
|
||
<option value="HGB">HGB (Hak Guna Bangunan)</option>
|
||
<option value="HGU">HGU (Hak Guna Usaha)</option>
|
||
<option value="HP">HP (Hak Pakai)</option>
|
||
</select>
|
||
<span class="form-error" id="error-parsil-status"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Luas (m2)</label>
|
||
<input type="number" id="form-parsil-luas" placeholder="Luas tanah" step="0.01" required />
|
||
<span class="form-error" id="error-parsil-luas"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Geometry (GeoJSON)</label>
|
||
<textarea id="form-parsil-geom" placeholder='{"type": "Polygon", "coordinates": [...]}' required></textarea>
|
||
<span class="form-help">Paste Polygon GeoJSON geometry</span>
|
||
<span class="form-error" id="error-parsil-geom"></span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-parsil">Cancel</button>
|
||
<button class="btn btn-primary" id="btn-save-parsil">Save Parsil</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Modal Add/Edit IBADAH ======= -->
|
||
<div class="modal-overlay" id="modal-ibadah">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h2 id="modal-ibadah-title">Add Rumah Ibadah</h2>
|
||
<button class="modal-close-btn" data-modal="modal-ibadah">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-ibadah">
|
||
<input type="hidden" id="form-ibadah-id" />
|
||
<div class="form-group">
|
||
<label>Nama Rumah Ibadah</label>
|
||
<input type="text" id="form-ibadah-nama" placeholder="Nama" required />
|
||
<span class="form-error" id="error-ibadah-nama"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Alamat</label>
|
||
<textarea id="form-ibadah-alamat" placeholder="Alamat lengkap" required></textarea>
|
||
<span class="form-error" id="error-ibadah-alamat"></span>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Latitude</label>
|
||
<input type="number" id="form-ibadah-lat" placeholder="Latitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-ibadah-lat"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Longitude</label>
|
||
<input type="number" id="form-ibadah-lng" placeholder="Longitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-ibadah-lng"></span>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-ibadah">Cancel</button>
|
||
<button class="btn btn-primary" id="btn-save-ibadah">Save Ibadah</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Modal Add/Edit MISKIN ======= -->
|
||
<div class="modal-overlay" id="modal-miskin">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h2 id="modal-miskin-title">Add Penduduk Miskin</h2>
|
||
<button class="modal-close-btn" data-modal="modal-miskin">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-miskin">
|
||
<input type="hidden" id="form-miskin-id" />
|
||
<div class="form-group">
|
||
<label>Nama KK (Kepala Keluarga)</label>
|
||
<input type="text" id="form-miskin-nama" placeholder="Nama KK" required />
|
||
<span class="form-error" id="error-miskin-nama"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Keterangan</label>
|
||
<textarea id="form-miskin-ket" placeholder="Keterangan tambahan"></textarea>
|
||
<span class="form-error" id="error-miskin-ket"></span>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Latitude</label>
|
||
<input type="number" id="form-miskin-lat" placeholder="Latitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-miskin-lat"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Longitude</label>
|
||
<input type="number" id="form-miskin-lng" placeholder="Longitude" step="0.00000001" required />
|
||
<span class="form-error" id="error-miskin-lng"></span>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-miskin">Cancel</button>
|
||
<button class="btn btn-primary" id="btn-save-miskin">Save Miskin</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Modal Choice Data Type ======= -->
|
||
<div class="modal-overlay" id="modal-choice">
|
||
<div class="modal" style="max-width: 400px;">
|
||
<div class="modal-header">
|
||
<h2 id="modal-choice-title">Pilih Jenis Data</h2>
|
||
<button class="modal-close-btn" data-modal="modal-choice">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label>Data apa yang baru saja Anda gambar?</label>
|
||
<select id="form-choice-type" class="search-box" style="width: 100%; border: 1px solid #cbd5e1; padding: 10px; border-radius: 6px; font-family: inherit;">
|
||
<!-- Options injected via JS -->
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-modal="modal-choice">Batal</button>
|
||
<button class="btn btn-primary" id="btn-choice-confirm">Lanjutkan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======= Toast Container ======= -->
|
||
<div id="toast-container" aria-live="polite"></div>
|
||
|
||
<!-- External Libs -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin=""></script>
|
||
<script src="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.16.0/dist/leaflet-geoman.min.js"></script>
|
||
<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
|
||
|
||
<!-- App Logic -->
|
||
<script src="js/app.js"></script>
|
||
|
||
<!-- Search Logic -->
|
||
<script>
|
||
document.getElementById('global-search').addEventListener('input', e => {
|
||
const f = e.target.value.toLowerCase();
|
||
const resDiv = document.getElementById('search-results');
|
||
resDiv.innerHTML = '';
|
||
|
||
if (!f || !window.appState) {
|
||
resDiv.classList.remove('active');
|
||
return;
|
||
}
|
||
|
||
const matched = [];
|
||
const pushMatch = (type, icon, name, desc, item) => {
|
||
matched.push({ type, icon, name, desc, item });
|
||
};
|
||
|
||
// Search SPBU
|
||
window.appState.data.spbu.forEach(item => {
|
||
const nama = item.nama || '';
|
||
const no = item.no_spbu || '';
|
||
if (nama.toLowerCase().includes(f) || no.toLowerCase().includes(f)) {
|
||
pushMatch('spbu', '⛽', nama, `SPBU - No: ${no}`, item);
|
||
}
|
||
});
|
||
|
||
// Search Jalan
|
||
window.appState.data.jalan.forEach(item => {
|
||
const nama = item.nama_jalan || '';
|
||
const status = item.status || '';
|
||
if (nama.toLowerCase().includes(f)) {
|
||
pushMatch('jalan', '🛣️', nama, `Jalan - ${status}`, item);
|
||
}
|
||
});
|
||
|
||
// Search Parsil
|
||
window.appState.data.parsil.forEach(item => {
|
||
const pemilik = item.nama_pemilik || '';
|
||
const status = item.status || '';
|
||
if (pemilik.toLowerCase().includes(f) || status.toLowerCase().includes(f)) {
|
||
pushMatch('parsil', '📐', `Pemilik: ${pemilik}`, `Status: ${status}`, item);
|
||
}
|
||
});
|
||
|
||
// Search Ibadah
|
||
window.appState.data.ibadah.forEach(item => {
|
||
const nama = item.nama || '';
|
||
if (nama.toLowerCase().includes(f)) {
|
||
pushMatch('ibadah', '🕌', nama, `Rumah Ibadah`, item);
|
||
}
|
||
});
|
||
|
||
// Search Miskin
|
||
window.appState.data.miskin.forEach(item => {
|
||
const kk = item.nama_kk || '';
|
||
if (kk.toLowerCase().includes(f)) {
|
||
pushMatch('miskin', '🏠', `KK: ${kk}`, `Penduduk Miskin`, item);
|
||
}
|
||
});
|
||
|
||
if (matched.length > 0) {
|
||
resDiv.innerHTML = matched.map((m, idx) => `
|
||
<div class="search-result-item" onclick="window.selectGlobalSearch(${idx})">
|
||
<span class="search-result-name"><span>${m.icon}</span> ${m.name}</span>
|
||
<span class="search-result-desc">${m.desc}</span>
|
||
</div>
|
||
`).join('');
|
||
// Store matches globally for access by onclick
|
||
window._currentGlobalMatches = matched;
|
||
} else {
|
||
resDiv.innerHTML = '<div style="padding:8px 10px; color:#94a3b8; font-size:12px;">Tidak ditemukan.</div>';
|
||
}
|
||
resDiv.classList.add('active');
|
||
});
|
||
|
||
window.selectGlobalSearch = function(idx) {
|
||
const match = window._currentGlobalMatches[idx];
|
||
if (!match) return;
|
||
|
||
document.getElementById('global-search').value = match.name.replace(/<[^>]*>?/gm, ''); // Remove span tags if any
|
||
document.getElementById('search-results').classList.remove('active');
|
||
|
||
const map = window.appState.mapInstance;
|
||
if (!map) return;
|
||
|
||
const type = match.type;
|
||
const item = match.item;
|
||
|
||
if (type === 'spbu' || type === 'ibadah' || type === 'miskin') {
|
||
const lat = parseFloat(item.latitude);
|
||
const lng = parseFloat(item.longitude);
|
||
if (!isNaN(lat) && !isNaN(lng)) {
|
||
map.flyTo([lat, lng], 17);
|
||
}
|
||
} else if (type === 'jalan' || type === 'parsil') {
|
||
try {
|
||
// geom is already an object in appState for these types
|
||
const layer = L.geoJSON(item.geom);
|
||
map.fitBounds(layer.getBounds(), { padding: [50, 50] });
|
||
} catch(e) {
|
||
console.error("Invalid GeoJSON/Geom", e);
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<!-- Logout Logic -->
|
||
<script>
|
||
document.getElementById('logout-btn').addEventListener('click', async () => {
|
||
try {
|
||
await fetch('backend/logout.php');
|
||
window.location.href = 'index.html';
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|