init: publish to github
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
// --- Fitur Pemetaan Kemiskinan ---
|
||||
|
||||
// Emoji Bubble Icon builder
|
||||
function makeIbadahIcon() {
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<div class="emoji-marker"><div class="bubble ibadah"><span>🕌</span></div></div>`,
|
||||
iconSize: [38, 38],
|
||||
iconAnchor: [19, 38],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function makeMiskinIcon(inRadius) {
|
||||
const cls = inRadius ? 'miskin-in' : 'miskin-out';
|
||||
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 ibadahIcon = makeIbadahIcon();
|
||||
const miskinMerahIcon = makeMiskinIcon(true);
|
||||
const miskinHijauIcon = makeMiskinIcon(false);
|
||||
|
||||
|
||||
let ibadahDataList = [];
|
||||
let miskinMarkerList = []; // Simpan referensi marker untuk update warna
|
||||
|
||||
let isResizing = false;
|
||||
let resizingCircle = null;
|
||||
let resizingIbadah = null;
|
||||
|
||||
map.on('mousemove', function(e) {
|
||||
if (isResizing && resizingCircle && resizingIbadah) {
|
||||
const center = resizingCircle.getLatLng();
|
||||
const newRadius = center.distanceTo(e.latlng);
|
||||
resizingCircle.setRadius(newRadius);
|
||||
resizingIbadah.radius = newRadius;
|
||||
|
||||
// Update data radius di marker juga
|
||||
rumahIbadahLayer.eachLayer(function(layer) {
|
||||
if (layer.ibadahData && layer.ibadahData.id === resizingIbadah.id && layer instanceof L.Marker) {
|
||||
layer.ibadahData.radius = newRadius;
|
||||
}
|
||||
});
|
||||
|
||||
updateSemuaWarnaMiskin();
|
||||
}
|
||||
});
|
||||
|
||||
map.on('mouseup', function(e) {
|
||||
if (isResizing) {
|
||||
isResizing = false;
|
||||
map.dragging.enable();
|
||||
if (resizingIbadah) {
|
||||
updateIbadah(resizingIbadah.id, resizingIbadah.nama, resizingIbadah.alamat, resizingIbadah.radius, resizingIbadah.lat, resizingIbadah.lng);
|
||||
}
|
||||
resizingCircle = null;
|
||||
resizingIbadah = null;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Rumah Ibadah ---
|
||||
function loadRumahIbadah() {
|
||||
rumahIbadahLayer.clearLayers();
|
||||
ibadahDataList = [];
|
||||
fetch('api/rumah_ibadah/read.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.data) {
|
||||
ibadahDataList = data.data;
|
||||
data.data.forEach(item => {
|
||||
addIbadahMarker(item);
|
||||
});
|
||||
updateSemuaWarnaMiskin(); // Update warna setelah memuat rumah ibadah
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addIbadahMarker(item) {
|
||||
const marker = L.marker([item.lat, item.lng], { icon: ibadahIcon, draggable: true });
|
||||
|
||||
// Lingkaran radius
|
||||
const circle = L.circle([item.lat, item.lng], {
|
||||
radius: item.radius,
|
||||
color: '#ff7800',
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
|
||||
circle.on('mousemove', function(e) {
|
||||
if (isResizing) return;
|
||||
const center = circle.getLatLng();
|
||||
const radius = circle.getRadius();
|
||||
const dist = center.distanceTo(e.latlng);
|
||||
|
||||
const metersPerPixel = map.distance(map.containerPointToLatLng([0,0]), map.containerPointToLatLng([0,1]));
|
||||
const tolerance = Math.max(radius * 0.1, metersPerPixel * 15); // toleransi 15px atau 10%
|
||||
|
||||
if (Math.abs(dist - radius) <= tolerance) {
|
||||
if (circle._path) circle._path.style.cursor = 'ew-resize';
|
||||
circle.nearEdge = true;
|
||||
} else {
|
||||
if (circle._path) circle._path.style.cursor = 'pointer';
|
||||
circle.nearEdge = false;
|
||||
}
|
||||
});
|
||||
|
||||
circle.on('mousedown', function(e) {
|
||||
if (circle.nearEdge) {
|
||||
map.dragging.disable();
|
||||
isResizing = true;
|
||||
resizingCircle = circle;
|
||||
resizingIbadah = item;
|
||||
L.DomEvent.stopPropagation(e);
|
||||
}
|
||||
});
|
||||
|
||||
marker.ibadahData = item;
|
||||
marker.circleLayer = circle;
|
||||
|
||||
const d = item;
|
||||
const popupContent = `
|
||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
||||
<h4 style="margin:0 0 5px 0;">Rumah Ibadah ${d.nama}</h4>
|
||||
<p style="margin: 0 0 5px 0;"><b>Alamat:</b> ${d.alamat}</p>
|
||||
<p style="margin: 0 0 10px 0;"><b>Radius:</b> ${d.radius} m</p>
|
||||
<div style="display:flex; gap:5px;">
|
||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditIbadahModal(${d.id})">Edit</button>
|
||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteIbadah(${d.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
marker.on('dragend', function(e) {
|
||||
const newPos = marker.getLatLng();
|
||||
circle.setLatLng(newPos);
|
||||
item.lat = newPos.lat;
|
||||
item.lng = newPos.lng;
|
||||
// Update ke DB
|
||||
updateIbadah(item.id, item.nama, item.alamat, item.radius, newPos.lat, newPos.lng);
|
||||
});
|
||||
|
||||
rumahIbadahLayer.addLayer(circle);
|
||||
rumahIbadahLayer.addLayer(marker);
|
||||
}
|
||||
|
||||
// Konteks Menu untuk Tambah Penduduk Miskin (klik kanan peta)
|
||||
map.on('contextmenu', function(e) {
|
||||
if (isDrawingMode) return;
|
||||
|
||||
const popupContent = `
|
||||
<div class="popup-form">
|
||||
<h4>Tambah Titik</h4>
|
||||
<button class="btn-danger" style="margin-top:5px;" onclick="formAddMiskin(${e.latlng.lat}, ${e.latlng.lng})">Tambah Penduduk Miskin</button>
|
||||
</div>
|
||||
`;
|
||||
L.popup().setLatLng(e.latlng).setContent(popupContent).openOn(map);
|
||||
});
|
||||
|
||||
// Map Click -> Form Add Rumah Ibadah
|
||||
map.on('click', function(e) {
|
||||
if (isDrawingMode) return;
|
||||
|
||||
if (window.currentAddMode === 'rumah_ibadah') {
|
||||
const lat = e.latlng.lat;
|
||||
const lng = e.latlng.lng;
|
||||
|
||||
// Reverse Geocoding
|
||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
|
||||
.then(res => res.json())
|
||||
.then(geoData => {
|
||||
const alamat = geoData.display_name || 'Alamat tidak ditemukan';
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nama Rumah Ibadah</label>
|
||||
<input type="text" id="modalIbadahNama" placeholder="Nama Rumah Ibadah">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Alamat (Auto)</label>
|
||||
<textarea id="modalIbadahAlamat" rows="3">${alamat}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Radius (m)</label>
|
||||
<input type="number" id="modalIbadahRadius" value="500">
|
||||
</div>
|
||||
`;
|
||||
|
||||
openModal("Tambah Rumah Ibadah", bodyHTML, function() {
|
||||
window.saveNewIbadah(lat, lng, 'modalIbadahNama', 'modalIbadahAlamat', 'modalIbadahRadius');
|
||||
});
|
||||
|
||||
window.deactivateAddMode();
|
||||
})
|
||||
.catch(err => {
|
||||
// Tetap buka modal meskipun gagal mendapatkan alamat
|
||||
const alamat = '';
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nama Rumah Ibadah</label>
|
||||
<input type="text" id="modalIbadahNama" placeholder="Nama Rumah Ibadah">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Alamat (Manual)</label>
|
||||
<textarea id="modalIbadahAlamat" rows="3" placeholder="Masukkan alamat manual">${alamat}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Radius (m)</label>
|
||||
<input type="number" id="modalIbadahRadius" value="500">
|
||||
</div>
|
||||
`;
|
||||
|
||||
openModal("Tambah Rumah Ibadah", bodyHTML, function() {
|
||||
window.saveNewIbadah(lat, lng, 'modalIbadahNama', 'modalIbadahAlamat', 'modalIbadahRadius');
|
||||
});
|
||||
|
||||
window.deactivateAddMode();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.saveNewIbadah = function(lat, lng, namaId, alamatId, radiusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const alamat = document.getElementById(alamatId).value;
|
||||
const radius = document.getElementById(radiusId).value;
|
||||
|
||||
if (!nama) {
|
||||
alert("Nama Rumah Ibadah harus diisi!");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('api/rumah_ibadah/create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, alamat, radius, lat, lng })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
closeModal();
|
||||
loadRumahIbadah();
|
||||
} else { alert(data.message); }
|
||||
});
|
||||
};
|
||||
|
||||
window.openEditIbadahModal = function(id) {
|
||||
let d = null;
|
||||
rumahIbadahLayer.eachLayer(function(layer) {
|
||||
if (layer.ibadahData && layer.ibadahData.id == id) d = layer.ibadahData;
|
||||
});
|
||||
if (!d) return;
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Rumah Ibadah</label>
|
||||
<input type="text" id="editIbadahNama" value="${d.nama}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Alamat</label>
|
||||
<textarea id="editIbadahAlamat" rows="2">${d.alamat}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Radius (m)</label>
|
||||
<input type="number" id="editIbadahRadius" value="${d.radius}" oninput="previewRadius(${d.id}, this.value)">
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
openModal("Edit Rumah Ibadah", bodyHTML, function() {
|
||||
window.saveEditIbadah(d.id, d.lat, d.lng, 'editIbadahNama', 'editIbadahAlamat', 'editIbadahRadius');
|
||||
});
|
||||
};
|
||||
|
||||
window.previewRadius = function(id, newRadius) {
|
||||
rumahIbadahLayer.eachLayer(function(layer) {
|
||||
// layer ini bisa marker atau circle, kita cek yang punya circleLayer (marker)
|
||||
if (layer.ibadahData && layer.ibadahData.id === id && layer.circleLayer) {
|
||||
layer.circleLayer.setRadius(parseFloat(newRadius));
|
||||
layer.ibadahData.radius = parseFloat(newRadius);
|
||||
updateSemuaWarnaMiskin(); // Update warna miskin real-time
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.saveEditIbadah = function(id, lat, lng, namaId, alamatId, radiusId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const alamat = document.getElementById(alamatId).value;
|
||||
const radius = document.getElementById(radiusId).value;
|
||||
updateIbadah(id, nama, alamat, radius, lat, lng);
|
||||
};
|
||||
|
||||
function updateIbadah(id, nama, alamat, radius, lat, lng) {
|
||||
fetch('api/rumah_ibadah/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, nama, alamat, radius, lat, lng })
|
||||
}).then(res => res.json()).then(data => {
|
||||
if(data.status === 'success') { map.closePopup(); loadRumahIbadah(); }
|
||||
});
|
||||
}
|
||||
|
||||
window.deleteIbadah = function(id) {
|
||||
openConfirmModal("Yakin hapus rumah ibadah ini?", function() {
|
||||
fetch('api/rumah_ibadah/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(); loadRumahIbadah(); }
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// --- Penduduk Miskin ---
|
||||
function loadPendudukMiskin() {
|
||||
pendudukMiskinLayer.clearLayers();
|
||||
miskinMarkerList = [];
|
||||
fetch('api/penduduk_miskin/read.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.data) {
|
||||
data.data.forEach(item => {
|
||||
addMiskinMarker(item);
|
||||
});
|
||||
updateSemuaWarnaMiskin();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addMiskinMarker(item) {
|
||||
const marker = L.marker([item.lat, item.lng], { icon: miskinHijauIcon, draggable: true });
|
||||
marker.miskinData = item;
|
||||
|
||||
const d = item;
|
||||
const popupContent = `
|
||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
||||
<h4 style="margin:0 0 5px 0;">Penduduk Miskin ${d.nama}</h4>
|
||||
<p style="margin: 0 0 10px 0;"><b>Bantuan:</b> ${d.kategori_bantuan}</p>
|
||||
<div style="display:flex; gap:5px;">
|
||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditMiskinModal(${d.id})">Edit</button>
|
||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteMiskin(${d.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
marker.on('dragend', function(e) {
|
||||
const newPos = marker.getLatLng();
|
||||
item.lat = newPos.lat;
|
||||
item.lng = newPos.lng;
|
||||
updateSemuaWarnaMiskin(); // Update warna segera
|
||||
// Simpan ke DB
|
||||
fetch('api/penduduk_miskin/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(item)
|
||||
});
|
||||
});
|
||||
|
||||
miskinMarkerList.push(marker);
|
||||
pendudukMiskinLayer.addLayer(marker);
|
||||
}
|
||||
|
||||
window.formAddMiskin = function(lat, lng) {
|
||||
const popupContent = `
|
||||
<div class="popup-form">
|
||||
<h4>Penduduk Miskin Baru</h4>
|
||||
<input type="text" id="miskinNama" placeholder="Nama Penduduk">
|
||||
<select id="miskinKategori">
|
||||
<option value="Makan">Bantuan Makan</option>
|
||||
<option value="Pemberdayaan">Pemberdayaan</option>
|
||||
</select>
|
||||
<button onclick="saveNewMiskin(${lat}, ${lng})">Simpan</button>
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng([lat, lng]).setContent(popupContent).openOn(map);
|
||||
};
|
||||
|
||||
window.saveNewMiskin = function(lat, lng) {
|
||||
const nama = document.getElementById('miskinNama').value;
|
||||
const kategori_bantuan = document.getElementById('miskinKategori').value;
|
||||
|
||||
fetch('api/penduduk_miskin/create.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, kategori_bantuan, lat, lng })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
map.closePopup();
|
||||
loadPendudukMiskin();
|
||||
} else { alert(data.message); }
|
||||
});
|
||||
};
|
||||
|
||||
window.openEditMiskinModal = function(id) {
|
||||
let d = null;
|
||||
pendudukMiskinLayer.eachLayer(function(layer) {
|
||||
if (layer.miskinData && layer.miskinData.id == id) d = layer.miskinData;
|
||||
});
|
||||
if (!d) return;
|
||||
|
||||
const bodyHTML = `
|
||||
<div class="form-group">
|
||||
<label>Nama Penduduk</label>
|
||||
<input type="text" id="editMiskinNama" value="${d.nama}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Bantuan</label>
|
||||
<select id="editMiskinKategori">
|
||||
<option value="Makan" ${d.kategori_bantuan === 'Makan' ? 'selected' : ''}>Bantuan Makan</option>
|
||||
<option value="Pemberdayaan" ${d.kategori_bantuan === 'Pemberdayaan' ? 'selected' : ''}>Pemberdayaan</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
map.closePopup();
|
||||
openModal("Edit Penduduk Miskin", bodyHTML, function() {
|
||||
window.saveEditMiskin(d.id, d.lat, d.lng, 'editMiskinNama', 'editMiskinKategori');
|
||||
});
|
||||
};
|
||||
|
||||
window.saveEditMiskin = function(id, lat, lng, namaId, kategoriId) {
|
||||
const nama = document.getElementById(namaId).value;
|
||||
const kategori_bantuan = document.getElementById(kategoriId).value;
|
||||
fetch('api/penduduk_miskin/update.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, nama, kategori_bantuan, lat, lng })
|
||||
}).then(res => res.json()).then(data => {
|
||||
if(data.status === 'success') { closeModal(); loadPendudukMiskin(); }
|
||||
});
|
||||
};
|
||||
|
||||
window.deleteMiskin = function(id) {
|
||||
openConfirmModal("Yakin hapus penduduk miskin ini?", function() {
|
||||
fetch('api/penduduk_miskin/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(); loadPendudukMiskin(); }
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Logika dinamis warna: Merah jika di dalam salah satu radius Rumah Ibadah, Hijau jika di luar
|
||||
function updateSemuaWarnaMiskin() {
|
||||
miskinMarkerList.forEach(marker => {
|
||||
let inRadius = false;
|
||||
const pLatlng = L.latLng(marker.miskinData.lat, marker.miskinData.lng);
|
||||
|
||||
for (let i = 0; i < ibadahDataList.length; i++) {
|
||||
const ibadah = ibadahDataList[i];
|
||||
const iLatlng = L.latLng(ibadah.lat, ibadah.lng);
|
||||
const dist = pLatlng.distanceTo(iLatlng); // meter
|
||||
|
||||
if (dist <= ibadah.radius) {
|
||||
inRadius = true;
|
||||
break; // Jika sudah masuk satu radius, langsung merah
|
||||
}
|
||||
}
|
||||
|
||||
if (inRadius) {
|
||||
marker.setIcon(makeMiskinIcon(true));
|
||||
} else {
|
||||
marker.setIcon(makeMiskinIcon(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial Load
|
||||
loadRumahIbadah();
|
||||
loadPendudukMiskin();
|
||||
Reference in New Issue
Block a user