initialize
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// --- Setup Leaflet Draw ---
|
||||
|
||||
// Layer terpisah untuk hasil gambar sementara sebelum disimpan
|
||||
const drawnItems = new L.FeatureGroup();
|
||||
map.addLayer(drawnItems);
|
||||
|
||||
const drawControl = new L.Control.Draw({
|
||||
draw: {
|
||||
polygon: {
|
||||
allowIntersection: false,
|
||||
showArea: true
|
||||
},
|
||||
polyline: {
|
||||
metric: true
|
||||
},
|
||||
circle: false,
|
||||
circlemarker: false,
|
||||
marker: false, // Marker default dimatikan, kita pakai klik peta untuk SPBU
|
||||
rectangle: false
|
||||
},
|
||||
edit: {
|
||||
featureGroup: drawnItems, // Kita tidak mengedit di drawnItems, karena data aslinya di jalanLayer/parsilLayer
|
||||
edit: false,
|
||||
remove: false
|
||||
}
|
||||
});
|
||||
// Hapus map.addControl(drawControl); agar tidak tampil UI bawaan LeafletJS
|
||||
|
||||
|
||||
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
||||
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
||||
isDrawingMode = true;
|
||||
});
|
||||
|
||||
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
||||
setTimeout(() => {
|
||||
isDrawingMode = false;
|
||||
window.currentDrawMode = null;
|
||||
window.activeDrawHandler = null;
|
||||
window.deactivateAddMode();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
window.activateDraw = function(type) {
|
||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||
if (!isAdmin) return;
|
||||
|
||||
console.log("-> activateDraw dipanggil untuk tipe:", type);
|
||||
|
||||
// Toggle: jika mode yang sama ditekan lagi, batalkan
|
||||
if (window.currentDrawMode === type) {
|
||||
console.log("Mode", type, "sudah aktif, membatalkan...");
|
||||
window.deactivateAddMode();
|
||||
return;
|
||||
}
|
||||
|
||||
window.deactivateAddMode();
|
||||
window.currentDrawMode = type;
|
||||
console.log("Mengaktifkan mode menggambar:", type);
|
||||
|
||||
if (type === 'polyline') {
|
||||
const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline);
|
||||
handler.enable();
|
||||
window.activeDrawHandler = handler;
|
||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan';
|
||||
} else if (type === 'polygon') {
|
||||
const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon);
|
||||
handler.enable();
|
||||
window.activeDrawHandler = handler;
|
||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil';
|
||||
}
|
||||
};
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function (e) {
|
||||
const type = e.layerType;
|
||||
const layer = e.layer;
|
||||
|
||||
// Convert layer to GeoJSON geometry
|
||||
const geoJson = layer.toGeoJSON().geometry;
|
||||
const geoJsonStr = JSON.stringify(geoJson);
|
||||
|
||||
if (type === 'polyline') {
|
||||
let length = 0;
|
||||
const latlngs = layer.getLatLngs();
|
||||
for (let i = 0; i < latlngs.length - 1; i++) {
|
||||
length += latlngs[i].distanceTo(latlngs[i + 1]);
|
||||
}
|
||||
|
||||
// Gunakan Modal alih-alih prompt
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Jalan</label>
|
||||
<input type="text" id="modalJalanNama" placeholder="Nama Jalan">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select id="modalJalanStatus">
|
||||
<option value="Nasional">Nasional</option>
|
||||
<option value="Provinsi">Provinsi</option>
|
||||
<option value="Kabupaten" selected>Kabupaten</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Panjang: ${length.toFixed(2)} m</label>
|
||||
</div>
|
||||
`;
|
||||
openModal("Tambah Jalan Baru", bodyHTML, function() {
|
||||
window.saveNewJalan(geoJsonStr, length, 'modalJalanNama', 'modalJalanStatus');
|
||||
});
|
||||
|
||||
}
|
||||
else if (type === 'polygon') {
|
||||
const latlngs = layer.getLatLngs()[0];
|
||||
let area = 0;
|
||||
if (L.GeometryUtil && L.GeometryUtil.geodesicArea) {
|
||||
area = L.GeometryUtil.geodesicArea(latlngs);
|
||||
}
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Pemilik/Area</label>
|
||||
<input type="text" id="modalParsilNama" placeholder="Contoh: Budi atau Sawah A">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status Tanah</label>
|
||||
<select id="modalParsilStatus">
|
||||
<option value="SHM" selected>SHM</option>
|
||||
<option value="HGB">HGB</option>
|
||||
<option value="HGU">HGU</option>
|
||||
<option value="HP">HP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Luas: ${area.toFixed(2)} m²</label>
|
||||
</div>
|
||||
`;
|
||||
openModal("Tambah Parsil Tanah", bodyHTML, function() {
|
||||
window.saveNewParsil(geoJsonStr, area, 'modalParsilNama', 'modalParsilStatus');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// --- Fitur Import GeoJSON ---
|
||||
|
||||
const fileGeoJson = document.getElementById('fileGeoJson');
|
||||
let geoJsonLayers = {};
|
||||
let geoJsonCounter = 0;
|
||||
|
||||
window.toggleGeoJsonMenu = function() {
|
||||
const list = document.getElementById('geoJsonFileList');
|
||||
const icon = document.getElementById('geoJsonToggleIcon');
|
||||
if (list.style.display === 'none') {
|
||||
list.style.display = 'block';
|
||||
icon.className = 'fas fa-minus';
|
||||
} else {
|
||||
list.style.display = 'none';
|
||||
icon.className = 'fas fa-plus';
|
||||
}
|
||||
};
|
||||
|
||||
// Cek apakah fitur GeoJSON ini terlihat seperti data penduduk miskin
|
||||
function isMiskinFeature(feature) {
|
||||
if (!feature || !feature.geometry || feature.geometry.type !== 'Point') return false;
|
||||
const props = feature.properties || {};
|
||||
const keys = Object.keys(props).map(k => k.toLowerCase());
|
||||
// Dianggap data miskin jika punya properti nama (dan bukan ibadah atau spbu)
|
||||
const hasNama = keys.some(k => ['nama', 'name', 'penduduk'].includes(k));
|
||||
const isIbadah = keys.some(k => ['jenis', 'radius', 'alamat'].includes(k));
|
||||
const isSpbu = keys.some(k => ['alamat_spbu', 'is_24_jam'].includes(k));
|
||||
return hasNama && !isIbadah && !isSpbu;
|
||||
}
|
||||
|
||||
fileGeoJson.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
try {
|
||||
const geoJsonData = JSON.parse(event.target.result);
|
||||
const fileName = file.name;
|
||||
|
||||
// Pisahkan fitur menjadi dua kelompok: miskin dan non-miskin
|
||||
const features = geoJsonData.type === 'FeatureCollection'
|
||||
? geoJsonData.features
|
||||
: [geoJsonData];
|
||||
|
||||
const miskinFeatures = features.filter(f => isMiskinFeature(f));
|
||||
const otherFeatures = features.filter(f => !isMiskinFeature(f));
|
||||
|
||||
// ---- Proses fitur miskin: Simpan ke DB, lalu tampilkan seperti penduduk miskin ----
|
||||
if (miskinFeatures.length > 0) {
|
||||
const bulkData = miskinFeatures.map(f => {
|
||||
const props = f.properties || {};
|
||||
const coords = f.geometry.coordinates;
|
||||
return {
|
||||
nama: props.nama || props.name || props.penduduk || 'Data Impor',
|
||||
kategori_bantuan: props.kategori_bantuan || props.kategori || props.bantuan || 'Makan',
|
||||
jumlah_jiwa: parseInt(props.jumlah_jiwa || props.jumlah || props.jiwa || 1, 10) || 1,
|
||||
lat: parseFloat(coords[1]),
|
||||
lng: parseFloat(coords[0])
|
||||
};
|
||||
});
|
||||
|
||||
fetch('../poverty/api/penduduk_miskin/bulk_create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(bulkData)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// Reload layer penduduk miskin agar marker tampil dengan edit/hapus/log bantuan
|
||||
if (typeof loadPendudukMiskin === 'function') {
|
||||
loadPendudukMiskin();
|
||||
}
|
||||
if (typeof window.refreshActivePanel === 'function') {
|
||||
window.refreshActivePanel();
|
||||
}
|
||||
showToast(data.message || 'Data berhasil diimpor ke layer Penduduk Miskin.', 'success');
|
||||
} else {
|
||||
showToast(data.message || 'Gagal menyimpan data miskin.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
showToast('Gagal terhubung ke server saat menyimpan data miskin.', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Proses fitur non-miskin: Render sebagai layer GeoJSON biasa ----
|
||||
if (otherFeatures.length > 0) {
|
||||
const layerId = 'gj_' + (++geoJsonCounter);
|
||||
const individualLayer = L.featureGroup();
|
||||
|
||||
const otherGeoJson = {
|
||||
type: 'FeatureCollection',
|
||||
features: otherFeatures
|
||||
};
|
||||
|
||||
L.geoJSON(otherGeoJson, {
|
||||
pointToLayer: function(feature, latlng) {
|
||||
const props = feature.properties || {};
|
||||
let emoji = props.emoji;
|
||||
|
||||
if (!emoji) {
|
||||
const text = JSON.stringify(props).toLowerCase();
|
||||
if (text.includes('spbu')) emoji = '⛽';
|
||||
else if (text.includes('masjid')) emoji = '🕌';
|
||||
else if (text.includes('gereja')) emoji = '⛪';
|
||||
else if (text.includes('vihara')) emoji = '🪷';
|
||||
else if (text.includes('pura')) emoji = '🛕';
|
||||
else if (text.includes('kelenteng')) emoji = '🏮';
|
||||
else emoji = '📍';
|
||||
}
|
||||
|
||||
let cls = 'miskin-out';
|
||||
if (emoji === '⛽') cls = 'spbu-24';
|
||||
else if (['🕌','⛪','🛕','🪷','🏮'].includes(emoji)) cls = 'ibadah';
|
||||
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div class="emoji-marker"><div class="bubble ${cls}"><span>${emoji}</span></div></div>`,
|
||||
iconSize: [38, 38],
|
||||
iconAnchor: [19, 38],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
|
||||
return L.marker(latlng, { icon: icon });
|
||||
},
|
||||
onEachFeature: function(feature, layer) {
|
||||
if (feature.properties) {
|
||||
let popupContent = '<h4>Informasi Feature</h4><ul style="list-style:none; padding:0; font-size:12px; margin: 0;">';
|
||||
for (let key in feature.properties) {
|
||||
popupContent += `<li><strong>${key}:</strong> ${feature.properties[key]}</li>`;
|
||||
}
|
||||
popupContent += '</ul>';
|
||||
layer.bindPopup(popupContent);
|
||||
}
|
||||
},
|
||||
style: function(feature) {
|
||||
return {
|
||||
color: (feature.properties && feature.properties.color) || '#3388ff',
|
||||
weight: 2,
|
||||
fillOpacity: 0.4
|
||||
};
|
||||
}
|
||||
}).addTo(individualLayer);
|
||||
|
||||
individualLayer.addTo(map);
|
||||
geoJsonLayers[layerId] = individualLayer;
|
||||
|
||||
// Tambahkan checkbox ke UI
|
||||
const container = document.getElementById('geoJsonLayersContainer');
|
||||
const label = document.createElement('label');
|
||||
label.className = 'layer-option';
|
||||
label.innerHTML = `<input type="checkbox" id="chk_${layerId}" checked> ${fileName}`;
|
||||
|
||||
label.querySelector('input').addEventListener('change', function(e) {
|
||||
if (e.target.checked) {
|
||||
map.addLayer(geoJsonLayers[layerId]);
|
||||
} else {
|
||||
map.removeLayer(geoJsonLayers[layerId]);
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(label);
|
||||
|
||||
if (miskinFeatures.length === 0) {
|
||||
showToast('File GeoJSON berhasil dimuat!', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset input file agar bisa import file yang sama
|
||||
fileGeoJson.value = '';
|
||||
|
||||
} catch (error) {
|
||||
showToast('Gagal memproses file GeoJSON. Pastikan format valid.', 'error');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// --- 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.");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// --- Fitur Jalan ---
|
||||
|
||||
const jalanColors = {
|
||||
'Nasional': '#ff0000', // Merah
|
||||
'Provinsi': '#0000ff', // Biru
|
||||
'Kabupaten': '#00ff00' // Hijau
|
||||
};
|
||||
|
||||
function loadJalan() {
|
||||
jalanLayer.clearLayers();
|
||||
fetch('../jalan/read.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.data) {
|
||||
data.data.forEach(item => {
|
||||
addJalanToMap(item);
|
||||
});
|
||||
}
|
||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
||||
});
|
||||
}
|
||||
|
||||
function addJalanToMap(item) {
|
||||
// geom adalah GeoJSON {type: "LineString", coordinates: [[lng, lat], ...]}
|
||||
const latlngs = item.geom.coordinates.map(coord => [coord[1], coord[0]]);
|
||||
|
||||
const polyline = L.polyline(latlngs, {
|
||||
color: jalanColors[item.status] || '#3388ff',
|
||||
weight: 5
|
||||
});
|
||||
|
||||
polyline.jalanData = item;
|
||||
|
||||
// Tampilkan label nama jalan sejajar dengan garis (diagonal)
|
||||
polyline.setText(item.nama, {
|
||||
center: true,
|
||||
offset: 0,
|
||||
attributes: {
|
||||
fill: '#000000',
|
||||
'font-weight': 'bold',
|
||||
'font-size': '14px',
|
||||
'dy': '7'
|
||||
}
|
||||
});
|
||||
|
||||
// Hitung popupContent sekali saat jalan dibuat
|
||||
const d = item;
|
||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||
let actionButtons = '';
|
||||
if (isAdmin) {
|
||||
actionButtons = `
|
||||
<div style="display:flex; gap:5px;">
|
||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditJalanModal(${d.id})">Edit</button>
|
||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteJalan(${d.id})">Hapus</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const popupContent = `
|
||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
||||
<h4 style="margin:0 0 5px 0;">Jalan ${d.nama}</h4>
|
||||
<p style="margin: 0 0 5px 0;"><b>Status:</b> ${d.status}</p>
|
||||
<p style="margin: 0 0 10px 0;"><b>Panjang:</b> ${d.panjang.toFixed(2)} m</p>
|
||||
${actionButtons}
|
||||
</div>
|
||||
`;
|
||||
|
||||
polyline.bindPopup(popupContent);
|
||||
|
||||
jalanLayer.addLayer(polyline);
|
||||
}
|
||||
|
||||
window.openEditJalanModal = function(id) {
|
||||
let d = null;
|
||||
jalanLayer.eachLayer(function(layer) {
|
||||
if (layer.jalanData && layer.jalanData.id == id) d = layer.jalanData;
|
||||
});
|
||||
if (!d) return;
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Jalan</label>
|
||||
<input type="text" id="editJalanNama" value="${d.nama}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select id="editJalanStatus">
|
||||
<option value="Nasional" ${d.status === 'Nasional' ? 'selected' : ''}>Nasional</option>
|
||||
<option value="Provinsi" ${d.status === 'Provinsi' ? 'selected' : ''}>Provinsi</option>
|
||||
<option value="Kabupaten" ${d.status === 'Kabupaten' ? 'selected' : ''}>Kabupaten</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Panjang: ${d.panjang.toFixed(2)} m</label>
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
openModal("Edit Jalan", bodyHTML, function() {
|
||||
window.saveEditJalan(d.id, 'editJalanNama', 'editJalanStatus');
|
||||
});
|
||||
};
|
||||
|
||||
window.saveEditJalan = function(id, namaId, statusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const status = document.getElementById(statusId).value;
|
||||
|
||||
fetch('../jalan/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, nama, status })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
closeModal();
|
||||
loadJalan();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.deleteJalan = function(id) {
|
||||
openConfirmModal("Yakin hapus jalan ini?", function() {
|
||||
fetch('../jalan/delete.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
loadJalan();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.saveNewJalan = function(geoJsonStr, panjang, namaId, statusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
if (!nama) {
|
||||
alert("Nama jalan harus diisi!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const status = document.getElementById(statusId).value;
|
||||
|
||||
const geom = JSON.parse(geoJsonStr);
|
||||
|
||||
fetch('../jalan/create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, status, panjang, geom })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
closeModal();
|
||||
loadJalan();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
// Initial Load
|
||||
loadJalan();
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// --- Fitur Parsil ---
|
||||
|
||||
const parsilColors = {
|
||||
'SHM': '#28a745', // Hijau
|
||||
'HGB': '#17a2b8', // Biru Muda
|
||||
'HGU': '#ffc107', // Kuning
|
||||
'HP': '#fd7e14' // Oranye
|
||||
};
|
||||
|
||||
function loadParsil() {
|
||||
parsilLayer.clearLayers();
|
||||
fetch('../parsil/read.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.data) {
|
||||
data.data.forEach(item => {
|
||||
addParsilToMap(item);
|
||||
});
|
||||
}
|
||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
||||
});
|
||||
}
|
||||
|
||||
function addParsilToMap(item) {
|
||||
// geom adalah GeoJSON Polygon
|
||||
// coordinates pada Polygon formatnya: [[[lng, lat], [lng, lat], ...]]
|
||||
// Leaflet Polygon butuh array of [lat, lng]
|
||||
const latlngs = item.geom.coordinates[0].map(coord => [coord[1], coord[0]]);
|
||||
|
||||
const polygon = L.polygon(latlngs, {
|
||||
color: parsilColors[item.status] || '#3388ff',
|
||||
fillColor: parsilColors[item.status] || '#3388ff',
|
||||
fillOpacity: 0.5,
|
||||
weight: 2
|
||||
});
|
||||
|
||||
polygon.parsilData = item;
|
||||
|
||||
const d = item;
|
||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||
let actionButtons = '';
|
||||
if (isAdmin) {
|
||||
actionButtons = `
|
||||
<div style="display:flex; gap:5px;">
|
||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditParsilModal(${d.id})">Edit</button>
|
||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteParsil(${d.id})">Hapus</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const popupContent = `
|
||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
||||
<h4 style="margin:0 0 5px 0;">Parsil ${d.nama || ''}</h4>
|
||||
<p style="margin: 0 0 5px 0;"><b>Status:</b> ${d.status}</p>
|
||||
<p style="margin: 0 0 10px 0;"><b>Luas:</b> ${d.luas.toFixed(2)} m²</p>
|
||||
${actionButtons}
|
||||
</div>
|
||||
`;
|
||||
polygon.bindPopup(popupContent);
|
||||
|
||||
parsilLayer.addLayer(polygon);
|
||||
}
|
||||
|
||||
window.openEditParsilModal = function(id) {
|
||||
let d = null;
|
||||
parsilLayer.eachLayer(function(layer) {
|
||||
if (layer.parsilData && layer.parsilData.id == id) d = layer.parsilData;
|
||||
});
|
||||
if (!d) return;
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Pemilik/Area</label>
|
||||
<input type="text" id="editParsilNama" value="${d.nama || ''}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select id="editParsilStatus">
|
||||
<option value="SHM" ${d.status === 'SHM' ? 'selected' : ''}>SHM</option>
|
||||
<option value="HGB" ${d.status === 'HGB' ? 'selected' : ''}>HGB</option>
|
||||
<option value="HGU" ${d.status === 'HGU' ? 'selected' : ''}>HGU</option>
|
||||
<option value="HP" ${d.status === 'HP' ? 'selected' : ''}>HP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Luas: ${d.luas.toFixed(2)} m²</label>
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
openModal("Edit Parsil Tanah", bodyHTML, function() {
|
||||
window.saveEditParsil(d.id, 'editParsilNama', 'editParsilStatus');
|
||||
});
|
||||
};
|
||||
|
||||
window.saveEditParsil = function(id, namaId, statusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const status = document.getElementById(statusId).value;
|
||||
|
||||
fetch('../parsil/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, nama, status })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
closeModal();
|
||||
loadParsil();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.deleteParsil = function(id) {
|
||||
openConfirmModal("Yakin hapus parsil ini?", function() {
|
||||
fetch('../parsil/delete.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
loadParsil();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.saveNewParsil = function(geoJsonStr, luas, namaId, statusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const status = document.getElementById(statusId).value;
|
||||
|
||||
const geom = JSON.parse(geoJsonStr);
|
||||
|
||||
fetch('../parsil/create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, status, luas, geom })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
closeModal();
|
||||
loadParsil();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
// Initial Load
|
||||
loadParsil();
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// --- Fitur SPBU ---
|
||||
|
||||
// Emoji Bubble Icon builder
|
||||
function makeSpbuIcon(is24) {
|
||||
const cls = is24 ? 'spbu-24' : 'spbu-not24';
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<div class="emoji-marker"><div class="bubble ${cls}"><span>⛽</span></div></div>`,
|
||||
iconSize: [38, 38],
|
||||
iconAnchor: [19, 38],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
const spbuGreenIcon = makeSpbuIcon(true);
|
||||
const spbuRedIcon = makeSpbuIcon(false);
|
||||
|
||||
let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil)
|
||||
|
||||
|
||||
// Fetch SPBU
|
||||
function loadSpbu() {
|
||||
spbuLayer.clearLayers();
|
||||
fetch('../spbu/read.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.data) {
|
||||
data.data.forEach(item => {
|
||||
addSpbuMarker(item);
|
||||
});
|
||||
}
|
||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
||||
});
|
||||
}
|
||||
|
||||
function addSpbuMarker(item) {
|
||||
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||
const icon = makeSpbuIcon(item.is_24_jam);
|
||||
const marker = L.marker([item.lat, item.lng], {
|
||||
icon: icon,
|
||||
draggable: isAdmin
|
||||
});
|
||||
|
||||
marker.spbuData = item; // Simpan data di objek marker
|
||||
|
||||
// Hitung popupContent sekali saat marker dibuat
|
||||
const d = item;
|
||||
let actionButtons = '';
|
||||
if (isAdmin) {
|
||||
actionButtons = `
|
||||
<div style="display:flex; gap:5px;">
|
||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditSpbuModal(${d.id})">Edit</button>
|
||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteSpbu(${d.id})">Hapus</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const popupContent = `
|
||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
||||
<h4 style="margin:0 0 5px 0;">SPBU ${d.nama}</h4>
|
||||
<p style="margin: 0 0 5px 0;"><b>No. WA:</b> ${d.no_wa}</p>
|
||||
<p style="margin: 0 0 10px 0;"><b>Buka 24 Jam:</b> ${d.is_24_jam ? 'Ya' : 'Tidak'}</p>
|
||||
${actionButtons}
|
||||
</div>
|
||||
`;
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
// Event drag untuk update koordinat
|
||||
marker.on('dragend', function(e) {
|
||||
const newPos = marker.getLatLng();
|
||||
updateSpbu(item.id, item.nama, item.no_wa, item.is_24_jam, newPos.lat, newPos.lng);
|
||||
});
|
||||
|
||||
spbuLayer.addLayer(marker);
|
||||
}
|
||||
|
||||
// Map Click -> Form Add
|
||||
map.on('click', function(e) {
|
||||
if (isDrawingMode) return; // Jangan muncul form jika sedang gambar garis/polygon
|
||||
|
||||
// Hanya proses jika mode tambah SPBU aktif
|
||||
if (window.currentAddMode === 'spbu') {
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Lat: ${e.latlng.lat.toFixed(6)}, Lng: ${e.latlng.lng.toFixed(6)}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nama SPBU</label>
|
||||
<input type="text" id="modalSpbuNama" placeholder="Nama SPBU">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>No. WA</label>
|
||||
<input type="text" id="modalSpbuWa" placeholder="No. WA">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Buka 24 Jam?</label>
|
||||
<div class="radio-group" style="margin-top:5px;">
|
||||
<label><input type="radio" name="modalSpbu24" value="1"> Ya</label>
|
||||
<label><input type="radio" name="modalSpbu24" value="0" checked> Tidak</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
openModal("Tambah SPBU Baru", bodyHTML, function() {
|
||||
window.saveNewSpbu(e.latlng.lat, e.latlng.lng, 'modalSpbuNama', 'modalSpbuWa', 'modalSpbu24');
|
||||
});
|
||||
|
||||
// Nonaktifkan mode tambah setelah modal muncul
|
||||
window.deactivateAddMode();
|
||||
}
|
||||
});
|
||||
|
||||
window.saveNewSpbu = function(lat, lng, namaId, waId, radioName) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
if (!nama) {
|
||||
alert("Nama SPBU harus diisi!");
|
||||
return;
|
||||
}
|
||||
const no_wa = document.getElementById(waId).value;
|
||||
const is_24_jam = document.querySelector(`input[name="${radioName}"]:checked`).value;
|
||||
|
||||
fetch('../spbu/create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, no_wa, is_24_jam, lat, lng })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
closeModal();
|
||||
loadSpbu();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.openEditSpbuModal = function(id) {
|
||||
let d = null;
|
||||
spbuLayer.eachLayer(function(layer) {
|
||||
if (layer.spbuData && layer.spbuData.id == id) d = layer.spbuData;
|
||||
});
|
||||
if (!d) return;
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama SPBU</label>
|
||||
<input type="text" id="editSpbuNama" value="${d.nama}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>No. WA</label>
|
||||
<input type="text" id="editSpbuWa" value="${d.no_wa}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Buka 24 Jam?</label>
|
||||
<div class="radio-group" style="margin-top:5px;">
|
||||
<label><input type="radio" name="editSpbu24" value="1" ${d.is_24_jam ? 'checked' : ''}> Ya</label>
|
||||
<label><input type="radio" name="editSpbu24" value="0" ${!d.is_24_jam ? 'checked' : ''}> Tidak</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
openModal("Edit SPBU", bodyHTML, function() {
|
||||
window.saveEditSpbu(d.id, d.lat, d.lng, 'editSpbuNama', 'editSpbuWa', 'editSpbu24');
|
||||
});
|
||||
};
|
||||
|
||||
window.saveEditSpbu = function(id, lat, lng, namaId, waId, radioName) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const no_wa = document.getElementById(waId).value;
|
||||
const is_24_jam = document.querySelector(`input[name="${radioName}"]:checked`).value;
|
||||
|
||||
updateSpbu(id, nama, no_wa, is_24_jam, lat, lng);
|
||||
};
|
||||
|
||||
function updateSpbu(id, nama, no_wa, is_24_jam, lat, lng) {
|
||||
fetch('../spbu/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, nama, no_wa, is_24_jam, lat, lng })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
closeModal();
|
||||
loadSpbu();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.deleteSpbu = function(id) {
|
||||
openConfirmModal("Yakin hapus SPBU ini?", function() {
|
||||
fetch('../spbu/delete.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
loadSpbu();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Initial Load
|
||||
loadSpbu();
|
||||
|
||||
Reference in New Issue
Block a user