feat: implement web GIS layout with sidebar, custom UI controls, and modular map feature integration
This commit is contained in:
@@ -13,6 +13,7 @@ if($result->num_rows > 0) {
|
|||||||
"id" => $row['id'],
|
"id" => $row['id'],
|
||||||
"nama" => $row['nama'],
|
"nama" => $row['nama'],
|
||||||
"kategori_bantuan" => $row['kategori_bantuan'],
|
"kategori_bantuan" => $row['kategori_bantuan'],
|
||||||
|
"jumlah_jiwa" => isset($row['jumlah_jiwa']) ? (int)$row['jumlah_jiwa'] : 1,
|
||||||
"lat" => (float)$row['lat'],
|
"lat" => (float)$row['lat'],
|
||||||
"lng" => (float)$row['lng']
|
"lng" => (float)$row['lng']
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ if($result->num_rows > 0) {
|
|||||||
$item = array(
|
$item = array(
|
||||||
"id" => $row['id'],
|
"id" => $row['id'],
|
||||||
"nama" => $row['nama'],
|
"nama" => $row['nama'],
|
||||||
|
"jenis" => $row['jenis'],
|
||||||
"alamat" => $row['alamat'],
|
"alamat" => $row['alamat'],
|
||||||
"radius" => (float)$row['radius'],
|
"radius" => (float)$row['radius'],
|
||||||
"lat" => (float)$row['lat'],
|
"lat" => (float)$row['lat'],
|
||||||
|
|||||||
+23
-10
@@ -84,10 +84,6 @@ body, html {
|
|||||||
|
|
||||||
/* Custom Layer Control Button */
|
/* Custom Layer Control Button */
|
||||||
.custom-layer-btn {
|
.custom-layer-btn {
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 1000;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
@@ -105,6 +101,18 @@ body, html {
|
|||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layerBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.right-panel-open #layerBtn {
|
||||||
|
right: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-layer-panel {
|
.custom-layer-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 80px;
|
top: 80px;
|
||||||
@@ -116,6 +124,11 @@ body, html {
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
display: none;
|
display: none;
|
||||||
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.right-panel-open .custom-layer-panel {
|
||||||
|
right: 340px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -138,13 +151,13 @@ body, html {
|
|||||||
.sidebar-toggle-btn {
|
.sidebar-toggle-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
top: 90px;
|
top: 190px;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
width: 36px;
|
border-radius: 8px;
|
||||||
height: 36px;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
background: white;
|
background: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -170,7 +183,7 @@ body, html {
|
|||||||
.left-sidebar {
|
.left-sidebar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
top: 136px; /* di bawah toggle button */
|
top: 250px; /* di bawah toggle button */
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -795,7 +808,7 @@ body, html {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
pointer-events: none;
|
pointer-events: auto; /* changed from none to allow click/drag */
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-marker .bubble {
|
.emoji-marker .bubble {
|
||||||
|
|||||||
@@ -37,36 +37,33 @@ map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
|||||||
isDrawingMode = false;
|
isDrawingMode = false;
|
||||||
window.currentDrawMode = null;
|
window.currentDrawMode = null;
|
||||||
window.activeDrawHandler = null;
|
window.activeDrawHandler = null;
|
||||||
deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.activateDraw = function(type) {
|
window.activateDraw = function(type) {
|
||||||
// Toggle: cek dari class button, lebih reliable daripada state variable
|
console.log("-> activateDraw dipanggil untuk tipe:", type);
|
||||||
const btnJalan = document.getElementById('btnMenuJalan');
|
|
||||||
const btnParsil = document.getElementById('btnMenuParsil');
|
|
||||||
const isJalanActive = btnJalan.classList.contains('active');
|
|
||||||
const isParsilActive = btnParsil.classList.contains('active');
|
|
||||||
|
|
||||||
if ((type === 'polyline' && isJalanActive) || (type === 'polygon' && isParsilActive)) {
|
// Toggle: jika mode yang sama ditekan lagi, batalkan
|
||||||
|
if (window.currentDrawMode === type) {
|
||||||
|
console.log("Mode", type, "sudah aktif, membatalkan...");
|
||||||
window.deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivateAddMode();
|
window.deactivateAddMode();
|
||||||
window.currentDrawMode = type;
|
window.currentDrawMode = type;
|
||||||
|
console.log("Mengaktifkan mode menggambar:", type);
|
||||||
|
|
||||||
if (type === 'polyline') {
|
if (type === 'polyline') {
|
||||||
const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline);
|
const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline);
|
||||||
handler.enable();
|
handler.enable();
|
||||||
window.activeDrawHandler = handler;
|
window.activeDrawHandler = handler;
|
||||||
btnJalan.classList.add('active');
|
|
||||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan';
|
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan';
|
||||||
} else if (type === 'polygon') {
|
} else if (type === 'polygon') {
|
||||||
const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon);
|
const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon);
|
||||||
handler.enable();
|
handler.enable();
|
||||||
window.activeDrawHandler = handler;
|
window.activeDrawHandler = handler;
|
||||||
btnParsil.classList.add('active');
|
|
||||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil';
|
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ window.saveEditJalan = function(id, namaId, statusId) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if(data.status === 'success') {
|
if(data.status === 'success') {
|
||||||
map.closePopup();
|
map.closePopup();
|
||||||
|
closeModal();
|
||||||
loadJalan();
|
loadJalan();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ map.on('mouseup', function(e) {
|
|||||||
isResizing = false;
|
isResizing = false;
|
||||||
map.dragging.enable();
|
map.dragging.enable();
|
||||||
if (resizingIbadah) {
|
if (resizingIbadah) {
|
||||||
updateIbadah(resizingIbadah.id, resizingIbadah.nama, resizingIbadah.alamat, resizingIbadah.radius, resizingIbadah.lat, resizingIbadah.lng);
|
updateIbadah(resizingIbadah.id, resizingIbadah.nama, resizingIbadah.jenis, resizingIbadah.alamat, resizingIbadah.radius, resizingIbadah.lat, resizingIbadah.lng);
|
||||||
}
|
}
|
||||||
resizingCircle = null;
|
resizingCircle = null;
|
||||||
resizingIbadah = null;
|
resizingIbadah = null;
|
||||||
@@ -89,11 +89,17 @@ function loadRumahIbadah() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addIbadahMarker(item) {
|
function addIbadahMarker(item) {
|
||||||
const marker = L.marker([item.lat, item.lng], { icon: makeIbadahIcon(item.jenis), draggable: true });
|
const lat = parseFloat(item.lat);
|
||||||
|
const lng = parseFloat(item.lng);
|
||||||
|
const radius = parseFloat(item.radius) || 100; // fallback if invalid
|
||||||
|
|
||||||
|
if (isNaN(lat) || isNaN(lng)) return;
|
||||||
|
|
||||||
|
const marker = L.marker([lat, lng], { icon: makeIbadahIcon(item.jenis), draggable: true });
|
||||||
|
|
||||||
// Lingkaran radius
|
// Lingkaran radius
|
||||||
const circle = L.circle([item.lat, item.lng], {
|
const circle = L.circle([lat, lng], {
|
||||||
radius: item.radius,
|
radius: radius,
|
||||||
color: '#ff7800',
|
color: '#ff7800',
|
||||||
weight: 2,
|
weight: 2,
|
||||||
fillColor: '#ff7800',
|
fillColor: '#ff7800',
|
||||||
@@ -153,7 +159,7 @@ function addIbadahMarker(item) {
|
|||||||
item.lat = newPos.lat;
|
item.lat = newPos.lat;
|
||||||
item.lng = newPos.lng;
|
item.lng = newPos.lng;
|
||||||
// Update ke DB
|
// Update ke DB
|
||||||
updateIbadah(item.id, item.nama, item.alamat, item.radius, newPos.lat, newPos.lng);
|
updateIbadah(item.id, item.nama, item.jenis, item.alamat, item.radius, newPos.lat, newPos.lng);
|
||||||
});
|
});
|
||||||
|
|
||||||
rumahIbadahLayer.addLayer(circle);
|
rumahIbadahLayer.addLayer(circle);
|
||||||
@@ -340,7 +346,11 @@ function updateIbadah(id, nama, jenis, alamat, radius, lat, lng) {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ id, nama, jenis, alamat, radius, lat, lng })
|
body: JSON.stringify({ id, nama, jenis, alamat, radius, lat, lng })
|
||||||
}).then(res => res.json()).then(data => {
|
}).then(res => res.json()).then(data => {
|
||||||
if(data.status === 'success') { map.closePopup(); loadRumahIbadah(); }
|
if(data.status === 'success') {
|
||||||
|
if (typeof closeModal === 'function') closeModal();
|
||||||
|
map.closePopup();
|
||||||
|
loadRumahIbadah();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +385,11 @@ function loadPendudukMiskin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addMiskinMarker(item) {
|
function addMiskinMarker(item) {
|
||||||
const marker = L.marker([item.lat, item.lng], { icon: makeMiskinIcon(false), draggable: true });
|
const lat = parseFloat(item.lat);
|
||||||
|
const lng = parseFloat(item.lng);
|
||||||
|
if (isNaN(lat) || isNaN(lng)) return;
|
||||||
|
|
||||||
|
const marker = L.marker([lat, lng], { icon: makeMiskinIcon(false), draggable: true });
|
||||||
marker.miskinData = item;
|
marker.miskinData = item;
|
||||||
|
|
||||||
const d = item;
|
const d = item;
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ window.saveEditParsil = function(id, namaId, statusId) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if(data.status === 'success') {
|
if(data.status === 'success') {
|
||||||
map.closePopup();
|
map.closePopup();
|
||||||
|
closeModal();
|
||||||
loadParsil();
|
loadParsil();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ function updateSpbu(id, nama, no_wa, is_24_jam, lat, lng) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if(data.status === 'success') {
|
if(data.status === 'success') {
|
||||||
map.closePopup();
|
map.closePopup();
|
||||||
|
closeModal();
|
||||||
loadSpbu();
|
loadSpbu();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ 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');
|
||||||
|
|
||||||
|
searchInput.addEventListener('focus', function() {
|
||||||
|
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) {
|
||||||
@@ -111,6 +115,7 @@ const layerBtn = document.getElementById('layerBtn');
|
|||||||
const layerPanel = document.getElementById('layerPanel');
|
const layerPanel = document.getElementById('layerPanel');
|
||||||
|
|
||||||
layerBtn.addEventListener('click', function() {
|
layerBtn.addEventListener('click', function() {
|
||||||
|
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
||||||
layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block';
|
layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,10 @@
|
|||||||
|
|
||||||
// ---- Open / Close Panel ----
|
// ---- Open / Close Panel ----
|
||||||
function openPanel(panel) {
|
function openPanel(panel) {
|
||||||
|
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
||||||
activePanel = panel;
|
activePanel = panel;
|
||||||
rightPanel.classList.add('open');
|
rightPanel.classList.add('open');
|
||||||
|
document.body.classList.add('right-panel-open');
|
||||||
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
||||||
const activeBtn = document.querySelector(`.sidebar-btn[data-panel="${panel}"]`);
|
const activeBtn = document.querySelector(`.sidebar-btn[data-panel="${panel}"]`);
|
||||||
if (activeBtn) activeBtn.classList.add('active');
|
if (activeBtn) activeBtn.classList.add('active');
|
||||||
@@ -64,8 +66,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closePanel() {
|
function closePanel() {
|
||||||
|
if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode();
|
||||||
activePanel = null;
|
activePanel = null;
|
||||||
rightPanel.classList.remove('open');
|
rightPanel.classList.remove('open');
|
||||||
|
document.body.classList.remove('right-panel-open');
|
||||||
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.sidebar-btn').forEach(b => b.classList.remove('active'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,25 +76,13 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
<!-- Right Data Panel (includes layer control) -->
|
|
||||||
<div class="right-panel" id="rightPanel">
|
|
||||||
<div class="right-panel-header">
|
|
||||||
<h3 id="rightPanelTitle">Data</h3>
|
|
||||||
<button class="right-panel-close" id="rightPanelClose"><i class="fas fa-times"></i></button>
|
|
||||||
</div>
|
|
||||||
<div class="right-panel-actions" id="rightPanelActions"></div>
|
|
||||||
<div class="right-panel-body" id="rightPanelBody">
|
|
||||||
<div class="panel-empty">Pilih menu di kiri untuk menampilkan data</div>
|
|
||||||
</div>
|
|
||||||
</div><!-- /right-panel -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Custom Layer Control Button -->
|
<!-- Custom Layer Control Button -->
|
||||||
<button class="custom-layer-btn" id="layerBtn" title="Atur Layer">
|
<button class="custom-layer-btn" id="layerBtn" title="Atur Layer">
|
||||||
<i class="fas fa-layer-group fa-lg"></i>
|
<i class="fas fa-layer-group fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div>
|
||||||
<!-- Custom Layer Control Panel -->
|
<!-- Custom Layer Control Panel -->
|
||||||
<div class="custom-layer-panel" id="layerPanel">
|
<div class="custom-layer-panel" id="layerPanel">
|
||||||
<h3>Daftar Layer</h3>
|
<h3>Daftar Layer</h3>
|
||||||
@@ -201,6 +189,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div><!-- /custom-layer-panel -->
|
</div><!-- /custom-layer-panel -->
|
||||||
|
|
||||||
|
<!-- Right Data Panel -->
|
||||||
|
<div class="right-panel" id="rightPanel">
|
||||||
|
<div class="right-panel-header">
|
||||||
|
<h3 id="rightPanelTitle">Data</h3>
|
||||||
|
<button class="right-panel-close" id="rightPanelClose"><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="right-panel-actions" id="rightPanelActions"></div>
|
||||||
|
<div class="right-panel-body" id="rightPanelBody">
|
||||||
|
<div class="panel-empty">Pilih menu di kiri untuk menampilkan data</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- /right-panel -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Map Container -->
|
<!-- Map Container -->
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
@@ -298,16 +300,16 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-textpath@1.2.3/leaflet.textpath.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/leaflet-textpath@1.2.3/leaflet.textpath.min.js"></script>
|
||||||
|
|
||||||
<!-- Main Map Initialization -->
|
<!-- Main Map Initialization -->
|
||||||
<script src="assets/js/map.js"></script>
|
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
||||||
<!-- Fitur & Komponen -->
|
<!-- Fitur & Komponen -->
|
||||||
<script src="assets/js/features/spbu.js"></script>
|
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/jalan.js"></script>
|
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/parsil.js"></script>
|
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/draw_control.js"></script>
|
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/geolocation.js"></script>
|
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/kemiskinan.js"></script>
|
<script src="assets/js/features/kemiskinan.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/geojson.js"></script>
|
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/panel.js"></script>
|
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user