feat: implement full CRUD functionality for jalan, parsil, and spbu modules including database integration and map interaction features

This commit is contained in:
Syariffullah
2026-06-11 16:40:58 +07:00
parent d10253a001
commit 0ae903c0d1
38 changed files with 3098 additions and 2578 deletions
+217 -198
View File
@@ -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 = `<span class="toast-icon">${icons[type] || '️'}</span><span>${message}</span>`;
container.appendChild(toast);
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);
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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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 = '<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
} 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 = `<strong>${res.type}:</strong> ${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 =
'<div style="padding: 10px 15px; color: #666; font-size: 14px;">Tidak ada hasil</div>';
} 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 = `<strong>${res.type}:</strong> ${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 = "";
};