function haversineDistance(lat1, lng1, lat2, lng2) { const R = 6371000; const toRad = d => d * Math.PI / 180; const dLat = toRad(lat2 - lat1); const dLng = toRad(lng2 - lng1); const a = Math.sin(dLat/2)**2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng/2)**2; return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } async function openLogBantuan(pmId, pmNama, pmLat, pmLng) { map.closePopup(); const [logRes, riRes] = await Promise.all([ callAPI(`log_bantuan.php?penduduk_miskin_id=${pmId}`), callAPI('rumah_ibadah.php') ]); const logs = logRes.data || []; const ris = riRes.data || []; // --- Determine best default RI --- let defaultRiId = ris.length > 0 ? ris[0].id : null; if (pmLat != null && pmLng != null && ris.length > 0) { // Find the RI whose circle the resident falls inside (if any), prefer that const insideRis = ris.filter(r => { const dist = haversineDistance(pmLat, pmLng, r.latitude, r.longitude); return dist <= (r.radius_meter || 500); }); if (insideRis.length > 0) { // Among RIs covering this resident, pick the closest center insideRis.sort((a, b) => haversineDistance(pmLat, pmLng, a.latitude, a.longitude) - haversineDistance(pmLat, pmLng, b.latitude, b.longitude) ); defaultRiId = insideRis[0].id; } else { // Not within any radius — pick nearest RI geometrically const sorted = [...ris].sort((a, b) => haversineDistance(pmLat, pmLng, a.latitude, a.longitude) - haversineDistance(pmLat, pmLng, b.latitude, b.longitude) ); defaultRiId = sorted[0].id; } } // Build RI options with distance label (admin only) or locked (petugas) let riSelectHTML; if (window.currentUser && window.currentUser.role === 'petugas_lapangan') { // Petugas can only see their own RI riSelectHTML = ``; } else { // Admin — show all, sorted by distance, with distance label if coords available const sorted = (pmLat != null && pmLng != null) ? [...ris].sort((a, b) => haversineDistance(pmLat, pmLng, a.latitude, a.longitude) - haversineDistance(pmLat, pmLng, b.latitude, b.longitude)) : ris; riSelectHTML = ` ${pmLat != null ? `
✅ = dalam radius · Diurutkan berdasarkan jarak terdekat
` : ''}`; } openModal(`Log Bantuan — ${pmNama}`, `
Riwayat Bantuan
${logs.length ? ` ${logs.slice(0, 2).map(l => `
${l.tipe_bantuan === 'Pemberdayaan' ? '📚 Pemberdayaan' : '🛍 Konsumtif'}
${esc(l.sub_kategori) || ''} ${l.keterangan ? '— ' + esc(l.keterangan) : ''}
📅 ${l.tanggal_bantuan} · ${esc(l.nama_rumah_ibadah)}
`).join('')} ${logs.length > 2 ? `
+${logs.length - 2} bantuan lainnya · Lihat detail lengkap di halaman Daftar Penduduk Miskin
` : ''} ` : '
Belum ada riwayat bantuan
'}
Tambah Bantuan Baru
${riSelectHTML}
`, ` `); } async function saveLog(pmId) { const body = { penduduk_miskin_id: pmId, rumah_ibadah_id: parseInt(document.getElementById('lb-ri').value), tipe_bantuan: document.getElementById('lb-tipe').value, tanggal_bantuan: document.getElementById('lb-tgl').value, sub_kategori: document.getElementById('lb-sub').value, keterangan: document.getElementById('lb-ket').value, nilai_bantuan: document.getElementById('lb-nilai').value || null }; const res = await callAPI('log_bantuan.php', 'POST', body); if (res.status === 'success') { toast('Log dicatat!', 'success'); closeModal(); switchTab('penduduk_miskin'); } else { toast(res.message, 'error'); } } async function recalcAllProximity() { await callAPI('penduduk_miskin.php?action=recalc_all', 'POST'); }