Files
UAS_SIG/admin.html
T

2162 lines
66 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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;
}
}
/* ---- Floating Panels Toggle ---- */
.map-floating-panel {
transition: width 0.3s ease, height 0.3s ease, background 0.3s ease;
overflow: hidden;
}
.map-floating-panel .panel-icon,
.map-floating-panel .panel-close-btn {
display: none;
}
.map-floating-panel .panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
@media (max-width: 900px) {
.map-floating-panel.collapsed {
width: 44px !important;
height: 44px !important;
min-height: 44px !important;
padding: 0 !important;
display: flex !important;
align-items: center;
justify-content: center;
cursor: pointer;
}
.map-floating-panel.collapsed:hover {
background: rgba(59, 130, 246, 0.2) !important;
border-color: rgba(59, 130, 246, 0.5) !important;
}
.map-floating-panel.collapsed > *:not(.panel-icon) {
display: none !important;
}
.map-floating-panel .panel-icon {
display: none;
font-size: 20px;
line-height: 1;
}
.map-floating-panel.collapsed .panel-icon {
display: block;
}
.map-floating-panel .panel-close-btn {
display: flex;
background: none;
border: none;
color: var(--clr-muted);
cursor: pointer;
font-size: 18px;
line-height: 1;
padding: 4px;
border-radius: 4px;
align-items: center;
justify-content: center;
}
.map-floating-panel .panel-close-btn:hover {
color: var(--clr-text);
background: rgba(255,255,255,0.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; }
@media (max-width: 900px) {
.app-wrapper { height: 100dvh; }
header { height: auto; min-height: 64px; padding: 10px 14px; gap: 10px; flex-wrap: wrap; }
.header-title h1 { font-size: 16px; }
.header-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; }
.content-panel-header { align-items: flex-start; flex-direction: column; gap: 10px; }
.content-panel-header .btn-group { width: 100%; flex-wrap: wrap; }
.content-panel-header .btn-group .btn { flex: 1; min-width: 130px; }
.table { min-width: 680px; }
.modal { width: calc(100vw - 24px); max-height: calc(100dvh - 24px); }
.modal-footer { flex-wrap: wrap; }
.modal-footer .btn { flex: 1; min-width: 120px; }
.search-panel { left: 82px; right: 12px; top: 12px; width: auto; margin-left: 0; transform: none; }
.routing-panel { top: auto; right: 12px; left: 82px; bottom: 12px; width: auto; }
}
@media (max-width: 560px) {
.sidebar { width: 56px; }
.sidebar-header { padding: 12px 8px; justify-content: center; }
.sidebar-logo-icon { width: 34px; height: 34px; font-size: 14px; }
.sidebar-toggle,
.sidebar-logo-text,
.sidebar-item-text,
.sidebar-item-badge,
.sidebar-section-title { display: none !important; }
.sidebar-nav { padding: 10px 0; }
.sidebar-section { padding: 8px 0; }
.sidebar-item { padding: 12px 14px; justify-content: center; }
.content-panel-body { padding: 12px; }
.search-box input,
.form-group input,
.form-group select,
.form-group textarea { font-size: 16px; }
.search-panel { left: 68px; right: 8px; top: 8px; padding: 10px; }
.search-results { max-height: 135px; }
.routing-panel { left: 68px; right: 8px; bottom: 8px; padding: 10px; }
.routing-panel h3,
.search-panel h3 { font-size: 11px; margin-bottom: 8px; }
}
</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 map-floating-panel collapsed" id="admin-search-panel" onclick="if(this.classList.contains('collapsed')) this.classList.remove('collapsed');">
<div class="panel-icon">🔍</div>
<div class="panel-header">
<h3 style="margin-bottom: 0;">🔍 Pencarian Global</h3>
<button class="panel-close-btn" onclick="event.stopPropagation(); document.getElementById('admin-search-panel').classList.add('collapsed');" title="Tutup">×</button>
</div>
<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" class="map-floating-panel collapsed" onclick="if(this.classList.contains('collapsed')) this.classList.remove('collapsed');" 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 class="panel-icon" style="color:#60a5fa;"></div>
<div class="panel-header" style="margin-bottom:10px;">
<div style="font-size:10px;font-weight:700;color:#60a5fa;text-transform:uppercase;letter-spacing:0.6px;margin-bottom:0;">⊕ Tambah Data</div>
<button class="panel-close-btn" onclick="event.stopPropagation(); document.getElementById('map-add-toolbar').classList.add('collapsed');" title="Tutup">×</button>
</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 map-floating-panel collapsed" id="legend-peta" onclick="if(this.classList.contains('collapsed')) this.classList.remove('collapsed');">
<div class="panel-icon">📋</div>
<div class="panel-header" style="margin-bottom:8px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px;">
<div style="font-size:11px;font-weight:700;color:var(--clr-text);text-transform:uppercase;margin:0;">Legenda</div>
<button class="panel-close-btn" onclick="event.stopPropagation(); document.getElementById('legend-peta').classList.add('collapsed');" title="Tutup">×</button>
</div>
<div class="legend-title" style="margin-top:0;">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.php';
} catch (err) {
console.error(err);
}
});
</script>
</body>
</html>