Files
2026-06-11 01:50:37 +07:00

151 lines
6.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = `<select class="form-select" id="lb-ri" disabled>
${ris.filter(r => String(r.id) === String(window.currentUser.rumah_ibadah_id))
.map(r => `<option value="${r.id}" selected>${esc(r.nama)} (${r.jenis})</option>`).join('')}
</select>`;
} 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 = `<select class="form-select" id="lb-ri">
${sorted.map(r => {
const dist = (pmLat != null && pmLng != null)
? Math.round(haversineDistance(pmLat, pmLng, r.latitude, r.longitude))
: null;
const distLabel = dist != null ? ` — ${dist < 1000 ? dist + ' m' : (dist/1000).toFixed(1) + ' km'}` : '';
const isInside = dist != null && dist <= (r.radius_meter || 500);
const label = `${esc(r.nama)} (${r.jenis})${distLabel}${isInside ? ' ✅' : ''}`;
const selected = String(r.id) === String(defaultRiId) ? 'selected' : '';
return `<option value="${r.id}" ${selected}>${label}</option>`;
}).join('')}
</select>
${pmLat != null ? `<div style="font-size:11px;color:#64748b;margin-top:4px">✅ = dalam radius · Diurutkan berdasarkan jarak terdekat</div>` : ''}`;
}
openModal(`Log Bantuan — ${pmNama}`, `
<div style="margin-bottom:16px">
<div class="layer-section-title">Riwayat Bantuan</div>
${logs.length ? `
${logs.slice(0, 2).map(l => `
<div class="log-item ${l.tipe_bantuan === 'Pemberdayaan' ? 'pemberdayaan' : 'konsumtif'}">
<div class="log-type">${l.tipe_bantuan === 'Pemberdayaan' ? '📚 Pemberdayaan' : '🛍 Konsumtif'}</div>
<div class="log-detail">${esc(l.sub_kategori) || ''} ${l.keterangan ? '— ' + esc(l.keterangan) : ''}</div>
<div class="log-date">📅 ${l.tanggal_bantuan} · ${esc(l.nama_rumah_ibadah)}</div>
</div>`).join('')}
${logs.length > 2 ? `<div style="font-size:11px;color:#64748b;text-align:center;padding:6px 0;border-top:1px solid #f1f5f9;margin-top:4px">+${logs.length - 2} bantuan lainnya · Lihat detail lengkap di halaman Daftar Penduduk Miskin</div>` : ''}
` : '<div class="loading">Belum ada riwayat bantuan</div>'}
</div>
<div class="layer-section-title">Tambah Bantuan Baru</div>
<div class="form-group">
<label class="form-label">Dari Rumah Ibadah *</label>
${riSelectHTML}
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Tipe Bantuan *</label>
<select class="form-select" id="lb-tipe">
<option value="Pemberdayaan">📚 Pemberdayaan (Pelatihan)</option>
<option value="Konsumtif">🛍 Konsumtif (Makan/Barang)</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Tanggal *</label>
<input class="form-input" id="lb-tgl" type="date" value="${new Date().toISOString().slice(0, 10)}"/>
</div>
</div>
<div class="form-group">
<label class="form-label">Sub Kategori</label>
<input class="form-input" id="lb-sub" placeholder="Pelatihan menjahit / Sembako…"/>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Keterangan</label>
<input class="form-input" id="lb-ket" placeholder="Opsional"/>
</div>
<div class="form-group">
<label class="form-label">Nilai (Rp)</label>
<input class="form-input" id="lb-nilai" type="number" placeholder="0"/>
</div>
</div>`,
`<button class="btn btn-ghost" onclick="closeModal()">Tutup</button>
<button class="btn btn-primary" onclick="saveLog(${pmId})"> Tambah Log</button>`);
}
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');
}