Files
UAS-SIG/ui/admin_detail.html
T
2026-06-03 20:05:50 +07:00

206 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detail Warga & Peta Lokal - Admin</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
</style>
</head>
<body class="bg-gray-100 text-gray-950 min-h-screen flex flex-col">
<nav class="bg-white border-b border-gray-200 p-4 flex items-center gap-4 sticky top-0 z-30 px-6">
<a href="admin_dasbor.html" class="hover:bg-gray-100 p-2 rounded-lg transition-colors text-gray-600">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
</a>
<h1 class="text-md font-bold text-gray-800">Detail Rekaman Data Validasi</h1>
</nav>
<main class="flex-grow p-4 md:p-6 max-w-5xl mx-auto w-full grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div class="md:col-span-2 space-y-6">
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200 space-y-4">
<div class="flex justify-between items-start border-b pb-3">
<div>
<h2 id="det_nama_kk" class="text-xl font-bold text-gray-900">-</h2>
<p class="text-xs text-gray-400 font-mono mt-1">Sistem ID: <span id="det_id_sistem">-</span></p>
</div>
<span id="det_badge_status" class="inline-block px-2.5 py-1 text-[10px] font-bold rounded-md">Pending</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
<div>
<span class="text-xs text-gray-400 block">Nomor Induk Kependudukan (NIK)</span>
<p id="det_nik" class="font-mono font-medium mt-0.5">-</p>
</div>
<div>
<span class="text-xs text-gray-400 block">Nomor Kartu Keluarga (No KK)</span>
<p id="det_no_kk" class="font-mono font-medium mt-0.5">-</p>
</div>
<div>
<span class="text-xs text-gray-400 block">Jumlah Tanggungan Keluarga</span>
<p id="det_tanggungan" class="font-medium mt-0.5">-</p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="bg-gray-50 p-4 border-b border-gray-200">
<h3 class="text-xs font-bold uppercase tracking-wider text-gray-500">Evaluasi Parameter Instrumen Kemiskinan BPS</h3>
</div>
<div class="divide-y divide-gray-100 text-sm">
<div class="p-4 flex justify-between"><span class="text-gray-500">Luas Lantai Per Kapita</span><span id="det_luas_lantai" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Material Bahan Lantai</span><span id="det_mat_lantai" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Material Bahan Dinding</span><span id="det_mat_dinding" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Fasilitas Sanitasi (MCK)</span><span id="det_sanitasi" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Sumber Air Minum Utama</span><span id="det_air_minum" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Sumber Penerangan Rumah</span><span id="det_penerangan" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Pekerjaan Utama Kepala Keluarga</span><span id="det_pekerjaan" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Estimasi Pendapatan Bulanan</span><span id="det_pendapatan" class="font-semibold text-emerald-700">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Frekuensi Makan Harian</span><span id="det_frekuensi_makan" class="font-semibold text-gray-800">-</span></div>
<div class="p-4 flex justify-between"><span class="text-gray-500">Kepemilikan Aset Kendaraan</span><span id="det_kepemilikan_aset" class="font-semibold text-gray-800">-</span></div>
</div>
</div>
</div>
<div class="space-y-6">
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200 space-y-3">
<h3 class="text-xs font-bold uppercase tracking-wider text-gray-400">Verifikasi Lokasi Spasial</h3>
<div class="h-56 w-full rounded-lg overflow-hidden border border-gray-200 z-0 relative">
<div id="map_detail_container" class="absolute inset-0"></div>
</div>
<div class="text-[11px] font-mono text-gray-500 space-y-0.5">
<p>Lat: <span id="det_lat">-</span></p>
<p>Lng: <span id="det_lng">-</span></p>
</div>
</div>
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-200 space-y-3 text-center">
<h3 class="text-xs font-bold uppercase tracking-wider text-gray-500 mb-2">Aksi Otoritas Dokumen</h3>
<button onclick="proses_keputusan_admin('Verified')" class="w-full py-3 bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-bold rounded-lg transition-colors shadow-sm">Setujui Sebagai Mustahik</button>
<button onclick="proses_keputusan_admin('Rejected')" class="w-full py-3 bg-red-600 hover:bg-red-700 text-white text-xs font-bold rounded-lg transition-colors shadow-sm">Tolak Pengajuan Bantuan</button>
</div>
</div>
</main>
<script>
// Ekstraksi parameter ID dari string URL query browser
const parameter_url = new URLSearchParams(window.location.search);
const id_warga_terpilih = parameter_url.get('id');
let instansi_peta_detail = null;
// Memformat nominal angka reguler menjadi format mata uang Rupiah Indonesia
function format_mata_uang_rupiah(angka) {
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(angka);
}
// Memetakan posisi koordinat spasial warga ke dalam objek Leaflet.js
function petakan_lokasi_warga(lat, lng, nama) {
if (!lat || !lng) return;
instansi_peta_detail = L.map('map_detail_container').setView([lat, lng], 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(instansi_peta_detail);
L.marker([lat, lng]).addTo(instansi_peta_detail)
.bindPopup(`<b>${nama}</b><br>Titik Lokasi Rumah`)
.openPopup();
}
// Merender seluruh string objek data yang ditarik secara aman ke komponen UI
function isi_komponen_antarmuka(data) {
// Keamanan XSS: Memanfaatkan textContent murni untuk mencegah injeksi skrip HTML
document.getElementById('det_id_sistem').textContent = data.id;
document.getElementById('det_nama_kk').textContent = data.nama_kk;
document.getElementById('det_nik').textContent = data.nik;
document.getElementById('det_no_kk').textContent = data.no_kk || '-';
document.getElementById('det_tanggungan').textContent = `${data.jumlah_tanggungan} Jiwa`;
document.getElementById('det_luas_lantai').textContent = data.luas_lantai_per_kapita || 'Belum diisi';
document.getElementById('det_mat_lantai').textContent = data.material_lantai || 'Belum diisi';
document.getElementById('det_mat_dinding').textContent = data.material_dinding || 'Belum diisi';
document.getElementById('det_sanitasi').textContent = data.fasilitas_sanitasi || 'Belum diisi';
document.getElementById('det_air_minum').textContent = data.sumber_air_minum || 'Belum diisi';
document.getElementById('det_penerangan').textContent = data.sumber_penerangan || 'Belum diisi';
document.getElementById('det_pekerjaan').textContent = data.pekerjaan_utama || 'Belum diisi';
document.getElementById('det_pendapatan').textContent = data.pendapatan_bulanan ? format_mata_uang_rupiah(data.pendapatan_bulanan) : 'Belum diisi';
document.getElementById('det_frekuensi_makan').textContent = data.frekuensi_makan || 'Belum diisi';
document.getElementById('det_kepemilikan_aset').textContent = data.kepemilikan_aset || 'Belum diisi';
document.getElementById('det_lat').textContent = data.lat;
document.getElementById('det_lng').textContent = data.lng;
const badge = document.getElementById('det_badge_status');
badge.textContent = data.status_validasi;
if (data.status_validasi.toLowerCase() === 'verified') {
badge.className = 'inline-block px-2.5 py-1 text-[10px] font-bold rounded-md bg-emerald-50 text-emerald-700 border border-emerald-200';
} else if (data.status_validasi.toLowerCase() === 'rejected') {
badge.className = 'inline-block px-2.5 py-1 text-[10px] font-bold rounded-md bg-red-50 text-red-700 border border-red-200';
} else {
badge.className = 'inline-block px-2.5 py-1 text-[10px] font-bold rounded-md bg-yellow-50 text-yellow-700 border border-yellow-200';
}
// Eksekusi pemetaan visual spasial
petakan_lokasi_warga(parseFloat(data.lat), parseFloat(data.lng), data.nama_kk);
}
// Meminta muatan data spesifik satu warga ke peladen Express Node.js
async function ambil_data_detail_warga() {
if (!id_warga_terpilih) {
return alert('ID parameter tidak ditemukan pada URL peramban.');
}
try {
/* Baris Kompleks: Melakukan pemanggilan asinkron GET menuju rute detail REST API
dengan menyertakan nilai parameter ID target untuk diolah kueri SQL JOIN oleh peladen. */
const http_response = await fetch(`http://localhost:3000/api/admin/mustahik/${id_warga_terpilih}`);
const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengambil data detail.');
isi_komponen_antarmuka(json_result);
} catch (error_sistem) {
alert('Kegagalan Koneksi Sistem: ' + error_sistem.message);
}
}
// Memproses mutasi perubahan status persetujuan data dari panel admin ke server
async function proses_keputusan_admin(keputusan_status) {
if (!confirm(`Apakah Anda yakin memvalidasi data ini dengan status ${keputusan_status}?`)) return;
try {
const http_response = await fetch(`http://localhost:3000/api/admin/verifikasi/${id_warga_terpilih}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status_validasi: keputusan_status })
});
const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status.');
alert('Status otorisasi data berhasil diperbarui!');
window.location.reload();
} catch (error_sistem) {
alert('Gagal mengeksekusi perintah: ' + error_sistem.message);
}
}
// Eksekusi inisialisasi pemuatan data awal
ambil_data_detail_warga();
</script>
</body>
</html>