Files
D1041231012_MuhammadArabbiG…/pendataanwarga2.html
T

917 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8" />
<title>SIG Pendataan Warga Kurang Mampu - Pontianak</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<style>
* { box-sizing: border-box; }
body { margin: 0; font-family: Arial; }
#map { height: 100vh; }
/* ══════════════════════════════════════
TOPBAR
══════════════════════════════════════ */
#topbar {
position: absolute;
top: 0; left: 0; right: 0;
height: 46px;
background: linear-gradient(90deg, #1a3a5c, #2e6da4);
z-index: 2000;
display: flex;
align-items: center;
padding: 0 14px;
gap: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
#topbar .tb-logo { font-size: 20px; }
#topbar .tb-title {
color: white;
font-size: 14px;
font-weight: 700;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#topbar .tb-badge {
background: rgba(255,255,255,0.18);
color: white;
font-size: 11px;
font-weight: 600;
padding: 3px 9px;
border-radius: 20px;
border: 1px solid rgba(255,255,255,0.3);
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
#topbar .tb-badge.admin-badge { background: rgba(46,204,113,0.3); border-color: rgba(46,204,113,0.5); }
#topbar .tb-badge.user-badge { background: rgba(255,255,255,0.15); }
#topbar .tb-user {
color: rgba(255,255,255,0.85);
font-size: 12px;
white-space: nowrap;
}
#topbar .tb-logout {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 5px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: background 0.2s;
white-space: nowrap;
}
#topbar .tb-logout:hover { background: rgba(255,255,255,0.28); }
/* ══════════════════════════════════════
INFO BANNER (khusus pengguna biasa)
══════════════════════════════════════ */
#infoBanner {
position: absolute;
top: 46px; left: 0; right: 0;
background: linear-gradient(90deg, #e8f4fd, #d0eaf8);
border-bottom: 1px solid #aed6f1;
padding: 7px 14px;
font-size: 12px;
color: #1a5276;
z-index: 1500;
display: none;
align-items: center;
gap: 8px;
}
/* ══════════════════════════════════════
PANELS — offset turun untuk topbar
══════════════════════════════════════ */
.panel-offset { top: 56px; } /* 46px topbar + 10px gap */
.control-box {
position: absolute;
top: 56px;
left: 50px;
background: white;
padding: 12px;
z-index: 1000;
border-radius: 8px;
width: 240px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
.control-box label {
font-size: 14px;
display: block;
margin-top: 5px;
}
/* ── readonly-mode: sembunyikan control box ── */
.control-box.readonly { display: none; }
/* ══════════════════════════════════════
TOMBOL TOGGLE — posisi tetap di kiri,
tidak ikut panel buka/tutup
══════════════════════════════════════ */
.toggle-btn {
position: absolute;
left: 8px;
width: 34px;
height: 34px;
background: white;
border: none;
border-radius: 6px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
cursor: pointer;
font-size: 17px;
z-index: 1100;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-btn:hover { background: #f0f4f8; }
/* Posisi vertikal tiap tombol — Admin (ada control-box) */
#toggleStatsBtn { top: 172px; }
#toggleLayerBtn { top: 214px; }
#toggleHeatmapBtn { top: 256px; }
/* Posisi vertikal — Pengguna (tidak ada control-box) */
body.readonly-mode #toggleStatsBtn { top: 66px; }
body.readonly-mode #toggleLayerBtn { top: 108px; }
body.readonly-mode #toggleHeatmapBtn { top: 150px; }
/* ══════════════════════════════════════
PANEL — muncul di samping kanan tombol
══════════════════════════════════════ */
.panel-box {
position: absolute;
left: 50px;
background: white;
padding: 12px;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
z-index: 1000;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.panel-box.hidden {
visibility: hidden;
opacity: 0;
pointer-events: none;
}
/* Ukuran & posisi per panel — Admin */
.stats-box { top: 166px; width: 240px; font-size: 14px; max-height: 300px; overflow-y: auto; }
.layer-box { top: 208px; width: 220px; }
.heatmap-box { top: 250px; width: 220px; }
/* Posisi panel — Pengguna */
body.readonly-mode .stats-box { top: 60px; }
body.readonly-mode .layer-box { top: 102px; }
body.readonly-mode .heatmap-box { top: 144px; }
/* Tooltip */
.toggle-btn .tooltip {
position: absolute;
left: 40px; top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.75);
color: white;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s;
}
.toggle-btn:hover .tooltip { opacity: 1; }
.popup-btn {
padding: 4px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
}
.popup-btn.edit { background: #eaf2fb; color: #1a3a5c; }
.popup-btn.hapus { background: #fdecea; color: #c0392b; }
.popup-btn:hover { opacity: 0.82; }
/* ── Readonly notice di popup ── */
.popup-readonly-note {
font-size: 11px;
color: #888;
font-style: italic;
margin-top: 6px;
}
@media (max-width: 768px) {
.stats-box, .layer-box { left: 10px; width: 220px; }
#topbar .tb-title { font-size: 12px; }
}
</style>
</head>
<body>
<!-- ══════════════════════════════════════
TOPBAR
══════════════════════════════════════ -->
<div id="topbar">
<span class="tb-logo">🗺️</span>
<span class="tb-title">SIG Pendataan Warga Kurang Mampu Pontianak</span>
<span class="tb-badge" id="roleBadge">&nbsp;</span>
<span class="tb-user" id="usernameLabel"></span>
<button class="tb-logout" onclick="doLogout()">🚪 Keluar</button>
</div>
<!-- ══════════════════════════════════════
INFO BANNER (Pengguna biasa)
══════════════════════════════════════ -->
<div id="infoBanner">
&nbsp;Anda masuk sebagai <strong>Pengguna</strong>. Anda hanya dapat melihat data. Hubungi Admin untuk mengubah data.
</div>
<!-- ══════════════════════════════════════
PANEL MODE INPUT (Admin only)
══════════════════════════════════════ -->
<div class="control-box" id="controlBox">
<b>Mode Input:</b>
<label><input type="radio" name="mode" value="none" checked> Tidak ada</label>
<label><input type="radio" name="mode" value="ibadah"> Rumah Ibadah</label>
<label><input type="radio" name="mode" value="warga"> Rumah Warga</label>
<hr>
</div>
<!-- ══════════════════════════════════════
TOMBOL TOGGLE — berdiri sendiri, posisi tetap
══════════════════════════════════════ -->
<button id="toggleStatsBtn" class="toggle-btn" onclick="toggleStatistik()" >📊<span class="tooltip">Statistik Analitis</span></button>
<button id="toggleLayerBtn" class="toggle-btn" onclick="toggleLayerBox()" >🗂️<span class="tooltip">Layer Control</span></button>
<button id="toggleHeatmapBtn" class="toggle-btn" onclick="toggleHeatmapBox()" >🔥<span class="tooltip">Heatmap</span></button>
<!-- ══════════════════════════════════════
PANEL STATISTIK
══════════════════════════════════════ -->
<div class="stats-box panel-box hidden" id="statsBox">
<b>STATISTIK ANALITIS</b>
<hr>
<div id="statistikContent">Memuat statistik...</div>
</div>
<!-- ══════════════════════════════════════
PANEL LAYER CONTROL
══════════════════════════════════════ -->
<div class="layer-box panel-box hidden" id="layerBox">
<b>LAYER CONTROL</b>
<hr>
<label>
<input type="checkbox" checked id="checkAllLayer" onchange="toggleSemuaLayer(this)">
Semua Layer
</label>
<hr>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerMasjid)"> Masjid</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerGereja)"> Gereja</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerVihara)"> Vihara</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerPura)"> Pura</label>
<hr>
<b>REKOMENDASI</b><br><br>
<label><input type="checkbox" checked onchange="toggleRekomendasi(this)"> Lokasi Baru</label>
<hr>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerWargaCover)"> Warga Tercover</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerWargaTidakCover)"> Warga Belum Tercover</label>
</div>
<!-- ══════════════════════════════════════
PANEL HEATMAP
══════════════════════════════════════ -->
<div class="heatmap-box panel-box hidden" id="heatmapBox">
<b>HEATMAP</b>
<hr>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('semua')"> Semua Warga</label><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('cover')"> Warga Tercover</label><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('tidakcover')"> Warga Belum Tercover</label><br><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('none')" checked> Nonaktifkan Heatmap</label>
</div>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.heat/dist/leaflet-heat.js"></script>
<script>
// ══════════════════════════════════════
// AUTENTIKASI — cek sesi
// ══════════════════════════════════════
var ROLE = sessionStorage.getItem('sig_role');
var USERNAME = sessionStorage.getItem('sig_username');
var LOGGEDIN = sessionStorage.getItem('sig_loggedin');
var IS_ADMIN = (ROLE === 'admin');
if (LOGGEDIN !== '1') {
// Belum login → redirect ke halaman login
window.location.href = 'login.html';
}
function doLogout() {
sessionStorage.clear();
window.location.href = 'login.html';
}
// ── Tampilkan info user di topbar ──────────────────────────────
document.getElementById('usernameLabel').textContent = USERNAME || '';
var badge = document.getElementById('roleBadge');
if (IS_ADMIN) {
badge.innerHTML = '🛡️ &nbsp;Admin';
badge.classList.add('admin-badge');
} else {
badge.innerHTML = '👤 &nbsp;Pengguna';
badge.classList.add('user-badge');
}
// ── Tampilkan info banner untuk pengguna biasa ─────────────────
if (!IS_ADMIN) {
document.getElementById('infoBanner').style.display = 'flex';
}
// ── Atur posisi panel (ada/tidaknya control-box) ───────────────
if (!IS_ADMIN) {
// Sembunyikan panel Mode Input
document.getElementById('controlBox').classList.add('readonly');
document.body.classList.add('readonly-mode');
// Geser panel lain ke atas
document.getElementById('statsBox').classList.add('readonly-pos');
document.getElementById('layerBox').classList.add('readonly-pos');
document.getElementById('heatmapBox').classList.add('readonly-pos');
}
// ══════════════════════════════════════
// INISIALISASI PETA
// ══════════════════════════════════════
var map = L.map('map').setView([-0.0263, 109.3425], 13);
var ibadahLayers = [];
var dataIbadah = [];
var dataWarga = [];
var heatmapLayer;
var wargaMarkers = {};
var layerRekomendasi = L.layerGroup().addTo(map);
var layerMasjid = L.layerGroup().addTo(map);
var layerGereja = L.layerGroup().addTo(map);
var layerVihara = L.layerGroup().addTo(map);
var layerPura = L.layerGroup().addTo(map);
var layerWargaCover = L.layerGroup().addTo(map);
var layerWargaTidakCover = L.layerGroup().addTo(map);
var heatmapSemua, heatmapCover, heatmapTidakCover;
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap'
}).addTo(map);
// ══════════════════════════════════════
// HELPER FUNGSI IKON
// ══════════════════════════════════════
function getMode() {
return document.querySelector('input[name="mode"]:checked').value;
}
function getColorByJenis(jenis) {
if (jenis === 'Masjid') return 'green';
if (jenis === 'Gereja') return 'blue';
if (jenis === 'Vihara') return 'orange';
if (jenis === 'Pura') return 'yellow';
return 'gray';
}
function getIconByJenis(jenis) {
return new L.Icon({
iconUrl: `https://maps.google.com/mapfiles/ms/icons/${getColorByJenis(jenis)}-dot.png`,
iconSize: [32, 32], iconAnchor: [16, 32]
});
}
function getWargaIcon(tercover = true) {
return new L.Icon({
iconUrl: `https://maps.google.com/mapfiles/ms/icons/${tercover ? 'red' : 'purple'}-dot.png`,
iconSize: [32, 32], iconAnchor: [16, 32]
});
}
// ══════════════════════════════════════
// POPUP BUILDER — konten beda per role
// ══════════════════════════════════════
function popupIbadah(nama, jenis, leafletId, radius, dbId) {
var sliderHtml = IS_ADMIN
? `<input type="range" min="100" max="2000" value="${radius}"
oninput="updateRadius(this.value, ${leafletId}, ${dbId})"><br><br>
<button class="popup-btn edit" onclick="editIbadah(${dbId}, ${JSON.stringify(nama)}, ${JSON.stringify(jenis)}, ${radius})">✏️ Edit</button>
<button class="popup-btn hapus" onclick="hapusIbadah(${dbId})">🗑 Hapus</button>`
: `<em class="popup-readonly-note">🔒 Hanya Admin yang dapat mengubah data</em>`;
return `<b>${nama}</b><br>
Jenis: ${jenis}<br>
Radius: <span id="rVal-${leafletId}">${radius}</span> m<br>
${sliderHtml}`;
}
function popupWarga(nama, jumlah, pengurus, tercover, dbId) {
var aksiHtml = IS_ADMIN
? `<button class="popup-btn edit" onclick="editWarga(${dbId}, '${nama.replace(/'/g,"\\'")}', ${jumlah})">✏️ Edit</button>
<button class="popup-btn hapus" onclick="hapusWarga(${dbId})">🗑 Hapus</button>`
: `<em class="popup-readonly-note">🔒 Hanya Admin yang dapat mengubah data</em>`;
return `<b>Warga Kurang Mampu</b><br>
Nama KK: ${nama}<br>
Jumlah KK: ${jumlah}<br>
Pengurus: ${pengurus}
${!tercover ? "<br><b style='color:red'>⚠ Belum Tercover</b>" : ''}<br><br>
${aksiHtml}`;
}
// ══════════════════════════════════════
// KLIK PETA — hanya Admin
// ══════════════════════════════════════
map.on('click', function(e) {
if (!IS_ADMIN) return; // Pengguna biasa tidak bisa klik input
var mode = getMode();
if (mode === 'none') { alert('Pilih mode input terlebih dahulu!'); return; }
if (mode === 'ibadah') {
L.popup().setLatLng(e.latlng).setContent(`
<b>Input Rumah Ibadah</b><br>
Nama:<br><input type="text" id="namaIbadah"><br><br>
Jenis:<br>
<select id="popupJenis">
<option value="Masjid">Masjid</option>
<option value="Gereja">Gereja</option>
<option value="Vihara">Vihara</option>
<option value="Pura">Pura</option>
</select><br><br>
<button onclick="simpanIbadah(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
`).openOn(map);
}
if (mode === 'warga') {
L.popup().setLatLng(e.latlng).setContent(`
<b>Input Data Warga</b><br>
Nama Kepala KK:<br><input type="text" id="namaKK"><br><br>
Jumlah KK:<br><input type="number" id="jumlahKK"><br><br>
<button onclick="simpanWarga(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
`).openOn(map);
}
});
// ══════════════════════════════════════
// SIMPAN IBADAH
// ══════════════════════════════════════
function simpanIbadah(lat, lng) {
var nama = document.getElementById('namaIbadah').value;
var jenis = document.getElementById('popupJenis').value;
if (!nama.trim()) { alert('Nama wajib diisi'); return; }
var color = getColorByJenis(jenis);
var icon = getIconByJenis(jenis);
var radius = 500;
var latlng = [lat, lng];
var targetLayer = { Masjid: layerMasjid, Gereja: layerGereja, Vihara: layerVihara, Pura: layerPura }[jenis] || layerMasjid;
var circle = L.circle(latlng, { radius, color, fillColor: color, fillOpacity: 0.3 }).addTo(targetLayer);
var marker = L.marker(latlng, { icon }).addTo(targetLayer);
var lid = circle._leaflet_id;
ibadahLayers[lid] = circle;
fetch('api/simpan_ibadah.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nama, jenis, lat, lng, radius })
})
.then(r => r.json())
.then(result => {
marker.bindPopup(popupIbadah(nama, jenis, lid, radius, result.id)).openPopup();
});
dataIbadah.push({ nama, jenis, lat, lng, circle });
updateStatistik();
map.closePopup();
}
// ══════════════════════════════════════
// SIMPAN WARGA
// ══════════════════════════════════════
function simpanWarga(lat, lng) {
var nama = document.getElementById('namaKK').value;
var jumlah = document.getElementById('jumlahKK').value;
if (!nama.trim()) { alert('Nama wajib diisi'); return; }
if (!jumlah || jumlah <= 0) { alert('Jumlah KK tidak valid'); return; }
var pengurus = cariPengurus(lat, lng);
var infoPengurus = pengurus ? `${pengurus.nama} (${pengurus.jenis})` : 'Tidak tercover';
var tercover = cekTercover(lat, lng);
fetch('api/simpan_warga.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nama, jumlah, lat, lng, pengurus: infoPengurus })
})
.then(r => r.json())
.then(result => {
var dbId = result.id;
dataWarga.push({ id: dbId, nama, jumlah, lat, lng });
updateStatistik();
buatRekomendasiLokasi();
buatHeatmap();
var targetLayer = tercover ? layerWargaCover : layerWargaTidakCover;
var marker = L.marker([lat, lng], { icon: getWargaIcon(tercover) }).addTo(targetLayer);
wargaMarkers[dbId] = marker;
marker.bindPopup(popupWarga(nama, jumlah, infoPengurus, tercover, dbId));
});
map.closePopup();
}
// ══════════════════════════════════════
// EDIT & HAPUS — hanya dipanggil admin
// ══════════════════════════════════════
function editWarga(id, namaLama, jumlahLama) {
if (!IS_ADMIN) return;
var namaBaru = prompt('Nama Kepala KK:', namaLama); if (!namaBaru) return;
var jumlahBaru = prompt('Jumlah KK:', jumlahLama); if (!jumlahBaru) return;
fetch('api/update_warga.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, nama: namaBaru, jumlah: jumlahBaru })
}).then(() => { alert('Data warga berhasil diupdate'); location.reload(); });
}
function editIbadah(id, namaLama, jenisLama, radiusLama) {
if (!IS_ADMIN) return;
var namaBaru = prompt('Nama Rumah Ibadah:', namaLama); if (!namaBaru) return;
var jenisBaru = prompt('Jenis (Masjid/Gereja/Vihara/Pura):', jenisLama); if (!jenisBaru) return;
var radiusBaru = prompt('Radius:', radiusLama); if (!radiusBaru) return;
fetch('api/update_ibadah.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, nama: namaBaru, jenis: jenisBaru, radius: radiusBaru })
}).then(() => { alert('Data rumah ibadah berhasil diupdate'); location.reload(); });
}
function hapusIbadah(id) {
if (!IS_ADMIN) return;
if (!confirm('Hapus data ini?')) return;
fetch('api/hapus_ibadah.php?id=' + id).then(() => location.reload());
}
function hapusWarga(id) {
if (!IS_ADMIN) return;
if (!confirm('Hapus data ini?')) return;
fetch('api/hapus_warga.php?id=' + id).then(() => location.reload());
}
// ══════════════════════════════════════
// UPDATE RADIUS — hanya admin
// ══════════════════════════════════════
function updateRadius(val, leafletId, dbId) {
if (!IS_ADMIN) return;
var circle = ibadahLayers[leafletId];
if (circle) circle.setRadius(Number(val));
var text = document.getElementById('rVal-' + leafletId);
if (text) text.innerHTML = val;
fetch('api/update_radius.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: dbId, radius: Number(val) })
});
updatePengurusWarga();
updateStatistik();
}
// ══════════════════════════════════════
// LOAD DATA (baca saja — semua role)
// ══════════════════════════════════════
window.onload = function() { loadData(); };
function loadData() {
fetch('api/get_data.php')
.then(r => r.json())
.then(data => {
L.geoJSON(data, {
pointToLayer: function(feature, latlng) {
if (feature.properties.tipe === 'ibadah') {
var jenis = feature.properties.jenis;
var color = getColorByJenis(jenis);
var targetLayer = { Masjid: layerMasjid, Gereja: layerGereja, Vihara: layerVihara, Pura: layerPura }[jenis] || layerMasjid;
var marker = L.marker(latlng, { icon: getIconByJenis(jenis) }).addTo(targetLayer);
var circle = L.circle(latlng, {
radius: feature.properties.radius,
color, fillColor: color, fillOpacity: 0.3
}).addTo(targetLayer);
var lid = circle._leaflet_id;
ibadahLayers[lid] = circle;
marker.bindPopup(
popupIbadah(
feature.properties.nama,
jenis,
lid,
feature.properties.radius,
feature.properties.id
)
);
dataIbadah.push({
nama: feature.properties.nama,
jenis,
lat: latlng.lat,
lng: latlng.lng,
circle
});
return marker;
}
if (feature.properties.tipe === 'warga') {
var tercover = feature.properties.pengurus !== 'Tidak tercover';
var targetLayer = tercover ? layerWargaCover : layerWargaTidakCover;
var marker = L.marker(latlng, { icon: getWargaIcon(tercover) }).addTo(targetLayer);
wargaMarkers[feature.properties.id] = marker;
marker.bindPopup(
popupWarga(
feature.properties.nama,
feature.properties.jumlah_kk,
feature.properties.pengurus,
tercover,
feature.properties.id
)
);
dataWarga.push({
id: feature.properties.id,
nama: feature.properties.nama,
jumlah: feature.properties.jumlah_kk,
lat: latlng.lat,
lng: latlng.lng
});
return marker;
}
}
}).addTo(map);
updateStatistik();
buatRekomendasiLokasi();
});
}
// ══════════════════════════════════════
// ANALISIS SPASIAL
// ══════════════════════════════════════
function cariPengurus(lat, lng) {
var terdekat = null, jarakMin = Infinity;
dataIbadah.forEach(item => {
if (!item.circle) return;
var jarak = map.distance([lat, lng], item.circle.getLatLng());
if (jarak <= item.circle.getRadius() && jarak < jarakMin) {
jarakMin = jarak; terdekat = item;
}
});
return terdekat;
}
function cekTercover(lat, lng) {
return dataIbadah.some(ibadah => {
if (!ibadah.circle) return false;
return map.distance([lat, lng], ibadah.circle.getLatLng()) <= ibadah.circle.getRadius();
});
}
function updatePengurusWarga() {
dataWarga.forEach(warga => {
var pengurusBaru = 'Tidak tercover';
dataIbadah.forEach(ibadah => {
if (!ibadah.circle) return;
var jarak = map.distance([warga.lat, warga.lng], ibadah.circle.getLatLng());
if (jarak <= ibadah.circle.getRadius()) {
pengurusBaru = `${ibadah.nama} (${ibadah.jenis})`;
}
});
fetch('api/update_pengurus.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: warga.id, pengurus: pengurusBaru })
});
var marker = wargaMarkers[warga.id];
if (!marker) return;
var tercover = pengurusBaru !== 'Tidak tercover';
marker.setIcon(getWargaIcon(tercover));
layerWargaCover.removeLayer(marker);
layerWargaTidakCover.removeLayer(marker);
(tercover ? layerWargaCover : layerWargaTidakCover).addLayer(marker);
marker.setPopupContent(
popupWarga(warga.nama, warga.jumlah, pengurusBaru, tercover, warga.id)
);
});
buatRekomendasiLokasi();
buatHeatmap();
}
// ══════════════════════════════════════
// STATISTIK
// ══════════════════════════════════════
function updateStatistik() {
var totalWarga = dataWarga.length;
var totalKK = dataWarga.reduce((s, w) => s + Number(w.jumlah || 0), 0);
var counts = { Masjid: 0, Gereja: 0, Vihara: 0, Pura: 0 };
dataIbadah.forEach(i => { if (counts[i.jenis] !== undefined) counts[i.jenis]++; });
var ranking = dataIbadah
.filter(i => i.circle)
.map(i => ({
nama: i.nama,
jenis: i.jenis,
total: dataWarga.filter(w => map.distance([w.lat, w.lng], i.circle.getLatLng()) <= i.circle.getRadius()).length
}))
.sort((a, b) => b.total - a.total);
var belum = dataWarga
.filter(w => !cekTercover(w.lat, w.lng))
.map(w => w.nama);
var html = `
<b>Total Warga Kurang Mampu:</b><br>${totalWarga}<br><br>
<b>Total KK:</b><br>${totalKK}<br><br>
<b>Total Rumah Ibadah:</b><br>
Masjid: ${counts.Masjid}<br>
Gereja: ${counts.Gereja}<br>
Vihara: ${counts.Vihara}<br>
Pura: ${counts.Pura}<br><br>
<b>Cakupan Warga Terbanyak:</b><br>
`;
ranking.forEach((item, i) => { html += `${i + 1}. ${item.nama} (${item.total} warga)<br>`; });
html += `<br><b>Warga Belum Tercover:</b><br>`;
html += belum.length === 0
? 'Semua warga sudah tercover'
: belum.map(n => `- ${n}`).join('<br>');
document.getElementById('statistikContent').innerHTML = html;
}
// ══════════════════════════════════════
// HEATMAP
// ══════════════════════════════════════
function buatHeatmap() {
var dataSemua = [], dataCover = [], dataTidakCover = [];
dataWarga.forEach(w => {
var pt = [w.lat, w.lng, (w.jumlah || 1) * 2];
dataSemua.push(pt);
(cekTercover(w.lat, w.lng) ? dataCover : dataTidakCover).push(pt);
});
var mk = (data, grad, r = 45, b = 18, mo = 0.5) =>
L.heatLayer(data, { radius: r, blur: b, maxZoom: 18, minOpacity: mo, gradient: grad });
[heatmapSemua, heatmapCover, heatmapTidakCover].forEach(h => { if (h) map.removeLayer(h); });
heatmapSemua = mk(dataSemua, { 0.2:'#d6ecff', 0.4:'#a8d8ff', 0.6:'#73c2fb', 0.8:'#3399ff', 1:'#0066cc' });
heatmapCover = mk(dataCover, { 0.2:'#b3ffb3', 0.4:'#66ff66', 0.6:'#00cc00', 0.8:'#009900', 1:'#006600' });
heatmapTidakCover = mk(dataTidakCover, { 0.2:'#ffd6d6', 0.4:'#ff8080', 0.6:'#ff3333', 0.8:'#e60000', 1:'#800000' }, 50, 20, 0.6);
}
function pilihHeatmap(jenis) {
buatHeatmap();
[heatmapSemua, heatmapCover, heatmapTidakCover].forEach(h => { if (h) map.removeLayer(h); });
var target = { semua: heatmapSemua, cover: heatmapCover, tidakcover: heatmapTidakCover }[jenis];
if (target) map.addLayer(target);
}
// ══════════════════════════════════════
// REKOMENDASI LOKASI
// ══════════════════════════════════════
function buatRekomendasiLokasi() {
layerRekomendasi.clearLayers();
var belum = dataWarga.filter(w => !cekTercover(w.lat, w.lng));
if (belum.length === 0) return;
var sumLat = belum.reduce((s, w) => s + w.lat, 0);
var sumLng = belum.reduce((s, w) => s + w.lng, 0);
var totalKK = belum.reduce((s, w) => s + (Number(w.jumlah) || 1), 0);
var centerLat = sumLat / belum.length;
var centerLng = sumLng / belum.length;
var rekIcon = new L.Icon({
iconUrl: 'https://maps.google.com/mapfiles/kml/paddle/wht-circle.png',
iconSize: [32, 32], iconAnchor: [16, 32]
});
L.marker([centerLat, centerLng], { icon: rekIcon })
.bindPopup(`<b>Rekomendasi Lokasi Baru</b><hr>
Jumlah Warga Belum Tercover: <b>${totalKK}</b><br><br>
Titik rekomendasi ini merupakan pusat area warga yang belum tercover rumah ibadah.`)
.addTo(layerRekomendasi);
}
// ══════════════════════════════════════
// TOGGLE PANEL
// ══════════════════════════════════════
function toggleStatistik() {
var s = document.getElementById('statsBox');
var l = document.getElementById('layerBox');
var h = document.getElementById('heatmapBox');
var isOpen = !s.classList.contains('hidden');
s.classList.toggle('hidden');
if (!isOpen) { l.classList.add('hidden'); h.classList.add('hidden'); }
}
function toggleLayerBox() {
var l = document.getElementById('layerBox');
var s = document.getElementById('statsBox');
var h = document.getElementById('heatmapBox');
var isOpen = !l.classList.contains('hidden');
l.classList.toggle('hidden');
if (!isOpen) { s.classList.add('hidden'); h.classList.add('hidden'); }
}
function toggleHeatmapBox() {
var h = document.getElementById('heatmapBox');
var s = document.getElementById('statsBox');
var l = document.getElementById('layerBox');
var isOpen = !h.classList.contains('hidden');
h.classList.toggle('hidden');
if (!isOpen) { s.classList.add('hidden'); l.classList.add('hidden'); }
}
function toggleLayer(checkbox, layer) {
checkbox.checked ? map.addLayer(layer) : map.removeLayer(layer);
}
function toggleSemuaLayer(checkbox) {
document.querySelectorAll('#layerBox input[type="checkbox"]').forEach(cb => cb.checked = checkbox.checked);
[layerMasjid, layerGereja, layerVihara, layerPura,
layerWargaCover, layerWargaTidakCover, layerRekomendasi,
heatmapSemua, heatmapCover, heatmapTidakCover]
.forEach(layer => { if (!layer) return; checkbox.checked ? map.addLayer(layer) : map.removeLayer(layer); });
}
function toggleRekomendasi(checkbox) {
checkbox.checked ? map.addLayer(layerRekomendasi) : map.removeLayer(layerRekomendasi);
}
</script>
</body>
</html>