Project Webgis2 SIG
This commit is contained in:
@@ -0,0 +1,916 @@
|
||||
<!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">⬤ –</span>
|
||||
<span class="tb-user" id="usernameLabel"></span>
|
||||
<button class="tb-logout" onclick="doLogout()">🚪 Keluar</button>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
INFO BANNER (Pengguna biasa)
|
||||
══════════════════════════════════════ -->
|
||||
<div id="infoBanner">
|
||||
ℹ️ 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 = '🛡️ Admin';
|
||||
badge.classList.add('admin-badge');
|
||||
} else {
|
||||
badge.innerHTML = '👤 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: '© 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>
|
||||
Reference in New Issue
Block a user