mengubah sistem menjadi lebih terfokus ke poverty

This commit is contained in:
Abimanyu Ridho
2026-06-11 01:50:37 +07:00
parent 739e360018
commit 468541b1c8
46 changed files with 5753 additions and 2805 deletions
+150
View File
@@ -0,0 +1,150 @@
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');
}