Files
WebGIS/WebGISPovertyMap/script.js
T
2026-06-09 16:03:17 +07:00

1083 lines
49 KiB
JavaScript

// ==========================================
// 1. INISIALISASI PETA & LAYER
// ==========================================
var map = L.map('map', { zoomControl: false }).setView([-0.0227, 109.3425], 14);
// Zoom control di kanan bawah (lebih bersih)
L.control.zoom({ position: 'bottomright' }).addTo(map);
// Custom pane initialization removed
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Layer Group untuk masing-masing data
var drawnItems = new L.FeatureGroup().addTo(map);
//var batasKotaLayer = L.layerGroup().addTo(map);
//var sungaiLayer = L.layerGroup().addTo(map);
var rumahIbadahLayer = L.layerGroup().addTo(map);
var radiusLayer = L.layerGroup().addTo(map);
var pendudukMiskinLayer = L.layerGroup().addTo(map);
var pendudukMiskinCluster = L.markerClusterGroup({
showCoverageOnHover: false,
zoomToBoundsOnClick: true,
maxClusterRadius: 50
}).addTo(map);
// Inisialisasi Heatmap kosong (akan diisi datanya saat load data)
var povertyHeatLayer = L.heatLayer([], { radius: 22, blur: 18, maxZoom: 17 }).addTo(map);
// Inisialisasi Layers Control (di kanan atas)
var overlayMaps = {
"🕌 Rumah Ibadah": rumahIbadahLayer,
"🔥 Kepadatan Kemiskinan (Heatmap)": povertyHeatLayer,
"👥 Semua Penduduk Miskin": pendudukMiskinCluster,
"👥 Penduduk Terdekat (Analisis)": pendudukMiskinLayer
};
L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map);
// ==========================================
// ICON DEFINITIONS
// ==========================================
// Unused marker icons removed
// Icon Rumah Ibadah Kustom — bangunan rumah ibadah bergaya universal
var rumahIbadahIcon = L.divIcon({
className: '',
html: `<div style="
width:38px; height:38px;
background: linear-gradient(135deg, #1e40af, #3b82f6);
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
box-shadow: 0 3px 12px rgba(59,130,246,0.55);
border: 2.5px solid rgba(255,255,255,0.3);
display:flex; align-items:center; justify-content:center;
"><svg style="transform:rotate(45deg); width:20px; height:20px;" fill="white" viewBox="0 0 24 24">
<path d="M12 2L2 7v1h2v13h16V8h2V7L12 2zm0 2.3L20 8.5V19H4V8.5L12 4.3zm-1 6.7v7h2v-7h-2zm-3 2v5h2v-5H8zm6 0v5h2v-5h-2zM11 8a1 1 0 100 2 1 1 0 000-2z"/>
</svg></div>`,
iconSize: [38, 38],
iconAnchor: [19, 38],
popupAnchor: [0, -38]
});
// Leaflet Draw toolbar removed
// ==========================================
// 3. FUNGSI LOAD DATA DARI DATABASE
// ==========================================
/*
function loadBatasKota() {
batasKotaLayer.clearLayers();
var grupKecamatan = {};
fetch('adminKecamatan.json')
.then(res => res.json())
.then(data => {
L.geoJSON(data, {
style: function () {
return { color: "#00FF00", weight: 2, opacity: 1, fillOpacity: 0.1 };
},
onEachFeature: function (feature, layer) {
var namaKec = feature.properties.Ket;
var popupHTML = `
<div class="popup-container">
<div class="popup-header" style="background: linear-gradient(135deg, #1e3a5f, #2563eb);">
<h4>${namaKec}</h4>
<span class="popup-type-badge">📍 Batas Kecamatan</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Wilayah</span>
<span class="info-value">Kota Pontianak</span>
</div>
</div>
</div>`;
layer.bindPopup(popupHTML);
if (!grupKecamatan[namaKec]) grupKecamatan[namaKec] = L.featureGroup();
grupKecamatan[namaKec].addLayer(layer);
}
}).addTo(batasKotaLayer);
})
.catch(e => console.log('Gagal memuat adminKecamatan.json', e));
}
function loadSungai() {
sungaiLayer.clearLayers();
fetch('sungai.json')
.then(res => res.json())
.then(data => {
L.geoJSON(data, {
style: function () {
return { color: "#0ea5e9", weight: 2, opacity: 1, fillOpacity: 0.1 };
},
onEachFeature: function (feature, layer) {
var namaSung = feature.properties.Ket;
var popupHTML = `
<div class="popup-container">
<div class="popup-header" style="background: linear-gradient(135deg, #0c4a6e, #0ea5e9);">
<h4>${namaSung}</h4>
<span class="popup-type-badge">🌊 Sungai</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Wilayah</span>
<span class="info-value">Kota Pontianak</span>
</div>
</div>
</div>`;
layer.bindPopup(popupHTML);
}
}).addTo(sungaiLayer);
})
.catch(e => console.log('Gagal memuat sungai.json', e));
}
*/
var databasePendudukMiskin = [];
function loadRumahIbadah() {
rumahIbadahLayer.clearLayers();
fetch('load.php?jenis=rumah_ibadah')
.then(res => res.json())
.then(dataRumahIbadah => {
fetch('load.php?jenis=penduduk_miskin')
.then(res => res.json())
.then(dataPendudukMiskin => {
databasePendudukMiskin = dataPendudukMiskin;
// Mengisi koordinat data kemiskinan ke dalam Heatmap
var heatPoints = dataPendudukMiskin.map(p => [
parseFloat(p.lat || p.latitude),
parseFloat(p.lng || p.longitude),
1.0
]);
povertyHeatLayer.setLatLngs(heatPoints);
// Marker Clustering untuk semua penduduk miskin
pendudukMiskinCluster.clearLayers();
dataPendudukMiskin.forEach(penduduk => {
var latPenduduk = parseFloat(penduduk.lat || penduduk.latitude);
var lngPenduduk = parseFloat(penduduk.lng || penduduk.longitude);
var statusBantuan = penduduk.bantuan || 'Belum Menerima';
var poorFamilyIcon = L.divIcon({
className: '',
html: `<div style="
width:14px; height:14px;
background: ${statusBantuan === 'Sudah Menerima' ? '#10b981' : '#ef4444'};
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 6px rgba(0,0,0,0.5);
"></div>`,
iconSize: [14, 14],
iconAnchor: [7, 7],
popupAnchor: [0, -7]
});
var actionButtons = '';
if (IS_ADMIN) {
actionButtons = `
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditPenduduk('${penduduk.id}', '${penduduk.nama_kk}', '${penduduk.alamat}', '${statusBantuan}', ${latPenduduk}, ${lngPenduduk})">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('penduduk_miskin', '${penduduk.id}')">🗑️ Hapus</button>
</div>
`;
}
var htmlIsi = `
<div class="popup-container" id="popup-penduduk-${penduduk.id}">
<div class="popup-header" style="background: linear-gradient(135deg, ${statusBantuan === 'Sudah Menerima' ? '#065f46, #10b981' : '#7f1d1d, #ef4444'});">
<h4>${penduduk.nama_kk}</h4>
<span class="popup-type-badge">👥 Data Sosial</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Alamat Lengkap</span>
<span class="info-value" style="font-size:11.5px; line-height:1.4;">${penduduk.alamat}</span>
</div>
<div class="info-row">
<span class="info-label">Status Bantuan</span>
<span class="info-value" style="color: ${statusBantuan === 'Sudah Menerima' ? '#34d399' : '#f87171'}; font-weight: 600;">${statusBantuan}</span>
</div>
${actionButtons}
</div>
</div>`;
var marker = L.marker([latPenduduk, lngPenduduk], { icon: poorFamilyIcon })
.bindPopup(htmlIsi);
pendudukMiskinCluster.addLayer(marker);
});
dataRumahIbadah.forEach(ibadah => {
var latIbadah = ibadah.lat || ibadah.latitude;
var lngIbadah = ibadah.lng || ibadah.longitude;
var radiusMeter = 500;
var ibadahId = ibadah.id;
var actionButtons = '';
if (IS_ADMIN) {
actionButtons = `
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditRumahIbadah('${ibadah.id}', '${ibadah.nama}', ${latIbadah}, ${lngIbadah})">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('rumah_ibadah', '${ibadah.id}')">🗑️ Hapus</button>
</div>
`;
}
// HTML untuk Popup Rumah Ibadah dengan range slider untuk mengatur radius analisis
var htmlIbadah = `
<div class="popup-container" id="popup-ibadah-${ibadahId}">
<div class="popup-header" style="background: linear-gradient(135deg, #1e3a5f, #3b82f6);">
<h4>${ibadah.nama}</h4>
<span class="popup-type-badge">🕌 Rumah Ibadah</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Radius Analisis</span>
<span class="info-value" id="radius-val-${ibadahId}">${radiusMeter} meter</span>
<input type="range" id="radius-slider-${ibadahId}" min="100" max="2500" step="50" value="${radiusMeter}" oninput="updatePopupRadius('${ibadahId}', ${latIbadah}, ${lngIbadah})" style="width:100%; margin-top:6px; accent-color:#3b82f6; cursor:pointer;">
</div>
${actionButtons}
</div>
</div>`;
var ibadahMarker = L.marker([latIbadah, lngIbadah], { icon: rumahIbadahIcon })
.bindPopup(htmlIbadah)
.addTo(rumahIbadahLayer);
databasePencarian.push({
namaLower: ibadah.nama.toLowerCase(),
namaAsli: ibadah.nama,
kategori: 'Rumah Ibadah',
ikon: '🕌',
tipe: 'titik',
layer: ibadahMarker
});
// Saat popup dibuka, jalankan fungsi updatePopupRadius
ibadahMarker.on('popupopen', function (e) {
map.flyTo(e.target.getLatLng(), 16, { animate: true });
setTimeout(function () {
updatePopupRadius(ibadahId, latIbadah, lngIbadah);
}, 50);
});
});
});
})
.catch(e => console.log('Belum ada data Rumah Ibadah', e));
}
// Fungsi untuk memperbarui lingkaran radius dan penduduk miskin terdekat secara interaktif
function updatePopupRadius(ibadahId, lat, lng) {
var slider = document.getElementById('radius-slider-' + ibadahId);
var label = document.getElementById('radius-val-' + ibadahId);
if (!slider || !label) return;
var newRadius = parseInt(slider.value);
label.textContent = newRadius + ' meter';
var centerPoint = L.latLng(lat, lng);
radiusLayer.clearLayers();
pendudukMiskinLayer.clearLayers();
// Gambar lingkaran radius baru
var areaRadius = L.circle(centerPoint, {
color: '#2563eb',
fillColor: '#3b82f6',
fillOpacity: 0.1,
radius: newRadius,
interactive: true
}).addTo(radiusLayer);
areaRadius.on('click', function (evt) { L.DomEvent.stopPropagation(evt); });
var totalKK = 0;
var sudahBantuan = 0;
var belumBantuan = 0;
var familiesInRadius = [];
// Saring dan tampilkan penduduk miskin yang berada dalam radius baru
databasePendudukMiskin.forEach(penduduk => {
var latPenduduk = penduduk.lat || penduduk.latitude;
var lngPenduduk = penduduk.lng || penduduk.longitude;
var pendudukLatLng = L.latLng(latPenduduk, lngPenduduk);
var jarak = centerPoint.distanceTo(pendudukLatLng);
if (jarak <= newRadius) {
totalKK++;
var statusBantuan = penduduk.bantuan || 'Belum Menerima';
if (statusBantuan === 'Sudah Menerima') {
sudahBantuan++;
} else {
belumBantuan++;
}
familiesInRadius.push(penduduk);
var warnaDot = statusBantuan === 'Sudah Menerima' ? '#10b981' : '#ef4444';
var redDot = L.circleMarker(pendudukLatLng, {
radius: 7,
color: '#ffffff',
fillColor: warnaDot,
fillOpacity: 1,
weight: 1.5
}).addTo(pendudukMiskinLayer);
redDot.on('click', function (e) {
L.DomEvent.stopPropagation(e);
var pendudukId = penduduk.id;
var namaKeluarga = penduduk.nama_kk ? penduduk.nama_kk : "Tanpa Nama";
var alamatLengkap = penduduk.alamat ? penduduk.alamat : "Alamat belum diinput manual";
var latAngka = parseFloat(latPenduduk);
var lngAngka = parseFloat(lngPenduduk);
var actionButtons = '';
if (IS_ADMIN) {
actionButtons = `
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditPenduduk('${penduid}', '${namaKeluarga}', '${alamatLengkap}', '${statusBantuan}', ${latAngka}, ${lngAngka})">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('penduduk_miskin', '${pendudukId}')">🗑️ Hapus</button>
</div>
`;
}
// Typo fix: penduid -> pendudukId
actionButtons = actionButtons.replace('\'${penduid}\'', `'${pendudukId}'`);
var htmlIsi = `
<div class="popup-container" id="popup-penduduk-${pendudukId}">
<div class="popup-header" style="background: linear-gradient(135deg, ${statusBantuan === 'Sudah Menerima' ? '#065f46, #10b981' : '#7f1d1d, #ef4444'});">
<h4>${namaKeluarga}</h4>
<span class="popup-type-badge">👥 Data Sosial</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Alamat Lengkap</span>
<span class="info-value" style="font-size:11.5px; line-height:1.4;">${alamatLengkap}</span>
</div>
<div class="info-row">
<span class="info-label">Status Bantuan</span>
<span class="info-value" style="color: ${statusBantuan === 'Sudah Menerima' ? '#34d399' : '#f87171'}; font-weight: 600;">${statusBantuan}</span>
</div>
${actionButtons}
</div>
</div>`;
redDot.bindPopup(htmlIsi).openPopup();
});
}
});
// Cari nama rumah ibadah dari databasePencarian
var ibadahObj = databasePencarian.find(item => item.layer && item.layer._popup && item.layer._popup.getContent().includes('popup-ibadah-' + ibadahId));
var ibadahName = ibadahObj ? ibadahObj.namaAsli : "Rumah Ibadah";
showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius);
}
map.on('click', function () {
if (sidebarPickMode) return; // Ditangani oleh listener sidebar
radiusLayer.clearLayers();
pendudukMiskinLayer.clearLayers();
closeDashboard();
});
loadRumahIbadah();
// SPBU & draw-related globals removed
// ==========================================
// 4. EVENT SAAT MENGGAMBAR SELESAI
// ==========================================
/*
map.on(L.Draw.Event.CREATED, function (e) {
var type = e.layerType, layer = e.layer;
var geojson = layer.toGeoJSON();
koordinatSementara = JSON.stringify(geojson.geometry);
var popupContent = "";
if (type === 'polyline') {
var panjangMeter = turf.length(geojson, { units: 'meters' }).toFixed(2);
popupContent = `
<div class="popup-container">
<div class="popup-header" style="background: linear-gradient(135deg, #134e4a, #0f766e);">
<h4>Input Data Jalan</h4>
<span class="popup-type-badge">🛣️ Polyline Baru</span>
</div>
<div class="popup-body">
<form onsubmit="saveJalan(event)">
<input type="text" id="j_nama" placeholder="Nama Jalan" required>
<select id="j_status">
<option value="Jalan Nasional">Jalan Nasional</option>
<option value="Jalan Provinsi">Jalan Provinsi</option>
<option value="Jalan Kabupaten">Jalan Kabupaten</option>
</select>
<span class="info-label">Panjang Terukur</span>
<input type="text" id="j_panjang" value="${panjangMeter} m" readonly>
<button type="submit" class="btn btn-save" style="width:100%; margin-top:4px;">💾 Simpan Jalan</button>
</form>
</div>
</div>`;
} else if (type === 'polygon') {
var luasMeter = turf.area(geojson).toFixed(2);
popupContent = `
<div class="popup-container">
<div class="popup-header" style="background: linear-gradient(135deg, #4a1d96, #7c3aed);">
<h4>Input Data Parsil</h4>
<span class="popup-type-badge">🏘️ Polygon Baru</span>
</div>
<div class="popup-body">
<form onsubmit="saveParsil(event)">
<input type="text" id="p_nama" placeholder="Nama Pemilik" required>
<select id="p_status">
<option value="SHM">SHM (Hak Milik)</option>
<option value="HGB">HGB (Guna Bangunan)</option>
<option value="HGU">HGU (Guna Usaha)</option>
<option value="HP">HP (Hak Pakai)</option>
</select>
<span class="info-label">Luas Terukur</span>
<input type="text" id="p_luas" value="${luasMeter} m²" readonly>
<button type="submit" class="btn btn-save" style="width:100%; margin-top:4px;">💾 Simpan Parsil</button>
</form>
</div>
</div>`;
} else if (type === 'marker') {
tempLat = layer.getLatLng().lat;
tempLng = layer.getLatLng().lng;
popupContent = `
<div class="popup-container">
<div class="popup-header">
<h4>Tambah Titik Baru</h4>
<span class="popup-type-badge">📍 Marker Baru</span>
</div>
<div class="popup-body">
<select id="kategori_marker" onchange="toggleFormMarker()" style="margin-bottom:12px; border-color:#3b82f6;">
<option value="spbu">⛽ Stasiun SPBU</option>
<option value="rumah_ibadah">🕌 Rumah Ibadah</option>
<option value="penduduk">👥 Penduduk Miskin</option>
</select>
<form id="form_spbu" onsubmit="saveSPBU(event)">
<input type="text" id="new_nama" placeholder="Nama SPBU" required>
<input type="text" id="new_nohp" placeholder="No HP">
<select id="new_kategori">
<option value="Ya">Buka 24 Jam</option>
<option value="Tidak">Reguler</option>
</select>
<button type="submit" class="btn btn-save" style="width:100%;">💾 Simpan SPBU</button>
</form>
<form id="form_rumah_ibadah" onsubmit="saveRumahIbadah(event)" style="display:none;">
<input type="text" id="ri_nama" placeholder="Nama Rumah Ibadah" required>
<button type="submit" class="btn btn-save" style="width:100%;">💾 Simpan Rumah Ibadah</button>
</form>
<form id="form_penduduk" onsubmit="savePenduduk(event)" style="display:none;">
<input type="text" id="pm_nama" placeholder="Nama Kepala Keluarga" required>
<button type="submit" class="btn btn-save" style="width:100%; background:#ef4444;">💾 Simpan Penduduk</button>
</form>
</div>
</div>`;
}
layer.bindPopup(popupContent).openPopup();
drawnItems.addLayer(layer);
});
*/
/*
// ==========================================
// 6. FUNGSI SIMPAN DATA
// ==========================================
function saveSPBU(e) {
e.preventDefault();
var formData = new FormData();
formData.append('nama_spbu', document.getElementById('new_nama').value);
formData.append('no_hp', document.getElementById('new_nohp').value);
formData.append('kategori', document.getElementById('new_kategori').value);
formData.append('latitude', tempLat);
formData.append('longitude', tempLng);
fetch('save.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => { alert(res); map.closePopup(); drawnItems.clearLayers(); loadSPBU(); })
.catch(error => console.error('Error:', error));
}
function saveJalan(e) {
e.preventDefault();
var formData = new FormData();
formData.append('nama_jalan', document.getElementById('j_nama').value);
formData.append('status', document.getElementById('j_status').value);
formData.append('panjang', document.getElementById('j_panjang').value);
formData.append('koordinat', koordinatSementara);
fetch('save_jalan.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => { alert(res); map.closePopup(); drawnItems.clearLayers(); loadJalan(); })
.catch(error => console.error('Error:', error));
}
function saveParsil(e) {
e.preventDefault();
var formData = new FormData();
formData.append('nama_pemilik', document.getElementById('p_nama').value);
formData.append('status', document.getElementById('p_status').value);
formData.append('luas', document.getElementById('p_luas').value);
formData.append('koordinat', koordinatSementara);
fetch('save_parsil.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => { alert(res); map.closePopup(); drawnItems.clearLayers(); loadParsil(); })
.catch(error => console.error('Error:', error));
}
function toggleFormMarker() {
var pilihan = document.getElementById('kategori_marker').value;
document.getElementById('form_spbu').style.display = pilihan === 'spbu' ? 'block' : 'none';
document.getElementById('form_rumah_ibadah').style.display = pilihan === 'rumah_ibadah' ? 'block' : 'none';
document.getElementById('form_penduduk').style.display = pilihan === 'penduduk' ? 'block' : 'none';
}
function saveRumahIbadah(e) {
e.preventDefault();
var formData = new FormData();
formData.append('nama', document.getElementById('ri_nama').value);
formData.append('latitude', tempLat);
formData.append('longitude', tempLng);
fetch('save_rumah_ibadah.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => { alert(res); map.closePopup(); drawnItems.clearLayers(); loadRumahIbadah(); })
.catch(error => console.error('Error:', error));
}
function savePenduduk(e) {
e.preventDefault();
var formData = new FormData();
formData.append('nama', document.getElementById('pm_nama').value);
formData.append('latitude', tempLat);
formData.append('longitude', tempLng);
fetch('save_penduduk_miskin.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => { alert(res); map.closePopup(); drawnItems.clearLayers(); loadRumahIbadah(); })
.catch(error => console.error('Error:', error));
}
*/
// ==========================================
// 7. HAPUS DATA
// ==========================================
function deleteData(tabel, id) {
if (confirm('Apakah Anda yakin ingin menghapus data ini?')) {
var formData = new FormData();
formData.append('id', id);
formData.append('tabel', tabel);
fetch('delete_data.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => {
alert(res);
map.closePopup();
loadRumahIbadah();
});
}
}
// ==========================================
// 8. EDIT DATA
// ==========================================
function formEditRumahIbadah(id, nama, lat, lng) {
setTimeout(function () {
document.getElementById(`popup-ibadah-${id}`).innerHTML = `
<div class="popup-header" style="background: linear-gradient(135deg, #1e3a5f, #3b82f6); padding:14px 16px 12px; border-radius: 0;">
<h4 style="margin:0; font-weight:700; font-size:15px; color:#fff;">Edit Rumah Ibadah</h4>
</div>
<div class="popup-body" style="padding:14px 16px;">
<form onsubmit="updateData(event, 'rumah_ibadah', '${id}')">
<input type="text" id="edit_nama_ibadah" value="${nama}" required>
<input type="hidden" id="edit_lat_ibadah" value="${lat}">
<input type="hidden" id="edit_lng_ibadah" value="${lng}">
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Update</button>
<button type="button" class="btn btn-delete" onclick="loadRumahIbadah(); map.closePopup();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function formEditPenduduk(id, nama, alamat, statusBantuan, lat, lng) {
setTimeout(function () {
document.getElementById(`popup-penduduk-${id}`).innerHTML = `
<div class="popup-header" style="background: linear-gradient(135deg, #7f1d1d, #ef4444); padding:14px 16px 12px; border-radius: 0;">
<h4 style="margin:0; font-weight:700; font-size:15px; color:#fff;">Edit Penduduk</h4>
</div>
<div class="popup-body" style="padding:14px 16px;">
<form onsubmit="updateData(event, 'penduduk_miskin', '${id}')">
<input type="text" id="edit_nama_penduduk" value="${nama}" required>
<textarea id="edit_alamat_penduduk" rows="2" required>${alamat}</textarea>
<select id="edit_status_bantuan" style="margin-bottom:12px; width:100%; padding:8px 10px; background-color:#0f172a; border:1px solid rgba(255,255,255,0.2); border-radius:6px; color:#f8fafc; font-size:13px; cursor:pointer;">
<option value="Belum Menerima" style="background-color:#1e293b; color:#f8fafc;" ${statusBantuan === 'Belum Menerima' ? 'selected' : ''}>Belum Menerima</option>
<option value="Sudah Menerima" style="background-color:#1e293b; color:#f8fafc;" ${statusBantuan === 'Sudah Menerima' ? 'selected' : ''}>Sudah Menerima</option>
</select>
<input type="hidden" id="edit_lat_penduduk" value="${lat}">
<input type="hidden" id="edit_lng_penduduk" value="${lng}">
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Update</button>
<button type="button" class="btn btn-delete" onclick="loadRumahIbadah(); map.closePopup();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
// ==========================================
// 9. UPDATE DATA
// ==========================================
function updateData(e, tabel, id) {
e.preventDefault();
var formData = new FormData();
formData.append('tabel', tabel);
formData.append('id', id);
if (tabel === 'rumah_ibadah') {
formData.append('nama', document.getElementById('edit_nama_ibadah').value);
formData.append('latitude', document.getElementById('edit_lat_ibadah').value);
formData.append('longitude', document.getElementById('edit_lng_ibadah').value);
} else if (tabel === 'penduduk_miskin') {
formData.append('nama', document.getElementById('edit_nama_penduduk').value);
formData.append('alamat', document.getElementById('edit_alamat_penduduk').value);
formData.append('bantuan', document.getElementById('edit_status_bantuan').value);
formData.append('latitude', document.getElementById('edit_lat_penduduk').value);
formData.append('longitude', document.getElementById('edit_lng_penduduk').value);
}
fetch('update_data.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => {
alert(res);
map.closePopup();
if (tabel === 'rumah_ibadah' || tabel === 'penduduk_miskin') {
loadRumahIbadah();
}
});
}
// ==========================================
// 10. LEGENDA PETA
// ==========================================
var legend = L.control({ position: 'bottomleft' });
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend');
div.innerHTML = `
<div class="legend-icon-btn">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="16" height="16">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
</div>
<div class="legend-content">
<h4>Legenda Peta</h4>
<span class="legend-group">Sosial & Keagamaan</span>
<div class="legend-item">
<div style="width:14px; height:14px; background: linear-gradient(135deg, #1e40af, #3b82f6); border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display:flex; align-items:center; justify-content:center; margin-right:10px; margin-left:3px; box-shadow: 0 1px 3px rgba(59,130,246,0.5);">
<svg style="transform:rotate(45deg); width:8px; height:8px;" fill="white" viewBox="0 0 24 24"><path d="M12 2L2 7v1h2v13h16V8h2V7L12 2zm0 2.3L20 8.5V19H4V8.5L12 4.3zm-1 6.7v7h2v-7h-2zm-3 2v5h2v-5H8zm6 0v5h2v-5h-2zM11 8a1 1 0 100 2 1 1 0 000-2z"/></svg>
</div>
Rumah Ibadah
</div>
<div class="legend-item"><span class="legend-color" style="background:#ef4444; border-color:#dc2626; border-radius:50%; height:10px; width:10px; margin-left:2px;"></span> Penduduk Miskin</div>
</div>`;
return div;
};
legend.addTo(map);
// ==========================================
// 11. SISTEM PENCARIAN
// ==========================================
var databasePencarian = [];
var ikonKategori = {
'Rumah Ibadah': '🕌',
};
var warnaBadge = {
'Rumah Ibadah': 'badge-Rumah-Ibadah',
};
function jalankanPencarian() {
var kataKunci = document.getElementById('search-input').value.toLowerCase();
var kategori = document.getElementById('search-category').value;
var wadahHasil = document.getElementById('search-results');
wadahHasil.innerHTML = '';
if (kataKunci.length < 2) {
wadahHasil.style.display = 'none';
return;
}
var hasil = databasePencarian.filter(function (item) {
var cocokNama = item.namaLower.includes(kataKunci);
var cocokKategori = (kategori === 'Semua') || (item.kategori === kategori);
return cocokNama && cocokKategori;
});
if (hasil.length > 0) {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `<div class="results-header">${hasil.length} hasil ditemukan</div>`;
hasil.forEach(function (item) {
var badgeClass = warnaBadge[item.kategori] || 'badge-default';
var ikon = ikonKategori[item.kategori] || '📍';
var bgColors = {
'Rumah Ibadah': 'rgba(59,130,246,0.12)'
};
var div = document.createElement('div');
div.className = 'result-item';
div.innerHTML = `
<div class="result-item-icon" style="background: ${bgColors[item.kategori] || 'rgba(148,163,184,0.1)'};">
${ikon}
</div>
<div class="result-item-text">
<div class="result-item-name">${item.namaAsli}</div>
<div class="result-item-sub">Klik untuk zoom ke lokasi</div>
</div>
<span class="badge-kategori ${badgeClass}">${item.kategori}</span>`;
div.onclick = function () {
if (item.tipe === 'titik') {
map.flyTo(item.layer.getLatLng(), 17, { animate: true, duration: 1.5 });
} else {
map.fitBounds(item.layer.getBounds());
}
item.layer.openPopup();
wadahHasil.style.display = 'none';
document.getElementById('search-input').value = item.namaAsli;
toggleClearBtn();
};
wadahHasil.appendChild(div);
});
} else {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `
<div class="result-empty">
<div class="result-empty-icon">🔍</div>
<div>Data tidak ditemukan</div>
<div style="font-size:11px; margin-top:4px; color:#475569;">Coba kata kunci lain</div>
</div>`;
}
}
function toggleClearBtn() {
var input = document.getElementById('search-input');
var clearBtn = document.getElementById('search-clear');
clearBtn.style.display = input.value.length > 0 ? 'flex' : 'none';
}
function clearSearch() {
document.getElementById('search-input').value = '';
document.getElementById('search-results').style.display = 'none';
document.getElementById('search-clear').style.display = 'none';
}
// Tutup hasil saat klik di luar
document.addEventListener('click', function (e) {
var wrapper = document.querySelector('.search-wrapper');
if (!wrapper.contains(e.target)) {
document.getElementById('search-results').style.display = 'none';
}
});
// Shortcut Cmd/Ctrl + K
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('search-input').focus();
}
if (e.key === 'Escape') {
clearSearch();
document.getElementById('search-input').blur();
}
});
// ==========================================
// 12. LOGIKA SIDEBAR INPUT TERPUSAT
// ==========================================
var sidebarPickMode = null; // 'ibadah' | 'penduduk'
var sidebarTempLat = 0, sidebarTempLng = 0;
// Buka/Tutup Sidebar
document.getElementById('sidebar-toggle').addEventListener('click', function () {
var sidebar = document.getElementById('input-sidebar');
sidebar.classList.toggle('open');
this.classList.toggle('open');
});
document.getElementById('sidebar-close').addEventListener('click', function () {
document.getElementById('input-sidebar').classList.remove('open');
document.getElementById('sidebar-toggle').classList.remove('open');
});
// Ganti Tab
function switchSidebarTab(tabName) {
document.querySelectorAll('.stab').forEach(function (t) { t.classList.remove('active'); });
document.querySelectorAll('.stab-content').forEach(function (c) { c.classList.remove('active'); });
document.querySelector('.stab[data-tab="' + tabName + '"]').classList.add('active');
document.getElementById('tab-' + tabName).classList.add('active');
// batalkan mode pick/draw jika ganti tab
deactivatePickMode();
}
// Aktifkan mode pilih titik di peta
function activatePickMode(jenis) {
sidebarPickMode = jenis;
document.getElementById('pick-overlay').classList.add('active');
var teks = { ibadah: 'Klik di peta untuk posisi Rumah Ibadah', penduduk: 'Klik di peta untuk posisi Penduduk Miskin' };
document.getElementById('pick-overlay-text').textContent = teks[jenis] || 'Klik lokasi di peta...';
var btnMap = { ibadah: 'btn-pick-ibadah', penduduk: 'btn-pick-penduduk' };
if (btnMap[jenis]) document.getElementById(btnMap[jenis]).classList.add('active-pick');
map.getContainer().style.cursor = 'crosshair';
}
// Batalkan semua mode
function deactivatePickMode() {
sidebarPickMode = null;
document.getElementById('pick-overlay').classList.remove('active');
document.querySelectorAll('.btn-pick-location').forEach(function (b) { b.classList.remove('active-pick'); });
map.getContainer().style.cursor = '';
}
// Tangkap klik peta untuk mode pick titik
map.on('click', function (e) {
if (!sidebarPickMode) return;
var lat = e.latlng.lat.toFixed(6);
var lng = e.latlng.lng.toFixed(6);
sidebarTempLat = e.latlng.lat;
sidebarTempLng = e.latlng.lng;
radiusLayer.clearLayers();
pendudukMiskinLayer.clearLayers();
if (sidebarPickMode === 'ibadah') {
var el = document.getElementById('coords-ibadah-val');
el.textContent = lat + ', ' + lng;
el.classList.add('has-coords');
} else if (sidebarPickMode === 'penduduk') {
var el = document.getElementById('coords-penduduk-val');
el.textContent = lat + ', ' + lng;
el.classList.add('has-coords');
var alamatInput = document.getElementById('sb_alamat_penduduk');
alamatInput.value = "Sedang mencari alamat..."; // Indikator loading
// Memanggil API Nominatim OpenStreetMap
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
.then(response => response.json())
.then(data => {
if (data && data.display_name) {
// Isi textarea alamat dengan hasil dari Nominatim
alamatInput.value = data.display_name;
} else {
alamatInput.value = "Alamat tidak ditemukan, silakan isi manual.";
}
})
.catch(error => {
console.error("Error Reverse Geocoding:", error);
alamatInput.value = "";
});
}
deactivatePickMode();
});
// ===== FUNGSI SIMPAN DARI SIDEBAR =====
function saveRumahIbadahSidebar() {
var coords = document.getElementById('coords-ibadah-val').textContent;
if (coords === 'Belum dipilih') { alert('Pilih lokasi di peta terlebih dahulu!'); return; }
var nama = document.getElementById('sb_nama_ibadah').value.trim();
if (!nama) { alert('Nama rumah ibadah wajib diisi!'); return; }
var formData = new FormData();
formData.append('nama', nama);
formData.append('latitude', sidebarTempLat);
formData.append('longitude', sidebarTempLng);
fetch('save_rumah_ibadah.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => {
alert(res);
document.getElementById('sb_nama_ibadah').value = '';
document.getElementById('coords-ibadah-val').textContent = 'Belum dipilih';
document.getElementById('coords-ibadah-val').classList.remove('has-coords');
sidebarTempLat = 0; sidebarTempLng = 0;
loadRumahIbadah();
}).catch(e => console.error(e));
}
function savePendudukSidebar() {
var coords = document.getElementById('coords-penduduk-val').textContent;
if (coords === 'Belum dipilih') { alert('Pilih lokasi di peta terlebih dahulu!'); return; }
var nama = document.getElementById('sb_nama_penduduk').value.trim();
var alamat = document.getElementById('sb_alamat_penduduk').value.trim();
var statusBantuan = document.getElementById('sb_status_bantuan').value;
if (!nama) { alert('Nama kepala keluarga wajib diisi!'); return; }
if (!alamat) { alert('Alamat wajib diisi!'); return; }
var formData = new FormData();
formData.append('nama', nama);
formData.append('alamat', alamat);
formData.append('bantuan', statusBantuan);
formData.append('latitude', sidebarTempLat);
formData.append('longitude', sidebarTempLng);
fetch('save_penduduk_miskin.php', { method: 'POST', body: formData })
.then(r => r.text()).then(res => {
alert(res);
document.getElementById('sb_nama_penduduk').value = '';
document.getElementById('sb_alamat_penduduk').value = '';
document.getElementById('sb_status_bantuan').value = 'Belum Menerima';
document.getElementById('coords-penduduk-val').textContent = 'Belum dipilih';
document.getElementById('coords-penduduk-val').classList.remove('has-coords');
sidebarTempLat = 0; sidebarTempLng = 0;
loadRumahIbadah();
}).catch(e => console.error(e));
}
// ==========================================
// 14. LOGIKA CUSTOM DROPDOWN
// ==========================================
document.addEventListener('DOMContentLoaded', function () {
var wrap = document.getElementById('custom-select-wrap');
var trigger = document.getElementById('custom-select-trigger');
var options = document.getElementById('custom-select-options');
var hiddenInput = document.getElementById('search-category');
var optionItems = document.querySelectorAll('.custom-option');
// Buka/Tutup dropdown saat kotak di-klik
wrap.addEventListener('click', function (e) {
e.stopPropagation();
options.classList.toggle('show');
});
// Saat salah satu pilihan (seperti SPBU atau Jalan) diklik
optionItems.forEach(function (item) {
item.addEventListener('click', function (e) {
e.stopPropagation();
// Hapus status aktif dari yang lain, pasang ke yang di-klik
optionItems.forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
// Ganti teks yang tampil dan simpan nilainya ke input tersembunyi
trigger.textContent = this.textContent;
hiddenInput.value = this.getAttribute('data-value');
// Tutup dropdown
options.classList.remove('show');
// Otomatis jalankan fungsi pencarian (kode aslimu)
jalankanPencarian();
});
});
// Tutup dropdown otomatis jika klik area manapun di luar peta/header
document.addEventListener('click', function () {
options.classList.remove('show');
});
});
// ==========================================
// 15. DYNAMIC STATS DASHBOARD SIDEBAR (CHART.JS)
// ==========================================
var myPovertyChart = null;
function showDashboard(ibadahName, totalKK, sudahBantuan, belumBantuan, familiesInRadius) {
document.getElementById('dashboard-target-name').textContent = ibadahName;
document.getElementById('stat-total-kk').textContent = totalKK;
document.getElementById('stat-sudah-bantuan').textContent = sudahBantuan;
document.getElementById('stat-belum-bantuan').textContent = belumBantuan;
// Tampilkan sidebar kanan
document.getElementById('dashboard-sidebar').classList.add('open');
// Buat daftar KK miskin dalam radius
var listContainer = document.getElementById('dashboard-families-list');
listContainer.innerHTML = '';
if (familiesInRadius.length === 0) {
listContainer.innerHTML = '<div class="families-empty">Tidak ada KK Miskin dalam radius ini</div>';
} else {
familiesInRadius.forEach(f => {
var item = document.createElement('div');
item.className = 'family-item';
var statusClass = f.bantuan === 'Sudah Menerima' ? 'badge-success' : 'badge-danger';
var statusText = f.bantuan === 'Sudah Menerima' ? 'Sudah' : 'Belum';
item.innerHTML = `
<div class="family-details">
<div class="family-name">${f.nama_kk}</div>
<div class="family-address">${f.alamat || 'Alamat tidak diisi'}</div>
</div>
<span class="family-badge ${statusClass}">${statusText}</span>
`;
// Klik untuk zoom/fly ke marker
item.onclick = function () {
var lat = parseFloat(f.lat || f.latitude);
var lng = parseFloat(f.lng || f.longitude);
map.flyTo([lat, lng], 18, { animate: true });
};
listContainer.appendChild(item);
});
}
// Gambar Chart.js
var ctx = document.getElementById('povertyChart').getContext('2d');
if (myPovertyChart) {
myPovertyChart.destroy();
}
var chartLabels = ['Sudah Menerima', 'Belum Menerima'];
var chartData = [sudahBantuan, belumBantuan];
var bgColors = ['#10b981', '#ef4444'];
if (totalKK === 0) {
chartLabels = ['Tidak Ada Data'];
chartData = [1];
bgColors = ['#475569'];
}
myPovertyChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: chartLabels,
datasets: [{
data: chartData,
backgroundColor: bgColors,
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
color: '#cbd5e1',
font: {
family: 'Plus Jakarta Sans',
size: 10
},
boxWidth: 12
}
}
},
cutout: '65%'
}
});
}
function closeDashboard() {
document.getElementById('dashboard-sidebar').classList.remove('open');
}