Files
webgis-poverty-mapping/radius.php
T
2026-06-10 12:25:43 +00:00

857 lines
32 KiB
PHP

<?php
include 'auth.php';
include 'koneksi.php';
$userRole = getUserRole();
$userIbadahId = getUserIbadahId();
$isAdmin = isAdminPemerintah();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analisis Radius Bantuan — WebGIS</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Plus Jakarta Sans', sans-serif; background: #0b132b; color: #fff; overflow: hidden; }
#map { height: 100vh; width: 100%; z-index: 1; }
/* SIDEBAR */
.sidebar {
position: absolute; top: 20px; left: 20px; z-index: 1000;
width: 270px; background: rgba(11,19,43,0.88);
backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(255,255,255,0.1); border-radius: 20px;
padding: 22px; box-shadow: 0 8px 40px rgba(0,0,0,0.6);
}
.sidebar-brand {
display: flex; align-items: center; gap: 10px;
font-size: 0.7rem; font-weight: 800; color: #5bc0be;
text-transform: uppercase; letter-spacing: 2px;
cursor: pointer; user-select: none;
justify-content: space-between;
}
.sidebar-brand span { display: flex; align-items: center; gap: 10px; }
.sidebar-brand .icon-wrap {
width: 34px; height: 34px; border-radius: 10px;
background: linear-gradient(135deg, #5bc0be, #3a506b);
display: flex; align-items: center; justify-content: center;
font-size: 1rem;
}
#chevron { transition: transform .3s; }
.sidebar.open #chevron { transform: rotate(180deg); }
.sidebar-body { overflow: hidden; max-height: 0; transition: max-height .4s ease; }
.sidebar.open .sidebar-body { max-height: 600px; }
.divider { border: none; border-top: 1px solid rgba(255,255,255,0.08); margin: 16px 0; }
.section-label {
font-size: 0.62rem; font-weight: 800; color: #5bc0be;
text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 10px;
}
.btn-mode {
width: 100%; display: flex; align-items: center; gap: 12px;
padding: 12px 14px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);
background: rgba(255,255,255,0.03); color: #e0e1dd; font-size: 0.85rem;
font-weight: 600; cursor: pointer; transition: all .25s; margin-bottom: 8px;
font-family: inherit;
}
.btn-mode i { font-size: 1.1rem; }
.btn-mode:hover { background: rgba(58,80,107,0.4); transform: translateX(4px); border-color: rgba(91,192,190,0.3); }
.btn-mode.active {
background: linear-gradient(135deg,#1c2541,#3a506b);
border-color: #5bc0be; color: #fff;
box-shadow: 0 4px 15px rgba(91,192,190,0.35);
}
.btn-mode.active i { color: #6fffe9; }
/* LEGEND */
.legend { margin-top: 4px; }
.legend-item { display: flex; align-items: center; gap: 9px; font-size: 0.78rem; color: #b0bec5; margin-bottom: 7px; }
.legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.legend-dot.ibadah { background: #6fffe9; box-shadow: 0 0 6px #6fffe9; }
.legend-dot.hijau { background: #2ecc71; box-shadow: 0 0 6px #2ecc71; }
.legend-dot.merah { background: #e74c3c; box-shadow: 0 0 6px #e74c3c; }
.legend-circle { width: 14px; height: 14px; border-radius: 50%; border: 2px solid #5bc0be; flex-shrink: 0; }
/* STATS BAR */
.stats-bar {
position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%);
z-index: 1000; display: flex; gap: 12px;
}
.stat-card {
background: rgba(11,19,43,0.88); backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,0.1); border-radius: 14px;
padding: 10px 20px; text-align: center; min-width: 120px;
}
.stat-card .val { font-size: 1.6rem; font-weight: 800; line-height: 1; }
.stat-card .lbl { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 1px; color: #78909c; margin-top: 3px; }
.stat-ibadah .val { color: #6fffe9; }
.stat-hijau .val { color: #2ecc71; }
.stat-merah .val { color: #e74c3c; }
/* TOAST */
#toast {
position: absolute; top: 24px; right: 24px; z-index: 9999;
background: rgba(11,19,43,0.95); border: 1px solid rgba(255,255,255,0.12);
border-radius: 14px; padding: 14px 20px; font-size: 0.82rem;
max-width: 280px; display: none; backdrop-filter: blur(12px);
box-shadow: 0 8px 30px rgba(0,0,0,0.5); animation: slideIn .3s ease;
}
@keyframes slideIn { from { opacity:0; transform:translateX(30px); } to { opacity:1; transform:translateX(0); } }
/* POPUP */
.leaflet-popup-content-wrapper {
background: rgba(14,23,44,0.96) !important; backdrop-filter: blur(16px);
border: 1px solid rgba(255,255,255,0.1); border-radius: 18px !important;
padding: 0 !important; overflow: hidden;
box-shadow: 0 15px 40px rgba(0,0,0,0.6) !important; color: #fff;
}
.leaflet-popup-tip { background: rgba(14,23,44,0.96) !important; }
.leaflet-popup-content { margin: 0 !important; width: 300px !important; }
.leaflet-popup-close-button { color: #fff !important; top: 12px !important; right: 12px !important; font-size: 18px !important; z-index: 10; }
.leaflet-popup-close-button:hover { color: #5bc0be !important; }
.p-header {
background: linear-gradient(135deg,#0b132b,#1c2541);
border-bottom: 1px solid rgba(255,255,255,0.08);
padding: 16px 40px 16px 18px;
font-size: 0.75rem; font-weight: 800;
text-transform: uppercase; letter-spacing: 1.5px; color: #6fffe9;
}
.p-body { padding: 18px; }
.f-label {
font-size: 0.65rem; font-weight: 700; color: #5bc0be;
text-transform: uppercase; letter-spacing: 1px;
display: block; margin-bottom: 6px;
}
.f-input {
width: 100%; background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.1);
border-radius: 10px; padding: 11px 13px; font-size: 0.84rem; color: #fff;
font-family: inherit; transition: border-color .25s; margin-bottom: 14px;
}
.f-input:focus { outline: none; border-color: #5bc0be; background: rgba(0,0,0,0.4); }
.f-input::placeholder { color: rgba(255,255,255,0.35); }
.btn-submit {
width: 100%; padding: 12px; border-radius: 11px; border: none; cursor: pointer;
font-weight: 700; font-size: 0.88rem; font-family: inherit;
background: linear-gradient(135deg,#5bc0be,#3a506b); color: #fff;
transition: transform .25s, box-shadow .25s;
}
.btn-submit:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(91,192,190,0.4); }
.info-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 0.82rem; }
.info-row .key { color: #78909c; }
.info-row .val { font-weight: 700; }
.badge-warna {
display: inline-block; padding: 4px 12px; border-radius: 20px;
font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
}
.badge-hijau { background: rgba(46,204,113,0.2); color: #2ecc71; border: 1px solid #2ecc71; }
.badge-merah { background: rgba(231,76,60,0.2); color: #e74c3c; border: 1px solid #e74c3c; }
</style>
</head>
<body>
<!-- SIDEBAR -->
<div class="sidebar" id="sidebar">
<div class="sidebar-brand" onclick="toggleSidebar()">
<span><div class="icon-wrap"><i class="bi bi-broadcast-pin"></i></div> Radius Bantuan</span>
<i class="bi bi-chevron-down" id="chevron"></i>
</div>
<div class="sidebar-body">
<hr class="divider">
<p class="section-label">Mode Input</p>
<button class="btn-mode active" id="btn-view" onclick="setMode('view')">
<i class="bi bi-eye-fill"></i> Mode Lihat
</button>
<?php if($isAdmin): ?>
<button class="btn-mode" id="btn-penduduk" onclick="setMode('penduduk')">
<i class="bi bi-person-fill" style="color:#f39c12"></i> Tambah Penduduk
</button>
<?php else: ?>
<button class="btn-mode" id="btn-ibadah" onclick="setMode('ibadah')">
<i class="bi bi-building-fill" style="color:#6fffe9"></i> Tambah Rumah Ibadah
</button>
<?php endif; ?>
<hr class="divider">
<p class="section-label">Legenda</p>
<div class="legend">
<div class="legend-item"><div class="legend-dot ibadah"></div> Rumah Ibadah</div>
<div class="legend-item"><div class="legend-circle"></div> Radius Jangkauan</div>
<div class="legend-item"><div class="legend-dot hijau"></div> Penduduk — Tercakup</div>
<div class="legend-item"><div class="legend-dot merah"></div> Penduduk — Belum Tercakup</div>
</div>
<hr class="divider">
<button class="btn-mode" onclick="loadData()" style="color:#6fffe9;">
<i class="bi bi-arrow-clockwise"></i> Refresh Data
</button>
<hr class="divider">
<a href="dashboard.php" class="btn-mode" style="text-decoration:none;">
<i class="bi bi-speedometer2" style="color:#f39c12"></i> Dashboard
</a>
<a href="logout.php" class="btn-mode" style="text-decoration:none;" onclick="return confirm('Yakin ingin logout?')">
<i class="bi bi-box-arrow-right" style="color:#e74c3c"></i> Logout
</a>
</div>
</div>
<!-- STATS BAR -->
<div class="stats-bar">
<div class="stat-card stat-ibadah">
<div class="val" id="stat-ibadah">0</div>
<div class="lbl">Rumah Ibadah</div>
</div>
<div class="stat-card stat-hijau">
<div class="val" id="stat-hijau">0</div>
<div class="lbl">Tercakup</div>
</div>
<div class="stat-card stat-merah">
<div class="val" id="stat-merah">0</div>
<div class="lbl">Belum Tercakup</div>
</div>
</div>
<!-- TOAST -->
<div id="toast"></div>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script>
// ===== ROLE CONFIG (dari PHP) =====
const USER_ROLE = '<?= $userRole ?>';
const USER_IBADAH_ID = <?= $userIbadahId ? $userIbadahId : 'null' ?>;
const IS_ADMIN = <?= $isAdmin ? 'true' : 'false' ?>;
// ===== INIT MAP =====
const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
L.control.zoom({ position: 'bottomright' }).addTo(map);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// ===== STATE =====
let currentMode = 'view';
let layerIbadah = L.layerGroup().addTo(map);
let layerPenduduk = L.layerGroup().addTo(map);
let allIbadah = [];
let allPenduduk = [];
// ===== ICON FACTORY =====
function makeIcon(color) {
const colors = {
cyan: '#6fffe9',
green: '#2ecc71',
red: '#e74c3c',
orange: '#f39c12'
};
const hex = colors[color] || colors.cyan;
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 42" width="32" height="42">
<filter id="sh"><feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="${hex}" flood-opacity="0.6"/></filter>
<path filter="url(#sh)" d="M16 0C9.4 0 4 5.4 4 12c0 9 12 26 12 26S28 21 28 12C28 5.4 22.6 0 16 0z" fill="${hex}"/>
<circle cx="16" cy="12" r="5" fill="#0b132b"/>
</svg>`;
return L.divIcon({
html: svg, className: '', iconSize: [32,42], iconAnchor: [16,42], popupAnchor: [0,-40]
});
}
// ===== SIDEBAR =====
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
}
// Buka sidebar saat load
document.getElementById('sidebar').classList.add('open');
// ===== MODE =====
function setMode(mode) {
currentMode = mode;
document.querySelectorAll('.btn-mode').forEach(b => b.classList.remove('active'));
document.getElementById('btn-' + mode).classList.add('active');
showToast(
mode === 'view' ? '<i class="bi bi-eye-fill"></i> Mode Lihat aktif' :
mode === 'ibadah' ? '<i class="bi bi-building-fill"></i> Klik peta untuk tambah Rumah Ibadah' :
'<i class="bi bi-person-fill"></i> Klik peta untuk tambah Penduduk',
mode === 'view' ? '#5bc0be' : '#f39c12'
);
}
// ===== TOAST =====
let toastTimer;
function showToast(msg, color = '#5bc0be') {
const el = document.getElementById('toast');
el.innerHTML = msg;
el.style.borderLeftColor = color;
el.style.borderLeft = `3px solid ${color}`;
el.style.display = 'block';
clearTimeout(toastTimer);
toastTimer = setTimeout(() => { el.style.display = 'none'; }, 3000);
}
// ===== STATS =====
function updateStats() {
document.getElementById('stat-ibadah').textContent = allIbadah.length;
const hijau = allPenduduk.filter(p => p.status_warna === 'Hijau').length;
const merah = allPenduduk.filter(p => p.status_warna === 'Merah').length;
document.getElementById('stat-hijau').textContent = hijau;
document.getElementById('stat-merah').textContent = merah;
}
// ===== LOAD DATA =====
async function loadData() {
try {
const res = await fetch('ambil.php');
const data = await res.json();
allIbadah = data.ibadah || [];
allPenduduk = data.penduduk || [];
renderIbadah();
renderPenduduk();
await cekRadius();
updateStats();
} catch(e) {
showToast('<i class="bi bi-exclamation-triangle"></i> Gagal memuat data.', '#e74c3c');
console.error(e);
}
}
// ===== RENDER IBADAH =====
function renderIbadah() {
layerIbadah.clearLayers();
allIbadah.forEach(d => {
const marker = L.marker([d.latitude, d.longitude], { icon: makeIcon('cyan') });
const circle = L.circle([d.latitude, d.longitude], {
radius: d.radius,
color: '#5bc0be', fillColor: '#5bc0be',
fillOpacity: 0.08, weight: 1.5, dashArray: '6,4'
});
// Tombol ubah radius: hanya admin_ibadah (pemilik)
const canEditRadius = (!IS_ADMIN && USER_IBADAH_ID === d.id);
const radiusBtn = canEditRadius ? `
const radiusBtn = canEditRadius ? `
<label class="f-label">
Nama Rumah Ibadah
</label>
<input
id="edit-nama-${d.id}"
class="f-input"
value="${d.nama_ibadah}"
>
<label class="f-label">
Radius
</label>
<div style="display:flex;gap:8px">
<input
type="number"
id="edit-radius-${d.id}"
class="f-input"
value="${d.radius}"
style="margin-bottom:0;flex:1"
>
<button
class="btn-submit"
style="width:auto;padding:0 16px"
onclick="updateIbadah(${d.id})">
<i class="bi bi-check-lg"></i>
</button>
</div>
` : '';
// Tombol hapus: hanya admin_ibadah (pemilik) yang bisa menghapus
const hapusBtn = (!IS_ADMIN && USER_IBADAH_ID === d.id) ? `
<button class="btn-submit" style="background: rgba(231,76,60,0.1); color: #e74c3c; border: 1px solid #e74c3c; margin-top: 12px; box-shadow: none;" onclick="hapusData('ibadah', ${d.id})"><i class="bi bi-trash"></i> Hapus Rumah Ibadah</button>` : '';
marker.bindPopup(`
<div class="p-header"><i class="bi bi-building-fill"></i> Rumah Ibadah</div>
<div class="p-body">
<div class="info-row"><span class="key">Nama</span><span class="val">${d.nama_ibadah}</span></div>
<div class="info-row"><span class="key">Radius</span><span class="val">${d.radius.toLocaleString()} m</span></div>
<div class="info-row"><span class="key">Koordinat</span><span class="val">${d.latitude.toFixed(5)}, ${d.longitude.toFixed(5)}</span></div>
${radiusBtn}
${hapusBtn}
</div>
`);
layerIbadah.addLayer(circle);
layerIbadah.addLayer(marker);
});
}
async function updatePenduduk(id) {
const nama =
document.getElementById(
'edit-penduduk-' + id
).value;
const fd = new FormData();
fd.append('action','update_penduduk');
fd.append('id',id);
fd.append('nama_penduduk',nama);
const res =
await fetch(
'ambil.php',
{
method:'POST',
body:fd
}
);
const data = await res.json();
alert(data.message);
if(data.status==='success'){
loadData();
}
}
// ===== RENDER PENDUDUK =====
function renderPenduduk() {
const editBtn = IS_ADMIN ? `
<label class="f-label">
Edit Nama Penduduk
</label>
<div style="display:flex;gap:8px">
<input
id="edit-penduduk-${d.id}"
class="f-input"
value="${d.nama_penduduk}"
style="margin-bottom:0;flex:1"
>
<button
class="btn-submit"
style="width:auto;padding:0 16px"
onclick="updatePenduduk(${d.id})">
<i class="bi bi-check-lg"></i>
</button>
</div>
` : '';
layerPenduduk.clearLayers();
allPenduduk.forEach(d => {
const color = d.status_warna === 'Hijau' ? 'green' : 'red';
const marker = L.marker([d.latitude, d.longitude], { icon: makeIcon(color) });
const badge = d.status_warna === 'Hijau'
? '<span class="badge-warna badge-hijau">Tercakup</span>'
: '<span class="badge-warna badge-merah">Belum Tercakup</span>';
// Tombol hapus: hanya admin_pemerintah
const hapusBtn = IS_ADMIN ? `
<button class="btn-submit" style="background: rgba(231,76,60,0.1); color: #e74c3c; border: 1px solid #e74c3c; margin-top: 12px; box-shadow: none;" onclick="hapusData('penduduk', ${d.id})"><i class="bi bi-trash"></i> Hapus Data Penduduk</button>` : '';
// Tombol konfirmasi bantuan: admin_ibadah saja, hanya penduduk tercakup
const bantuanStatus = d.status_bantuan || 'Belum';
const bantuanBadge = bantuanStatus === 'Sudah'
? '<span class="badge-warna badge-hijau">Sudah Disalurkan</span>'
: '<span class="badge-warna badge-merah">Belum Disalurkan</span>';
const konfirmasiBtn = (!IS_ADMIN && d.status_warna === 'Hijau' && bantuanStatus === 'Belum') ? `
<button class="btn-submit" style="background: rgba(46,204,113,0.1); color: #2ecc71; border: 1px solid #2ecc71; margin-top: 8px; box-shadow: none;" onclick="konfirmasiBantuan(${d.id})"><i class="bi bi-check-circle"></i> Konfirmasi Bantuan</button>` : '';
marker.bindPopup(`
<div class="p-header"><i class="bi bi-person-fill"></i> Data Penduduk</div>
<div class="p-body">
<div class="info-row"><span class="key">Nama</span><span class="val">${d.nama_penduduk}</span></div>
<div class="info-row"><span class="key">Status</span>${badge}</div>
<div class="info-row"><span class="key">Bantuan</span>${bantuanBadge}</div>
<div class="info-row"><span class="key">Koordinat</span><span class="val">${d.latitude.toFixed(5)}, ${d.longitude.toFixed(5)}</span></div>
${hapusBtn}
${konfirmasiBtn}
</div>
`);
layerPenduduk.addLayer(marker);
});
}
// ===== CEK RADIUS (Proximity Analysis dengan Turf.js) =====
async function cekRadius() {
if (allIbadah.length === 0 || allPenduduk.length === 0) return;
for (const penduduk of allPenduduk) {
const ptPenduduk = turf.point([penduduk.longitude, penduduk.latitude]);
let dalamRadius = false;
for (const ibadah of allIbadah) {
const ptIbadah = turf.point([ibadah.longitude, ibadah.latitude]);
// turf.distance mengembalikan km, ubah ke meter
const jarakM = turf.distance(ptPenduduk, ptIbadah, { units: 'kilometers' }) * 1000;
if (jarakM <= ibadah.radius) {
dalamRadius = true;
break;
}
}
const warnaTarget = dalamRadius ? 'Hijau' : 'Merah';
// Hanya kirim update jika status berubah (efisiensi request)
if (penduduk.status_warna !== warnaTarget) {
penduduk.status_warna = warnaTarget; // update lokal
await updateWarnaPenduduk(penduduk.id, warnaTarget);
}
}
renderPenduduk();
updateStats();
}
if (isset($_POST['action']) && $_POST['action'] === 'update_penduduk') {
if (!$isAdmin) {
echo json_encode([
"status" => "error",
"message" => "Tidak ada akses"
]);
exit;
}
$id = (int)$_POST['id'];
$nama = mysqli_real_escape_string(
$conn,
$_POST['nama_penduduk']
);
$sql = "
UPDATE tabel_penduduk
SET nama_penduduk='$nama'
WHERE id=$id
";
if (mysqli_query($conn,$sql)) {
echo json_encode([
"status"=>"success",
"message"=>"Penduduk berhasil diperbarui"
]);
} else {
echo json_encode([
"status"=>"error",
"message"=>mysqli_error($conn)
]);
}
exit;
}
if (isset($_POST['action']) && $_POST['action'] === 'update_ibadah') {
if ($isAdmin) {
echo json_encode([
"status"=>"error",
"message"=>"Tidak ada akses"
]);
exit;
}
$id = (int)$_POST['id'];
if ($id !== $userIbadahId) {
echo json_encode([
"status"=>"error",
"message"=>"Rumah ibadah bukan milik anda"
]);
exit;
}
$nama = mysqli_real_escape_string(
$conn,
$_POST['nama_ibadah']
);
$radius = (int)$_POST['radius'];
$sql = "
UPDATE tabel_ibadah
SET
nama_ibadah='$nama',
radius=$radius
WHERE id=$id
";
if (mysqli_query($conn,$sql)) {
echo json_encode([
"status"=>"success",
"message"=>"Rumah ibadah berhasil diperbarui"
]);
} else {
echo json_encode([
"status"=>"error",
"message"=>mysqli_error($conn)
]);
}
exit;
}
// ===== UPDATE STATUS WARNA KE SERVER =====
async function updateWarnaPenduduk(id, warna) {
try {
const fd = new FormData();
fd.append('action', 'update_warna');
fd.append('id', id);
fd.append('status_warna', warna);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const data = await res.json();
console.log('Update warna:', data.message);
} catch(e) {
console.error('Gagal update warna:', e);
}
}
// ===== HAPUS DATA =====
async function hapusData(type, id) {
if(!confirm("Yakin ingin menghapus data ini?")) return;
try {
const fd = new FormData();
fd.append('action', 'hapus');
fd.append('type_hapus', type);
fd.append('id', id);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const text = await res.text();
try {
const data = JSON.parse(text);
if (data.status === 'success') {
map.closePopup();
showToast(`<i class="bi bi-trash"></i> ${data.message}`, '#e74c3c');
await loadData();
} else {
showToast(`<i class="bi bi-x-circle"></i> ${data.message}`, '#e74c3c');
}
} catch(parseErr) {
showToast(`<i class="bi bi-bug"></i> Error Server: ${text.substring(0, 50)}...`, '#e74c3c');
console.error('Response bukan JSON:', text);
}
} catch(e) {
showToast('<i class="bi bi-wifi-off"></i> Gagal menghapus data.', '#e74c3c');
console.error(e);
}
}
// ===== KLIK PETA =====
map.on('click', function(e) {
if (currentMode === 'view') return;
const { lat, lng } = e.latlng;
if (currentMode === 'ibadah') {
const content = `
<div class="p-header"><i class="bi bi-building-fill"></i> Tambah Rumah Ibadah</div>
<div class="p-body">
<label class="f-label">Nama Rumah Ibadah</label>
<input id="inp-nama-ibadah" class="f-input" type="text" placeholder="cth: Masjid Al-Hikmah" required>
<label class="f-label">Radius Jangkauan (meter)</label>
<input id="inp-radius" class="f-input" type="number" placeholder="cth: 500" value="500" min="50" max="50000" required>
<button class="btn-submit" onclick="simpanIbadah(${lat}, ${lng})">
<i class="bi bi-check2-circle"></i> Simpan Rumah Ibadah
</button>
</div>`;
L.popup({ minWidth: 300 }).setLatLng(e.latlng).setContent(content).openOn(map);
}
if (currentMode === 'penduduk') {
const content = `
<div class="p-header"><i class="bi bi-person-fill"></i> Tambah Data Penduduk</div>
<div class="p-body">
<label class="f-label">Nama Penduduk</label>
<input id="inp-nama-penduduk" class="f-input" type="text" placeholder="cth: Budi Santoso" required>
<button class="btn-submit" onclick="simpanPenduduk(${lat}, ${lng})">
<i class="bi bi-check2-circle"></i> Simpan Penduduk
</button>
</div>`;
L.popup({ minWidth: 300 }).setLatLng(e.latlng).setContent(content).openOn(map);
}
});
// ===== SIMPAN IBADAH =====
async function simpanIbadah(lat, lng) {
const nama = document.getElementById('inp-nama-ibadah')?.value?.trim();
const radius = parseInt(document.getElementById('inp-radius')?.value);
if (!nama) { showToast('<i class="bi bi-exclamation"></i> Nama tidak boleh kosong!', '#e74c3c'); return; }
if (!radius || radius < 1) { showToast('<i class="bi bi-exclamation"></i> Radius tidak valid!', '#e74c3c'); return; }
try {
const fd = new FormData();
fd.append('type', 'ibadah');
fd.append('nama_ibadah', nama);
fd.append('latitude', lat);
fd.append('longitude', lng);
fd.append('radius', radius);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const text = await res.text(); // Ambil teks mentah dulu untuk debug
try {
const data = JSON.parse(text);
if (data.status === 'success') {
map.closePopup();
showToast(`<i class="bi bi-check-circle-fill"></i> ${data.message}`, '#2ecc71');
await loadData();
} else {
showToast(`<i class="bi bi-x-circle"></i> ${data.message}`, '#e74c3c');
}
} catch(parseErr) {
// Jika bukan JSON, tampilkan 50 karakter pertama dari teks error-nya
showToast(`<i class="bi bi-bug"></i> Error Server: ${text.substring(0, 50)}...`, '#e74c3c');
console.error('Response bukan JSON:', text);
}
} catch(e) {
showToast('<i class="bi bi-wifi-off"></i> Gagal terhubung ke ambil.php', '#e74c3c');
console.error(e);
}
}
// ===== SIMPAN PENDUDUK =====
async function simpanPenduduk(lat, lng) {
const nama = document.getElementById('inp-nama-penduduk')?.value?.trim();
if (!nama) { showToast('<i class="bi bi-exclamation"></i> Nama tidak boleh kosong!', '#e74c3c'); return; }
try {
const fd = new FormData();
fd.append('type', 'penduduk');
fd.append('nama_penduduk', nama);
fd.append('latitude', lat);
fd.append('longitude', lng);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const text = await res.text(); // Ambil teks mentah
try {
const data = JSON.parse(text);
if (data.status === 'success') {
map.closePopup();
showToast(`<i class="bi bi-check-circle-fill"></i> ${data.message}`, '#2ecc71');
await loadData();
} else {
showToast(`<i class="bi bi-x-circle"></i> ${data.message}`, '#e74c3c');
}
} catch(parseErr) {
showToast(`<i class="bi bi-bug"></i> Error Server: ${text.substring(0, 50)}...`, '#e74c3c');
console.error('Response bukan JSON:', text);
}
} catch(e) {
showToast('<i class="bi bi-wifi-off"></i> Gagal terhubung ke ambil.php', '#e74c3c');
console.error(e);
}
}
// ===== UPDATE RADIUS =====
async function updateRadius(id) {
const input = document.getElementById('edit-radius-' + id);
const radius = parseInt(input?.value);
if (!radius || radius < 50) { showToast('<i class="bi bi-exclamation"></i> Radius minimal 50m!', '#e74c3c'); return; }
try {
const fd = new FormData();
fd.append('action', 'update_radius');
fd.append('id', id);
fd.append('radius', radius);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const text = await res.text();
try {
const data = JSON.parse(text);
if (data.status === 'success') {
map.closePopup();
showToast(`<i class="bi bi-check-circle-fill"></i> ${data.message}`, '#2ecc71');
await loadData();
} else {
showToast(`<i class="bi bi-x-circle"></i> ${data.message}`, '#e74c3c');
}
} catch(e) {
showToast(`<i class="bi bi-bug"></i> Error Server`, '#e74c3c');
}
} catch(e) {
showToast('<i class="bi bi-wifi-off"></i> Gagal update radius', '#e74c3c');
}
}
async function updateIbadah(id) {
const nama =
document.getElementById(
'edit-nama-' + id
).value;
const radius =
document.getElementById(
'edit-radius-' + id
).value;
const fd = new FormData();
fd.append('action','update_ibadah');
fd.append('id',id);
fd.append('nama_ibadah',nama);
fd.append('radius',radius);
const res =
await fetch(
'ambil.php',
{
method:'POST',
body:fd
}
);
const data = await res.json();
alert(data.message);
if(data.status==='success'){
loadData();
}
}
// ===== KONFIRMASI BANTUAN =====
async function konfirmasiBantuan(id) {
if(!confirm("Konfirmasi bahwa bantuan telah disalurkan?")) return;
try {
const fd = new FormData();
fd.append('action', 'konfirmasi_bantuan');
fd.append('id', id);
const res = await fetch('ambil.php', { method: 'POST', body: fd });
const text = await res.text();
try {
const data = JSON.parse(text);
if (data.status === 'success') {
map.closePopup();
showToast(`<i class="bi bi-check-circle-fill"></i> ${data.message}`, '#2ecc71');
await loadData();
} else {
showToast(`<i class="bi bi-x-circle"></i> ${data.message}`, '#e74c3c');
}
} catch(e) {
showToast(`<i class="bi bi-bug"></i> Error Server`, '#e74c3c');
}
} catch(e) {
showToast('<i class="bi bi-wifi-off"></i> Gagal konfirmasi bantuan', '#e74c3c');
}
}
// ===== BOOT =====
loadData();
</script>
</body>
</html>