Files
2026-06-11 21:14:16 +07:00

935 lines
24 KiB
HTML
Raw Permalink 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>
<head>
<title>WebGIS SPBU</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
<style>
:root {
--primary: #5b5cf6;
--primary-strong: #4f46e5;
--primary-soft: #ecebff;
--success: #16a34a;
--danger: #ef4444;
--warning: #f97316;
--info: #2563eb;
--ink: #0f172a;
--muted: #667085;
--line: #e6eaf2;
--panel: rgba(255, 255, 255, 0.96);
--shadow: 0 18px 45px rgba(15, 23, 42, 0.14);
}
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Inter, "Segoe UI", Arial, sans-serif;
color: var(--ink);
background: #eef2f7;
height: 100vh;
overflow: hidden;
}
button,
input {
font: inherit;
}
.navbar {
position: fixed;
top: 14px;
left: 18px;
right: 18px;
z-index: 1200;
height: 64px;
padding: 10px 14px;
border: 1px solid rgba(226, 232, 240, 0.92);
border-radius: 14px;
background: rgba(255, 255, 255, 0.97);
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12);
backdrop-filter: blur(16px);
display: grid;
grid-template-columns: minmax(320px, 1fr) auto;
align-items: center;
gap: 16px;
box-sizing: border-box;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
overflow: hidden;
}
.brand-icon {
width: 36px;
height: 36px;
border-radius: 9px;
display: grid;
place-items: center;
color: var(--primary);
background: var(--primary-soft);
border: 1px solid #d9d8ff;
font-size: 18px;
flex: 0 0 auto;
}
.navbar h2 {
margin: 0;
font-size: 15px;
color: var(--primary-strong);
line-height: 1.2;
white-space: nowrap;
}
.navbar p {
margin: 2px 0 0;
color: var(--muted);
font-size: 11px;
max-width: 540px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.top-actions {
display: flex;
align-items: center;
gap: 6px;
}
.top-actions {
justify-self: end;
}
.navbar button,
.panel-button,
.action-chip {
border: 1px solid var(--line);
border-radius: 11px;
background: #fff;
color: #475467;
cursor: pointer;
font-weight: 700;
transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.action-chip {
display: inline-flex;
align-items: center;
justify-content: center;
height: 40px;
padding: 0 14px;
color: #344054;
background: #fff;
white-space: nowrap;
cursor: default;
}
.status-text {
color: #667085;
border-color: #edf0f6;
background: #f8fafc;
}
.map-stage {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
#map {
position: absolute;
inset: 0;
z-index: 1;
}
.left-panel,
#sidebar,
.stats-card {
position: absolute;
z-index: 1100;
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 16px;
background: var(--panel);
box-shadow: var(--shadow);
backdrop-filter: blur(16px);
box-sizing: border-box;
}
.left-panel {
top: 92px;
left: 18px;
bottom: 18px;
width: 292px;
padding: 16px;
overflow-y: auto;
overflow-x: hidden;
}
#sidebar {
top: 92px;
right: 18px;
bottom: 18px;
width: 410px;
padding: 18px;
overflow-y: auto;
overflow-x: hidden;
}
.stats-card {
left: 336px;
bottom: 24px;
width: 430px;
padding: 16px 18px;
}
.mode-switch {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
padding: 4px;
margin-bottom: 18px;
border: 1px solid var(--line);
border-radius: 12px;
background: #f8fafc;
}
.mode-switch button {
min-height: 40px;
padding: 0 10px;
border: 1px solid transparent;
border-radius: 9px;
color: #667085;
background: transparent;
box-shadow: none;
font-size: 13px;
font-weight: 800;
}
.mode-switch button.active {
color: #fff;
border-color: transparent;
background: var(--primary);
box-shadow: 0 8px 18px rgba(91, 92, 246, 0.24);
}
.panel-button {
min-height: 44px;
padding: 0 12px;
color: #667085;
background: #f8fafc;
border-color: #e8edf5;
border-radius: 10px;
font-size: 13px;
font-weight: 800;
}
.panel-button.active {
color: #fff;
border-color: transparent;
background: var(--primary);
box-shadow: 0 8px 18px rgba(91, 92, 246, 0.24);
}
.panel-section {
margin-bottom: 18px;
}
.panel-section h3,
.section h3,
.stats-title {
margin: 0 0 10px;
color: #667085;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.type-list {
display: grid;
gap: 7px;
}
.panel-button {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
text-align: left;
}
.tool-icon {
width: 28px;
height: 28px;
display: inline-grid;
place-items: center;
border-radius: 8px;
color: #667085;
background: #eef2f7;
font-size: 15px;
line-height: 1;
flex: 0 0 28px;
}
.panel-button.active .tool-icon {
color: var(--primary);
background: #fff;
}
.legend-list {
display: grid;
gap: 9px;
color: #536179;
font-size: 13px;
}
.legend-item {
display: flex;
align-items: center;
gap: 9px;
}
.legend-dot {
width: 11px;
height: 11px;
border-radius: 99px;
flex: 0 0 auto;
}
.sidebar-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.sidebar-title {
margin: 0;
color: var(--primary-strong);
font-size: 18px;
line-height: 1.2;
}
.sidebar-subtitle {
margin: 7px 0 0;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.panel-close {
width: 30px;
height: 30px;
border: none;
border-radius: 8px;
color: #667085;
background: transparent;
cursor: default;
font-size: 18px;
}
.section {
margin-bottom: 18px;
}
.section h3 {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--line);
}
.section-count {
min-width: 24px;
height: 24px;
padding: 0 8px;
display: inline-grid;
place-items: center;
border-radius: 99px;
background: #eef2ff;
color: var(--primary-strong);
font-size: 11px;
letter-spacing: 0;
}
.card {
padding: 14px;
margin-bottom: 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: #fff;
cursor: pointer;
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.card:hover {
border-color: #c8cdfd;
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08);
transform: translateY(-1px);
}
.title {
font-size: 16px;
font-weight: 800;
color: var(--ink);
margin-bottom: 6px;
}
.card-body {
color: #175cd3;
font-size: 13px;
font-weight: 800;
margin-bottom: 6px;
}
.meta {
font-size: 12px;
color: var(--muted);
line-height: 1.45;
}
.card-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
margin-top: 12px;
}
.badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 4px 8px;
border-radius: 7px;
font-size: 11px;
font-weight: 800;
color: white;
}
.green {
background: #dcfce7;
color: #15803d;
}
.red {
background: #fee2e2;
color: #dc2626;
}
.blue {
background: #dbeafe;
color: #1d4ed8;
}
.orange {
background: #ffedd5;
color: #ea580c;
}
.gray {
background: #eef2f7;
color: #475467;
}
.delete-btn,
.secondary-btn {
min-width: 42px;
min-height: 38px;
border: 1px solid var(--line);
border-radius: 10px;
padding: 8px 10px;
font-size: 12px;
font-weight: 800;
cursor: pointer;
background: #fff;
color: #667085;
}
.delete-btn:hover {
color: #dc2626;
border-color: #fecaca;
background: #fff5f5;
}
.secondary-btn:hover {
color: #1d4ed8;
border-color: #bfdbfe;
background: #eff6ff;
}
.delete-btn:disabled,
.secondary-btn:disabled {
background: #f3f4f6;
color: #a2aab8;
cursor: not-allowed;
}
.custom-point-icon {
background: transparent;
border: none;
}
.point-dot {
width: 20px;
height: 20px;
display: block;
border-radius: 999px;
border: 3px solid white;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.35);
}
.empty-state {
padding: 14px;
border-radius: 12px;
border: 1px dashed #d7dce7;
background: #f8fafc;
color: var(--muted);
font-size: 13px;
line-height: 1.5;
}
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 2000;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(15, 23, 42, 0.46);
backdrop-filter: blur(4px);
box-sizing: border-box;
}
.modal-backdrop.is-open {
display: flex;
}
.edit-modal {
width: min(460px, 100%);
max-height: min(720px, 92vh);
overflow: hidden;
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 16px;
background: #fff;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.26);
display: flex;
flex-direction: column;
}
.edit-modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
padding: 18px 20px 14px;
border-bottom: 1px solid var(--line);
}
.edit-modal-title {
margin: 0;
color: var(--ink);
font-size: 18px;
line-height: 1.25;
}
.edit-modal-subtitle {
margin: 5px 0 0;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.edit-modal-close {
width: 34px;
height: 34px;
border: 1px solid var(--line);
border-radius: 10px;
color: #667085;
background: #fff;
cursor: pointer;
font-size: 18px;
font-weight: 800;
}
.edit-modal-body {
padding: 18px 20px;
overflow-y: auto;
}
.form-grid {
display: grid;
gap: 14px;
}
.form-field {
display: grid;
gap: 7px;
}
.form-field label {
color: #344054;
font-size: 12px;
font-weight: 800;
}
.form-field input,
.form-field select,
.form-field textarea {
width: 100%;
min-height: 42px;
border: 1px solid #dbe2ec;
border-radius: 10px;
padding: 10px 12px;
color: var(--ink);
background: #fff;
box-sizing: border-box;
outline: none;
font-size: 13px;
}
.form-field textarea {
min-height: 86px;
resize: vertical;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(91, 92, 246, 0.12);
}
.form-error {
display: none;
padding: 10px 12px;
border-radius: 10px;
color: #b42318;
background: #fff1f3;
font-size: 12px;
font-weight: 700;
}
.form-error.is-visible {
display: block;
}
.edit-modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 20px 18px;
border-top: 1px solid var(--line);
background: #fbfcff;
}
.modal-btn {
min-height: 40px;
border: 1px solid var(--line);
border-radius: 10px;
padding: 0 14px;
color: #475467;
background: #fff;
cursor: pointer;
font-size: 13px;
font-weight: 800;
}
.modal-btn.primary {
color: #fff;
border-color: transparent;
background: var(--primary);
box-shadow: 0 8px 18px rgba(91, 92, 246, 0.24);
}
.filter-box {
display: grid;
gap: 6px;
}
.filter-item {
display: flex;
align-items: center;
gap: 9px;
min-height: 38px;
padding: 7px 10px;
border-radius: 10px;
color: #536179;
background: #f8fafc;
font-size: 12px;
font-weight: 700;
cursor: pointer;
box-sizing: border-box;
}
.filter-item input {
width: 16px;
height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
.stats-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
padding-top: 10px;
border-top: 1px solid var(--line);
}
.stat-value {
color: var(--primary);
font-size: 24px;
font-weight: 900;
line-height: 1;
}
.stat-label {
margin-top: 6px;
color: #667085;
font-size: 10px;
font-weight: 800;
text-transform: uppercase;
}
.leaflet-control-zoom {
border: none !important;
box-shadow: var(--shadow) !important;
}
.leaflet-top.leaflet-left {
top: 108px;
left: 320px;
}
.leaflet-top.leaflet-right {
top: 92px;
right: 446px;
}
.leaflet-control-zoom a,
.leaflet-control-layers {
border-radius: 10px !important;
}
@media (max-width: 1180px) {
.navbar {
grid-template-columns: minmax(260px, 1fr) auto;
}
#sidebar {
width: 360px;
}
.stats-card {
left: 336px;
width: 360px;
}
}
@media (max-width: 900px) {
body {
overflow: auto;
}
.map-stage {
height: auto;
min-height: 100vh;
display: grid;
grid-template-rows: 70vh auto auto auto;
padding-top: 92px;
overflow: visible;
box-sizing: border-box;
}
.navbar,
.left-panel,
#sidebar,
.stats-card,
#map {
position: relative;
inset: auto;
}
.navbar {
position: fixed;
top: 10px;
left: 10px;
right: 10px;
height: 58px;
grid-template-columns: 1fr;
}
.navbar p {
display: none;
}
#map {
height: 70vh;
width: 100%;
}
.leaflet-top.leaflet-left {
top: 12px;
left: 12px;
}
.leaflet-top.leaflet-right {
top: 12px;
right: 12px;
}
.left-panel,
#sidebar,
.stats-card {
width: auto;
margin: 12px;
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.1);
}
.stats-card {
order: 3;
}
#sidebar {
order: 4;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="navbar">
<div class="brand">
<div class="brand-icon"></div>
<div>
<h2>Admin WebGIS SPBU</h2>
<p id="mapHint">Mode view aktif. Pindah ke edit jika ingin menambah atau menghapus data.</p>
</div>
</div>
<div class="top-actions">
<span class="action-chip status-text">Admin SPBU</span>
</div>
</div>
<div class="map-stage">
<div id="map"></div>
<aside class="left-panel">
<div class="mode-switch">
<button id="modeViewBtn" onclick="setMode('view')">◉ Lihat</button>
<button id="modeEditBtn" onclick="setMode('edit')">✎ Edit</button>
</div>
<div class="panel-section">
<h3>Alat Peta</h3>
<div class="type-list">
<button class="panel-button" id="typeSpbuBtn" onclick="setDataType('spbu')"><span class="tool-icon">S</span><span>Tambah SPBU</span></button>
<button class="panel-button" id="typeKemiskinanBtn" onclick="setDataType('kemiskinan')"><span class="tool-icon">K</span><span>Tambah Kemiskinan</span></button>
<button class="panel-button" id="typeMasjidBtn" onclick="setDataType('masjid')"><span class="tool-icon">M</span><span>Tambah Masjid</span></button>
<button class="panel-button" id="typeJalanBtn" onclick="setDataType('jalan')"><span class="tool-icon">J</span><span>Tambah Jalan</span></button>
<button class="panel-button" id="typeKavlingBtn" onclick="setDataType('kavling')"><span class="tool-icon">V</span><span>Tambah Kavling</span></button>
</div>
</div>
<div id="layerFilters"></div>
<div class="panel-section">
<h3>Legenda Peta</h3>
<div class="legend-list">
<div class="legend-item"><span class="legend-dot" style="background:#28a745"></span>SPBU 24 jam</div>
<div class="legend-item"><span class="legend-dot" style="background:#dc3545"></span>SPBU tidak 24 jam</div>
<div class="legend-item"><span class="legend-dot" style="background:#ff8c00"></span>Kemiskinan</div>
<div class="legend-item"><span class="legend-dot" style="background:#007bff"></span>Masjid dan radius</div>
<div class="legend-item"><span class="legend-dot" style="background:#6f42c1"></span>Kavling</div>
</div>
</div>
</aside>
<section class="stats-card">
<div class="stats-head">
<h3 class="stats-title">Statistik Wilayah</h3>
</div>
<div class="stats-grid">
<div>
<div class="stat-value" id="statSpbu">0</div>
<div class="stat-label">Total SPBU</div>
</div>
<div>
<div class="stat-value" id="statMasjid">0</div>
<div class="stat-label">Masjid</div>
</div>
<div>
<div class="stat-value" id="statJalan">0</div>
<div class="stat-label">Jalan</div>
</div>
<div>
<div class="stat-value" id="statKavling">0</div>
<div class="stat-label">Kavling</div>
</div>
</div>
</section>
<div id="sidebar"></div>
</div>
<div class="modal-backdrop" id="editModalBackdrop" aria-hidden="true">
<form class="edit-modal" id="editModalForm">
<div class="edit-modal-header">
<div>
<h2 class="edit-modal-title" id="editModalTitle">Edit Data</h2>
<p class="edit-modal-subtitle" id="editModalSubtitle">Perbarui atribut data terpilih.</p>
</div>
<button class="edit-modal-close" id="editModalClose" type="button">×</button>
</div>
<div class="edit-modal-body">
<div class="form-error" id="editModalError"></div>
<div class="form-grid" id="editModalFields"></div>
</div>
<div class="edit-modal-actions">
<button class="modal-btn" id="editModalCancel" type="button">Batal</button>
<button class="modal-btn primary" type="submit">Simpan Perubahan</button>
</div>
</form>
</div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script src="js/map.js"></script>
<script src="js/line.js"></script>
<script src="js/polygon.js"></script>
<script src="js/loadData.js"></script>
</body>
</html>