feat: implement full CRUD functionality for jalan, parsil, and spbu modules including database integration and map interaction features
This commit is contained in:
@@ -1,30 +0,0 @@
|
|||||||
// --- Fitur Geolocation ---
|
|
||||||
|
|
||||||
// Tambahkan tombol di dalam wadah zoom control
|
|
||||||
const geoBtn = document.createElement('button');
|
|
||||||
geoBtn.className = 'custom-layer-btn';
|
|
||||||
geoBtn.style.position = 'relative';
|
|
||||||
geoBtn.style.top = '0';
|
|
||||||
geoBtn.style.left = '0';
|
|
||||||
geoBtn.style.right = 'auto';
|
|
||||||
geoBtn.innerHTML = '<i class="fas fa-crosshairs fa-lg"></i>';
|
|
||||||
geoBtn.title = 'Lokasi Saya';
|
|
||||||
document.querySelector('.custom-zoom-control').appendChild(geoBtn);
|
|
||||||
|
|
||||||
let userMarker = null;
|
|
||||||
|
|
||||||
geoBtn.addEventListener('click', function() {
|
|
||||||
map.locate({setView: true, maxZoom: 16});
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on('locationfound', function(e) {
|
|
||||||
if (userMarker) {
|
|
||||||
map.removeLayer(userMarker);
|
|
||||||
}
|
|
||||||
userMarker = L.marker(e.latlng).addTo(map)
|
|
||||||
.bindPopup("Anda berada di sini!").openPopup();
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on('locationerror', function(e) {
|
|
||||||
alert("Gagal mendapatkan lokasi Anda.");
|
|
||||||
});
|
|
||||||
+466
-160
@@ -1,17 +1,38 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="id">
|
<html lang="id">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta
|
||||||
<title>WebGIS — Sistem Informasi Geografis | Portofolio Tugas</title>
|
name="viewport"
|
||||||
<meta name="description" content="Portofolio proyek WebGIS mata kuliah Sistem Informasi Geografis. Kumpulan aplikasi pemetaan interaktif berbasis web menggunakan Leaflet.js, PHP, dan MySQL." />
|
content="width=device-width, initial-scale=1.0"
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
/>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<title>WebGIS — Sistem Informasi Geografis | Portofolio Tugas</title>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
<meta
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
name="description"
|
||||||
|
content="Portofolio proyek WebGIS mata kuliah Sistem Informasi Geografis. Kumpulan aplikasi pemetaan interaktif berbasis web menggunakan Leaflet.js, PHP, dan MySQL."
|
||||||
|
/>
|
||||||
|
<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@300;400;500;600;700;800;900&family=Space+Grotesk:wght@400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
/* ===== CSS RESET & BASE ===== */
|
/* ===== CSS RESET & BASE ===== */
|
||||||
*, *::before, *::after {
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -31,17 +52,19 @@
|
|||||||
--clr-white: #ffffff;
|
--clr-white: #ffffff;
|
||||||
--grad-primary: linear-gradient(135deg, #4f46e5, #7c3aed);
|
--grad-primary: linear-gradient(135deg, #4f46e5, #7c3aed);
|
||||||
--grad-card: linear-gradient(145deg, #ffffff, #f8fafc);
|
--grad-card: linear-gradient(145deg, #ffffff, #f8fafc);
|
||||||
--shadow-glow: 0 0 40px rgba(79, 70, 229, 0.10);
|
--shadow-glow: 0 0 40px rgba(79, 70, 229, 0.1);
|
||||||
--shadow-card: 0 4px 24px rgba(15, 23, 42, 0.08);
|
--shadow-card: 0 4px 24px rgba(15, 23, 42, 0.08);
|
||||||
--radius: 16px;
|
--radius: 16px;
|
||||||
--radius-sm: 10px;
|
--radius-sm: 10px;
|
||||||
--transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
--transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
html { scroll-behavior: smooth; }
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
background-color: var(--clr-bg);
|
background-color: var(--clr-bg);
|
||||||
color: var(--clr-text);
|
color: var(--clr-text);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -49,15 +72,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ===== SCROLLBAR ===== */
|
/* ===== SCROLLBAR ===== */
|
||||||
::-webkit-scrollbar { width: 6px; }
|
::-webkit-scrollbar {
|
||||||
::-webkit-scrollbar-track { background: var(--clr-bg); }
|
width: 6px;
|
||||||
::-webkit-scrollbar-thumb { background: var(--clr-primary); border-radius: 3px; }
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--clr-bg);
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--clr-primary);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== CANVAS BG ===== */
|
/* ===== CANVAS BG ===== */
|
||||||
#bg-canvas {
|
#bg-canvas {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0;
|
top: 0;
|
||||||
width: 100%; height: 100%;
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@@ -75,7 +107,9 @@
|
|||||||
/* ===== NAVBAR ===== */
|
/* ===== NAVBAR ===== */
|
||||||
.navbar {
|
.navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0; right: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
background: rgba(240, 244, 248, 0.75);
|
background: rgba(240, 244, 248, 0.75);
|
||||||
@@ -122,7 +156,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-logo-text {
|
.nav-logo-text {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: var(--clr-text);
|
color: var(--clr-text);
|
||||||
@@ -162,13 +196,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero::before {
|
.hero::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%; left: 50%;
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 900px;
|
width: 900px;
|
||||||
height: 900px;
|
height: 900px;
|
||||||
background: radial-gradient(circle, rgba(79,70,229,0.07) 0%, rgba(124,58,237,0.04) 40%, transparent 70%);
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(79, 70, 229, 0.07) 0%,
|
||||||
|
rgba(124, 58, 237, 0.04) 40%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,15 +229,23 @@
|
|||||||
animation: fadeSlideDown 0.8s ease both;
|
animation: fadeSlideDown 0.8s ease both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-badge i { font-size: 0.75rem; animation: pulse 2s infinite; }
|
.hero-badge i {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 1; }
|
0%,
|
||||||
50% { opacity: 0.4; }
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-title {
|
.hero-title {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: clamp(2.4rem, 6vw, 4.2rem);
|
font-size: clamp(2.4rem, 6vw, 4.2rem);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
@@ -237,7 +285,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero-stat-num {
|
.hero-stat-num {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: var(--grad-primary);
|
background: var(--grad-primary);
|
||||||
@@ -318,8 +366,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scrollDown {
|
@keyframes scrollDown {
|
||||||
0% { opacity: 1; transform: translateY(0); }
|
0% {
|
||||||
100% { opacity: 0; transform: translateY(10px); }
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== SECTION ===== */
|
/* ===== SECTION ===== */
|
||||||
@@ -337,7 +391,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: clamp(1.8rem, 4vw, 2.6rem);
|
font-size: clamp(1.8rem, 4vw, 2.6rem);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--clr-text);
|
color: var(--clr-text);
|
||||||
@@ -387,7 +441,9 @@
|
|||||||
.project-card:hover {
|
.project-card:hover {
|
||||||
transform: translateY(-6px);
|
transform: translateY(-6px);
|
||||||
border-color: rgba(79, 70, 229, 0.35);
|
border-color: rgba(79, 70, 229, 0.35);
|
||||||
box-shadow: 0 16px 48px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(79,70,229,0.1);
|
box-shadow:
|
||||||
|
0 16px 48px rgba(15, 23, 42, 0.14),
|
||||||
|
0 0 0 1px rgba(79, 70, 229, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card:hover .card-arrow {
|
.project-card:hover .card-arrow {
|
||||||
@@ -420,7 +476,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-num {
|
.card-num {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--clr-muted);
|
color: var(--clr-muted);
|
||||||
@@ -449,7 +505,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--clr-text);
|
color: var(--clr-text);
|
||||||
@@ -481,11 +537,31 @@
|
|||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-blue { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }
|
.tag-blue {
|
||||||
.tag-purple { background: #f5f3ff; color: #6d28d9; border: 1px solid #ddd6fe; }
|
background: #eff6ff;
|
||||||
.tag-green { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }
|
color: #1d4ed8;
|
||||||
.tag-orange { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
|
border: 1px solid #bfdbfe;
|
||||||
.tag-pink { background: #fdf2f8; color: #9d174d; border: 1px solid #fbcfe8; }
|
}
|
||||||
|
.tag-purple {
|
||||||
|
background: #f5f3ff;
|
||||||
|
color: #6d28d9;
|
||||||
|
border: 1px solid #ddd6fe;
|
||||||
|
}
|
||||||
|
.tag-green {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #065f46;
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
}
|
||||||
|
.tag-orange {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #9a3412;
|
||||||
|
border: 1px solid #fed7aa;
|
||||||
|
}
|
||||||
|
.tag-pink {
|
||||||
|
background: #fdf2f8;
|
||||||
|
color: #9d174d;
|
||||||
|
border: 1px solid #fbcfe8;
|
||||||
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
border-top: 1px solid var(--clr-border);
|
border-top: 1px solid var(--clr-border);
|
||||||
@@ -523,19 +599,34 @@
|
|||||||
animation: statusPulse 2s infinite;
|
animation: statusPulse 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot.active { background: var(--clr-accent); box-shadow: 0 0 8px var(--clr-accent); }
|
.status-dot.active {
|
||||||
.status-dot.pending { background: var(--clr-warn); box-shadow: 0 0 8px var(--clr-warn); }
|
background: var(--clr-accent);
|
||||||
.status-dot.inactive { background: var(--clr-muted); }
|
box-shadow: 0 0 8px var(--clr-accent);
|
||||||
|
}
|
||||||
|
.status-dot.pending {
|
||||||
|
background: var(--clr-warn);
|
||||||
|
box-shadow: 0 0 8px var(--clr-warn);
|
||||||
|
}
|
||||||
|
.status-dot.inactive {
|
||||||
|
background: var(--clr-muted);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes statusPulse {
|
@keyframes statusPulse {
|
||||||
0%, 100% { opacity: 1; }
|
0%,
|
||||||
50% { opacity: 0.4; }
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== CARD ACCENT LINES ===== */
|
/* ===== CARD ACCENT LINES ===== */
|
||||||
.card-glow-line {
|
.card-glow-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; right: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
border-radius: var(--radius) var(--radius) 0 0;
|
border-radius: var(--radius) var(--radius) 0 0;
|
||||||
}
|
}
|
||||||
@@ -600,7 +691,9 @@
|
|||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tech-item:hover { color: var(--clr-text); }
|
.tech-item:hover {
|
||||||
|
color: var(--clr-text);
|
||||||
|
}
|
||||||
|
|
||||||
.tech-item i {
|
.tech-item i {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
@@ -618,7 +711,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-logo {
|
.footer-logo {
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
font-family: "Space Grotesk", sans-serif;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--clr-text);
|
color: var(--clr-text);
|
||||||
@@ -657,19 +750,33 @@
|
|||||||
|
|
||||||
/* ===== ANIMATIONS ===== */
|
/* ===== ANIMATIONS ===== */
|
||||||
@keyframes fadeSlideDown {
|
@keyframes fadeSlideDown {
|
||||||
from { opacity: 0; transform: translateY(-20px); }
|
from {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeSlideUp {
|
@keyframes fadeSlideUp {
|
||||||
from { opacity: 0; transform: translateY(30px); }
|
from {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reveal {
|
.reveal {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(30px);
|
transform: translateY(30px);
|
||||||
transition: opacity 0.7s ease, transform 0.7s ease;
|
transition:
|
||||||
|
opacity 0.7s ease,
|
||||||
|
transform 0.7s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reveal.visible {
|
.reveal.visible {
|
||||||
@@ -679,52 +786,112 @@
|
|||||||
|
|
||||||
/* ===== RESPONSIVE ===== */
|
/* ===== RESPONSIVE ===== */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.hero-stats { gap: 24px; }
|
.hero-stats {
|
||||||
.projects-grid { grid-template-columns: 1fr; }
|
gap: 24px;
|
||||||
.section-header { flex-direction: column; align-items: flex-start; }
|
}
|
||||||
|
.projects-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.section-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- ===== CANVAS BACKGROUND ===== -->
|
<!-- ===== CANVAS BACKGROUND ===== -->
|
||||||
<canvas id="bg-canvas"></canvas>
|
<canvas id="bg-canvas"></canvas>
|
||||||
|
|
||||||
<!-- ===== NAVBAR ===== -->
|
<!-- ===== NAVBAR ===== -->
|
||||||
<nav class="navbar" id="navbar">
|
<nav
|
||||||
|
class="navbar"
|
||||||
|
id="navbar"
|
||||||
|
>
|
||||||
<div class="nav-inner">
|
<div class="nav-inner">
|
||||||
<a href="#" class="nav-logo" id="logo-link">
|
<a
|
||||||
<div class="nav-logo-icon"><i class="fa-solid fa-map-location-dot"></i></div>
|
href="#"
|
||||||
|
class="nav-logo"
|
||||||
|
id="logo-link"
|
||||||
|
>
|
||||||
|
<div class="nav-logo-icon">
|
||||||
|
<i class="fa-solid fa-map-location-dot"></i>
|
||||||
|
</div>
|
||||||
<div class="nav-logo-text">
|
<div class="nav-logo-text">
|
||||||
WebGIS
|
WebGIS
|
||||||
<span>Sistem Informasi Geografis</span>
|
<span>Sistem Informasi Geografis</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="nav-badge"><i class="fa-solid fa-graduation-cap"></i> Portofolio Tugas</div>
|
<div class="nav-badge">
|
||||||
|
<i class="fa-solid fa-graduation-cap"></i> Portofolio Tugas
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- ===== HERO ===== -->
|
<!-- ===== HERO ===== -->
|
||||||
<section class="hero" id="home">
|
<section
|
||||||
|
class="hero"
|
||||||
|
id="home"
|
||||||
|
>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="hero-badge" id="hero-badge">
|
<div
|
||||||
|
class="hero-badge"
|
||||||
|
id="hero-badge"
|
||||||
|
>
|
||||||
<i class="fa-solid fa-circle"></i>
|
<i class="fa-solid fa-circle"></i>
|
||||||
Sistem Informasi Geografis
|
Sistem Informasi Geografis
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="hero-title" id="hero-title">
|
<h1
|
||||||
|
class="hero-title"
|
||||||
|
id="hero-title"
|
||||||
|
>
|
||||||
Portofolio Proyek<br />
|
Portofolio Proyek<br />
|
||||||
<span class="gradient-text">WebGIS Interaktif</span>
|
<span class="gradient-text">WebGIS Interaktif</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="hero-sub" id="hero-sub">
|
<p
|
||||||
Kumpulan tugas pemetaan berbasis web menggunakan teknologi geospasial modern —
|
class="hero-sub"
|
||||||
dari peta jalan, persil, hingga visualisasi data sosial.
|
id="hero-sub"
|
||||||
|
>
|
||||||
|
Kumpulan tugas pemetaan berbasis web menggunakan teknologi geospasial
|
||||||
|
modern — dari peta jalan, persil, hingga visualisasi data sosial.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="hero-stats" id="hero-stats">
|
<div id="student-card" style="
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
background: rgba(79, 70, 229, 0.07);
|
||||||
|
border: 1px solid rgba(79, 70, 229, 0.2);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 12px 22px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
">
|
||||||
|
<div style="width: 42px; height: 42px; border-radius: 50%; background: var(--grad-primary); display: flex; align-items: center; justify-content: center; font-size: 1.2rem;">👤</div>
|
||||||
|
<div style="text-align: left;">
|
||||||
|
<div style="font-weight: 700; font-size: 0.95rem; color: var(--clr-text);">Syariffullah</div>
|
||||||
|
<div style="font-size: 0.78rem; color: var(--clr-muted); font-family: 'Space Grotesk', monospace; letter-spacing: 0.03em;">NIM: D1041231018</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 1px; height: 32px; background: rgba(79,70,229,0.2);"></div>
|
||||||
|
<div style="font-size: 0.78rem; color: var(--clr-muted); text-align: left;">
|
||||||
|
<div><i class="fa-solid fa-graduation-cap" style="color: var(--clr-primary);"></i> Sistem Informasi Geografis</div>
|
||||||
|
<div style="margin-top:2px;"><i class="fa-solid fa-microchip" style="color: var(--clr-secondary);"></i> Teknik Informatika</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="hero-stats"
|
||||||
|
id="hero-stats"
|
||||||
|
>
|
||||||
<div class="hero-stat">
|
<div class="hero-stat">
|
||||||
<div class="hero-stat-num" id="stat-projects">4</div>
|
<div
|
||||||
|
class="hero-stat-num"
|
||||||
|
id="stat-projects"
|
||||||
|
>
|
||||||
|
4
|
||||||
|
</div>
|
||||||
<div class="hero-stat-label">Proyek</div>
|
<div class="hero-stat-label">Proyek</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-divider"></div>
|
<div class="hero-divider"></div>
|
||||||
@@ -739,7 +906,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="#projects" class="hero-cta" id="hero-cta">
|
<a
|
||||||
|
href="#projects"
|
||||||
|
class="hero-cta"
|
||||||
|
id="hero-cta"
|
||||||
|
>
|
||||||
<i class="fa-solid fa-layer-group"></i>
|
<i class="fa-solid fa-layer-group"></i>
|
||||||
Lihat Semua Proyek
|
Lihat Semua Proyek
|
||||||
</a>
|
</a>
|
||||||
@@ -757,27 +928,45 @@
|
|||||||
<p class="tech-strip-label">Teknologi yang Digunakan</p>
|
<p class="tech-strip-label">Teknologi yang Digunakan</p>
|
||||||
<div class="tech-items">
|
<div class="tech-items">
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-solid fa-map" style="color:#4f46e5"></i>
|
<i
|
||||||
|
class="fa-solid fa-map"
|
||||||
|
style="color: #4f46e5"
|
||||||
|
></i>
|
||||||
Leaflet.js
|
Leaflet.js
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-brands fa-php" style="color:#7c3aed"></i>
|
<i
|
||||||
|
class="fa-brands fa-php"
|
||||||
|
style="color: #7c3aed"
|
||||||
|
></i>
|
||||||
PHP
|
PHP
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-solid fa-database" style="color:#059669"></i>
|
<i
|
||||||
|
class="fa-solid fa-database"
|
||||||
|
style="color: #059669"
|
||||||
|
></i>
|
||||||
MySQL
|
MySQL
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-brands fa-html5" style="color:#ea580c"></i>
|
<i
|
||||||
|
class="fa-brands fa-html5"
|
||||||
|
style="color: #ea580c"
|
||||||
|
></i>
|
||||||
HTML / CSS / JS
|
HTML / CSS / JS
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-solid fa-globe" style="color:#db2777"></i>
|
<i
|
||||||
|
class="fa-solid fa-globe"
|
||||||
|
style="color: #db2777"
|
||||||
|
></i>
|
||||||
GeoJSON
|
GeoJSON
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-item">
|
<div class="tech-item">
|
||||||
<i class="fa-solid fa-server" style="color:#b45309"></i>
|
<i
|
||||||
|
class="fa-solid fa-server"
|
||||||
|
style="color: #b45309"
|
||||||
|
></i>
|
||||||
XAMPP
|
XAMPP
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -785,32 +974,58 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ===== PROJECTS ===== -->
|
<!-- ===== PROJECTS ===== -->
|
||||||
<section class="section" id="projects">
|
<section
|
||||||
|
class="section"
|
||||||
|
id="projects"
|
||||||
|
>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="section-header reveal">
|
<div class="section-header reveal">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-label"><i class="fa-solid fa-folder-open"></i> Daftar Proyek</p>
|
<p class="section-label">
|
||||||
|
<i class="fa-solid fa-folder-open"></i> Daftar Proyek
|
||||||
|
</p>
|
||||||
<h2 class="section-title">Semua Tugas WebGIS</h2>
|
<h2 class="section-title">Semua Tugas WebGIS</h2>
|
||||||
<p class="section-desc">Klik pada kartu proyek untuk membuka aplikasi pemetaan interaktif.</p>
|
<p class="section-desc">
|
||||||
|
Klik pada kartu proyek untuk membuka aplikasi pemetaan interaktif.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="projects-grid">
|
<div class="projects-grid">
|
||||||
|
|
||||||
<!-- ===== PROJECT 1: POVERTY ===== -->
|
<!-- ===== PROJECT 1: POVERTY ===== -->
|
||||||
<a href="/webgis/poverty/index.php" class="project-card" id="card-poverty" style="animation-delay: 0.05s;">
|
<a
|
||||||
<div class="card-glow-line" style="background: linear-gradient(90deg, #38bdf8, #818cf8);"></div>
|
href="/webgis/poverty/index.php"
|
||||||
<div class="card-arrow"><i class="fa-solid fa-arrow-up-right"></i></div>
|
class="project-card"
|
||||||
|
id="card-poverty"
|
||||||
|
style="animation-delay: 0.05s"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-glow-line"
|
||||||
|
style="background: linear-gradient(90deg, #38bdf8, #818cf8)"
|
||||||
|
></div>
|
||||||
|
<div class="card-arrow">
|
||||||
|
<i class="fa-solid fa-arrow-up-right"></i>
|
||||||
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-icon" style="background: rgba(56,189,248,0.12); border: 1px solid rgba(56,189,248,0.2);">
|
<div
|
||||||
|
class="card-icon"
|
||||||
|
style="
|
||||||
|
background: rgba(56, 189, 248, 0.12);
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.2);
|
||||||
|
"
|
||||||
|
>
|
||||||
🏠
|
🏠
|
||||||
</div>
|
</div>
|
||||||
<span class="card-num">01</span>
|
<span class="card-num">01</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">Pemetaan Kemiskinan<br/>Kota Pontianak</h3>
|
<h3 class="card-title">
|
||||||
|
Pemetaan Kemiskinan<br />Kota Pontianak
|
||||||
|
</h3>
|
||||||
<p class="card-desc">
|
<p class="card-desc">
|
||||||
Aplikasi WebGIS untuk memetakan sebaran penduduk miskin, rumah ibadah, dan log bantuan sosial di Kota Pontianak. Dilengkapi manajemen data spasial berbasis peran.
|
Aplikasi WebGIS untuk memetakan sebaran penduduk miskin, rumah
|
||||||
|
ibadah, dan log bantuan sosial di Kota Pontianak. Dilengkapi
|
||||||
|
manajemen data spasial berbasis peran.
|
||||||
</p>
|
</p>
|
||||||
<div class="card-tags">
|
<div class="card-tags">
|
||||||
<span class="tag tag-blue">Leaflet.js</span>
|
<span class="tag tag-blue">Leaflet.js</span>
|
||||||
@@ -826,16 +1041,35 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="card-status">
|
<div class="card-status">
|
||||||
<div class="status-dot active"></div>
|
<div class="status-dot active"></div>
|
||||||
<span style="color: var(--clr-muted); font-size: 0.75rem;">Aktif</span>
|
<span style="color: var(--clr-muted); font-size: 0.75rem"
|
||||||
|
>Aktif</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- ===== PROJECT 2: JALAN ===== -->
|
<!-- ===== PROJECT 2: JALAN ===== -->
|
||||||
<a href="/webgis/jalan/" class="project-card" id="card-jalan" style="animation-delay: 0.1s; cursor: default;" onclick="return false;">
|
<a
|
||||||
<div class="card-glow-line" style="background: linear-gradient(90deg, #818cf8, #f472b6);"></div>
|
href="/webgis/jalan/index.php"
|
||||||
|
class="project-card"
|
||||||
|
id="card-jalan"
|
||||||
|
style="animation-delay: 0.1s"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-glow-line"
|
||||||
|
style="background: linear-gradient(90deg, #818cf8, #f472b6)"
|
||||||
|
></div>
|
||||||
|
<div class="card-arrow">
|
||||||
|
<i class="fa-solid fa-arrow-up-right"></i>
|
||||||
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-icon" style="background: rgba(129,140,248,0.12); border: 1px solid rgba(129,140,248,0.2);">
|
<div
|
||||||
|
class="card-icon"
|
||||||
|
style="
|
||||||
|
background: rgba(129, 140, 248, 0.12);
|
||||||
|
border: 1px solid rgba(129, 140, 248, 0.2);
|
||||||
|
"
|
||||||
|
>
|
||||||
🛣️
|
🛣️
|
||||||
</div>
|
</div>
|
||||||
<span class="card-num">02</span>
|
<span class="card-num">02</span>
|
||||||
@@ -843,7 +1077,9 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">Pemetaan Jaringan<br />Jalan</h3>
|
<h3 class="card-title">Pemetaan Jaringan<br />Jalan</h3>
|
||||||
<p class="card-desc">
|
<p class="card-desc">
|
||||||
Visualisasi dan analisis jaringan jalan menggunakan data spasial. Mencakup klasifikasi jalan, kondisi, dan data atribut terkait infrastruktur jalan.
|
Visualisasi dan analisis jaringan jalan menggunakan data
|
||||||
|
spasial. Mencakup klasifikasi jalan, kondisi, dan data atribut
|
||||||
|
terkait infrastruktur jalan.
|
||||||
</p>
|
</p>
|
||||||
<div class="card-tags">
|
<div class="card-tags">
|
||||||
<span class="tag tag-purple">WebGIS</span>
|
<span class="tag tag-purple">WebGIS</span>
|
||||||
@@ -852,24 +1088,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<span class="card-link-btn" style="color: var(--clr-muted);">
|
<span class="card-link-btn">
|
||||||
Segera Hadir <i class="fa-solid fa-clock"></i>
|
Buka Proyek <i class="fa-solid fa-arrow-right"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-status">
|
<div class="card-status">
|
||||||
<div class="status-dot pending"></div>
|
<div class="status-dot active"></div>
|
||||||
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
|
<span style="color: var(--clr-muted); font-size: 0.75rem"
|
||||||
|
>Aktif</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-overlay">
|
|
||||||
<div class="coming-soon-badge">🚧 Coming Soon</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- ===== PROJECT 3: PARSIL ===== -->
|
<!-- ===== PROJECT 3: PARSIL ===== -->
|
||||||
<a href="/webgis/parsil/" class="project-card" id="card-parsil" style="animation-delay: 0.15s; cursor: default;" onclick="return false;">
|
<a
|
||||||
<div class="card-glow-line" style="background: linear-gradient(90deg, #34d399, #38bdf8);"></div>
|
href="/webgis/parsil/index.php"
|
||||||
|
class="project-card"
|
||||||
|
id="card-parsil"
|
||||||
|
style="animation-delay: 0.15s"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-glow-line"
|
||||||
|
style="background: linear-gradient(90deg, #34d399, #38bdf8)"
|
||||||
|
></div>
|
||||||
|
<div class="card-arrow">
|
||||||
|
<i class="fa-solid fa-arrow-up-right"></i>
|
||||||
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-icon" style="background: rgba(52,211,153,0.12); border: 1px solid rgba(52,211,153,0.2);">
|
<div
|
||||||
|
class="card-icon"
|
||||||
|
style="
|
||||||
|
background: rgba(52, 211, 153, 0.12);
|
||||||
|
border: 1px solid rgba(52, 211, 153, 0.2);
|
||||||
|
"
|
||||||
|
>
|
||||||
🗺️
|
🗺️
|
||||||
</div>
|
</div>
|
||||||
<span class="card-num">03</span>
|
<span class="card-num">03</span>
|
||||||
@@ -877,7 +1129,9 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">Pemetaan Bidang<br />Tanah (Parsil)</h3>
|
<h3 class="card-title">Pemetaan Bidang<br />Tanah (Parsil)</h3>
|
||||||
<p class="card-desc">
|
<p class="card-desc">
|
||||||
Sistem informasi geografis untuk pemetaan bidang tanah, kepemilikan lahan, dan informasi pertanahan berbasis web yang terintegrasi dengan data spasial.
|
Sistem informasi geografis untuk pemetaan bidang tanah,
|
||||||
|
kepemilikan lahan, dan informasi pertanahan berbasis web yang
|
||||||
|
terintegrasi dengan data spasial.
|
||||||
</p>
|
</p>
|
||||||
<div class="card-tags">
|
<div class="card-tags">
|
||||||
<span class="tag tag-green">Land Parcel</span>
|
<span class="tag tag-green">Land Parcel</span>
|
||||||
@@ -886,24 +1140,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<span class="card-link-btn" style="color: var(--clr-muted);">
|
<span class="card-link-btn">
|
||||||
Segera Hadir <i class="fa-solid fa-clock"></i>
|
Buka Proyek <i class="fa-solid fa-arrow-right"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-status">
|
<div class="card-status">
|
||||||
<div class="status-dot pending"></div>
|
<div class="status-dot active"></div>
|
||||||
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
|
<span style="color: var(--clr-muted); font-size: 0.75rem"
|
||||||
|
>Aktif</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-overlay">
|
|
||||||
<div class="coming-soon-badge">🚧 Coming Soon</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- ===== PROJECT 4: SPBU ===== -->
|
<!-- ===== PROJECT 4: SPBU ===== -->
|
||||||
<a href="/webgis/spbu/" class="project-card" id="card-spbu" style="animation-delay: 0.2s; cursor: default;" onclick="return false;">
|
<a
|
||||||
<div class="card-glow-line" style="background: linear-gradient(90deg, #fb923c, #facc15);"></div>
|
href="/webgis/spbu/index.php"
|
||||||
|
class="project-card"
|
||||||
|
id="card-spbu"
|
||||||
|
style="animation-delay: 0.2s"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-glow-line"
|
||||||
|
style="background: linear-gradient(90deg, #fb923c, #facc15)"
|
||||||
|
></div>
|
||||||
|
<div class="card-arrow">
|
||||||
|
<i class="fa-solid fa-arrow-up-right"></i>
|
||||||
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-icon" style="background: rgba(251,146,60,0.12); border: 1px solid rgba(251,146,60,0.2);">
|
<div
|
||||||
|
class="card-icon"
|
||||||
|
style="
|
||||||
|
background: rgba(251, 146, 60, 0.12);
|
||||||
|
border: 1px solid rgba(251, 146, 60, 0.2);
|
||||||
|
"
|
||||||
|
>
|
||||||
⛽
|
⛽
|
||||||
</div>
|
</div>
|
||||||
<span class="card-num">04</span>
|
<span class="card-num">04</span>
|
||||||
@@ -911,7 +1181,9 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">Pemetaan Lokasi<br />SPBU</h3>
|
<h3 class="card-title">Pemetaan Lokasi<br />SPBU</h3>
|
||||||
<p class="card-desc">
|
<p class="card-desc">
|
||||||
Peta interaktif untuk menampilkan sebaran Stasiun Pengisian Bahan Bakar Umum (SPBU) beserta informasi detail, analisis jangkauan, dan fasilitas yang tersedia.
|
Peta interaktif untuk menampilkan sebaran Stasiun Pengisian
|
||||||
|
Bahan Bakar Umum (SPBU) beserta informasi detail, analisis
|
||||||
|
jangkauan, dan fasilitas yang tersedia.
|
||||||
</p>
|
</p>
|
||||||
<div class="card-tags">
|
<div class="card-tags">
|
||||||
<span class="tag tag-orange">POI Mapping</span>
|
<span class="tag tag-orange">POI Mapping</span>
|
||||||
@@ -920,19 +1192,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<span class="card-link-btn" style="color: var(--clr-muted);">
|
<span class="card-link-btn">
|
||||||
Segera Hadir <i class="fa-solid fa-clock"></i>
|
Buka Proyek <i class="fa-solid fa-arrow-right"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-status">
|
<div class="card-status">
|
||||||
<div class="status-dot pending"></div>
|
<div class="status-dot active"></div>
|
||||||
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
|
<span style="color: var(--clr-muted); font-size: 0.75rem"
|
||||||
|
>Aktif</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-overlay">
|
|
||||||
<div class="coming-soon-badge">🚧 Coming Soon</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -943,27 +1213,48 @@
|
|||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
<div class="footer-logo">Web<span>GIS</span></div>
|
<div class="footer-logo">Web<span>GIS</span></div>
|
||||||
<div class="footer-course">
|
<div class="footer-course">
|
||||||
Mata Kuliah Sistem Informasi Geografis · Portofolio Tugas Mahasiswa
|
Mata Kuliah Sistem Informasi Geografis · Portofolio
|
||||||
|
Tugas Mahasiswa
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;">
|
<div
|
||||||
<span style="font-size:0.8rem; color: var(--clr-muted);">
|
style="
|
||||||
<i class="fa-solid fa-map-location-dot" style="color: var(--clr-primary);"></i>
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span style="font-size: 0.8rem; color: var(--clr-muted)">
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-map-location-dot"
|
||||||
|
style="color: var(--clr-primary)"
|
||||||
|
></i>
|
||||||
Leaflet.js
|
Leaflet.js
|
||||||
</span>
|
</span>
|
||||||
<span style="color: var(--clr-border);">|</span>
|
<span style="color: var(--clr-border)">|</span>
|
||||||
<span style="font-size:0.8rem; color: var(--clr-muted);">
|
<span style="font-size: 0.8rem; color: var(--clr-muted)">
|
||||||
<i class="fa-solid fa-database" style="color: var(--clr-accent);"></i>
|
<i
|
||||||
|
class="fa-solid fa-database"
|
||||||
|
style="color: var(--clr-accent)"
|
||||||
|
></i>
|
||||||
MySQL Spatial
|
MySQL Spatial
|
||||||
</span>
|
</span>
|
||||||
<span style="color: var(--clr-border);">|</span>
|
<span style="color: var(--clr-border)">|</span>
|
||||||
<span style="font-size:0.8rem; color: var(--clr-muted);">
|
<span style="font-size: 0.8rem; color: var(--clr-muted)">
|
||||||
<i class="fa-brands fa-php" style="color: var(--clr-secondary);"></i>
|
<i
|
||||||
|
class="fa-brands fa-php"
|
||||||
|
style="color: var(--clr-secondary)"
|
||||||
|
></i>
|
||||||
PHP / XAMPP
|
PHP / XAMPP
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-divider"></div>
|
<div class="footer-divider"></div>
|
||||||
|
<p style="font-size: 0.9rem; color: var(--clr-text); font-weight: 600; margin-bottom: 6px;">
|
||||||
|
Syariffullah <span style="font-family: 'Space Grotesk', monospace; color: var(--clr-primary); font-size: 0.82rem; background: rgba(79,70,229,0.08); padding: 2px 8px; border-radius: 6px;">D1041231018</span>
|
||||||
|
</p>
|
||||||
<p class="footer-copy">
|
<p class="footer-copy">
|
||||||
© 2025 WebGIS Portfolio · Sistem Informasi Geografis
|
© 2025 WebGIS Portfolio · Sistem Informasi Geografis
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -973,8 +1264,8 @@
|
|||||||
<script>
|
<script>
|
||||||
/* ----- CANVAS PARTICLE / GRID BG ----- */
|
/* ----- CANVAS PARTICLE / GRID BG ----- */
|
||||||
(function () {
|
(function () {
|
||||||
const canvas = document.getElementById('bg-canvas');
|
const canvas = document.getElementById("bg-canvas");
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
let w, h, animId;
|
let w, h, animId;
|
||||||
const DOTS = [];
|
const DOTS = [];
|
||||||
const LINES = [];
|
const LINES = [];
|
||||||
@@ -985,18 +1276,24 @@
|
|||||||
h = canvas.height = window.innerHeight;
|
h = canvas.height = window.innerHeight;
|
||||||
}
|
}
|
||||||
resize();
|
resize();
|
||||||
window.addEventListener('resize', resize);
|
window.addEventListener("resize", resize);
|
||||||
|
|
||||||
// Grid lines
|
// Grid lines
|
||||||
function drawGrid() {
|
function drawGrid() {
|
||||||
ctx.strokeStyle = 'rgba(79,70,229,0.06)';
|
ctx.strokeStyle = "rgba(79,70,229,0.06)";
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
const step = 60;
|
const step = 60;
|
||||||
for (let x = 0; x < w; x += step) {
|
for (let x = 0; x < w; x += step) {
|
||||||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, 0);
|
||||||
|
ctx.lineTo(x, h);
|
||||||
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
for (let y = 0; y < h; y += step) {
|
for (let y = 0; y < h; y += step) {
|
||||||
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, y);
|
||||||
|
ctx.lineTo(w, y);
|
||||||
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,7 +1305,7 @@
|
|||||||
vx: (Math.random() - 0.5) * 0.3,
|
vx: (Math.random() - 0.5) * 0.3,
|
||||||
vy: (Math.random() - 0.5) * 0.3,
|
vy: (Math.random() - 0.5) * 0.3,
|
||||||
r: Math.random() * 2 + 0.5,
|
r: Math.random() * 2 + 0.5,
|
||||||
a: Math.random()
|
a: Math.random(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,8 +1314,9 @@
|
|||||||
drawGrid();
|
drawGrid();
|
||||||
|
|
||||||
// Move and draw dots
|
// Move and draw dots
|
||||||
DOTS.forEach(d => {
|
DOTS.forEach((d) => {
|
||||||
d.x += d.vx; d.y += d.vy;
|
d.x += d.vx;
|
||||||
|
d.y += d.vy;
|
||||||
if (d.x < 0) d.x = w;
|
if (d.x < 0) d.x = w;
|
||||||
if (d.x > w) d.x = 0;
|
if (d.x > w) d.x = 0;
|
||||||
if (d.y < 0) d.y = h;
|
if (d.y < 0) d.y = h;
|
||||||
@@ -1053,45 +1351,53 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
/* ----- NAVBAR SCROLL ----- */
|
/* ----- NAVBAR SCROLL ----- */
|
||||||
const navbar = document.getElementById('navbar');
|
const navbar = document.getElementById("navbar");
|
||||||
window.addEventListener('scroll', () => {
|
window.addEventListener("scroll", () => {
|
||||||
navbar.classList.toggle('scrolled', window.scrollY > 40);
|
navbar.classList.toggle("scrolled", window.scrollY > 40);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ----- SCROLL REVEAL ----- */
|
/* ----- SCROLL REVEAL ----- */
|
||||||
const revealEls = document.querySelectorAll('.reveal');
|
const revealEls = document.querySelectorAll(".reveal");
|
||||||
const revealObs = new IntersectionObserver((entries) => {
|
const revealObs = new IntersectionObserver(
|
||||||
entries.forEach(e => {
|
(entries) => {
|
||||||
|
entries.forEach((e) => {
|
||||||
if (e.isIntersecting) {
|
if (e.isIntersecting) {
|
||||||
e.target.classList.add('visible');
|
e.target.classList.add("visible");
|
||||||
revealObs.unobserve(e.target);
|
revealObs.unobserve(e.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, { threshold: 0.15 });
|
},
|
||||||
revealEls.forEach(el => revealObs.observe(el));
|
{ threshold: 0.15 },
|
||||||
|
);
|
||||||
|
revealEls.forEach((el) => revealObs.observe(el));
|
||||||
|
|
||||||
/* ----- CARD REVEAL ----- */
|
/* ----- CARD REVEAL ----- */
|
||||||
const cardEls = document.querySelectorAll('.project-card');
|
const cardEls = document.querySelectorAll(".project-card");
|
||||||
const cardObs = new IntersectionObserver((entries) => {
|
const cardObs = new IntersectionObserver(
|
||||||
entries.forEach(e => {
|
(entries) => {
|
||||||
|
entries.forEach((e) => {
|
||||||
if (e.isIntersecting) {
|
if (e.isIntersecting) {
|
||||||
e.target.style.opacity = '1';
|
e.target.style.opacity = "1";
|
||||||
e.target.style.transform = 'translateY(0)';
|
e.target.style.transform = "translateY(0)";
|
||||||
cardObs.unobserve(e.target);
|
cardObs.unobserve(e.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, { threshold: 0.1 });
|
},
|
||||||
cardEls.forEach(card => {
|
{ threshold: 0.1 },
|
||||||
card.style.opacity = '0';
|
);
|
||||||
card.style.transform = 'translateY(30px)';
|
cardEls.forEach((card) => {
|
||||||
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
card.style.opacity = "0";
|
||||||
|
card.style.transform = "translateY(30px)";
|
||||||
|
card.style.transition = "opacity 0.6s ease, transform 0.6s ease";
|
||||||
cardObs.observe(card);
|
cardObs.observe(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ----- SMOOTH HERO CTA ----- */
|
/* ----- SMOOTH HERO CTA ----- */
|
||||||
document.getElementById('hero-cta').addEventListener('click', (e) => {
|
document.getElementById("hero-cta").addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById('projects').scrollIntoView({ behavior: 'smooth' });
|
document
|
||||||
|
.getElementById("projects")
|
||||||
|
.scrollIntoView({ behavior: "smooth" });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
// --- Fitur Autentikasi dan Manajemen User (RBAC) ---
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
window.currentUser = null;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
const authWidget = document.getElementById('authWidget');
|
||||||
|
const loginModal = document.getElementById('loginModal');
|
||||||
|
const loginUsernameInput = document.getElementById('loginUsername');
|
||||||
|
const loginPasswordInput = document.getElementById('loginPassword');
|
||||||
|
const loginSubmitBtn = document.getElementById('loginSubmitBtn');
|
||||||
|
const loginErrorMsg = document.getElementById('loginErrorMsg');
|
||||||
|
const closeLoginModal = document.getElementById('closeLoginModal');
|
||||||
|
|
||||||
|
// Startup Session Check
|
||||||
|
function checkSession() {
|
||||||
|
fetch('../poverty/api/check_session.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success' && data.isLoggedIn) {
|
||||||
|
window.currentUser = data.data;
|
||||||
|
} else {
|
||||||
|
window.currentUser = null;
|
||||||
|
}
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Session check failed:", err);
|
||||||
|
window.currentUser = null;
|
||||||
|
updateAuthUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI based on logged-in state
|
||||||
|
function updateAuthUI() {
|
||||||
|
if (!authWidget) return;
|
||||||
|
|
||||||
|
if (window.currentUser) {
|
||||||
|
// Logged In state
|
||||||
|
const roleLabel = window.currentUser.role === 'admin' ? 'Admin' : 'Pengelola';
|
||||||
|
|
||||||
|
authWidget.innerHTML = `
|
||||||
|
<div class="user-profile-pill">
|
||||||
|
<div class="user-profile-info">
|
||||||
|
<div class="user-profile-name">👤 ${escHtml(window.currentUser.username)}</div>
|
||||||
|
<div class="user-profile-role">${roleLabel}</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-profile-logout" id="authLogoutBtn" title="Logout">
|
||||||
|
<i class="fas fa-sign-out-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('authLogoutBtn').addEventListener('click', logout);
|
||||||
|
} else {
|
||||||
|
// Logged Out state
|
||||||
|
authWidget.innerHTML = `
|
||||||
|
<button class="btn-login" id="authLoginBtn"><i class="fas fa-sign-in-alt"></i> Login</button>
|
||||||
|
`;
|
||||||
|
document.getElementById('authLoginBtn').addEventListener('click', showLoginModal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAllLayers() {
|
||||||
|
if (typeof loadJalan === 'function') loadJalan();
|
||||||
|
if (window.refreshActivePanel) window.refreshActivePanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login Modal logic
|
||||||
|
function showLoginModal() {
|
||||||
|
loginUsernameInput.value = '';
|
||||||
|
loginPasswordInput.value = '';
|
||||||
|
loginErrorMsg.style.display = 'none';
|
||||||
|
loginModal.classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoginModal() {
|
||||||
|
loginModal.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeLoginModal) {
|
||||||
|
closeLoginModal.addEventListener('click', hideLoginModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginSubmitBtn) {
|
||||||
|
loginSubmitBtn.addEventListener('click', function() {
|
||||||
|
const username = loginUsernameInput.value.trim();
|
||||||
|
const password = loginPasswordInput.value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
loginErrorMsg.textContent = 'Username dan password wajib diisi';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('../poverty/api/login.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
window.currentUser = data.data;
|
||||||
|
hideLoginModal();
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
showToast('Login berhasil', 'success');
|
||||||
|
} else {
|
||||||
|
loginErrorMsg.textContent = data.message || 'Login gagal';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
loginErrorMsg.textContent = 'Koneksi ke server terputus';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
fetch('../poverty/api/logout.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
window.currentUser = null;
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
showToast('Logout berhasil', 'success');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
showToast('Gagal logout', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function escHtml(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast Notification helper
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
if (typeof window.showToast === 'function') {
|
||||||
|
window.showToast(message, type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run session check on initialization
|
||||||
|
checkSession();
|
||||||
|
})();
|
||||||
@@ -29,12 +29,11 @@ const drawControl = new L.Control.Draw({
|
|||||||
|
|
||||||
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
||||||
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
||||||
isDrawingMode = true;
|
window.currentDrawMode = window.currentDrawMode || 'drawing';
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isDrawingMode = false;
|
|
||||||
window.currentDrawMode = null;
|
window.currentDrawMode = null;
|
||||||
window.activeDrawHandler = null;
|
window.activeDrawHandler = null;
|
||||||
window.deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ function makeSpbuIcon(is24) {
|
|||||||
const spbuGreenIcon = makeSpbuIcon(true);
|
const spbuGreenIcon = makeSpbuIcon(true);
|
||||||
const spbuRedIcon = makeSpbuIcon(false);
|
const spbuRedIcon = makeSpbuIcon(false);
|
||||||
|
|
||||||
let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil)
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch SPBU
|
// Fetch SPBU
|
||||||
function loadSpbu() {
|
function loadSpbu() {
|
||||||
@@ -75,7 +73,8 @@ function addSpbuMarker(item) {
|
|||||||
|
|
||||||
// Map Click -> Form Add
|
// Map Click -> Form Add
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
if (isDrawingMode) return; // Jangan muncul form jika sedang gambar garis/polygon
|
// Jangan muncul form jika sedang dalam draw mode (jalan/parsil)
|
||||||
|
if (window.currentDrawMode) return;
|
||||||
|
|
||||||
// Hanya proses jika mode tambah SPBU aktif
|
// Hanya proses jika mode tambah SPBU aktif
|
||||||
if (window.currentAddMode === 'spbu') {
|
if (window.currentAddMode === 'spbu') {
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
// Inisialisasi Peta Jalan
|
||||||
|
// Koordinat awal: [-0.0263, 109.3425] (Pontianak)
|
||||||
|
const map = L.map("map", { zoomControl: false }).setView(
|
||||||
|
[-0.0263, 109.3425],
|
||||||
|
13,
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== Toast Notification =====
|
||||||
|
window.showToast = function (message, type = "success", duration = 3500) {
|
||||||
|
const icons = { success: "✅", error: "❌", info: "ℹ️", warning: "⚠️" };
|
||||||
|
const container = document.getElementById("toastContainer");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const toast = document.createElement("div");
|
||||||
|
toast.className = `toast toast-${type}`;
|
||||||
|
toast.innerHTML = `<span class="toast-icon">${icons[type] || "ℹ️"}</span><span>${message}</span>`;
|
||||||
|
container.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.add("hiding");
|
||||||
|
toast.addEventListener("animationend", () => toast.remove());
|
||||||
|
}, duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom Zoom Control Logic
|
||||||
|
document.getElementById("zoomInBtn").addEventListener("click", function () {
|
||||||
|
map.zoomIn();
|
||||||
|
});
|
||||||
|
document.getElementById("zoomOutBtn").addEventListener("click", function () {
|
||||||
|
map.zoomOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Base Map dari OpenStreetMap
|
||||||
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Inisialisasi FeatureGroups untuk masing-masing layer
|
||||||
|
const jalanLayer = L.featureGroup().addTo(map);
|
||||||
|
|
||||||
|
const searchInput = document.getElementById("searchInput");
|
||||||
|
const searchClear = document.getElementById("searchClear");
|
||||||
|
|
||||||
|
searchInput.addEventListener("focus", function () {
|
||||||
|
if (typeof window.deactivateAddMode === "function")
|
||||||
|
window.deactivateAddMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
searchInput.addEventListener("input", function () {
|
||||||
|
const val = this.value.toLowerCase();
|
||||||
|
if (val.length > 0) {
|
||||||
|
searchClear.style.visibility = "visible";
|
||||||
|
window.showSearchResults(val);
|
||||||
|
} else {
|
||||||
|
searchClear.style.visibility = "hidden";
|
||||||
|
document.getElementById("searchResults").style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
searchClear.addEventListener("click", function () {
|
||||||
|
searchInput.value = "";
|
||||||
|
searchClear.style.visibility = "hidden";
|
||||||
|
document.getElementById("searchResults").style.display = "none";
|
||||||
|
searchInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// UI Logic: Custom Layer Control Toggle
|
||||||
|
const layerBtn = document.getElementById("layerBtn");
|
||||||
|
const layerPanel = document.getElementById("layerPanel");
|
||||||
|
|
||||||
|
if (layerBtn && layerPanel) {
|
||||||
|
layerBtn.addEventListener("click", function () {
|
||||||
|
if (typeof window.deactivateAddMode === "function")
|
||||||
|
window.deactivateAddMode();
|
||||||
|
layerPanel.style.display =
|
||||||
|
layerPanel.style.display === "block" ? "none" : "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sembunyikan layer panel saat klik di peta
|
||||||
|
map.on("click", function () {
|
||||||
|
if (layerPanel) layerPanel.style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logic untuk Toggle Layer Visibility
|
||||||
|
const setupLayerToggle = (id, layer) => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.addEventListener("change", function (e) {
|
||||||
|
e.target.checked ? map.addLayer(layer) : map.removeLayer(layer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupLayerToggle("layerJalan", jalanLayer);
|
||||||
|
|
||||||
|
// --- Sub-layer Toggle (expand/collapse) ---
|
||||||
|
window.toggleSubLayer = function (subId, iconEl) {
|
||||||
|
const sub = document.getElementById(subId);
|
||||||
|
if (!sub) return;
|
||||||
|
const isHidden = sub.style.display === "none";
|
||||||
|
sub.style.display = isHidden ? "" : "none";
|
||||||
|
iconEl.classList.toggle("collapsed", !isHidden);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Sub-layer Filter ---
|
||||||
|
window.applySubFilter = function (type) {
|
||||||
|
if (type === "jalan") {
|
||||||
|
const checked = [...document.querySelectorAll(".sub-jalan:checked")].map(
|
||||||
|
(el) => el.value,
|
||||||
|
);
|
||||||
|
jalanLayer.eachLayer((layer) => {
|
||||||
|
if (!layer.jalanData) return;
|
||||||
|
const visible = checked.includes(layer.jalanData.status);
|
||||||
|
layer.setStyle({ opacity: visible ? 1 : 0, fillOpacity: visible ? 0.5 : 0 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Modal Logic ---
|
||||||
|
const unifiedModal = document.getElementById("unifiedModal");
|
||||||
|
const modalTitle = document.getElementById("modalTitle");
|
||||||
|
const modalBody = document.getElementById("modalBody");
|
||||||
|
|
||||||
|
window.openModal = function (title, bodyHTML, saveCallback) {
|
||||||
|
if (!unifiedModal) return;
|
||||||
|
modalTitle.textContent = title;
|
||||||
|
modalBody.innerHTML = bodyHTML;
|
||||||
|
unifiedModal.classList.add("show");
|
||||||
|
|
||||||
|
const currentSaveBtn = document.getElementById("modalSaveBtn");
|
||||||
|
if (currentSaveBtn) {
|
||||||
|
const newSaveBtn = currentSaveBtn.cloneNode(true);
|
||||||
|
currentSaveBtn.parentNode.replaceChild(newSaveBtn, currentSaveBtn);
|
||||||
|
newSaveBtn.addEventListener("click", saveCallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.closeModal = function () {
|
||||||
|
if (unifiedModal) unifiedModal.classList.remove("show");
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmModal = document.getElementById("confirmModal");
|
||||||
|
const confirmMessage = document.getElementById("confirmMessage");
|
||||||
|
|
||||||
|
window.openConfirmModal = function (msg, confirmCallback) {
|
||||||
|
if (!confirmModal) return;
|
||||||
|
confirmMessage.textContent = msg;
|
||||||
|
confirmModal.classList.add("show");
|
||||||
|
|
||||||
|
const currentYesBtn = document.getElementById("confirmYesBtn");
|
||||||
|
if (currentYesBtn) {
|
||||||
|
const newYesBtn = currentYesBtn.cloneNode(true);
|
||||||
|
currentYesBtn.parentNode.replaceChild(newYesBtn, currentYesBtn);
|
||||||
|
newYesBtn.addEventListener("click", function () {
|
||||||
|
confirmModal.classList.remove("show");
|
||||||
|
confirmCallback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.closeConfirmModal = function () {
|
||||||
|
if (confirmModal) confirmModal.classList.remove("show");
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Add Mode (untuk draw control) ---
|
||||||
|
window.currentDrawMode = null;
|
||||||
|
window.activeDrawHandler = null;
|
||||||
|
|
||||||
|
window.deactivateAddMode = function () {
|
||||||
|
if (window.activeDrawHandler) {
|
||||||
|
try { window.activeDrawHandler.disable(); } catch(e) {}
|
||||||
|
window.activeDrawHandler = null;
|
||||||
|
}
|
||||||
|
window.currentDrawMode = null;
|
||||||
|
document.getElementById("map").style.cursor = "";
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// ===== Panel Management =====
|
||||||
|
// Mengelola Left Sidebar Toggle + Right Panel + Layer Control
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
// ---- State ----
|
||||||
|
let activePanel = null;
|
||||||
|
|
||||||
|
// ---- Elements ----
|
||||||
|
const sidebarToggleBtn = document.getElementById('sidebarToggleBtn');
|
||||||
|
const sidebarToggleIcon = document.getElementById('sidebarToggleIcon');
|
||||||
|
const leftSidebar = document.getElementById('leftSidebar');
|
||||||
|
const rightPanel = document.getElementById('rightPanel');
|
||||||
|
const rightPanelTitle = document.getElementById('rightPanelTitle');
|
||||||
|
const rightPanelActions = document.getElementById('rightPanelActions');
|
||||||
|
const rightPanelBody = document.getElementById('rightPanelBody');
|
||||||
|
const rightPanelClose = document.getElementById('rightPanelClose');
|
||||||
|
|
||||||
|
// ---- Sidebar Toggle ----
|
||||||
|
if (sidebarToggleBtn) {
|
||||||
|
sidebarToggleBtn.addEventListener('click', function () {
|
||||||
|
const isOpen = leftSidebar.style.display !== 'none';
|
||||||
|
if (isOpen) {
|
||||||
|
leftSidebar.style.display = 'none';
|
||||||
|
sidebarToggleBtn.classList.remove('open');
|
||||||
|
sidebarToggleIcon.className = 'fas fa-chevron-right';
|
||||||
|
} else {
|
||||||
|
leftSidebar.style.display = 'flex';
|
||||||
|
sidebarToggleBtn.classList.add('open');
|
||||||
|
sidebarToggleIcon.className = 'fas fa-chevron-left';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Sidebar Buttons ----
|
||||||
|
document.querySelectorAll('.sidebar-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
const panel = this.dataset.panel;
|
||||||
|
if (activePanel === panel) {
|
||||||
|
closePanel();
|
||||||
|
} else {
|
||||||
|
openPanel(panel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rightPanelClose) {
|
||||||
|
rightPanelClose.addEventListener('click', closePanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Open / Close Panel ----
|
||||||
|
function openPanel(panel) {
|
||||||
|
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
||||||
|
activePanel = panel;
|
||||||
|
rightPanel.classList.add('open');
|
||||||
|
document.body.classList.add('right-panel-open');
|
||||||
|
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
const activeBtn = document.querySelector(`.sidebar-btn[data-panel="${panel}"]`);
|
||||||
|
if (activeBtn) activeBtn.classList.add('active');
|
||||||
|
renderPanel(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePanel() {
|
||||||
|
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
||||||
|
activePanel = null;
|
||||||
|
rightPanel.classList.remove('open');
|
||||||
|
document.body.classList.remove('right-panel-open');
|
||||||
|
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.refreshActivePanel = function () {
|
||||||
|
if (activePanel && activePanel !== 'layer') renderPanel(activePanel);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Render Panel by Type ----
|
||||||
|
function renderPanel(type) {
|
||||||
|
rightPanelBody.innerHTML = '<div class="panel-empty">Memuat data...</div>';
|
||||||
|
rightPanelActions.innerHTML = '';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'jalan': renderJalanPanel(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// ===== Jalan Panel ========
|
||||||
|
// ==========================
|
||||||
|
function renderJalanPanel() {
|
||||||
|
rightPanelTitle.textContent = '🛣️ Daftar Jalan';
|
||||||
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
rightPanelActions.innerHTML = `
|
||||||
|
<button class="btn-panel-add" id="btnPanelAddJalan">
|
||||||
|
<i class="fas fa-plus"></i> Tambah Jalan
|
||||||
|
</button>`;
|
||||||
|
document.getElementById('btnPanelAddJalan').addEventListener('click', () => {
|
||||||
|
window.activateDraw('polyline');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
jalanLayer.eachLayer(l => {
|
||||||
|
if (l.jalanData) items.push(l.jalanData);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data Jalan</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rightPanelBody.innerHTML = '';
|
||||||
|
items.forEach(d => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'data-card';
|
||||||
|
let actionButtons = '';
|
||||||
|
if (isAdmin) {
|
||||||
|
actionButtons = `
|
||||||
|
<div class="data-card-actions">
|
||||||
|
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
||||||
|
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="data-card-main">
|
||||||
|
<div class="data-card-icon" style="background:#e0e7ff; color:#4f46e5; padding:8px; border-radius:8px; font-size:18px;">🛣️</div>
|
||||||
|
<div class="data-card-info">
|
||||||
|
<div class="data-card-name">${escHtml(d.nama)}</div>
|
||||||
|
<div class="data-card-sub">📏 ${d.panjang.toFixed(2)} m • 🏷️ ${escHtml(d.status)}</div>
|
||||||
|
</div>
|
||||||
|
${actionButtons}
|
||||||
|
</div>`;
|
||||||
|
if (isAdmin) {
|
||||||
|
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); window.openEditJalanModal(d.id); });
|
||||||
|
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); window.deleteJalan(d.id); });
|
||||||
|
}
|
||||||
|
card.addEventListener('click', () => {
|
||||||
|
jalanLayer.eachLayer(l => {
|
||||||
|
if (l.jalanData && l.jalanData.id == d.id) {
|
||||||
|
map.fitBounds(l.getBounds()); l.openPopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
rightPanelBody.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Utility ----
|
||||||
|
function escHtml(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Expose ----
|
||||||
|
window.openRightPanel = openPanel;
|
||||||
|
|
||||||
|
})();
|
||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- DATABASE: webgis_jalan
|
||||||
|
-- Tabel: jalan (Pemetaan Jaringan Jalan)
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
CREATE DATABASE IF NOT EXISTS webgis_jalan CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE webgis_jalan;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS jalan (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
nama VARCHAR(255) NOT NULL,
|
||||||
|
status ENUM('Nasional', 'Provinsi', 'Kabupaten') NOT NULL DEFAULT 'Kabupaten',
|
||||||
|
panjang DOUBLE NOT NULL DEFAULT 0,
|
||||||
|
geom LINESTRING NOT NULL,
|
||||||
|
SPATIAL INDEX (geom)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
$host = "localhost";
|
$host = "localhost";
|
||||||
$user = "root";
|
$user = "root";
|
||||||
$pass = "";
|
$pass = "";
|
||||||
$db = "webgis";
|
$db = "webgis_jalan";
|
||||||
|
|
||||||
// Coba koneksi ke server dan pilih database
|
// Coba koneksi ke server dan pilih database
|
||||||
$conn = new mysqli($host, $user, $pass, $db);
|
$conn = new mysqli($host, $user, $pass, $db);
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
+42
-187
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WebGIS Pemetaan Kemiskinan</title>
|
<title>WebGIS Pemetaan Jaringan Jalan</title>
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="search-icon">
|
<div class="search-icon">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="search-input" id="searchInput" placeholder="Cari SPBU, jalan, parsil, rumah ibadah...">
|
<input type="text" class="search-input" id="searchInput" placeholder="Cari jalan...">
|
||||||
<button class="search-clear" id="searchClear">
|
<button class="search-clear" id="searchClear">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -51,39 +51,50 @@
|
|||||||
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Tombol Kembali ke Beranda -->
|
||||||
|
<a href="/webgis/index.html" class="btn-back-home" title="Kembali ke Beranda">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
<span>Beranda</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-back-home {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 70px;
|
||||||
|
z-index: 1100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #4f46e5;
|
||||||
|
border: 1.5px solid rgba(79, 70, 229, 0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 2px 12px rgba(79, 70, 229, 0.12);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-back-home:hover {
|
||||||
|
background: #4f46e5;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #4f46e5;
|
||||||
|
box-shadow: 0 4px 16px rgba(79, 70, 229, 0.3);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.btn-back-home i { font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Left Sidebar Menu -->
|
<!-- Left Sidebar Menu -->
|
||||||
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
||||||
<div class="sidebar-section-label">Umum</div>
|
<div class="sidebar-section-label">Menu</div>
|
||||||
<button class="sidebar-btn" id="menuSpbu" data-panel="spbu" title="SPBU">
|
|
||||||
<span class="sidebar-emoji">⛽</span>
|
|
||||||
<span class="sidebar-label">SPBU</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuJalan" data-panel="jalan" title="Jalan">
|
<button class="sidebar-btn" id="menuJalan" data-panel="jalan" title="Jalan">
|
||||||
<span class="sidebar-emoji">🛣️</span>
|
<span class="sidebar-emoji">🛣️</span>
|
||||||
<span class="sidebar-label">Jalan</span>
|
<span class="sidebar-label">Jalan</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sidebar-btn" id="menuParsil" data-panel="parsil" title="Parsil Tanah">
|
|
||||||
<span class="sidebar-emoji">🗺️</span>
|
|
||||||
<span class="sidebar-label">Parsil</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="sidebar-divider"></div>
|
|
||||||
|
|
||||||
<div class="sidebar-section-label">Poverty</div>
|
|
||||||
<button class="sidebar-btn" id="menuIbadah" data-panel="ibadah" title="Rumah Ibadah">
|
|
||||||
<span class="sidebar-emoji">🕌</span>
|
|
||||||
<span class="sidebar-label">Ibadah</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuMiskin" data-panel="miskin" title="Penduduk Miskin">
|
|
||||||
<span class="sidebar-emoji">🏠</span>
|
|
||||||
<span class="sidebar-label">Miskin</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="sidebar-divider" id="sidebarAdminDivider" style="display:none;"></div>
|
|
||||||
<button class="sidebar-btn" id="menuUsers" style="display:none;" title="Manajemen Pengguna">
|
|
||||||
<span class="sidebar-emoji">👥</span>
|
|
||||||
<span class="sidebar-label">User</span>
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
@@ -100,24 +111,6 @@
|
|||||||
|
|
||||||
<div class="layer-section-title">Infrastruktur & Lahan</div>
|
<div class="layer-section-title">Infrastruktur & Lahan</div>
|
||||||
|
|
||||||
<!-- SPBU -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerSpbu" checked> SPBU
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subSpbu', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subSpbu" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="1" checked onchange="applySubFilter('spbu')"> 24 Jam
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="0" checked onchange="applySubFilter('spbu')"> Tidak 24 Jam
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Jalan -->
|
<!-- Jalan -->
|
||||||
<div class="layer-group-item">
|
<div class="layer-group-item">
|
||||||
<div class="layer-group-header">
|
<div class="layer-group-header">
|
||||||
@@ -139,30 +132,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Parsil -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerParsil" checked> Parsil Tanah
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subParsil', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subParsil" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="SHM" checked onchange="applySubFilter('parsil')"> SHM
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGB" checked onchange="applySubFilter('parsil')"> HGB
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGU" checked onchange="applySubFilter('parsil')"> HGU
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HP" checked onchange="applySubFilter('parsil')"> HP
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layer-section-title">Pemetaan Kemiskinan</div>
|
<div class="layer-section-title">Pemetaan Kemiskinan</div>
|
||||||
|
|
||||||
<!-- Rumah Ibadah -->
|
<!-- Rumah Ibadah -->
|
||||||
@@ -255,55 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Bantuan Modal -->
|
|
||||||
<div id="logBantuanModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width:500px; max-height:85vh; overflow:hidden; display:flex; flex-direction:column;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 id="logBantuanTitle">Log Bantuan</h3>
|
|
||||||
<span class="modal-close" onclick="closeLogBantuanModal()">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="flex:1; overflow-y:auto; gap:0; padding:0;">
|
|
||||||
<!-- Riwayat -->
|
|
||||||
<div style="padding:15px; border-bottom:1px solid #eee;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Riwayat Bantuan</h4>
|
|
||||||
<div id="logBantuanList" style="max-height:220px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
|
|
||||||
<div class="panel-empty" style="padding:20px;">Belum ada riwayat</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Form Tambah -->
|
|
||||||
<div style="padding:15px;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Tambah Log Bantuan</h4>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Rumah Ibadah</label>
|
|
||||||
<select id="logIbadahId"></select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tipe Bantuan</label>
|
|
||||||
<select id="logTipeBantuan">
|
|
||||||
<option value="Sembako">Sembako</option>
|
|
||||||
<option value="Uang Tunai">Uang Tunai</option>
|
|
||||||
<option value="Pakaian">Pakaian</option>
|
|
||||||
<option value="Pendidikan">Pendidikan</option>
|
|
||||||
<option value="Kesehatan">Kesehatan</option>
|
|
||||||
<option value="Lainnya">Lainnya</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tanggal</label>
|
|
||||||
<input type="date" id="logTanggal">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Keterangan (opsional)</label>
|
|
||||||
<textarea id="logKeterangan" rows="2" placeholder="Catatan tambahan..."></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn-save" id="logSaveBtn">Simpan Log</button>
|
|
||||||
<button class="btn-cancel" onclick="closeLogBantuanModal()">Tutup</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Floating Profile / Auth Control -->
|
<!-- Floating Profile / Auth Control -->
|
||||||
<div id="authWidget" class="auth-widget">
|
<div id="authWidget" class="auth-widget">
|
||||||
@@ -334,68 +255,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Management Modal -->
|
|
||||||
<div id="userManagementModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width: 700px; max-width: 95%;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>Manajemen Pengguna (Admin)</h3>
|
|
||||||
<span class="modal-close" id="closeUserManagementModal">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="gap: 15px;">
|
|
||||||
<!-- Form Tambah/Edit User -->
|
|
||||||
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; gap: 10px;">
|
|
||||||
<h4 id="userFormTitle" style="font-size: 13px; font-weight:700; color:#334155; margin-bottom: 2px;">Tambah User Baru</h4>
|
|
||||||
<input type="hidden" id="manageUserId" value="">
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Username</label>
|
|
||||||
<input type="text" id="manageUsername" placeholder="Username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Password (Kosongkan jika tidak diubah)</label>
|
|
||||||
<input type="password" id="managePassword" placeholder="Password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Role</label>
|
|
||||||
<select id="manageRole">
|
|
||||||
<option value="pengelola" selected>Pengelola Rumah Ibadah</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" id="manageIbadahGroup">
|
|
||||||
<label>Rumah Ibadah yang Dikelola</label>
|
|
||||||
<select id="manageIbadahId">
|
|
||||||
<!-- Dikonstruksi dinamis dari data rumah ibadah -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 5px;">
|
|
||||||
<button id="btnCancelUserEdit" class="btn-cancel" style="padding: 6px 12px; display:none;">Batal</button>
|
|
||||||
<button id="btnSaveUser" class="btn-save" style="padding: 6px 16px;">Simpan User</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabel Daftar User -->
|
|
||||||
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #cbd5e1; border-radius: 8px;">
|
|
||||||
<table style="width: 100%; border-collapse: collapse; font-size: 13px; text-align: left;">
|
|
||||||
<thead>
|
|
||||||
<tr style="background: #f1f5f9; border-bottom: 1px solid #cbd5e1; color:#475569; position: sticky; top: 0; z-index: 10;">
|
|
||||||
<th style="padding: 10px 12px;">Username</th>
|
|
||||||
<th style="padding: 10px 12px;">Role</th>
|
|
||||||
<th style="padding: 10px 12px;">Rumah Ibadah</th>
|
|
||||||
<th style="padding: 10px 12px; text-align: center;">Aksi</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="userTableBody">
|
|
||||||
<!-- Diisi dinamis -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toast Notification Container -->
|
<!-- Toast Notification Container -->
|
||||||
<div id="toastContainer"></div>
|
<div id="toastContainer"></div>
|
||||||
@@ -412,17 +272,12 @@
|
|||||||
<!-- Main Map Initialization -->
|
<!-- Main Map Initialization -->
|
||||||
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
||||||
<!-- Fitur & Komponen -->
|
<!-- Fitur & Komponen -->
|
||||||
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/kemiskinan.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
|
||||||
|
|
||||||
<!-- Core Auth Feature -->
|
|
||||||
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
||||||
|
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
include_once '../db_connect.php';
|
include_once 'db_connect.php';
|
||||||
|
|
||||||
$query = "SELECT id, nama, status, panjang, ST_AsGeoJSON(geom) as geom FROM jalan";
|
$query = "SELECT id, nama, status, panjang, ST_AsGeoJSON(geom) as geom FROM jalan";
|
||||||
$result = $conn->query($query);
|
$result = $conn->query($query);
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ const drawControl = new L.Control.Draw({
|
|||||||
|
|
||||||
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
||||||
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
||||||
isDrawingMode = true;
|
window.currentDrawMode = window.currentDrawMode || 'drawing';
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isDrawingMode = false;
|
|
||||||
window.currentDrawMode = null;
|
window.currentDrawMode = null;
|
||||||
window.activeDrawHandler = null;
|
window.activeDrawHandler = null;
|
||||||
window.deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ function makeSpbuIcon(is24) {
|
|||||||
const spbuGreenIcon = makeSpbuIcon(true);
|
const spbuGreenIcon = makeSpbuIcon(true);
|
||||||
const spbuRedIcon = makeSpbuIcon(false);
|
const spbuRedIcon = makeSpbuIcon(false);
|
||||||
|
|
||||||
let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil)
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch SPBU
|
// Fetch SPBU
|
||||||
function loadSpbu() {
|
function loadSpbu() {
|
||||||
@@ -75,7 +73,8 @@ function addSpbuMarker(item) {
|
|||||||
|
|
||||||
// Map Click -> Form Add
|
// Map Click -> Form Add
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
if (isDrawingMode) return; // Jangan muncul form jika sedang gambar garis/polygon
|
// Jangan muncul form jika sedang dalam draw mode (jalan/parsil)
|
||||||
|
if (window.currentDrawMode) return;
|
||||||
|
|
||||||
// Hanya proses jika mode tambah SPBU aktif
|
// Hanya proses jika mode tambah SPBU aktif
|
||||||
if (window.currentAddMode === 'spbu') {
|
if (window.currentAddMode === 'spbu') {
|
||||||
|
|||||||
+128
-109
@@ -1,246 +1,265 @@
|
|||||||
// Inisialisasi Peta
|
// Inisialisasi Peta
|
||||||
const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
const map = L.map("map", { zoomControl: false }).setView(
|
||||||
|
[-0.0263, 109.3425],
|
||||||
|
13,
|
||||||
|
);
|
||||||
|
|
||||||
// ===== Toast Notification =====
|
// ===== Toast Notification =====
|
||||||
window.showToast = function(message, type = 'success', duration = 3500) {
|
window.showToast = function (message, type = "success", duration = 3500) {
|
||||||
const icons = { success: '✅', error: '❌', info: 'ℹ️', warning: '⚠️' };
|
const icons = { success: "✅", error: "❌", info: "ℹ️", warning: "⚠️" };
|
||||||
const container = document.getElementById('toastContainer');
|
const container = document.getElementById("toastContainer");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement("div");
|
||||||
toast.className = `toast toast-${type}`;
|
toast.className = `toast toast-${type}`;
|
||||||
toast.innerHTML = `<span class="toast-icon">${icons[type] || 'ℹ️'}</span><span>${message}</span>`;
|
toast.innerHTML = `<span class="toast-icon">${icons[type] || "ℹ️"}</span><span>${message}</span>`;
|
||||||
container.appendChild(toast);
|
container.appendChild(toast);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.classList.add('hiding');
|
toast.classList.add("hiding");
|
||||||
toast.addEventListener('animationend', () => toast.remove());
|
toast.addEventListener("animationend", () => toast.remove());
|
||||||
}, duration);
|
}, duration);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom Zoom Control Logic
|
// Custom Zoom Control Logic
|
||||||
document.getElementById('zoomInBtn').addEventListener('click', function() { map.zoomIn(); });
|
document.getElementById("zoomInBtn").addEventListener("click", function () {
|
||||||
document.getElementById('zoomOutBtn').addEventListener('click', function() { map.zoomOut(); });
|
map.zoomIn();
|
||||||
|
});
|
||||||
|
document.getElementById("zoomOutBtn").addEventListener("click", function () {
|
||||||
|
map.zoomOut();
|
||||||
|
});
|
||||||
|
|
||||||
// Base Map dari OpenStreetMap
|
// Base Map dari OpenStreetMap
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
// Inisialisasi FeatureGroups untuk masing-masing layer
|
// Inisialisasi FeatureGroups untuk masing-masing layer
|
||||||
const spbuLayer = L.featureGroup().addTo(map);
|
|
||||||
const jalanLayer = L.featureGroup().addTo(map);
|
|
||||||
const parsilLayer = L.featureGroup().addTo(map);
|
const parsilLayer = L.featureGroup().addTo(map);
|
||||||
const rumahIbadahLayer = L.featureGroup().addTo(map);
|
|
||||||
const pendudukMiskinLayer = L.featureGroup().addTo(map);
|
|
||||||
const geoJsonLayer = L.featureGroup().addTo(map);
|
|
||||||
|
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById("searchInput");
|
||||||
const searchClear = document.getElementById('searchClear');
|
const searchClear = document.getElementById("searchClear");
|
||||||
|
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.addEventListener('focus', function() {
|
searchInput.addEventListener("focus", function () {
|
||||||
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
if (typeof window.deactivateAddMode === "function")
|
||||||
|
window.deactivateAddMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
searchInput.addEventListener('input', function() {
|
searchInput.addEventListener("input", function () {
|
||||||
const val = this.value.toLowerCase();
|
const val = this.value.toLowerCase();
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
searchClear.style.visibility = 'visible';
|
searchClear.style.visibility = "visible";
|
||||||
window.showSearchResults(val);
|
window.showSearchResults(val);
|
||||||
} else {
|
} else {
|
||||||
searchClear.style.visibility = 'hidden';
|
searchClear.style.visibility = "hidden";
|
||||||
document.getElementById('searchResults').style.display = 'none';
|
document.getElementById("searchResults").style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchClear) {
|
if (searchClear) {
|
||||||
searchClear.addEventListener('click', function() {
|
searchClear.addEventListener("click", function () {
|
||||||
searchInput.value = '';
|
searchInput.value = "";
|
||||||
searchClear.style.visibility = 'hidden';
|
searchClear.style.visibility = "hidden";
|
||||||
document.getElementById('searchResults').style.display = 'none';
|
document.getElementById("searchResults").style.display = "none";
|
||||||
searchInput.focus();
|
searchInput.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.showSearchResults = function (query) {
|
window.showSearchResults = function (query) {
|
||||||
const resultsContainer = document.getElementById('searchResults');
|
const resultsContainer = document.getElementById("searchResults");
|
||||||
if (!resultsContainer) return;
|
if (!resultsContainer) return;
|
||||||
resultsContainer.innerHTML = '';
|
resultsContainer.innerHTML = "";
|
||||||
let results = [];
|
let results = [];
|
||||||
|
|
||||||
if (typeof spbuLayer !== 'undefined') {
|
if (typeof parsilLayer !== "undefined") {
|
||||||
spbuLayer.eachLayer(layer => {
|
parsilLayer.eachLayer((layer) => {
|
||||||
if (layer.spbuData && layer.spbuData.nama && layer.spbuData.nama.toLowerCase().includes(query)) {
|
if (
|
||||||
results.push({ type: 'SPBU', nama: layer.spbuData.nama, layer: layer });
|
layer.parsilData &&
|
||||||
}
|
layer.parsilData.nama &&
|
||||||
|
layer.parsilData.nama.toLowerCase().includes(query)
|
||||||
|
) {
|
||||||
|
results.push({
|
||||||
|
type: "Parsil",
|
||||||
|
nama: layer.parsilData.nama,
|
||||||
|
layer: layer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof jalanLayer !== 'undefined') {
|
|
||||||
jalanLayer.eachLayer(layer => {
|
|
||||||
if (layer.jalanData && layer.jalanData.nama && layer.jalanData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Jalan', nama: layer.jalanData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof parsilLayer !== 'undefined') {
|
|
||||||
parsilLayer.eachLayer(layer => {
|
|
||||||
if (layer.parsilData && layer.parsilData.nama && layer.parsilData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Parsil', nama: layer.parsilData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
resultsContainer.innerHTML = '<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
|
resultsContainer.innerHTML =
|
||||||
|
'<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
|
||||||
} else {
|
} else {
|
||||||
results.forEach(res => {
|
results.forEach((res) => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement("div");
|
||||||
item.style.padding = '10px 15px';
|
item.style.padding = "10px 15px";
|
||||||
item.style.cursor = 'pointer';
|
item.style.cursor = "pointer";
|
||||||
item.style.borderBottom = '1px solid #eee';
|
item.style.borderBottom = "1px solid #eee";
|
||||||
item.style.fontSize = '14px';
|
item.style.fontSize = "14px";
|
||||||
item.innerHTML = `<strong>${res.type}:</strong> ${res.nama}`;
|
item.innerHTML = `<strong>${res.type}:</strong> ${res.nama}`;
|
||||||
item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f8f9fa');
|
item.addEventListener(
|
||||||
item.addEventListener('mouseleave', () => item.style.backgroundColor = 'white');
|
"mouseenter",
|
||||||
item.addEventListener('click', () => {
|
() => (item.style.backgroundColor = "#f8f9fa"),
|
||||||
|
);
|
||||||
|
item.addEventListener(
|
||||||
|
"mouseleave",
|
||||||
|
() => (item.style.backgroundColor = "white"),
|
||||||
|
);
|
||||||
|
item.addEventListener("click", () => {
|
||||||
if (res.layer instanceof L.Marker) {
|
if (res.layer instanceof L.Marker) {
|
||||||
map.setView(res.layer.getLatLng(), 17);
|
map.setView(res.layer.getLatLng(), 17);
|
||||||
} else if (res.layer.getBounds) {
|
} else if (res.layer.getBounds) {
|
||||||
map.fitBounds(res.layer.getBounds());
|
map.fitBounds(res.layer.getBounds());
|
||||||
}
|
}
|
||||||
res.layer.openPopup();
|
res.layer.openPopup();
|
||||||
resultsContainer.style.display = 'none';
|
resultsContainer.style.display = "none";
|
||||||
});
|
});
|
||||||
resultsContainer.appendChild(item);
|
resultsContainer.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resultsContainer.style.display = 'block';
|
resultsContainer.style.display = "block";
|
||||||
};
|
};
|
||||||
|
|
||||||
// UI Logic: Custom Layer Control Toggle
|
// UI Logic: Custom Layer Control Toggle
|
||||||
const layerBtn = document.getElementById('layerBtn');
|
const layerBtn = document.getElementById("layerBtn");
|
||||||
const layerPanel = document.getElementById('layerPanel');
|
const layerPanel = document.getElementById("layerPanel");
|
||||||
|
|
||||||
if (layerBtn && layerPanel) {
|
if (layerBtn && layerPanel) {
|
||||||
layerBtn.addEventListener('click', function() {
|
layerBtn.addEventListener("click", function () {
|
||||||
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
if (typeof window.deactivateAddMode === "function")
|
||||||
layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block';
|
window.deactivateAddMode();
|
||||||
|
layerPanel.style.display =
|
||||||
|
layerPanel.style.display === "block" ? "none" : "block";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sembunyikan layer panel saat klik di peta
|
// Sembunyikan layer panel saat klik di peta
|
||||||
map.on('click', function() {
|
map.on("click", function () {
|
||||||
if (layerPanel) layerPanel.style.display = 'none';
|
if (layerPanel) layerPanel.style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logic untuk Toggle Layer Visibility
|
// Logic untuk Toggle Layer Visibility
|
||||||
const setupLayerToggle = (id, layer) => {
|
const setupLayerToggle = (id, layer) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) {
|
if (el) {
|
||||||
el.addEventListener('change', function(e) {
|
el.addEventListener("change", function (e) {
|
||||||
e.target.checked ? map.addLayer(layer) : map.removeLayer(layer);
|
e.target.checked ? map.addLayer(layer) : map.removeLayer(layer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setupLayerToggle('layerSpbu', spbuLayer);
|
setupLayerToggle("layerParsil", parsilLayer);
|
||||||
setupLayerToggle('layerJalan', jalanLayer);
|
|
||||||
setupLayerToggle('layerParsil', parsilLayer);
|
|
||||||
setupLayerToggle('layerRumahIbadah', rumahIbadahLayer);
|
|
||||||
setupLayerToggle('layerMiskin', pendudukMiskinLayer);
|
|
||||||
|
|
||||||
// --- Sub-layer Toggle (expand/collapse) ---
|
// --- Sub-layer Toggle (expand/collapse) ---
|
||||||
window.toggleSubLayer = function (subId, iconEl) {
|
window.toggleSubLayer = function (subId, iconEl) {
|
||||||
const sub = document.getElementById(subId);
|
const sub = document.getElementById(subId);
|
||||||
if (!sub) return;
|
if (!sub) return;
|
||||||
const isHidden = sub.style.display === 'none';
|
const isHidden = sub.style.display === "none";
|
||||||
sub.style.display = isHidden ? '' : 'none';
|
sub.style.display = isHidden ? "" : "none";
|
||||||
iconEl.classList.toggle('collapsed', !isHidden);
|
iconEl.classList.toggle("collapsed", !isHidden);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Sub-layer Filter ---
|
// --- Sub-layer Filter ---
|
||||||
window.applySubFilter = function (type) {
|
window.applySubFilter = function (type) {
|
||||||
if (type === 'spbu') {
|
if (type === "spbu") {
|
||||||
const checked = [...document.querySelectorAll('.sub-spbu:checked')].map(el => el.value === '1');
|
const checked = [...document.querySelectorAll(".sub-spbu:checked")].map(
|
||||||
spbuLayer.eachLayer(layer => {
|
(el) => el.value === "1",
|
||||||
|
);
|
||||||
|
spbuLayer.eachLayer((layer) => {
|
||||||
if (layer.spbuData === undefined) return;
|
if (layer.spbuData === undefined) return;
|
||||||
if (checked.includes(layer.spbuData.is_24_jam)) {
|
if (checked.includes(layer.spbuData.is_24_jam)) {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "");
|
||||||
} else {
|
} else {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "none");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'jalan') {
|
} else if (type === "jalan") {
|
||||||
const checked = [...document.querySelectorAll('.sub-jalan:checked')].map(el => el.value);
|
const checked = [...document.querySelectorAll(".sub-jalan:checked")].map(
|
||||||
jalanLayer.eachLayer(layer => {
|
(el) => el.value,
|
||||||
|
);
|
||||||
|
jalanLayer.eachLayer((layer) => {
|
||||||
if (!layer.jalanData) return;
|
if (!layer.jalanData) return;
|
||||||
if (checked.includes(layer.jalanData.status)) {
|
if (checked.includes(layer.jalanData.status)) {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "");
|
||||||
} else {
|
} else {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "none");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'parsil') {
|
} else if (type === "parsil") {
|
||||||
const checked = [...document.querySelectorAll('.sub-parsil:checked')].map(el => el.value);
|
const checked = [...document.querySelectorAll(".sub-parsil:checked")].map(
|
||||||
parsilLayer.eachLayer(layer => {
|
(el) => el.value,
|
||||||
|
);
|
||||||
|
parsilLayer.eachLayer((layer) => {
|
||||||
if (!layer.parsilData) return;
|
if (!layer.parsilData) return;
|
||||||
if (checked.includes(layer.parsilData.status)) {
|
if (checked.includes(layer.parsilData.status)) {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "");
|
||||||
} else {
|
} else {
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
layer.getElement &&
|
||||||
|
layer.getElement() &&
|
||||||
|
(layer.getElement().style.display = "none");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Modal Logic ---
|
// --- Modal Logic ---
|
||||||
const unifiedModal = document.getElementById('unifiedModal');
|
const unifiedModal = document.getElementById("unifiedModal");
|
||||||
const modalTitle = document.getElementById('modalTitle');
|
const modalTitle = document.getElementById("modalTitle");
|
||||||
const modalBody = document.getElementById('modalBody');
|
const modalBody = document.getElementById("modalBody");
|
||||||
|
|
||||||
window.openModal = function (title, bodyHTML, saveCallback) {
|
window.openModal = function (title, bodyHTML, saveCallback) {
|
||||||
if (!unifiedModal) return;
|
if (!unifiedModal) return;
|
||||||
modalTitle.textContent = title;
|
modalTitle.textContent = title;
|
||||||
modalBody.innerHTML = bodyHTML;
|
modalBody.innerHTML = bodyHTML;
|
||||||
unifiedModal.classList.add('show');
|
unifiedModal.classList.add("show");
|
||||||
|
|
||||||
const currentSaveBtn = document.getElementById('modalSaveBtn');
|
const currentSaveBtn = document.getElementById("modalSaveBtn");
|
||||||
if (currentSaveBtn) {
|
if (currentSaveBtn) {
|
||||||
const newSaveBtn = currentSaveBtn.cloneNode(true);
|
const newSaveBtn = currentSaveBtn.cloneNode(true);
|
||||||
currentSaveBtn.parentNode.replaceChild(newSaveBtn, currentSaveBtn);
|
currentSaveBtn.parentNode.replaceChild(newSaveBtn, currentSaveBtn);
|
||||||
newSaveBtn.addEventListener('click', saveCallback);
|
newSaveBtn.addEventListener("click", saveCallback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.closeModal = function () {
|
window.closeModal = function () {
|
||||||
if (unifiedModal) unifiedModal.classList.remove('show');
|
if (unifiedModal) unifiedModal.classList.remove("show");
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmModal = document.getElementById('confirmModal');
|
const confirmModal = document.getElementById("confirmModal");
|
||||||
const confirmMessage = document.getElementById('confirmMessage');
|
const confirmMessage = document.getElementById("confirmMessage");
|
||||||
|
|
||||||
window.openConfirmModal = function (msg, confirmCallback) {
|
window.openConfirmModal = function (msg, confirmCallback) {
|
||||||
if (!confirmModal) return;
|
if (!confirmModal) return;
|
||||||
confirmMessage.textContent = msg;
|
confirmMessage.textContent = msg;
|
||||||
confirmModal.classList.add('show');
|
confirmModal.classList.add("show");
|
||||||
|
|
||||||
const currentYesBtn = document.getElementById('confirmYesBtn');
|
const currentYesBtn = document.getElementById("confirmYesBtn");
|
||||||
if (currentYesBtn) {
|
if (currentYesBtn) {
|
||||||
const newYesBtn = currentYesBtn.cloneNode(true);
|
const newYesBtn = currentYesBtn.cloneNode(true);
|
||||||
currentYesBtn.parentNode.replaceChild(newYesBtn, currentYesBtn);
|
currentYesBtn.parentNode.replaceChild(newYesBtn, currentYesBtn);
|
||||||
newYesBtn.addEventListener('click', function() {
|
newYesBtn.addEventListener("click", function () {
|
||||||
confirmModal.classList.remove('show');
|
confirmModal.classList.remove("show");
|
||||||
confirmCallback();
|
confirmCallback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.closeConfirmModal = function () {
|
window.closeConfirmModal = function () {
|
||||||
if (confirmModal) confirmModal.classList.remove('show');
|
if (confirmModal) confirmModal.classList.remove("show");
|
||||||
};
|
};
|
||||||
|
|
||||||
window.currentAddMode = null; // 'spbu', 'jalan', 'parsil'
|
window.currentAddMode = null; // 'spbu', 'jalan', 'parsil'
|
||||||
@@ -250,10 +269,10 @@ window.activateAddMode = function(mode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.currentAddMode = mode;
|
window.currentAddMode = mode;
|
||||||
document.getElementById('map').style.cursor = 'crosshair';
|
document.getElementById("map").style.cursor = "crosshair";
|
||||||
};
|
};
|
||||||
|
|
||||||
window.deactivateAddMode = function () {
|
window.deactivateAddMode = function () {
|
||||||
window.currentAddMode = null;
|
window.currentAddMode = null;
|
||||||
document.getElementById('map').style.cursor = '';
|
document.getElementById("map").style.cursor = "";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -153,7 +153,17 @@
|
|||||||
rightPanelTitle.textContent = '🛣️ Daftar Jalan';
|
rightPanelTitle.textContent = '🛣️ Daftar Jalan';
|
||||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
rightPanelActions.innerHTML = `
|
||||||
|
<button class="btn-panel-add" id="btnPanelAddJalan">
|
||||||
|
<i class="fas fa-plus"></i> Tambah Jalan
|
||||||
|
</button>`;
|
||||||
|
document.getElementById('btnPanelAddJalan').addEventListener('click', () => {
|
||||||
|
window.activateDraw('polyline');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
rightPanelActions.innerHTML = '';
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
jalanLayer.eachLayer(l => {
|
jalanLayer.eachLayer(l => {
|
||||||
@@ -209,7 +219,17 @@
|
|||||||
rightPanelTitle.textContent = '🗺️ Daftar Parsil Tanah';
|
rightPanelTitle.textContent = '🗺️ Daftar Parsil Tanah';
|
||||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
rightPanelActions.innerHTML = `
|
||||||
|
<button class="btn-panel-add" id="btnPanelAddParsil">
|
||||||
|
<i class="fas fa-plus"></i> Tambah Parsil
|
||||||
|
</button>`;
|
||||||
|
document.getElementById('btnPanelAddParsil').addEventListener('click', () => {
|
||||||
|
window.activateDraw('polygon');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
rightPanelActions.innerHTML = '';
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
parsilLayer.eachLayer(l => {
|
parsilLayer.eachLayer(l => {
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- DATABASE: webgis_parsil
|
||||||
|
-- Tabel: parsil (Bidang Tanah / Parsil)
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
CREATE DATABASE IF NOT EXISTS webgis_parsil CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE webgis_parsil;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS parsil (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
nama VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
status ENUM('SHM', 'HGB', 'HGU', 'HP') NOT NULL DEFAULT 'SHM',
|
||||||
|
luas DOUBLE NOT NULL DEFAULT 0,
|
||||||
|
geom POLYGON NOT NULL,
|
||||||
|
SPATIAL INDEX (geom)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
$host = "localhost";
|
||||||
|
$user = "root";
|
||||||
|
$pass = "";
|
||||||
|
$db = "webgis_parsil";
|
||||||
|
|
||||||
|
// Coba koneksi ke server dan pilih database
|
||||||
|
$conn = new mysqli($host, $user, $pass, $db);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
// Set charset
|
||||||
|
$conn->set_charset("utf8mb4");
|
||||||
|
|
||||||
|
// Jika di-include oleh file API, biarkan $conn tersedia
|
||||||
|
?>
|
||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
+42
-167
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WebGIS Pemetaan Jaringan Jalan</title>
|
<title>WebGIS Pemetaan Bidang Tanah (Parsil)</title>
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="search-icon">
|
<div class="search-icon">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="search-input" id="searchInput" placeholder="Cari SPBU, jalan, parsil, rumah ibadah...">
|
<input type="text" class="search-input" id="searchInput" placeholder="Cari parsil...">
|
||||||
<button class="search-clear" id="searchClear">
|
<button class="search-clear" id="searchClear">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -51,22 +51,50 @@
|
|||||||
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Tombol Kembali ke Beranda -->
|
||||||
|
<a href="/webgis/index.html" class="btn-back-home" title="Kembali ke Beranda">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
<span>Beranda</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-back-home {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 70px;
|
||||||
|
z-index: 1100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #4f46e5;
|
||||||
|
border: 1.5px solid rgba(79, 70, 229, 0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 2px 12px rgba(79, 70, 229, 0.12);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-back-home:hover {
|
||||||
|
background: #4f46e5;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #4f46e5;
|
||||||
|
box-shadow: 0 4px 16px rgba(79, 70, 229, 0.3);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.btn-back-home i { font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Left Sidebar Menu -->
|
<!-- Left Sidebar Menu -->
|
||||||
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
||||||
<div class="sidebar-section-label">Umum</div>
|
<div class="sidebar-section-label">Menu</div>
|
||||||
<button class="sidebar-btn" id="menuSpbu" data-panel="spbu" title="SPBU">
|
|
||||||
<span class="sidebar-emoji">⛽</span>
|
|
||||||
<span class="sidebar-label">SPBU</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuJalan" data-panel="jalan" title="Jalan">
|
|
||||||
<span class="sidebar-emoji">🛣️</span>
|
|
||||||
<span class="sidebar-label">Jalan</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuParsil" data-panel="parsil" title="Parsil Tanah">
|
<button class="sidebar-btn" id="menuParsil" data-panel="parsil" title="Parsil Tanah">
|
||||||
<span class="sidebar-emoji">🗺️</span>
|
<span class="sidebar-emoji">🗺️</span>
|
||||||
<span class="sidebar-label">Parsil</span>
|
<span class="sidebar-label">Parsil</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
@@ -83,45 +111,6 @@
|
|||||||
|
|
||||||
<div class="layer-section-title">Infrastruktur & Lahan</div>
|
<div class="layer-section-title">Infrastruktur & Lahan</div>
|
||||||
|
|
||||||
<!-- SPBU -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerSpbu" checked> SPBU
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subSpbu', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subSpbu" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="1" checked onchange="applySubFilter('spbu')"> 24 Jam
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="0" checked onchange="applySubFilter('spbu')"> Tidak 24 Jam
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Jalan -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerJalan" checked> Jalan
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subJalan', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subJalan" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Nasional" checked onchange="applySubFilter('jalan')"> Nasional
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Provinsi" checked onchange="applySubFilter('jalan')"> Provinsi
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Kabupaten" checked onchange="applySubFilter('jalan')"> Kabupaten
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Parsil -->
|
<!-- Parsil -->
|
||||||
<div class="layer-group-item">
|
<div class="layer-group-item">
|
||||||
<div class="layer-group-header">
|
<div class="layer-group-header">
|
||||||
@@ -215,55 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Bantuan Modal -->
|
|
||||||
<div id="logBantuanModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width:500px; max-height:85vh; overflow:hidden; display:flex; flex-direction:column;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 id="logBantuanTitle">Log Bantuan</h3>
|
|
||||||
<span class="modal-close" onclick="closeLogBantuanModal()">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="flex:1; overflow-y:auto; gap:0; padding:0;">
|
|
||||||
<!-- Riwayat -->
|
|
||||||
<div style="padding:15px; border-bottom:1px solid #eee;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Riwayat Bantuan</h4>
|
|
||||||
<div id="logBantuanList" style="max-height:220px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
|
|
||||||
<div class="panel-empty" style="padding:20px;">Belum ada riwayat</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Form Tambah -->
|
|
||||||
<div style="padding:15px;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Tambah Log Bantuan</h4>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Rumah Ibadah</label>
|
|
||||||
<select id="logIbadahId"></select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tipe Bantuan</label>
|
|
||||||
<select id="logTipeBantuan">
|
|
||||||
<option value="Sembako">Sembako</option>
|
|
||||||
<option value="Uang Tunai">Uang Tunai</option>
|
|
||||||
<option value="Pakaian">Pakaian</option>
|
|
||||||
<option value="Pendidikan">Pendidikan</option>
|
|
||||||
<option value="Kesehatan">Kesehatan</option>
|
|
||||||
<option value="Lainnya">Lainnya</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tanggal</label>
|
|
||||||
<input type="date" id="logTanggal">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Keterangan (opsional)</label>
|
|
||||||
<textarea id="logKeterangan" rows="2" placeholder="Catatan tambahan..."></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn-save" id="logSaveBtn">Simpan Log</button>
|
|
||||||
<button class="btn-cancel" onclick="closeLogBantuanModal()">Tutup</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Floating Profile / Auth Control -->
|
<!-- Floating Profile / Auth Control -->
|
||||||
<div id="authWidget" class="auth-widget">
|
<div id="authWidget" class="auth-widget">
|
||||||
@@ -294,68 +235,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Management Modal -->
|
|
||||||
<div id="userManagementModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width: 700px; max-width: 95%;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>Manajemen Pengguna (Admin)</h3>
|
|
||||||
<span class="modal-close" id="closeUserManagementModal">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="gap: 15px;">
|
|
||||||
<!-- Form Tambah/Edit User -->
|
|
||||||
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; gap: 10px;">
|
|
||||||
<h4 id="userFormTitle" style="font-size: 13px; font-weight:700; color:#334155; margin-bottom: 2px;">Tambah User Baru</h4>
|
|
||||||
<input type="hidden" id="manageUserId" value="">
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Username</label>
|
|
||||||
<input type="text" id="manageUsername" placeholder="Username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Password (Kosongkan jika tidak diubah)</label>
|
|
||||||
<input type="password" id="managePassword" placeholder="Password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Role</label>
|
|
||||||
<select id="manageRole">
|
|
||||||
<option value="pengelola" selected>Pengelola Rumah Ibadah</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" id="manageIbadahGroup">
|
|
||||||
<label>Rumah Ibadah yang Dikelola</label>
|
|
||||||
<select id="manageIbadahId">
|
|
||||||
<!-- Dikonstruksi dinamis dari data rumah ibadah -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 5px;">
|
|
||||||
<button id="btnCancelUserEdit" class="btn-cancel" style="padding: 6px 12px; display:none;">Batal</button>
|
|
||||||
<button id="btnSaveUser" class="btn-save" style="padding: 6px 16px;">Simpan User</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabel Daftar User -->
|
|
||||||
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #cbd5e1; border-radius: 8px;">
|
|
||||||
<table style="width: 100%; border-collapse: collapse; font-size: 13px; text-align: left;">
|
|
||||||
<thead>
|
|
||||||
<tr style="background: #f1f5f9; border-bottom: 1px solid #cbd5e1; color:#475569; position: sticky; top: 0; z-index: 10;">
|
|
||||||
<th style="padding: 10px 12px;">Username</th>
|
|
||||||
<th style="padding: 10px 12px;">Role</th>
|
|
||||||
<th style="padding: 10px 12px;">Rumah Ibadah</th>
|
|
||||||
<th style="padding: 10px 12px; text-align: center;">Aksi</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="userTableBody">
|
|
||||||
<!-- Diisi dinamis -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toast Notification Container -->
|
<!-- Toast Notification Container -->
|
||||||
<div id="toastContainer"></div>
|
<div id="toastContainer"></div>
|
||||||
@@ -372,17 +252,12 @@
|
|||||||
<!-- Main Map Initialization -->
|
<!-- Main Map Initialization -->
|
||||||
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
||||||
<!-- Fitur & Komponen -->
|
<!-- Fitur & Komponen -->
|
||||||
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
|
||||||
|
|
||||||
<!-- Core Auth Feature -->
|
|
||||||
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
||||||
|
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
include_once '../db_connect.php';
|
include_once 'db_connect.php';
|
||||||
|
|
||||||
$query = "SELECT id, nama, status, luas, ST_AsGeoJSON(geom) as geom FROM parsil";
|
$query = "SELECT id, nama, status, luas, ST_AsGeoJSON(geom) as geom FROM parsil";
|
||||||
$result = $conn->query($query);
|
$result = $conn->query($query);
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule poverty updated: 3aa1182a09...227a7091b5
@@ -29,12 +29,11 @@ const drawControl = new L.Control.Draw({
|
|||||||
|
|
||||||
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
||||||
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
||||||
isDrawingMode = true;
|
window.currentDrawMode = window.currentDrawMode || 'drawing';
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isDrawingMode = false;
|
|
||||||
window.currentDrawMode = null;
|
window.currentDrawMode = null;
|
||||||
window.activeDrawHandler = null;
|
window.activeDrawHandler = null;
|
||||||
window.deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ function makeSpbuIcon(is24) {
|
|||||||
const spbuGreenIcon = makeSpbuIcon(true);
|
const spbuGreenIcon = makeSpbuIcon(true);
|
||||||
const spbuRedIcon = makeSpbuIcon(false);
|
const spbuRedIcon = makeSpbuIcon(false);
|
||||||
|
|
||||||
let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil)
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch SPBU
|
// Fetch SPBU
|
||||||
function loadSpbu() {
|
function loadSpbu() {
|
||||||
@@ -75,7 +73,8 @@ function addSpbuMarker(item) {
|
|||||||
|
|
||||||
// Map Click -> Form Add
|
// Map Click -> Form Add
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
if (isDrawingMode) return; // Jangan muncul form jika sedang gambar garis/polygon
|
// Jangan muncul form jika sedang dalam draw mode (jalan/parsil)
|
||||||
|
if (window.currentDrawMode) return;
|
||||||
|
|
||||||
// Hanya proses jika mode tambah SPBU aktif
|
// Hanya proses jika mode tambah SPBU aktif
|
||||||
if (window.currentAddMode === 'spbu') {
|
if (window.currentAddMode === 'spbu') {
|
||||||
|
|||||||
+11
-46
@@ -29,11 +29,6 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|||||||
|
|
||||||
// Inisialisasi FeatureGroups untuk masing-masing layer
|
// Inisialisasi FeatureGroups untuk masing-masing layer
|
||||||
const spbuLayer = L.featureGroup().addTo(map);
|
const spbuLayer = L.featureGroup().addTo(map);
|
||||||
const jalanLayer = L.featureGroup().addTo(map);
|
|
||||||
const parsilLayer = L.featureGroup().addTo(map);
|
|
||||||
const rumahIbadahLayer = L.featureGroup().addTo(map);
|
|
||||||
const pendudukMiskinLayer = L.featureGroup().addTo(map);
|
|
||||||
const geoJsonLayer = L.featureGroup().addTo(map);
|
|
||||||
|
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById('searchInput');
|
||||||
const searchClear = document.getElementById('searchClear');
|
const searchClear = document.getElementById('searchClear');
|
||||||
@@ -78,22 +73,6 @@ window.showSearchResults = function(query) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof jalanLayer !== 'undefined') {
|
|
||||||
jalanLayer.eachLayer(layer => {
|
|
||||||
if (layer.jalanData && layer.jalanData.nama && layer.jalanData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Jalan', nama: layer.jalanData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof parsilLayer !== 'undefined') {
|
|
||||||
parsilLayer.eachLayer(layer => {
|
|
||||||
if (layer.parsilData && layer.parsilData.nama && layer.parsilData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Parsil', nama: layer.parsilData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
resultsContainer.innerHTML = '<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
|
resultsContainer.innerHTML = '<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -148,10 +127,6 @@ const setupLayerToggle = (id, layer) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setupLayerToggle('layerSpbu', spbuLayer);
|
setupLayerToggle('layerSpbu', spbuLayer);
|
||||||
setupLayerToggle('layerJalan', jalanLayer);
|
|
||||||
setupLayerToggle('layerParsil', parsilLayer);
|
|
||||||
setupLayerToggle('layerRumahIbadah', rumahIbadahLayer);
|
|
||||||
setupLayerToggle('layerMiskin', pendudukMiskinLayer);
|
|
||||||
|
|
||||||
// --- Sub-layer Toggle (expand/collapse) ---
|
// --- Sub-layer Toggle (expand/collapse) ---
|
||||||
window.toggleSubLayer = function(subId, iconEl) {
|
window.toggleSubLayer = function(subId, iconEl) {
|
||||||
@@ -174,26 +149,6 @@ window.applySubFilter = function(type) {
|
|||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'jalan') {
|
|
||||||
const checked = [...document.querySelectorAll('.sub-jalan:checked')].map(el => el.value);
|
|
||||||
jalanLayer.eachLayer(layer => {
|
|
||||||
if (!layer.jalanData) return;
|
|
||||||
if (checked.includes(layer.jalanData.status)) {
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
|
||||||
} else {
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'parsil') {
|
|
||||||
const checked = [...document.querySelectorAll('.sub-parsil:checked')].map(el => el.value);
|
|
||||||
parsilLayer.eachLayer(layer => {
|
|
||||||
if (!layer.parsilData) return;
|
|
||||||
if (checked.includes(layer.parsilData.status)) {
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
|
||||||
} else {
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,7 +198,11 @@ window.closeConfirmModal = function() {
|
|||||||
if (confirmModal) confirmModal.classList.remove('show');
|
if (confirmModal) confirmModal.classList.remove('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
window.currentAddMode = null; // 'spbu', 'jalan', 'parsil'
|
// --- Add Mode (untuk SPBU klik peta) ---
|
||||||
|
window.currentAddMode = null;
|
||||||
|
window.currentDrawMode = null;
|
||||||
|
window.activeDrawHandler = null;
|
||||||
|
|
||||||
window.activateAddMode = function(mode) {
|
window.activateAddMode = function(mode) {
|
||||||
if (window.currentAddMode === mode) {
|
if (window.currentAddMode === mode) {
|
||||||
window.deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
@@ -254,6 +213,12 @@ window.activateAddMode = function(mode) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.deactivateAddMode = function() {
|
window.deactivateAddMode = function() {
|
||||||
|
if (window.activeDrawHandler) {
|
||||||
|
try { window.activeDrawHandler.disable(); } catch(e) {}
|
||||||
|
window.activeDrawHandler = null;
|
||||||
|
}
|
||||||
window.currentAddMode = null;
|
window.currentAddMode = null;
|
||||||
|
window.currentDrawMode = null;
|
||||||
document.getElementById('map').style.cursor = '';
|
document.getElementById('map').style.cursor = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,17 @@
|
|||||||
rightPanelTitle.textContent = '🛣️ Daftar Jalan';
|
rightPanelTitle.textContent = '🛣️ Daftar Jalan';
|
||||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
rightPanelActions.innerHTML = `
|
||||||
|
<button class="btn-panel-add" id="btnPanelAddJalan">
|
||||||
|
<i class="fas fa-plus"></i> Tambah Jalan
|
||||||
|
</button>`;
|
||||||
|
document.getElementById('btnPanelAddJalan').addEventListener('click', () => {
|
||||||
|
window.activateDraw('polyline');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
rightPanelActions.innerHTML = '';
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
jalanLayer.eachLayer(l => {
|
jalanLayer.eachLayer(l => {
|
||||||
@@ -209,7 +219,17 @@
|
|||||||
rightPanelTitle.textContent = '🗺️ Daftar Parsil Tanah';
|
rightPanelTitle.textContent = '🗺️ Daftar Parsil Tanah';
|
||||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
rightPanelActions.innerHTML = `
|
||||||
|
<button class="btn-panel-add" id="btnPanelAddParsil">
|
||||||
|
<i class="fas fa-plus"></i> Tambah Parsil
|
||||||
|
</button>`;
|
||||||
|
document.getElementById('btnPanelAddParsil').addEventListener('click', () => {
|
||||||
|
window.activateDraw('polygon');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
rightPanelActions.innerHTML = '';
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
parsilLayer.eachLayer(l => {
|
parsilLayer.eachLayer(l => {
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
require_once('../poverty/api/auth_helper.php');
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
include_once '../db_connect.php';
|
include_once('db_connect.php');
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- DATABASE: webgis_spbu
|
||||||
|
-- Tabel: spbu (Lokasi SPBU)
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
CREATE DATABASE IF NOT EXISTS webgis_spbu CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE webgis_spbu;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spbu (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
nama VARCHAR(255) NOT NULL,
|
||||||
|
no_wa VARCHAR(20) NOT NULL DEFAULT '',
|
||||||
|
is_24_jam TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
lat DOUBLE NOT NULL,
|
||||||
|
lng DOUBLE NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
$host = "localhost";
|
||||||
|
$user = "root";
|
||||||
|
$pass = "";
|
||||||
|
$db = "webgis_spbu";
|
||||||
|
|
||||||
|
// Coba koneksi ke server dan pilih database
|
||||||
|
$conn = new mysqli($host, $user, $pass, $db);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
// Set charset
|
||||||
|
$conn->set_charset("utf8mb4");
|
||||||
|
|
||||||
|
// Jika di-include oleh file API, biarkan $conn tersedia
|
||||||
|
?>
|
||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
+42
-174
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WebGIS Pemetaan Jaringan Jalan</title>
|
<title>WebGIS Pemetaan Lokasi SPBU</title>
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="search-icon">
|
<div class="search-icon">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="search-input" id="searchInput" placeholder="Cari SPBU, jalan, parsil, rumah ibadah...">
|
<input type="text" class="search-input" id="searchInput" placeholder="Cari SPBU...">
|
||||||
<button class="search-clear" id="searchClear">
|
<button class="search-clear" id="searchClear">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -51,22 +51,50 @@
|
|||||||
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
<i class="fas fa-chevron-right" id="sidebarToggleIcon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Tombol Kembali ke Beranda -->
|
||||||
|
<a href="/webgis/index.html" class="btn-back-home" title="Kembali ke Beranda">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
<span>Beranda</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-back-home {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 70px;
|
||||||
|
z-index: 1100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #4f46e5;
|
||||||
|
border: 1.5px solid rgba(79, 70, 229, 0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 2px 12px rgba(79, 70, 229, 0.12);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-back-home:hover {
|
||||||
|
background: #4f46e5;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #4f46e5;
|
||||||
|
box-shadow: 0 4px 16px rgba(79, 70, 229, 0.3);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.btn-back-home i { font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Left Sidebar Menu -->
|
<!-- Left Sidebar Menu -->
|
||||||
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
||||||
<div class="sidebar-section-label">Umum</div>
|
<div class="sidebar-section-label">Menu</div>
|
||||||
<button class="sidebar-btn" id="menuSpbu" data-panel="spbu" title="SPBU">
|
<button class="sidebar-btn" id="menuSpbu" data-panel="spbu" title="SPBU">
|
||||||
<span class="sidebar-emoji">⛽</span>
|
<span class="sidebar-emoji">⛽</span>
|
||||||
<span class="sidebar-label">SPBU</span>
|
<span class="sidebar-label">SPBU</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sidebar-btn" id="menuJalan" data-panel="jalan" title="Jalan">
|
|
||||||
<span class="sidebar-emoji">🛣️</span>
|
|
||||||
<span class="sidebar-label">Jalan</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuParsil" data-panel="parsil" title="Parsil Tanah">
|
|
||||||
<span class="sidebar-emoji">🗺️</span>
|
|
||||||
<span class="sidebar-label">Parsil</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
@@ -101,51 +129,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Jalan -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerJalan" checked> Jalan
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subJalan', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subJalan" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Nasional" checked onchange="applySubFilter('jalan')"> Nasional
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Provinsi" checked onchange="applySubFilter('jalan')"> Provinsi
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Kabupaten" checked onchange="applySubFilter('jalan')"> Kabupaten
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Parsil -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerParsil" checked> Parsil Tanah
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subParsil', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subParsil" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="SHM" checked onchange="applySubFilter('parsil')"> SHM
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGB" checked onchange="applySubFilter('parsil')"> HGB
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGU" checked onchange="applySubFilter('parsil')"> HGU
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HP" checked onchange="applySubFilter('parsil')"> HP
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- GeoJSON Eksternal -->
|
<!-- GeoJSON Eksternal -->
|
||||||
@@ -215,55 +198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Bantuan Modal -->
|
|
||||||
<div id="logBantuanModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width:500px; max-height:85vh; overflow:hidden; display:flex; flex-direction:column;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 id="logBantuanTitle">Log Bantuan</h3>
|
|
||||||
<span class="modal-close" onclick="closeLogBantuanModal()">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="flex:1; overflow-y:auto; gap:0; padding:0;">
|
|
||||||
<!-- Riwayat -->
|
|
||||||
<div style="padding:15px; border-bottom:1px solid #eee;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Riwayat Bantuan</h4>
|
|
||||||
<div id="logBantuanList" style="max-height:220px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
|
|
||||||
<div class="panel-empty" style="padding:20px;">Belum ada riwayat</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Form Tambah -->
|
|
||||||
<div style="padding:15px;">
|
|
||||||
<h4 style="font-size:13px; font-weight:600; color:#555; margin-bottom:10px;">Tambah Log Bantuan</h4>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Rumah Ibadah</label>
|
|
||||||
<select id="logIbadahId"></select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tipe Bantuan</label>
|
|
||||||
<select id="logTipeBantuan">
|
|
||||||
<option value="Sembako">Sembako</option>
|
|
||||||
<option value="Uang Tunai">Uang Tunai</option>
|
|
||||||
<option value="Pakaian">Pakaian</option>
|
|
||||||
<option value="Pendidikan">Pendidikan</option>
|
|
||||||
<option value="Kesehatan">Kesehatan</option>
|
|
||||||
<option value="Lainnya">Lainnya</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tanggal</label>
|
|
||||||
<input type="date" id="logTanggal">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Keterangan (opsional)</label>
|
|
||||||
<textarea id="logKeterangan" rows="2" placeholder="Catatan tambahan..."></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn-save" id="logSaveBtn">Simpan Log</button>
|
|
||||||
<button class="btn-cancel" onclick="closeLogBantuanModal()">Tutup</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Floating Profile / Auth Control -->
|
<!-- Floating Profile / Auth Control -->
|
||||||
<div id="authWidget" class="auth-widget">
|
<div id="authWidget" class="auth-widget">
|
||||||
@@ -294,68 +229,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Management Modal -->
|
|
||||||
<div id="userManagementModal" class="unified-modal">
|
|
||||||
<div class="modal-content" style="width: 700px; max-width: 95%;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>Manajemen Pengguna (Admin)</h3>
|
|
||||||
<span class="modal-close" id="closeUserManagementModal">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="gap: 15px;">
|
|
||||||
<!-- Form Tambah/Edit User -->
|
|
||||||
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; gap: 10px;">
|
|
||||||
<h4 id="userFormTitle" style="font-size: 13px; font-weight:700; color:#334155; margin-bottom: 2px;">Tambah User Baru</h4>
|
|
||||||
<input type="hidden" id="manageUserId" value="">
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Username</label>
|
|
||||||
<input type="text" id="manageUsername" placeholder="Username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Password (Kosongkan jika tidak diubah)</label>
|
|
||||||
<input type="password" id="managePassword" placeholder="Password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Role</label>
|
|
||||||
<select id="manageRole">
|
|
||||||
<option value="pengelola" selected>Pengelola Rumah Ibadah</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" id="manageIbadahGroup">
|
|
||||||
<label>Rumah Ibadah yang Dikelola</label>
|
|
||||||
<select id="manageIbadahId">
|
|
||||||
<!-- Dikonstruksi dinamis dari data rumah ibadah -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 5px;">
|
|
||||||
<button id="btnCancelUserEdit" class="btn-cancel" style="padding: 6px 12px; display:none;">Batal</button>
|
|
||||||
<button id="btnSaveUser" class="btn-save" style="padding: 6px 16px;">Simpan User</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabel Daftar User -->
|
|
||||||
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #cbd5e1; border-radius: 8px;">
|
|
||||||
<table style="width: 100%; border-collapse: collapse; font-size: 13px; text-align: left;">
|
|
||||||
<thead>
|
|
||||||
<tr style="background: #f1f5f9; border-bottom: 1px solid #cbd5e1; color:#475569; position: sticky; top: 0; z-index: 10;">
|
|
||||||
<th style="padding: 10px 12px;">Username</th>
|
|
||||||
<th style="padding: 10px 12px;">Role</th>
|
|
||||||
<th style="padding: 10px 12px;">Rumah Ibadah</th>
|
|
||||||
<th style="padding: 10px 12px; text-align: center;">Aksi</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="userTableBody">
|
|
||||||
<!-- Diisi dinamis -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toast Notification Container -->
|
<!-- Toast Notification Container -->
|
||||||
<div id="toastContainer"></div>
|
<div id="toastContainer"></div>
|
||||||
@@ -373,16 +247,10 @@
|
|||||||
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
||||||
<!-- Fitur & Komponen -->
|
<!-- Fitur & Komponen -->
|
||||||
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
|
||||||
|
|
||||||
<!-- Core Auth Feature -->
|
|
||||||
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
||||||
|
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
include_once '../db_connect.php';
|
include_once 'db_connect.php';
|
||||||
|
|
||||||
$query = "SELECT * FROM spbu";
|
$query = "SELECT * FROM spbu";
|
||||||
$result = $conn->query($query);
|
$result = $conn->query($query);
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
require_once '../auth_helper.php';
|
include_once 'db_connect.php';
|
||||||
requireAdmin();
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body, html {
|
body,
|
||||||
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: 'Google Sans Flex', 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family:
|
||||||
|
"Google Sans Flex", "Inter", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +133,6 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
right: 340px;
|
right: 340px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.custom-layer-panel h3 {
|
.custom-layer-panel h3 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -431,12 +432,24 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon-spbu { background: #f0fdf4; }
|
.card-icon-spbu {
|
||||||
.card-icon-jalan { background: #eff6ff; }
|
background: #f0fdf4;
|
||||||
.card-icon-parsil { background: #fefce8; }
|
}
|
||||||
.card-icon-ibadah { background: #fff7ed; }
|
.card-icon-jalan {
|
||||||
.card-icon-miskin-out { background: #f0fdf4; }
|
background: #eff6ff;
|
||||||
.card-icon-miskin-in { background: #fef2f2; }
|
}
|
||||||
|
.card-icon-parsil {
|
||||||
|
background: #fefce8;
|
||||||
|
}
|
||||||
|
.card-icon-ibadah {
|
||||||
|
background: #fff7ed;
|
||||||
|
}
|
||||||
|
.card-icon-miskin-out {
|
||||||
|
background: #f0fdf4;
|
||||||
|
}
|
||||||
|
.card-icon-miskin-in {
|
||||||
|
background: #fef2f2;
|
||||||
|
}
|
||||||
|
|
||||||
.data-card-info {
|
.data-card-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -470,11 +483,26 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-green { background: #dcfce7; color: #16a34a; }
|
.badge-green {
|
||||||
.badge-red { background: #fee2e2; color: #dc2626; }
|
background: #dcfce7;
|
||||||
.badge-blue { background: #dbeafe; color: #2563eb; }
|
color: #16a34a;
|
||||||
.badge-orange { background: #ffedd5; color: #ea580c; }
|
}
|
||||||
.badge-yellow { background: #fef9c3; color: #ca8a04; }
|
.badge-red {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
.badge-blue {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
.badge-orange {
|
||||||
|
background: #ffedd5;
|
||||||
|
color: #ea580c;
|
||||||
|
}
|
||||||
|
.badge-yellow {
|
||||||
|
background: #fef9c3;
|
||||||
|
color: #ca8a04;
|
||||||
|
}
|
||||||
|
|
||||||
.data-card-actions {
|
.data-card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -520,7 +548,9 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
.data-card-expand {
|
.data-card-expand {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
transition: max-height 0.3s ease, padding 0.2s;
|
transition:
|
||||||
|
max-height 0.3s ease,
|
||||||
|
padding 0.2s;
|
||||||
border-top: 0px solid #f3f4f6;
|
border-top: 0px solid #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +653,8 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.popup-form input[type="text"],
|
.popup-form input[type="text"],
|
||||||
|
.form-group input[type="password"],
|
||||||
|
.popup-form input[type="password"],
|
||||||
.popup-form input[type="number"] {
|
.popup-form input[type="number"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@@ -720,6 +752,7 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
|
|
||||||
.modal-body input[type="text"],
|
.modal-body input[type="text"],
|
||||||
.modal-body input[type="number"],
|
.modal-body input[type="number"],
|
||||||
|
.modal-body input[type="password"],
|
||||||
.modal-body input[type="date"],
|
.modal-body input[type="date"],
|
||||||
.modal-body select,
|
.modal-body select,
|
||||||
.modal-body textarea {
|
.modal-body textarea {
|
||||||
@@ -862,16 +895,30 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes markerPop {
|
@keyframes markerPop {
|
||||||
0% { transform: rotate(-45deg) scale(0); }
|
0% {
|
||||||
100% { transform: rotate(-45deg) scale(1); }
|
transform: rotate(-45deg) scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(-45deg) scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Warna bubble per tipe */
|
/* Warna bubble per tipe */
|
||||||
.emoji-marker .bubble.spbu-24 { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
.emoji-marker .bubble.spbu-24 {
|
||||||
.emoji-marker .bubble.spbu-not24 { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||||
.emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); }
|
}
|
||||||
.emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
.emoji-marker .bubble.spbu-not24 {
|
||||||
.emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||||
|
}
|
||||||
|
.emoji-marker .bubble.ibadah {
|
||||||
|
background: linear-gradient(135deg, #f97316, #ea580c);
|
||||||
|
}
|
||||||
|
.emoji-marker .bubble.miskin-in {
|
||||||
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||||
|
}
|
||||||
|
.emoji-marker .bubble.miskin-out {
|
||||||
|
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Toast Notification ===== */
|
/* ===== Toast Notification ===== */
|
||||||
#toastContainer {
|
#toastContainer {
|
||||||
@@ -908,21 +955,44 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
animation: toastSlideOut 0.3s ease forwards;
|
animation: toastSlideOut 0.3s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-success { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
.toast-success {
|
||||||
.toast-error { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||||
.toast-info { background: linear-gradient(135deg, #6366f1, #4f46e5); }
|
}
|
||||||
.toast-warning { background: linear-gradient(135deg, #f59e0b, #d97706); }
|
.toast-error {
|
||||||
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||||
|
}
|
||||||
|
.toast-info {
|
||||||
|
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||||
|
}
|
||||||
|
.toast-warning {
|
||||||
|
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||||
|
}
|
||||||
|
|
||||||
.toast-icon { font-size: 16px; flex-shrink: 0; }
|
.toast-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes toastSlideIn {
|
@keyframes toastSlideIn {
|
||||||
0% { opacity: 0; transform: translateY(20px) scale(0.9); }
|
0% {
|
||||||
100% { opacity: 1; transform: translateY(0) scale(1); }
|
opacity: 0;
|
||||||
|
transform: translateY(20px) scale(0.9);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes toastSlideOut {
|
@keyframes toastSlideOut {
|
||||||
0% { opacity: 1; transform: translateY(0) scale(1); }
|
0% {
|
||||||
100% { opacity: 0; transform: translateY(10px) scale(0.95); }
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px) scale(0.95);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auth Widget Styling */
|
/* Auth Widget Styling */
|
||||||
@@ -1023,4 +1093,3 @@ body.right-panel-open .auth-widget {
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-bottom: 1px solid #e2e8f0;
|
border-bottom: 1px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user