add poverty

This commit is contained in:
tistopandita
2026-06-11 13:56:18 +07:00
parent 7ef7620d2a
commit 5386300498
4 changed files with 1421 additions and 227 deletions
+162 -7
View File
@@ -137,6 +137,12 @@
.butuh { background: #ffedd5; color: #7c2d12; }
.rentan { background: #fef9c3; color: #713f12; }
.mampu { background: #dcfce7; color: #14532d; }
/* Status pengajuan */
.status-menunggu { background: #fef3c7; color: #92400e; }
.status-diverifikasi { background: #dbeafe; color: #1e40af; }
.status-disetujui { background: #dcfce7; color: #14532d; }
.status-ditolak { background: #fee2e2; color: #7f1d1d; }
.status-selesai { background: #e0e7ff; color: #3730a3; }
.bar-row { display: grid; grid-template-columns: 150px 1fr 48px; gap: 10px; align-items: center; margin: 10px 0; font-size: .86rem; }
.bar-track { height: 12px; border-radius: 99px; background: #e2e8f0; overflow: hidden; }
.bar-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
@@ -203,8 +209,10 @@
<button data-section="bantuan">Data Bantuan</button>
<button data-section="kriteria">Kriteria SAW</button>
<button data-section="ibadah">⛪ Tempat Ibadah</button>
<button data-section="pengajuan">📋 Pengajuan Bantuan</button>
<button data-section="peta">Preview Peta</button>
<a href="/poverty-map.html">Buka Peta Poverty</a>
<a href="/pengajuan.html">Form Pengajuan Publik</a>
<a href="/admin.html">Admin SPBU</a>
</nav>
</aside>
@@ -343,6 +351,9 @@
<label>Jenis<select id="ibadah_jenis" required></select></label>
<label>Alamat<input id="ibadah_alamat" required /></label>
<label>Kelurahan<input id="ibadah_kelurahan" required /></label>
<label>Kapasitas Bantuan (KK)<input id="ibadah_kapasitas" type="number" min="0" value="0" /></label>
<div></div>
<label class="full">Keterangan / Catatan<textarea id="ibadah_keterangan" placeholder="Jadwal bantuan, kontak, dll."></textarea></label>
<div class="full">
<label>Koordinat dari Peta</label>
<div id="ibadahMap" class="ibadah-map"></div>
@@ -365,13 +376,73 @@
<div class="card-head"><h3>Daftar Tempat Ibadah</h3><span id="ibadahCount">0 data</span></div>
<div class="table-wrap">
<table>
<thead><tr><th>#</th><th>Nama</th><th>Jenis</th><th>Alamat</th><th>Kelurahan</th><th>Koordinat</th><th>Aksi</th></tr></thead>
<thead><tr><th>#</th><th>Nama</th><th>Jenis</th><th>Alamat</th><th>Kelurahan</th><th>Koordinat</th><th>Kapasitas</th><th>Aksi</th></tr></thead>
<tbody id="ibadahBody"></tbody>
</table>
</div>
</article>
</section>
<!-- ═══════════════════════ PENGAJUAN BANTUAN ═══════════════════════ -->
<section class="section" id="pengajuan">
<div class="stats" style="grid-template-columns:repeat(3,minmax(0,1fr))">
<article class="stat"><span>Total Pengajuan</span><strong id="statPengajuanTotal">0</strong></article>
<article class="stat"><span>Menunggu</span><strong id="statPengajuanMenunggu">0</strong></article>
<article class="stat"><span>Disetujui</span><strong id="statPengajuanDisetujui">0</strong></article>
</div>
<article class="card" style="margin-top:18px">
<div class="card-head">
<h3>Daftar Pengajuan Bantuan</h3>
<select id="filterPengajuanStatus" style="min-width:160px;border-radius:10px;padding:7px 10px;border:1px solid #dbe2ea;font-size:.82rem">
<option value="">Semua Status</option>
<option value="menunggu">Menunggu</option>
<option value="diverifikasi">Diverifikasi</option>
<option value="disetujui">Disetujui</option>
<option value="ditolak">Ditolak</option>
<option value="selesai">Selesai</option>
</select>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>#</th><th>Pemohon</th><th>NIK</th><th>Wilayah</th><th>Alasan</th><th>Tgl Pengajuan</th><th>Status</th><th>Aksi</th></tr></thead>
<tbody id="pengajuanBody"></tbody>
</table>
</div>
</article>
<!-- Detail / Update Status Modal (inline) -->
<article class="card" id="pengajuanDetailCard" style="display:none;margin-top:18px">
<div class="card-head">
<h3>Update Status Pengajuan</h3>
<button class="btn secondary small" onclick="el('pengajuanDetailCard').style.display='none'">Tutup</button>
</div>
<div class="card-body">
<p id="pengajuanDetailInfo" style="color:#475569;margin:0 0 14px"></p>
<div class="form-grid">
<label>Status Baru
<select id="pengajuanNewStatus">
<option value="menunggu">Menunggu</option>
<option value="diverifikasi">Diverifikasi</option>
<option value="disetujui">Disetujui</option>
<option value="ditolak">Ditolak</option>
<option value="selesai">Selesai</option>
</select>
</label>
<label>Hubungkan ke Keluarga (opsional)
<select id="pengajuanKeluarga"><option value="">— Tidak dihubungkan —</option></select>
</label>
<label class="full">Catatan Admin
<textarea id="pengajuanCatatan" placeholder="Alasan penolakan, catatan verifikasi, dll."></textarea>
</label>
</div>
<input type="hidden" id="pengajuanEditId" />
<div class="actions">
<button class="btn" id="savePengajuanStatus">Simpan Status</button>
</div>
</div>
</article>
</section>
<section class="section" id="peta">
<article class="card">
<div class="card-head"><h3>Preview Peta Keluarga Prioritas</h3><span>Klik peta untuk mengisi koordinat form keluarga.</span></div>
@@ -384,7 +455,7 @@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API = "/api/poverty";
const state = { keluarga: [], bantuan: [], riwayat: [], kriteria: [], ibadah: [], dashboard: null, analytics: null };
const state = { keluarga: [], bantuan: [], riwayat: [], kriteria: [], ibadah: [], pengajuan: [], dashboard: null, analytics: null };
const today = new Date().toISOString().slice(0, 10);
const DEFAULT_CENTER = [-0.0263, 109.3425];
const IBADAH_TYPES = ["Gereja Katolik", "GPIB", "GKI", "HKBP", "Pentakosta"];
@@ -469,6 +540,12 @@
function refreshSelects() {
el("riwayat_keluarga").innerHTML = state.keluarga.map((item) => `<option value="${item.id_keluarga}">${esc(item.nama_kepala_keluarga)} - ${esc(item.kategori || "")}</option>`).join("");
el("riwayat_bantuan").innerHTML = state.bantuan.map((item) => `<option value="${item.id_bantuan}">${esc(item.jenis_bantuan)} (${rp(item.nilai_bantuan)})</option>`).join("");
// Populate pengajuan keluarga select (with leading empty option)
const pengajuanSel = el("pengajuanKeluarga");
if (pengajuanSel) {
pengajuanSel.innerHTML = `<option value="">— Tidak dihubungkan —</option>` +
state.keluarga.map((item) => `<option value="${item.id_keluarga}">#${item.ranking||'-'} ${esc(item.nama_kepala_keluarga)} (${esc(item.wilayah)})</option>`).join("");
}
}
function renderBantuan() {
@@ -497,9 +574,37 @@
<td>${esc(item.alamat)}</td>
<td>${esc(item.kelurahan)}</td>
<td>${Number(item.lat).toFixed(6)}, ${Number(item.lng).toFixed(6)}</td>
<td>${item.kapasitas_bantuan || 0} KK</td>
<td><button class="btn secondary small" onclick="editIbadah(${item.id})">Edit</button> <button class="btn danger small" onclick="hapus('ibadah', ${item.id})">Hapus</button></td>
</tr>
`).join("") || `<tr><td colspan="7">Belum ada data tempat ibadah.</td></tr>`;
`).join("") || `<tr><td colspan="8">Belum ada data tempat ibadah.</td></tr>`;
}
function pengajuanBadgeClass(status) {
return `status-${status || 'menunggu'}`;
}
function renderPengajuan() {
const rows = state.pengajuan;
el("statPengajuanTotal").textContent = rows.length;
el("statPengajuanMenunggu").textContent = rows.filter(r => r.status === "menunggu").length;
el("statPengajuanDisetujui").textContent = rows.filter(r => r.status === "disetujui").length;
el("pengajuanBody").innerHTML = rows.map((item, idx) => `
<tr>
<td>${idx + 1}</td>
<td><strong>${esc(item.nama_pemohon)}</strong></td>
<td>${esc(item.nik)}</td>
<td>${esc(item.wilayah)}</td>
<td style="max-width:200px;white-space:normal;font-size:.82rem">${esc(item.alasan)}</td>
<td>${fmt(item.tanggal_pengajuan)}</td>
<td><span class="badge ${pengajuanBadgeClass(item.status)}">${esc(item.status)}</span></td>
<td>
<button class="btn secondary small" onclick="openPengajuanEdit(${item.id_pengajuan})">Review</button>
<button class="btn danger small" onclick="hapusPengajuan(${item.id_pengajuan})">Hapus</button>
</td>
</tr>
`).join("") || `<tr><td colspan="8">Belum ada pengajuan.</td></tr>`;
}
let ibadahMap, ibadahMarker, ibadahManual = false;
@@ -565,6 +670,8 @@
kelurahan: el("ibadah_kelurahan").value.trim(),
lat: Number(el("ibadah_lat").value),
lng: Number(el("ibadah_lng").value),
kapasitas_bantuan: Number(el("ibadah_kapasitas").value) || 0,
keterangan: el("ibadah_keterangan").value.trim() || null,
};
}
@@ -594,11 +701,12 @@
}
async function loadAll() {
const [dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah] = await Promise.all([
api("/dashboard"), api("/analytics"), api("/keluarga"), api("/bantuan"), api("/riwayat-bantuan"), api("/kriteria"), api("/ibadah")
const [dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah, pengajuan] = await Promise.all([
api("/dashboard"), api("/analytics"), api("/keluarga"), api("/bantuan"),
api("/riwayat-bantuan"), api("/kriteria"), api("/ibadah"), api("/pengajuan")
]);
Object.assign(state, { dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah });
renderDashboard(); renderKeluarga(); renderBantuan(); renderKriteria(); renderIbadah();
Object.assign(state, { dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah, pengajuan });
renderDashboard(); renderKeluarga(); renderBantuan(); renderKriteria(); renderIbadah(); renderPengajuan();
}
function familyPayload() {
@@ -634,6 +742,8 @@
function resetIbadah() {
el("ibadahForm").reset();
el("id_ibadah").value = "";
el("ibadah_kapasitas").value = 0;
el("ibadah_keterangan").value = "";
el("ibadah_lat").readOnly = true;
el("ibadah_lng").readOnly = true;
ibadahManual = false;
@@ -675,6 +785,8 @@
el("ibadah_jenis").value = item.jenis;
el("ibadah_alamat").value = item.alamat;
el("ibadah_kelurahan").value = item.kelurahan;
el("ibadah_kapasitas").value = item.kapasitas_bantuan || 0;
el("ibadah_keterangan").value = item.keterangan || "";
el("ibadahSubmit").textContent = "Update Tempat Ibadah";
el("cancelIbadahEdit").style.display = "inline-flex";
setTimeout(() => {
@@ -682,6 +794,27 @@
el("ibadah").scrollIntoView({ behavior: "smooth", block: "start" });
}, 100);
};
window.openPengajuanEdit = (id) => {
const item = state.pengajuan.find(r => r.id_pengajuan === id);
if (!item) return;
document.querySelector('[data-section="pengajuan"]').click();
el("pengajuanEditId").value = id;
el("pengajuanNewStatus").value = item.status;
el("pengajuanCatatan").value = item.catatan_admin || "";
el("pengajuanKeluarga").value = item.id_keluarga || "";
el("pengajuanDetailInfo").textContent =
`Pemohon: ${item.nama_pemohon} | NIK: ${item.nik} | Wilayah: ${item.wilayah} | Tgl: ${String(item.tanggal_pengajuan||'').slice(0,10)}`;
el("pengajuanDetailCard").style.display = "block";
el("pengajuanDetailCard").scrollIntoView({ behavior: "smooth" });
};
window.hapusPengajuan = async (id) => {
if (!confirm("Hapus pengajuan ini?")) return;
await api(`/pengajuan/${id}`, { method: "DELETE" });
toast("Pengajuan berhasil dihapus.");
await loadAll();
};
window.hapus = async (resource, id) => {
if (!confirm("Hapus/nonaktifkan data ini?")) return;
await api(`/${resource}/${id}`, { method: "DELETE" });
@@ -757,6 +890,28 @@
el("familySearch").addEventListener("input", renderKeluarga);
el("recalculateBtn").addEventListener("click", async () => { await api("/recalculate", { method: "POST" }); toast("SAW berhasil dihitung ulang."); await loadAll(); });
el("filterPengajuanStatus").addEventListener("change", async () => {
const status = el("filterPengajuanStatus").value;
state.pengajuan = await api(`/pengajuan${status ? '?status='+status : ''}`);
renderPengajuan();
});
el("savePengajuanStatus").addEventListener("click", async () => {
const id = el("pengajuanEditId").value;
if (!id) return;
await api(`/pengajuan/${id}/status`, {
method: "PUT",
body: JSON.stringify({
status: el("pengajuanNewStatus").value,
catatan_admin: el("pengajuanCatatan").value.trim(),
id_keluarga: el("pengajuanKeluarga").value || null,
}),
});
toast("Status pengajuan berhasil diperbarui.");
el("pengajuanDetailCard").style.display = "none";
await loadAll();
});
fillOptions("pekerjaan", labels.pekerjaan);
fillOptions("pendidikan", labels.pendidikan);
fillOptions("kondisi_rumah", labels.kondisi_rumah);