From 0ae903c0d1939baec11c9095d1afc350f715adaf Mon Sep 17 00:00:00 2001 From: Syariffullah Date: Thu, 11 Jun 2026 16:40:58 +0700 Subject: [PATCH] feat: implement full CRUD functionality for jalan, parsil, and spbu modules including database integration and map interaction features --- geolocation.js | 30 - index.html | 2498 ++++++++++++--------- jalan/assets/js/features/auth.js | 155 ++ jalan/assets/js/features/draw_control.js | 3 +- jalan/assets/js/features/spbu.js | 5 +- jalan/assets/js/map.js | 179 ++ jalan/assets/js/panel.js | 159 ++ jalan/create.php | 4 +- jalan/database.sql | 16 + jalan/db_connect.php | 2 +- jalan/delete.php | 4 +- jalan/index.php | 231 +- jalan/read.php | 2 +- jalan/update.php | 4 +- parsil/assets/js/features/draw_control.js | 3 +- parsil/assets/js/features/spbu.js | 5 +- parsil/assets/js/map.js | 415 ++-- parsil/assets/js/panel.js | 28 +- parsil/create.php | 4 +- parsil/database.sql | 16 + parsil/db_connect.php | 17 + parsil/delete.php | 4 +- parsil/index.php | 211 +- parsil/read.php | 2 +- parsil/update.php | 4 +- poverty | 2 +- spbu/assets/js/features/draw_control.js | 3 +- spbu/assets/js/features/spbu.js | 5 +- spbu/assets/js/map.js | 57 +- spbu/assets/js/panel.js | 28 +- spbu/create.php | 4 +- spbu/database.sql | 16 + spbu/db_connect.php | 17 + spbu/delete.php | 4 +- spbu/index.php | 218 +- spbu/read.php | 2 +- spbu/update.php | 4 +- style.css | 1315 ++++++----- 38 files changed, 3098 insertions(+), 2578 deletions(-) delete mode 100644 geolocation.js create mode 100644 jalan/assets/js/features/auth.js create mode 100644 jalan/assets/js/map.js create mode 100644 jalan/assets/js/panel.js create mode 100644 jalan/database.sql create mode 100644 parsil/database.sql create mode 100644 parsil/db_connect.php create mode 100644 spbu/database.sql create mode 100644 spbu/db_connect.php diff --git a/geolocation.js b/geolocation.js deleted file mode 100644 index aa6179b..0000000 --- a/geolocation.js +++ /dev/null @@ -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 = ''; -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."); -}); diff --git a/index.html b/index.html index 6a28344..f70b996 100644 --- a/index.html +++ b/index.html @@ -1,1098 +1,1404 @@ - + - - - - WebGIS β€” Sistem Informasi Geografis | Portofolio Tugas - - - - - - - - - - - - - - - - -
-
-
- - Sistem Informasi Geografis -
- -

- Portofolio Proyek
- WebGIS Interaktif -

- -

- Kumpulan tugas pemetaan berbasis web menggunakan teknologi geospasial modern β€” - dari peta jalan, persil, hingga visualisasi data sosial. -

- -
-
-
4
-
Proyek
-
-
-
-
GIS
-
Mata Kuliah
-
-
-
-
Web
-
Platform
-
-
- - - - Lihat Semua Proyek - - -
-
- Scroll -
-
-
- - -
-
-

Teknologi yang Digunakan

-
-
- - Leaflet.js -
-
- - PHP -
-
- - MySQL -
-
- - HTML / CSS / JS -
-
- - GeoJSON -
-
- - XAMPP -
-
-
-
- - -
- -
- - - - - - - + + + + WebGIS Ò€” Sistem Informasi Geografis | Portofolio Tugas + + + + + + + + + + + + + + + +
+
+
+ + Sistem Informasi Geografis +
+ +

+ Portofolio Proyek
+ WebGIS Interaktif +

+ +

+ Kumpulan tugas pemetaan berbasis web menggunakan teknologi geospasial + modern β€” dari peta jalan, persil, hingga visualisasi data sosial. +

+ +
+
πŸ‘€
+
+
Syariffullah
+
NIM: D1041231018
+
+
+
+
Sistem Informasi Geografis
+
Teknik Informatika
+
+
+ +
+
+
+ 4 +
+
Proyek
+
+
+
+
GIS
+
Mata Kuliah
+
+
+
+
Web
+
Platform
+
+
+ + + + Lihat Semua Proyek + + +
+
+ Scroll +
+
+
+ + +
+
+

Teknologi yang Digunakan

+
+
+ + Leaflet.js +
+
+ + PHP +
+
+ + MySQL +
+
+ + HTML / CSS / JS +
+
+ + GeoJSON +
+
+ + XAMPP +
+
+
+
+ + +
+ +
+ + + + + + + diff --git a/jalan/assets/js/features/auth.js b/jalan/assets/js/features/auth.js new file mode 100644 index 0000000..2c83cfa --- /dev/null +++ b/jalan/assets/js/features/auth.js @@ -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 = ` +
+ + +
+ `; + + document.getElementById('authLogoutBtn').addEventListener('click', logout); + } else { + // Logged Out state + authWidget.innerHTML = ` + + `; + 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,'"'); + } + + // 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(); +})(); diff --git a/jalan/assets/js/features/draw_control.js b/jalan/assets/js/features/draw_control.js index a1e7026..59eeb5a 100644 --- a/jalan/assets/js/features/draw_control.js +++ b/jalan/assets/js/features/draw_control.js @@ -29,12 +29,11 @@ const drawControl = new L.Control.Draw({ // Flag untuk menghindari munculnya form SPBU saat sedang menggambar map.on(L.Draw.Event.DRAWSTART, function (e) { - isDrawingMode = true; + window.currentDrawMode = window.currentDrawMode || 'drawing'; }); map.on(L.Draw.Event.DRAWSTOP, function (e) { setTimeout(() => { - isDrawingMode = false; window.currentDrawMode = null; window.activeDrawHandler = null; window.deactivateAddMode(); diff --git a/jalan/assets/js/features/spbu.js b/jalan/assets/js/features/spbu.js index 07d168f..137a1ef 100644 --- a/jalan/assets/js/features/spbu.js +++ b/jalan/assets/js/features/spbu.js @@ -15,8 +15,6 @@ function makeSpbuIcon(is24) { const spbuGreenIcon = makeSpbuIcon(true); const spbuRedIcon = makeSpbuIcon(false); -let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil) - // Fetch SPBU function loadSpbu() { @@ -75,7 +73,8 @@ function addSpbuMarker(item) { // Map Click -> Form Add 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 if (window.currentAddMode === 'spbu') { diff --git a/jalan/assets/js/map.js b/jalan/assets/js/map.js new file mode 100644 index 0000000..b89473e --- /dev/null +++ b/jalan/assets/js/map.js @@ -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 = `${icons[type] || "ℹ️"}${message}`; + 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: + '© OpenStreetMap 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 = ""; +}; + diff --git a/jalan/assets/js/panel.js b/jalan/assets/js/panel.js new file mode 100644 index 0000000..965402d --- /dev/null +++ b/jalan/assets/js/panel.js @@ -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 = '
Memuat data...
'; + 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 = ` + `; + 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 = '
Belum ada data Jalan
'; + return; + } + + rightPanelBody.innerHTML = ''; + items.forEach(d => { + const card = document.createElement('div'); + card.className = 'data-card'; + let actionButtons = ''; + if (isAdmin) { + actionButtons = ` +
+ + +
+ `; + } + card.innerHTML = ` +
+
πŸ›£οΈ
+
+
${escHtml(d.nama)}
+
πŸ“ ${d.panjang.toFixed(2)} m β€’ 🏷️ ${escHtml(d.status)}
+
+ ${actionButtons} +
`; + 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,'"'); + } + + // ---- Expose ---- + window.openRightPanel = openPanel; + +})(); diff --git a/jalan/create.php b/jalan/create.php index 7d65e09..3e1286b 100644 --- a/jalan/create.php +++ b/jalan/create.php @@ -1,8 +1,6 @@ - WebGIS Pemetaan Kemiskinan + WebGIS Pemetaan Jaringan Jalan @@ -30,7 +30,7 @@
- + @@ -51,39 +51,50 @@ + + + + Beranda + + + + @@ -100,24 +111,6 @@
Infrastruktur & Lahan
- -
-
- - -
- -
-
@@ -139,30 +132,6 @@
- -
-
- - -
- -
-
Pemetaan Kemiskinan
@@ -255,55 +224,7 @@ - -
- -
+
@@ -334,68 +255,7 @@
- -
- -
+
@@ -412,17 +272,12 @@ - - - - - - + diff --git a/jalan/read.php b/jalan/read.php index ade48aa..10eb614 100644 --- a/jalan/read.php +++ b/jalan/read.php @@ -1,6 +1,6 @@ query($query); diff --git a/jalan/update.php b/jalan/update.php index 3e65d91..8e3db52 100644 --- a/jalan/update.php +++ b/jalan/update.php @@ -1,8 +1,6 @@ { - isDrawingMode = false; window.currentDrawMode = null; window.activeDrawHandler = null; window.deactivateAddMode(); diff --git a/parsil/assets/js/features/spbu.js b/parsil/assets/js/features/spbu.js index 07d168f..137a1ef 100644 --- a/parsil/assets/js/features/spbu.js +++ b/parsil/assets/js/features/spbu.js @@ -15,8 +15,6 @@ function makeSpbuIcon(is24) { const spbuGreenIcon = makeSpbuIcon(true); const spbuRedIcon = makeSpbuIcon(false); -let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil) - // Fetch SPBU function loadSpbu() { @@ -75,7 +73,8 @@ function addSpbuMarker(item) { // Map Click -> Form Add 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 if (window.currentAddMode === 'spbu') { diff --git a/parsil/assets/js/map.js b/parsil/assets/js/map.js index db153cc..3a25e01 100644 --- a/parsil/assets/js/map.js +++ b/parsil/assets/js/map.js @@ -1,259 +1,278 @@ // 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 ===== -window.showToast = function(message, type = 'success', duration = 3500) { - const icons = { success: 'βœ…', error: '❌', info: 'ℹ️', warning: '⚠️' }; - const container = document.getElementById('toastContainer'); - if (!container) return; +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 = `${icons[type] || 'ℹ️'}${message}`; - container.appendChild(toast); + const toast = document.createElement("div"); + toast.className = `toast toast-${type}`; + toast.innerHTML = `${icons[type] || "ℹ️"}${message}`; + container.appendChild(toast); - setTimeout(() => { - toast.classList.add('hiding'); - toast.addEventListener('animationend', () => toast.remove()); - }, duration); + 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(); }); +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: '© OpenStreetMap contributors' +L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: + '© OpenStreetMap contributors', }).addTo(map); // 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 rumahIbadahLayer = L.featureGroup().addTo(map); -const pendudukMiskinLayer = L.featureGroup().addTo(map); -const geoJsonLayer = L.featureGroup().addTo(map); -const searchInput = document.getElementById('searchInput'); -const searchClear = document.getElementById('searchClear'); +const searchInput = document.getElementById("searchInput"); +const searchClear = document.getElementById("searchClear"); if (searchInput) { - searchInput.addEventListener('focus', function() { - if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode(); - }); + 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'; - } - }); + 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"; + } + }); } if (searchClear) { - searchClear.addEventListener('click', function() { - searchInput.value = ''; - searchClear.style.visibility = 'hidden'; - document.getElementById('searchResults').style.display = 'none'; - searchInput.focus(); - }); + searchClear.addEventListener("click", function () { + searchInput.value = ""; + searchClear.style.visibility = "hidden"; + document.getElementById("searchResults").style.display = "none"; + searchInput.focus(); + }); } -window.showSearchResults = function(query) { - const resultsContainer = document.getElementById('searchResults'); - if (!resultsContainer) return; - resultsContainer.innerHTML = ''; - let results = []; - - if (typeof spbuLayer !== 'undefined') { - spbuLayer.eachLayer(layer => { - if (layer.spbuData && layer.spbuData.nama && layer.spbuData.nama.toLowerCase().includes(query)) { - results.push({ type: 'SPBU', nama: layer.spbuData.nama, layer: layer }); - } - }); - } +window.showSearchResults = function (query) { + const resultsContainer = document.getElementById("searchResults"); + if (!resultsContainer) return; + resultsContainer.innerHTML = ""; + let results = []; - 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 (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) { - resultsContainer.innerHTML = '
Tidak ada hasil
'; - } else { - results.forEach(res => { - const item = document.createElement('div'); - item.style.padding = '10px 15px'; - item.style.cursor = 'pointer'; - item.style.borderBottom = '1px solid #eee'; - item.style.fontSize = '14px'; - item.innerHTML = `${res.type}: ${res.nama}`; - item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f8f9fa'); - item.addEventListener('mouseleave', () => item.style.backgroundColor = 'white'); - item.addEventListener('click', () => { - if (res.layer instanceof L.Marker) { - map.setView(res.layer.getLatLng(), 17); - } else if (res.layer.getBounds) { - map.fitBounds(res.layer.getBounds()); - } - res.layer.openPopup(); - resultsContainer.style.display = 'none'; - }); - resultsContainer.appendChild(item); - }); - } - resultsContainer.style.display = 'block'; + if (results.length === 0) { + resultsContainer.innerHTML = + '
Tidak ada hasil
'; + } else { + results.forEach((res) => { + const item = document.createElement("div"); + item.style.padding = "10px 15px"; + item.style.cursor = "pointer"; + item.style.borderBottom = "1px solid #eee"; + item.style.fontSize = "14px"; + item.innerHTML = `${res.type}: ${res.nama}`; + item.addEventListener( + "mouseenter", + () => (item.style.backgroundColor = "#f8f9fa"), + ); + item.addEventListener( + "mouseleave", + () => (item.style.backgroundColor = "white"), + ); + item.addEventListener("click", () => { + if (res.layer instanceof L.Marker) { + map.setView(res.layer.getLatLng(), 17); + } else if (res.layer.getBounds) { + map.fitBounds(res.layer.getBounds()); + } + res.layer.openPopup(); + resultsContainer.style.display = "none"; + }); + resultsContainer.appendChild(item); + }); + } + resultsContainer.style.display = "block"; }; // UI Logic: Custom Layer Control Toggle -const layerBtn = document.getElementById('layerBtn'); -const layerPanel = document.getElementById('layerPanel'); +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'; - }); + 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'; +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); - }); - } + const el = document.getElementById(id); + if (el) { + el.addEventListener("change", function (e) { + e.target.checked ? map.addLayer(layer) : map.removeLayer(layer); + }); + } }; -setupLayerToggle('layerSpbu', spbuLayer); -setupLayerToggle('layerJalan', jalanLayer); -setupLayerToggle('layerParsil', parsilLayer); -setupLayerToggle('layerRumahIbadah', rumahIbadahLayer); -setupLayerToggle('layerMiskin', pendudukMiskinLayer); +setupLayerToggle("layerParsil", parsilLayer); // --- 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); +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 === 'spbu') { - const checked = [...document.querySelectorAll('.sub-spbu:checked')].map(el => el.value === '1'); - spbuLayer.eachLayer(layer => { - if (layer.spbuData === undefined) return; - if (checked.includes(layer.spbuData.is_24_jam)) { - layer.getElement && layer.getElement() && (layer.getElement().style.display = ''); - } else { - 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'); - } - }); - } +window.applySubFilter = function (type) { + if (type === "spbu") { + const checked = [...document.querySelectorAll(".sub-spbu:checked")].map( + (el) => el.value === "1", + ); + spbuLayer.eachLayer((layer) => { + if (layer.spbuData === undefined) return; + if (checked.includes(layer.spbuData.is_24_jam)) { + layer.getElement && + layer.getElement() && + (layer.getElement().style.display = ""); + } else { + 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"); + } + }); + } }; // --- Modal Logic --- -const unifiedModal = document.getElementById('unifiedModal'); -const modalTitle = document.getElementById('modalTitle'); -const modalBody = document.getElementById('modalBody'); +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.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'); +window.closeModal = function () { + if (unifiedModal) unifiedModal.classList.remove("show"); }; -const confirmModal = document.getElementById('confirmModal'); -const confirmMessage = document.getElementById('confirmMessage'); +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.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'); +window.closeConfirmModal = function () { + if (confirmModal) confirmModal.classList.remove("show"); }; window.currentAddMode = null; // 'spbu', 'jalan', 'parsil' -window.activateAddMode = function(mode) { - if (window.currentAddMode === mode) { - window.deactivateAddMode(); - return; - } - window.currentAddMode = mode; - document.getElementById('map').style.cursor = 'crosshair'; +window.activateAddMode = function (mode) { + if (window.currentAddMode === mode) { + window.deactivateAddMode(); + return; + } + window.currentAddMode = mode; + document.getElementById("map").style.cursor = "crosshair"; }; -window.deactivateAddMode = function() { - window.currentAddMode = null; - document.getElementById('map').style.cursor = ''; +window.deactivateAddMode = function () { + window.currentAddMode = null; + document.getElementById("map").style.cursor = ""; }; diff --git a/parsil/assets/js/panel.js b/parsil/assets/js/panel.js index 1aa68d6..2a8febd 100644 --- a/parsil/assets/js/panel.js +++ b/parsil/assets/js/panel.js @@ -152,8 +152,18 @@ function renderJalanPanel() { rightPanelTitle.textContent = 'πŸ›£οΈ Daftar Jalan'; const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin'); - - rightPanelActions.innerHTML = ''; + + if (isAdmin) { + rightPanelActions.innerHTML = ` + `; + document.getElementById('btnPanelAddJalan').addEventListener('click', () => { + window.activateDraw('polyline'); + }); + } else { + rightPanelActions.innerHTML = ''; + } const items = []; jalanLayer.eachLayer(l => { @@ -208,8 +218,18 @@ function renderParsilPanel() { rightPanelTitle.textContent = 'πŸ—ΊοΈ Daftar Parsil Tanah'; const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin'); - - rightPanelActions.innerHTML = ''; + + if (isAdmin) { + rightPanelActions.innerHTML = ` + `; + document.getElementById('btnPanelAddParsil').addEventListener('click', () => { + window.activateDraw('polygon'); + }); + } else { + rightPanelActions.innerHTML = ''; + } const items = []; parsilLayer.eachLayer(l => { diff --git a/parsil/create.php b/parsil/create.php index 2ebb088..64457d9 100644 --- a/parsil/create.php +++ b/parsil/create.php @@ -1,8 +1,6 @@ connect_error) { + die("Koneksi gagal: " . $conn->connect_error); +} +// Set charset +$conn->set_charset("utf8mb4"); + +// Jika di-include oleh file API, biarkan $conn tersedia +?> diff --git a/parsil/delete.php b/parsil/delete.php index aca7cf9..1ca6a63 100644 --- a/parsil/delete.php +++ b/parsil/delete.php @@ -1,8 +1,6 @@ - WebGIS Pemetaan Jaringan Jalan + WebGIS Pemetaan Bidang Tanah (Parsil) @@ -30,7 +30,7 @@
- + @@ -51,22 +51,50 @@ + + + + Beranda + + + + @@ -83,45 +111,6 @@
Infrastruktur & Lahan
- -
-
- - -
- -
- - -
-
- - -
- -
-
@@ -215,55 +204,7 @@
- -
- -
+
@@ -294,68 +235,7 @@
- -
- -
+
@@ -372,17 +252,12 @@ - - - - - - + diff --git a/parsil/read.php b/parsil/read.php index f24bacf..6903043 100644 --- a/parsil/read.php +++ b/parsil/read.php @@ -1,6 +1,6 @@ query($query); diff --git a/parsil/update.php b/parsil/update.php index 9e27353..5b1092c 100644 --- a/parsil/update.php +++ b/parsil/update.php @@ -1,8 +1,6 @@ { - isDrawingMode = false; window.currentDrawMode = null; window.activeDrawHandler = null; window.deactivateAddMode(); diff --git a/spbu/assets/js/features/spbu.js b/spbu/assets/js/features/spbu.js index 07d168f..137a1ef 100644 --- a/spbu/assets/js/features/spbu.js +++ b/spbu/assets/js/features/spbu.js @@ -15,8 +15,6 @@ function makeSpbuIcon(is24) { const spbuGreenIcon = makeSpbuIcon(true); const spbuRedIcon = makeSpbuIcon(false); -let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil) - // Fetch SPBU function loadSpbu() { @@ -75,7 +73,8 @@ function addSpbuMarker(item) { // Map Click -> Form Add 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 if (window.currentAddMode === 'spbu') { diff --git a/spbu/assets/js/map.js b/spbu/assets/js/map.js index db153cc..f51c6c4 100644 --- a/spbu/assets/js/map.js +++ b/spbu/assets/js/map.js @@ -29,11 +29,6 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { // 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 rumahIbadahLayer = L.featureGroup().addTo(map); -const pendudukMiskinLayer = L.featureGroup().addTo(map); -const geoJsonLayer = L.featureGroup().addTo(map); const searchInput = document.getElementById('searchInput'); const searchClear = document.getElementById('searchClear'); @@ -77,22 +72,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) { resultsContainer.innerHTML = '
Tidak ada hasil
'; @@ -148,10 +127,6 @@ const setupLayerToggle = (id, layer) => { }; setupLayerToggle('layerSpbu', spbuLayer); -setupLayerToggle('layerJalan', jalanLayer); -setupLayerToggle('layerParsil', parsilLayer); -setupLayerToggle('layerRumahIbadah', rumahIbadahLayer); -setupLayerToggle('layerMiskin', pendudukMiskinLayer); // --- Sub-layer Toggle (expand/collapse) --- window.toggleSubLayer = function(subId, iconEl) { @@ -174,26 +149,6 @@ window.applySubFilter = function(type) { 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'); }; -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) { if (window.currentAddMode === mode) { window.deactivateAddMode(); @@ -254,6 +213,12 @@ window.activateAddMode = function(mode) { }; window.deactivateAddMode = function() { + if (window.activeDrawHandler) { + try { window.activeDrawHandler.disable(); } catch(e) {} + window.activeDrawHandler = null; + } window.currentAddMode = null; + window.currentDrawMode = null; document.getElementById('map').style.cursor = ''; }; + diff --git a/spbu/assets/js/panel.js b/spbu/assets/js/panel.js index 1aa68d6..2a8febd 100644 --- a/spbu/assets/js/panel.js +++ b/spbu/assets/js/panel.js @@ -152,8 +152,18 @@ function renderJalanPanel() { rightPanelTitle.textContent = 'πŸ›£οΈ Daftar Jalan'; const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin'); - - rightPanelActions.innerHTML = ''; + + if (isAdmin) { + rightPanelActions.innerHTML = ` + `; + document.getElementById('btnPanelAddJalan').addEventListener('click', () => { + window.activateDraw('polyline'); + }); + } else { + rightPanelActions.innerHTML = ''; + } const items = []; jalanLayer.eachLayer(l => { @@ -208,8 +218,18 @@ function renderParsilPanel() { rightPanelTitle.textContent = 'πŸ—ΊοΈ Daftar Parsil Tanah'; const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin'); - - rightPanelActions.innerHTML = ''; + + if (isAdmin) { + rightPanelActions.innerHTML = ` + `; + document.getElementById('btnPanelAddParsil').addEventListener('click', () => { + window.activateDraw('polygon'); + }); + } else { + rightPanelActions.innerHTML = ''; + } const items = []; parsilLayer.eachLayer(l => { diff --git a/spbu/create.php b/spbu/create.php index 225f9fb..d86b19f 100644 --- a/spbu/create.php +++ b/spbu/create.php @@ -1,8 +1,8 @@ connect_error) { + die("Koneksi gagal: " . $conn->connect_error); +} +// Set charset +$conn->set_charset("utf8mb4"); + +// Jika di-include oleh file API, biarkan $conn tersedia +?> diff --git a/spbu/delete.php b/spbu/delete.php index e0afdcf..1209b92 100644 --- a/spbu/delete.php +++ b/spbu/delete.php @@ -1,8 +1,6 @@ - WebGIS Pemetaan Jaringan Jalan + WebGIS Pemetaan Lokasi SPBU @@ -30,7 +30,7 @@
- + @@ -51,22 +51,50 @@ + + + + Beranda + + + + @@ -101,51 +129,6 @@ - -
-
- - -
- -
- - -
-
- - -
- -
- @@ -215,55 +198,7 @@ - -
- -
+
@@ -294,68 +229,7 @@
- -
- -
+
@@ -373,16 +247,10 @@ - - - - - - - + diff --git a/spbu/read.php b/spbu/read.php index 942d823..f40b27f 100644 --- a/spbu/read.php +++ b/spbu/read.php @@ -1,6 +1,6 @@ query($query); diff --git a/spbu/update.php b/spbu/update.php index 83e5fb9..d23a7d4 100644 --- a/spbu/update.php +++ b/spbu/update.php @@ -1,8 +1,6 @@ .layer-group-header { - display: flex; - align-items: center; - justify-content: space-between; + display: flex; + align-items: center; + justify-content: space-between; } .layer-toggle-icon { - cursor: pointer; - padding: 2px 6px; - color: #666; - font-size: 11px; - border-radius: 4px; - transition: background 0.2s; - flex-shrink: 0; + cursor: pointer; + padding: 2px 6px; + color: #666; + font-size: 11px; + border-radius: 4px; + transition: background 0.2s; + flex-shrink: 0; } .layer-toggle-icon:hover { - background: #f0f0f0; - color: #333; + background: #f0f0f0; + color: #333; } .layer-toggle-icon i { - transition: transform 0.2s; + transition: transform 0.2s; } .layer-toggle-icon.collapsed i { - transform: rotate(-90deg); + transform: rotate(-90deg); } .sub-layer-list { - padding-left: 18px; - margin-top: 4px; - border-left: 2px solid #eee; - overflow: hidden; - transition: max-height 0.2s ease; + padding-left: 18px; + margin-top: 4px; + border-left: 2px solid #eee; + overflow: hidden; + transition: max-height 0.2s ease; } .sub-option { - font-size: 12px !important; - color: #555; - margin-bottom: 3px !important; + font-size: 12px !important; + color: #555; + margin-bottom: 3px !important; } /* ===== Emoji Marker (Pin Bubble) ===== */ .emoji-marker { - display: flex; - flex-direction: column; - align-items: center; - pointer-events: auto; /* changed from none to allow click/drag */ + display: flex; + flex-direction: column; + align-items: center; + pointer-events: auto; /* changed from none to allow click/drag */ } .emoji-marker .bubble { - width: 38px; - height: 38px; - border-radius: 50% 50% 50% 0; - transform: rotate(-45deg); - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 3px 10px rgba(0,0,0,0.25); - border: 2px solid rgba(255,255,255,0.8); - animation: markerPop 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; + width: 38px; + height: 38px; + border-radius: 50% 50% 50% 0; + transform: rotate(-45deg); + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25); + border: 2px solid rgba(255, 255, 255, 0.8); + animation: markerPop 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; } .emoji-marker .bubble span { - transform: rotate(45deg); - font-size: 18px; - line-height: 1; + transform: rotate(45deg); + font-size: 18px; + line-height: 1; } @keyframes markerPop { - 0% { transform: rotate(-45deg) scale(0); } - 100% { transform: rotate(-45deg) scale(1); } + 0% { + transform: rotate(-45deg) scale(0); + } + 100% { + transform: rotate(-45deg) scale(1); + } } /* Warna bubble per tipe */ -.emoji-marker .bubble.spbu-24 { background: linear-gradient(135deg, #22c55e, #16a34a); } -.emoji-marker .bubble.spbu-not24 { 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); } +.emoji-marker .bubble.spbu-24 { + background: linear-gradient(135deg, #22c55e, #16a34a); +} +.emoji-marker .bubble.spbu-not24 { + 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 ===== */ #toastContainer { - position: fixed; - bottom: 28px; - left: 50%; - transform: translateX(-50%); - z-index: 9999; - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - pointer-events: none; + position: fixed; + bottom: 28px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; } .toast { - display: flex; - align-items: center; - gap: 10px; - padding: 12px 20px; - border-radius: 12px; - font-size: 13px; - font-weight: 500; - color: white; - box-shadow: 0 6px 24px rgba(0,0,0,0.18); - backdrop-filter: blur(6px); - pointer-events: auto; - animation: toastSlideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; - max-width: 340px; - text-align: center; + display: flex; + align-items: center; + gap: 10px; + padding: 12px 20px; + border-radius: 12px; + font-size: 13px; + font-weight: 500; + color: white; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18); + backdrop-filter: blur(6px); + pointer-events: auto; + animation: toastSlideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; + max-width: 340px; + text-align: center; } .toast.hiding { - animation: toastSlideOut 0.3s ease forwards; + animation: toastSlideOut 0.3s ease forwards; } -.toast-success { background: linear-gradient(135deg, #22c55e, #16a34a); } -.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-success { + background: linear-gradient(135deg, #22c55e, #16a34a); +} +.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 { - 0% { opacity: 0; transform: translateY(20px) scale(0.9); } - 100% { opacity: 1; transform: translateY(0) scale(1); } + 0% { + opacity: 0; + transform: translateY(20px) scale(0.9); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } } @keyframes toastSlideOut { - 0% { opacity: 1; transform: translateY(0) scale(1); } - 100% { opacity: 0; transform: translateY(10px) scale(0.95); } + 0% { + opacity: 1; + transform: translateY(0) scale(1); + } + 100% { + opacity: 0; + transform: translateY(10px) scale(0.95); + } } /* Auth Widget Styling */ .auth-widget { - position: absolute; - top: 20px; - right: 80px; - z-index: 1000; - transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: absolute; + top: 20px; + right: 80px; + z-index: 1000; + transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); } body.right-panel-open .auth-widget { - right: 400px; + right: 400px; } .btn-login { - background: linear-gradient(135deg, #6366f1, #4f46e5); - color: white; - border: none; - border-radius: 24px; - height: 48px; - padding: 0 24px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); - display: flex; - align-items: center; - gap: 8px; - transition: all 0.2s ease; + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: white; + border: none; + border-radius: 24px; + height: 48px; + padding: 0 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s ease; } .btn-login:hover { - box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45); - transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45); + transform: translateY(-1px); } .user-profile-pill { - background-color: white; - border-radius: 24px; - height: 48px; - padding: 0 8px 0 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - display: flex; - align-items: center; - gap: 12px; - border: 1px solid rgba(226, 232, 240, 0.8); - backdrop-filter: blur(10px); - background: rgba(255, 255, 255, 0.9); + background-color: white; + border-radius: 24px; + height: 48px; + padding: 0 8px 0 16px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + gap: 12px; + border: 1px solid rgba(226, 232, 240, 0.8); + backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.9); } .user-profile-info { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .user-profile-name { - font-size: 12px; - font-weight: 700; - color: #1e1e2e; + font-size: 12px; + font-weight: 700; + color: #1e1e2e; } .user-profile-role { - font-size: 9px; - font-weight: 600; - color: #6366f1; - text-transform: uppercase; + font-size: 9px; + font-weight: 600; + color: #6366f1; + text-transform: uppercase; } .btn-profile-logout { - width: 32px; - height: 32px; - border-radius: 50%; - border: none; - background: #f1f5f9; - color: #64748b; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; + width: 32px; + height: 32px; + border-radius: 50%; + border: none; + background: #f1f5f9; + color: #64748b; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; } .btn-profile-logout:hover { - background: #fee2e2; - color: #ef4444; + background: #fee2e2; + color: #ef4444; } /* User management table styling */ #userManagementModal table th { - padding: 10px 12px; - font-weight: 600; - background-color: #f1f5f9; - border-bottom: 1px solid #cbd5e1; + padding: 10px 12px; + font-weight: 600; + background-color: #f1f5f9; + border-bottom: 1px solid #cbd5e1; } #userManagementModal table td { - padding: 10px 12px; - border-bottom: 1px solid #e2e8f0; + padding: 10px 12px; + border-bottom: 1px solid #e2e8f0; } -