857 lines
32 KiB
PHP
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>
|