feat : fix fetch bug

This commit is contained in:
unknown
2026-06-09 12:28:44 +07:00
parent 1d55676e3e
commit f6231c1475
7 changed files with 152 additions and 128 deletions
+67 -24
View File
@@ -66,7 +66,7 @@
</tr> </tr>
</thead> </thead>
<tbody id="tabel_body_mustahik" class="divide-y divide-gray-200 text-sm text-gray-700"> <tbody id="tabel_body_mustahik" class="divide-y divide-gray-200 text-sm text-gray-700">
</tbody> </tbody>
</table> </table>
</div> </div>
<div id="pesan_tabel_kosong" class="p-10 text-center text-gray-400 text-sm hidden">Tidak ada rekaman data warga di dalam sistem.</div> <div id="pesan_tabel_kosong" class="p-10 text-center text-gray-400 text-sm hidden">Tidak ada rekaman data warga di dalam sistem.</div>
@@ -74,8 +74,33 @@
</main> </main>
<div id="modal_konfirmasi" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-gray-900 bg-opacity-50 backdrop-blur-sm transition-opacity">
<div class="bg-white p-6 rounded-2xl shadow-2xl w-full max-w-sm border border-gray-200 transform transition-all">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-yellow-50 flex items-center justify-center text-yellow-500 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
</div>
<div>
<h3 class="text-lg font-bold text-gray-900">Konfirmasi Aksi</h3>
<p class="text-xs text-gray-500 mt-1">Tindakan ini akan mengubah basis data.</p>
</div>
</div>
<p id="teks_pesan_modal" class="text-sm text-gray-700 mb-6 py-2 border-y border-gray-100">
Apakah Anda yakin ingin mengubah status data ini?
</p>
<div class="flex justify-end gap-3">
<button onclick="tutup_modal()" class="px-4 py-2.5 text-xs font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-xl transition-colors">Batalkan</button>
<button onclick="eksekusi_perubahan_status()" id="btn_modal_setuju" class="px-4 py-2.5 text-xs font-bold text-white bg-gray-900 hover:bg-gray-800 rounded-xl transition-colors shadow-sm">Ya, Lanjutkan</button>
</div>
</div>
</div>
<script> <script>
// Fungsi membersihkan dan memperbarui isi baris tabel secara aman (XSS Prevention) let antrean_id_warga = null;
let antrean_status_baru = null;
function muat_data_ke_tabel_admin(daftar_mustahik) { function muat_data_ke_tabel_admin(daftar_mustahik) {
const tbody = document.getElementById('tabel_body_mustahik'); const tbody = document.getElementById('tabel_body_mustahik');
const pesan_kosong = document.getElementById('pesan_tabel_kosong'); const pesan_kosong = document.getElementById('pesan_tabel_kosong');
@@ -100,9 +125,6 @@
const baris = document.createElement('tr'); const baris = document.createElement('tr');
baris.className = 'hover:bg-gray-50 transition-colors'; baris.className = 'hover:bg-gray-50 transition-colors';
/* Baris Kompleks: Kita menyusun kerangka visual baris menggunakan template literal,
namun pengisian teks string sensitif tetap diserahkan ke fungsi properti textContent
pada sub-elemen guna meminimalkan celah eksploitasi kode HTML injeksi berbahaya. */
baris.innerHTML = ` baris.innerHTML = `
<td class="p-4 text-xs font-mono text-gray-400">${indeks + 1}</td> <td class="p-4 text-xs font-mono text-gray-400">${indeks + 1}</td>
<td class="p-4 font-semibold text-gray-900 kolom-nama"></td> <td class="p-4 font-semibold text-gray-900 kolom-nama"></td>
@@ -119,7 +141,6 @@
</td> </td>
`; `;
// Menyuntikkan nilai teks string secara terisolasi dan aman
baris.querySelector('.kolom-nama').textContent = warga.nama_kk; baris.querySelector('.kolom-nama').textContent = warga.nama_kk;
baris.querySelector('.kolom-nik').textContent = warga.nik; baris.querySelector('.kolom-nik').textContent = warga.nik;
baris.querySelector('.kolom-nokk').textContent = warga.no_kk || '-'; baris.querySelector('.kolom-nokk').textContent = warga.no_kk || '-';
@@ -139,7 +160,6 @@
tbody.appendChild(baris); tbody.appendChild(baris);
}); });
// Perbarui papan indikator agregat atas secara asinkron
document.getElementById('label_total_baris').textContent = `${daftar_mustahik.length} Rekaman`; document.getElementById('label_total_baris').textContent = `${daftar_mustahik.length} Rekaman`;
document.getElementById('stat_total_warga').textContent = daftar_mustahik.length; document.getElementById('stat_total_warga').textContent = daftar_mustahik.length;
document.getElementById('stat_pending_warga').textContent = hitung_pending; document.getElementById('stat_pending_warga').textContent = hitung_pending;
@@ -147,13 +167,13 @@
document.getElementById('stat_rejected_warga').textContent = hitung_rejected; document.getElementById('stat_rejected_warga').textContent = hitung_rejected;
} }
// Meminta daftar seluruh entitas warga dari sistem peladen Express
async function ambil_data_admin_mustahik() { async function ambil_data_admin_mustahik() {
const banner_error = document.getElementById('banner_error_admin'); const banner_error = document.getElementById('banner_error_admin');
try { try {
banner_error.classList.add('hidden'); banner_error.classList.add('hidden');
const http_response = await fetch('http://localhost:3000/api/admin/mustahik'); // Diubah ke relative path
const http_response = await fetch('/api/admin/mustahik');
const json_result = await http_response.json(); const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal menarik data dari server.'); if (!http_response.ok) throw new Error(json_result.error || 'Gagal menarik data dari server.');
@@ -166,44 +186,67 @@
} }
} }
// Mengubah status persetujuan verifikasi warga via metode AJAX PATCH menuju backend function ubah_status_verifikasi(id_warga, status_baru) {
async function ubah_status_verifikasi(id_warga, status_baru) { antrean_id_warga = id_warga;
const banner_error = document.getElementById('banner_error_admin'); antrean_status_baru = status_baru;
if (!confirm(`Apakah Anda yakin ingin mengubah status data ini menjadi ${status_baru}?`)) { const teks_pesan = document.getElementById('teks_pesan_modal');
return; teks_pesan.innerHTML = `Apakah Anda yakin ingin mengubah status validasi menjadi <span class="font-bold text-gray-900">${status_baru}</span>?`;
const tombol_setuju = document.getElementById('btn_modal_setuju');
if (status_baru === 'Verified') {
tombol_setuju.className = 'px-4 py-2.5 text-xs font-bold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl transition-colors shadow-sm';
} else {
tombol_setuju.className = 'px-4 py-2.5 text-xs font-bold text-white bg-red-600 hover:bg-red-700 rounded-xl transition-colors shadow-sm';
} }
document.getElementById('modal_konfirmasi').classList.remove('hidden');
}
function tutup_modal() {
document.getElementById('modal_konfirmasi').classList.add('hidden');
antrean_id_warga = null;
antrean_status_baru = null;
}
async function eksekusi_perubahan_status() {
if (!antrean_id_warga || !antrean_status_baru) return;
const banner_error = document.getElementById('banner_error_admin');
const tombol_setuju = document.getElementById('btn_modal_setuju');
tombol_setuju.disabled = true;
tombol_setuju.textContent = 'Memproses...';
try { try {
/* Baris Kompleks: Melakukan transmisi parsial status data menggunakan metode HTTP PATCH // Diubah ke relative path
menuju endpoint verifikasi admin, membawa payload status terenkapsulasi JSON const http_response = await fetch(`/api/admin/verifikasi/${antrean_id_warga}`, {
sehingga perubahan status langsung tercermin di file SQLite tanpa memuat ulang layar. */
const http_response = await fetch(`http://localhost:3000/api/admin/verifikasi/${id_warga}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status_validasi: status_baru }) body: JSON.stringify({ status_validasi: antrean_status_baru })
}); });
const json_result = await http_response.json(); const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status verifikasi.'); if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status verifikasi.');
// Segera sinkronkan ulang isi tabel setelah proses mutasi data berhasil tutup_modal();
ambil_data_admin_mustahik(); ambil_data_admin_mustahik();
} catch (error_sistem) { } catch (error_sistem) {
tutup_modal();
banner_error.textContent = 'Gagal memproses mutasi data: ' + error_sistem.message; banner_error.textContent = 'Gagal memproses mutasi data: ' + error_sistem.message;
banner_error.classList.remove('hidden'); banner_error.classList.remove('hidden');
} finally {
tombol_setuju.disabled = false;
tombol_setuju.textContent = 'Ya, Lanjutkan';
} }
} }
// Mengakhiri sesi masuk administrator utama
function eksekusi_logout_admin() { function eksekusi_logout_admin() {
localStorage.removeItem('token_admin'); localStorage.removeItem('token_admin');
window.location.href = 'index.html'; window.location.href = 'index.html';
} }
// Inisialisasi awal penarikan data saat halaman terbuka sempurna
ambil_data_admin_mustahik(); ambil_data_admin_mustahik();
</script> </script>
</body> </body>
+66 -18
View File
@@ -85,27 +85,49 @@
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-200 space-y-3 text-center"> <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> <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="ubah_status_verifikasi(id_warga_terpilih, '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> <button onclick="ubah_status_verifikasi(id_warga_terpilih, '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>
</div> </div>
</main> </main>
<div id="modal_konfirmasi" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-gray-900 bg-opacity-50 backdrop-blur-sm transition-opacity">
<div class="bg-white p-6 rounded-2xl shadow-2xl w-full max-w-sm border border-gray-200 transform transition-all">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-yellow-50 flex items-center justify-center text-yellow-500 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
</div>
<div>
<h3 class="text-lg font-bold text-gray-900">Konfirmasi Aksi</h3>
<p class="text-xs text-gray-500 mt-1">Tindakan ini akan mengubah basis data.</p>
</div>
</div>
<p id="teks_pesan_modal" class="text-sm text-gray-700 mb-6 py-2 border-y border-gray-100">
Apakah Anda yakin ingin mengubah status data ini?
</p>
<div class="flex justify-end gap-3">
<button onclick="tutup_modal()" class="px-4 py-2.5 text-xs font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-xl transition-colors">Batalkan</button>
<button onclick="eksekusi_perubahan_status()" id="btn_modal_setuju" class="px-4 py-2.5 text-xs font-bold text-white bg-gray-900 hover:bg-gray-800 rounded-xl transition-colors shadow-sm">Ya, Lanjutkan</button>
</div>
</div>
</div>
<script> <script>
// Ekstraksi parameter ID dari string URL query browser
const parameter_url = new URLSearchParams(window.location.search); const parameter_url = new URLSearchParams(window.location.search);
const id_warga_terpilih = parameter_url.get('id'); const id_warga_terpilih = parameter_url.get('id');
let instansi_peta_detail = null; let instansi_peta_detail = null;
let antrean_id_warga = null;
let antrean_status_baru = null;
// Memformat nominal angka reguler menjadi format mata uang Rupiah Indonesia
function format_mata_uang_rupiah(angka) { function format_mata_uang_rupiah(angka) {
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(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) { function petakan_lokasi_warga(lat, lng, nama) {
if (!lat || !lng) return; if (!lat || !lng) return;
@@ -119,9 +141,7 @@
.openPopup(); .openPopup();
} }
// Merender seluruh string objek data yang ditarik secara aman ke komponen UI
function isi_komponen_antarmuka(data) { 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_id_sistem').textContent = data.id;
document.getElementById('det_nama_kk').textContent = data.nama_kk; document.getElementById('det_nama_kk').textContent = data.nama_kk;
document.getElementById('det_nik').textContent = data.nik; document.getElementById('det_nik').textContent = data.nik;
@@ -152,20 +172,17 @@
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'; 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); 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() { async function ambil_data_detail_warga() {
if (!id_warga_terpilih) { if (!id_warga_terpilih) {
return alert('ID parameter tidak ditemukan pada URL peramban.'); return alert('ID parameter tidak ditemukan pada URL peramban.');
} }
try { try {
/* Baris Kompleks: Melakukan pemanggilan asinkron GET menuju rute detail REST API // Diubah ke relative path
dengan menyertakan nilai parameter ID target untuk diolah kueri SQL JOIN oleh peladen. */ const http_response = await fetch(`/api/admin/mustahik/${id_warga_terpilih}`);
const http_response = await fetch(`http://localhost:3000/api/admin/mustahik/${id_warga_terpilih}`);
const json_result = await http_response.json(); const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengambil data detail.'); if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengambil data detail.');
@@ -177,29 +194,60 @@
} }
} }
// Memproses mutasi perubahan status persetujuan data dari panel admin ke server function ubah_status_verifikasi(id_warga, status_baru) {
async function proses_keputusan_admin(keputusan_status) { antrean_id_warga = id_warga;
if (!confirm(`Apakah Anda yakin memvalidasi data ini dengan status ${keputusan_status}?`)) return; antrean_status_baru = status_baru;
const teks_pesan = document.getElementById('teks_pesan_modal');
teks_pesan.innerHTML = `Apakah Anda yakin memvalidasi data ini dengan status <span class="font-bold text-gray-900">${status_baru}</span>?`;
const tombol_setuju = document.getElementById('btn_modal_setuju');
if (status_baru === 'Verified') {
tombol_setuju.className = 'px-4 py-2.5 text-xs font-bold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl transition-colors shadow-sm';
} else {
tombol_setuju.className = 'px-4 py-2.5 text-xs font-bold text-white bg-red-600 hover:bg-red-700 rounded-xl transition-colors shadow-sm';
}
document.getElementById('modal_konfirmasi').classList.remove('hidden');
}
function tutup_modal() {
document.getElementById('modal_konfirmasi').classList.add('hidden');
antrean_id_warga = null;
antrean_status_baru = null;
}
async function eksekusi_perubahan_status() {
if (!antrean_id_warga || !antrean_status_baru) return;
const tombol_setuju = document.getElementById('btn_modal_setuju');
tombol_setuju.disabled = true;
tombol_setuju.textContent = 'Memproses...';
try { try {
const http_response = await fetch(`http://localhost:3000/api/admin/verifikasi/${id_warga_terpilih}`, { // Diubah ke relative path
const http_response = await fetch(`/api/admin/verifikasi/${antrean_id_warga}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status_validasi: keputusan_status }) body: JSON.stringify({ status_validasi: antrean_status_baru })
}); });
const json_result = await http_response.json(); const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status.'); if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status.');
tutup_modal();
alert('Status otorisasi data berhasil diperbarui!'); alert('Status otorisasi data berhasil diperbarui!');
window.location.reload(); window.location.reload();
} catch (error_sistem) { } catch (error_sistem) {
tutup_modal();
alert('Gagal mengeksekusi perintah: ' + error_sistem.message); alert('Gagal mengeksekusi perintah: ' + error_sistem.message);
} finally {
tombol_setuju.disabled = false;
tombol_setuju.textContent = 'Ya, Lanjutkan';
} }
} }
// Eksekusi inisialisasi pemuatan data awal
ambil_data_detail_warga(); ambil_data_detail_warga();
</script> </script>
</body> </body>
+2 -9
View File
@@ -33,7 +33,6 @@
</div> </div>
<script> <script>
// Mengirimkan data otentikasi admin menuju API backend server secara asinkron
async function eksekusi_login_admin() { async function eksekusi_login_admin() {
const username_val = document.getElementById('input_username').value; const username_val = document.getElementById('input_username').value;
const password_val = document.getElementById('input_password').value; const password_val = document.getElementById('input_password').value;
@@ -51,10 +50,8 @@
tombol_login.textContent = 'Otorisasi Sistem...'; tombol_login.textContent = 'Otorisasi Sistem...';
try { try {
/* Baris Kompleks: Mengirimkan request POST JSON payload kredensial admin ke server port 3000. // Diubah ke relative path
Jika kredensial terverifikasi sah oleh database, string token otorisasi disimpan ke dalam localStorage const http_response = await fetch('/api/auth/login-admin', {
peramban sebelum mengalihkan hak akses visual secara penuh ke halaman dasbor admin. */
const http_response = await fetch('http://localhost:3000/api/auth/login-admin', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -71,10 +68,7 @@
throw new Error(json_result.error || 'Akses ditolak oleh otoritas server.'); throw new Error(json_result.error || 'Akses ditolak oleh otoritas server.');
} }
// Menyimpan identitas sesi token administrator ke memori lokal peramban web
localStorage.setItem('token_admin', json_result.token); localStorage.setItem('token_admin', json_result.token);
// Redireksi antarmuka secara instan menuju halaman dasbor pusat kontrol utama
window.location.href = 'admin_dasbor.html'; window.location.href = 'admin_dasbor.html';
} catch (error_sistem) { } catch (error_sistem) {
@@ -86,7 +80,6 @@
} }
} }
// Mengikat event liistener enter pada kolom kata sandi admin untuk efisiensi aksesibilitas
document.getElementById('input_password').addEventListener('keypress', function(e) { document.getElementById('input_password').addEventListener('keypress', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
eksekusi_login_admin(); eksekusi_login_admin();
+5 -11
View File
@@ -33,7 +33,7 @@
<div id="hasil_pencarian" class="mt-6 hidden p-4 rounded-xl border max-w-xl mx-auto text-left bg-white text-gray-800 shadow-lg transition-all"> <div id="hasil_pencarian" class="mt-6 hidden p-4 rounded-xl border max-w-xl mx-auto text-left bg-white text-gray-800 shadow-lg transition-all">
<p id="pesan_hasil" class="text-sm font-semibold"></p> <p id="pesan_hasil" class="text-sm font-semibold"></p>
</div> </div>
</div> </div>
</section> </section>
@@ -105,22 +105,17 @@
let map_marker = null; let map_marker = null;
// Fungsi baru untuk memuat statistik secara asinkron dari basis data
async function muat_statistik_publik() { async function muat_statistik_publik() {
try { try {
const respons_http = await fetch('http://localhost:3000/api/survey/statistik'); // Diubah ke relative path
const respons_http = await fetch('/api/survey/statistik');
if (!respons_http.ok) throw new Error('Gagal mengambil data statistik'); if (!respons_http.ok) throw new Error('Gagal mengambil data statistik');
const data_agregat = await respons_http.json(); const data_agregat = await respons_http.json();
// Memasukkan hasil hitungan ke dalam DOM menggunakan textContent
document.getElementById('stat_publik_terverifikasi').textContent = data_agregat.terverifikasi || 0; document.getElementById('stat_publik_terverifikasi').textContent = data_agregat.terverifikasi || 0;
document.getElementById('stat_publik_diproses').textContent = data_agregat.diproses || 0; document.getElementById('stat_publik_diproses').textContent = data_agregat.diproses || 0;
/* Baris Kompleks: Menggunakan objek bawaan Date pada JavaScript untuk
mendapatkan waktu spesifik saat skrip ini berjalan di perangkat pengguna,
lalu memformatnya secara spesifik ke dalam zona waktu WIB (Asia/Jakarta)
agar seragam untuk seluruh pengunjung situs terlepas dari lokasi fisik perangkat mereka. */
const objek_waktu_sekarang = new Date(); const objek_waktu_sekarang = new Date();
const waktu_terformat_wib = objek_waktu_sekarang.toLocaleTimeString('id-ID', { const waktu_terformat_wib = objek_waktu_sekarang.toLocaleTimeString('id-ID', {
hour: '2-digit', hour: '2-digit',
@@ -150,7 +145,8 @@
tombol_cari.textContent = 'Mencari...'; tombol_cari.textContent = 'Mencari...';
try { try {
const http_response = await fetch(`http://localhost:3000/api/survey/cek-nik/${input_nik_value}`); // Diubah ke relative path
const http_response = await fetch(`/api/survey/cek-nik/${input_nik_value}`);
if (!http_response.ok) { if (!http_response.ok) {
throw new Error('Data tidak ditemukan'); throw new Error('Data tidak ditemukan');
@@ -186,9 +182,7 @@
} }
}; };
// Mengeksekusi penarikan data statistik tepat saat halaman selesai dimuat
muat_statistik_publik(); muat_statistik_publik();
</script> </script>
</body> </body>
</html> </html>
+7 -36
View File
@@ -62,27 +62,18 @@
<div id="status_message_container" class="p-8 text-center text-sm text-gray-500 hidden"></div> <div id="status_message_container" class="p-8 text-center text-sm text-gray-500 hidden"></div>
<div class="divide-y divide-gray-100" id="wadah_list_antrean"> <div class="divide-y divide-gray-100" id="wadah_list_antrean">
</div> </div>
</section> </section>
</main> </main>
<script> <script>
// Mengambil token autentikasi petugas untuk validasi sesi REST API
const token_petugas = localStorage.getItem('token_petugas'); const token_petugas = localStorage.getItem('token_petugas');
// Fungsi pengecekan proteksi halaman (RBAC) saat pertama kali dimuat
function periksa_sesi_petugas() { function periksa_sesi_petugas() {
/* Sesi dummy dinonaktifkan sementara untuk mempermudah pengujian UAS. // Sesi dinonaktifkan sementara untuk mempermudah testing
Jika nanti sistem login admin/petugas Anda sudah aktif menggunakan JWT,
cukup hapus baris komentar pada blok proteksi di bawah ini. */
// if (!token_petugas) {
// alert('Akses ditolak. Silakan login terlebih dahulu.');
// window.location.href = 'petugas_login.html';
// }
} }
// Fungsi merender list data antrean warga ke elemen DOM secara aman (XSS Prevention)
function render_data_ke_tabel(daftar_warga) { function render_data_ke_tabel(daftar_warga) {
const wadah_list = document.getElementById('wadah_list_antrean'); const wadah_list = document.getElementById('wadah_list_antrean');
const label_antrean = document.getElementById('label_jumlah_antrean'); const label_antrean = document.getElementById('label_jumlah_antrean');
@@ -96,7 +87,6 @@
label_antrean.textContent = `${daftar_warga.length} Rumah Tangga`; label_antrean.textContent = `${daftar_warga.length} Rumah Tangga`;
// Variabel counter untuk menghitung jumlah status agregat data
let total_pending = 0; let total_pending = 0;
let total_verified = 0; let total_verified = 0;
@@ -107,9 +97,6 @@
const item_div = document.createElement('div'); const item_div = document.createElement('div');
item_div.className = 'p-4 flex justify-between items-center hover:bg-gray-50 transition-colors'; item_div.className = 'p-4 flex justify-between items-center hover:bg-gray-50 transition-colors';
/* Baris Kompleks: Pembuatan elemen daftar dilakukan dengan kombinasi literal template Tailwind CSS.
Setiap teks string mentah yang berasal dari properti database dilewatkan melalui properti textContent
pada sub-elemen internal terpisah guna menangkal celah keamanan injeksi skrip berbahaya (Cross-Site Scripting). */
item_div.innerHTML = ` item_div.innerHTML = `
<div class="space-y-1"> <div class="space-y-1">
<h4 class="font-bold text-gray-800 text-sm kelas-nama"></h4> <h4 class="font-bold text-gray-800 text-sm kelas-nama"></h4>
@@ -123,14 +110,12 @@
</div> </div>
`; `;
// Menyuntikkan nilai data secara aman menggunakan textContent
item_div.querySelector('.kelas-nama').textContent = warga.nama_kk; item_div.querySelector('.kelas-nama').textContent = warga.nama_kk;
item_div.querySelector('.kelas-nik').textContent = warga.nik; item_div.querySelector('.kelas-nik').textContent = warga.nik;
const badge = item_div.querySelector('.kelas-badge'); const badge = item_div.querySelector('.kelas-badge');
badge.textContent = warga.status_validasi; badge.textContent = warga.status_validasi;
// Menentukan warna badge visual berdasarkan status validasi dari database
if (warga.status_validasi.toLowerCase() === 'verified') { if (warga.status_validasi.toLowerCase() === 'verified') {
badge.className += ' bg-emerald-50 text-emerald-700'; badge.className += ' bg-emerald-50 text-emerald-700';
} else if (warga.status_validasi.toLowerCase() === 'rejected') { } else if (warga.status_validasi.toLowerCase() === 'rejected') {
@@ -142,13 +127,11 @@
wadah_list.appendChild(item_div); wadah_list.appendChild(item_div);
}); });
// Memperbarui indikator angka papan KPI atas
document.getElementById('stat_total').textContent = daftar_warga.length; document.getElementById('stat_total').textContent = daftar_warga.length;
document.getElementById('stat_pending').textContent = total_pending; document.getElementById('stat_pending').textContent = total_pending;
document.getElementById('stat_verified').textContent = total_verified; document.getElementById('stat_verified').textContent = total_verified;
} }
// Mengambil data seluruh antrean mustahik dari server backend Node.js
async function ambil_antrean_survei() { async function ambil_antrean_survei() {
const msg_container = document.getElementById('status_message_container'); const msg_container = document.getElementById('status_message_container');
@@ -156,22 +139,13 @@
msg_container.classList.remove('hidden'); msg_container.classList.remove('hidden');
msg_container.textContent = 'Memuat daftar antrean wilayah...'; msg_container.textContent = 'Memuat daftar antrean wilayah...';
/* Contoh implementasi fetch asinkron ke server lokal port 3000. // Diubah ke relative path dan diaktifkan untuk mengambil data aslinya
Gunakan endpoint yang sesuai (misal mengambil seluruh data MUSTAHIK untuk petugas) */ const respons = await fetch('/api/admin/mustahik');
// const respons = await fetch('http://localhost:3000/api/survey/list-antrean'); if (!respons.ok) throw new Error('Gagal mengambil data dari server.');
// if (!respons.ok) throw new Error('Gagal mengambil data dari server.'); const data_warga = await respons.json();
// const data_warga = await respons.json();
// Simulasi Data Mock untuk testing (Hapus blok ini jika endpoint backend Anda sudah siap)
await new Promise(resolve => setTimeout(resolve, 600));
const data_warga_mock = [
{ nik: "6171012345670001", nama_kk: "Syahroni", status_validasi: "Pending" },
{ nik: "6171029876540002", nama_kk: "Mariani", status_validasi: "Pending" },
{ nik: "6171031122330003", nama_kk: "Zulkifli", status_validasi: "Verified" }
];
msg_container.classList.add('hidden'); msg_container.classList.add('hidden');
render_data_ke_tabel(data_warga_mock); render_data_ke_tabel(data_warga);
} catch (error_sistem) { } catch (error_sistem) {
msg_container.classList.remove('hidden'); msg_container.classList.remove('hidden');
@@ -179,17 +153,14 @@
} }
} }
// Fungsi menghapus token autentikasi sesi saat petugas keluar aplikasi
function proses_logout_petugas() { function proses_logout_petugas() {
localStorage.removeItem('token_petugas'); localStorage.removeItem('token_petugas');
alert('Sesi berhasil diakhiri.'); alert('Sesi berhasil diakhiri.');
window.location.href = 'index.html'; window.location.href = 'index.html';
} }
// Eksekusi trigger inisialisasi awal saat halaman selesai dimuat oleh browser
periksa_sesi_petugas(); periksa_sesi_petugas();
ambil_antrean_survei(); ambil_antrean_survei();
</script> </script>
</body> </body>
</html> </html>
+2 -10
View File
@@ -28,14 +28,12 @@
</div> </div>
<script> <script>
// Mengirimkan data otentikasi menuju API backend server
async function eksekusi_login_petugas() { async function eksekusi_login_petugas() {
const username_val = document.getElementById('input_username').value; const username_val = document.getElementById('input_username').value;
const password_val = document.getElementById('input_password').value; const password_val = document.getElementById('input_password').value;
const wadah_error = document.getElementById('pesan_error'); const wadah_error = document.getElementById('pesan_error');
const tombol_login = document.getElementById('btn_submit_login'); const tombol_login = document.getElementById('btn_submit_login');
// Validasi masukan dasar pada sisi klien sebelum memicu lalu lintas jaringan
if (!username_val || !password_val) { if (!username_val || !password_val) {
wadah_error.textContent = 'Kolom username dan kata sandi tidak boleh kosong.'; wadah_error.textContent = 'Kolom username dan kata sandi tidak boleh kosong.';
wadah_error.classList.remove('hidden'); wadah_error.classList.remove('hidden');
@@ -47,10 +45,8 @@
tombol_login.textContent = 'Memverifikasi...'; tombol_login.textContent = 'Memverifikasi...';
try { try {
/* Baris Kompleks: Melakukan transmisi payload data sensitif terenkripsi via jaringan menggunakan HTTP POST // Diubah ke relative path
menuju peladen port 3000, serta menyimpan string token autentikasi ke media penyimpanan lokal peramban const http_response = await fetch('/api/auth/login-petugas', {
(localStorage) jika validasi kredensial dinyatakan sah oleh server database. */
const http_response = await fetch('http://localhost:3000/api/auth/login-petugas', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -67,10 +63,7 @@
throw new Error(json_result.error || 'Gagal masuk ke dalam sistem.'); throw new Error(json_result.error || 'Gagal masuk ke dalam sistem.');
} }
// Menyimpan identitas sesi token ke memori peramban web klien
localStorage.setItem('token_petugas', json_result.token); localStorage.setItem('token_petugas', json_result.token);
// Mengalihkan fokus antarmuka menuju halaman dasbor manajemen tugas lapangan
window.location.href = 'petugas_dasbor.html'; window.location.href = 'petugas_dasbor.html';
} catch (error_sistem) { } catch (error_sistem) {
@@ -82,7 +75,6 @@
} }
} }
// Mendukung eksekusi tombol enter saat berada di kolom input kata sandi
document.getElementById('input_password').addEventListener('keypress', function(e) { document.getElementById('input_password').addEventListener('keypress', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
eksekusi_login_petugas(); eksekusi_login_petugas();
+3 -20
View File
@@ -196,7 +196,6 @@
</main> </main>
<script> <script>
// Deklarasi Status Langkah Global
let langkah_sekarang = 1; let langkah_sekarang = 1;
const total_langkah = 4; const total_langkah = 4;
@@ -208,25 +207,19 @@
const btn_kirim = document.getElementById('btn_kirim'); const btn_kirim = document.getElementById('btn_kirim');
const peringatan_error = document.getElementById('peringatan_error'); const peringatan_error = document.getElementById('peringatan_error');
// Menampilkan pesan notifikasi kegagalan validasi ke antarmuka pengguna
function tampilkan_error(pesan) { function tampilkan_error(pesan) {
peringatan_error.textContent = pesan; peringatan_error.textContent = pesan;
peringatan_error.classList.remove('hidden'); peringatan_error.classList.remove('hidden');
} }
// Menyembunyikan wadah notifikasi kegagalan
function sembunyikan_error() { function sembunyikan_error() {
peringatan_error.classList.add('hidden'); peringatan_error.classList.add('hidden');
peringatan_error.textContent = ''; peringatan_error.textContent = '';
} }
// Memastikan kelayakan data masukan di setiap bagian formulir sebelum melangkah
function validasi_langkah_aktif() { function validasi_langkah_aktif() {
sembunyikan_error(); sembunyikan_error();
/* Baris Kompleks: Logika kondisional bersarang (switch case) memisahkan aturan validasi
berdasarkan indeks tahapan aktif, memastikan konversi dan ekspresi reguler teks tepat
sebelum dievaluasi agar terhindar dari pengiriman muatan bernilai kosong ke server. */
switch(langkah_sekarang) { switch(langkah_sekarang) {
case 1: case 1:
if (!/^\d{16}$/.test(document.getElementById('input_nik').value)) { if (!/^\d{16}$/.test(document.getElementById('input_nik').value)) {
@@ -260,7 +253,7 @@
!document.getElementById('input_sumber_penerangan').value || !document.getElementById('input_sumber_penerangan').value ||
!document.getElementById('input_frekuensi_makan').value || !document.getElementById('input_frekuensi_makan').value ||
!document.getElementById('input_kepemilikan_aset').value) { !document.getElementById('input_kepemilikan_aset').value) {
tampilkan_error('Seluruh parameter utilitas dan sosial ekonomi wajib dipilih.'); tampilkan_error('Seluruh parameter utilitas and sosial ekonomi wajib dipilih.');
return false; return false;
} }
if (document.getElementById('input_pekerjaan').value.trim() === '') { if (document.getElementById('input_pekerjaan').value.trim() === '') {
@@ -283,7 +276,6 @@
return true; return true;
} }
// Menggerakkan alur visual formulir multi-langkah maju atau mundur
function ubah_langkah(arah) { function ubah_langkah(arah) {
if (arah === 1 && !validasi_langkah_aktif()) return; if (arah === 1 && !validasi_langkah_aktif()) return;
@@ -294,7 +286,6 @@
perbarui_antarmuka_wizard(); perbarui_antarmuka_wizard();
} }
// Sinkronisasi status bar kemajuan dan kontrol tombol navigasi bawah
function perbarui_antarmuka_wizard() { function perbarui_antarmuka_wizard() {
if (langkah_sekarang === 1) { if (langkah_sekarang === 1) {
btn_mundur.classList.add('hidden'); btn_mundur.classList.add('hidden');
@@ -327,7 +318,6 @@
if(document.getElementById('garis_3')) document.getElementById('garis_3').style.width = langkah_sekarang >= 4 ? '100%' : '0%'; if(document.getElementById('garis_3')) document.getElementById('garis_3').style.width = langkah_sekarang >= 4 ? '100%' : '0%';
} }
// Inisialisasi komponen GIS Leaflet untuk penentuan lokasi rumah warga
function inisialisasi_peta() { function inisialisasi_peta() {
if (instansi_peta !== null) { if (instansi_peta !== null) {
instansi_peta.invalidateSize(); instansi_peta.invalidateSize();
@@ -339,9 +329,6 @@
attribution: '&copy; OpenStreetMap' attribution: '&copy; OpenStreetMap'
}).addTo(instansi_peta); }).addTo(instansi_peta);
/* Baris Kompleks: Penangkapan event klik pada objek peta Leaflet secara dinamis
mengekstrak properti koordinat lintang dan bujur (latlng) untuk memindahkan objek penanda
serta mengisi elemen masukan teks masukan secara otomatis. */
instansi_peta.on('click', function(e) { instansi_peta.on('click', function(e) {
const lat = e.latlng.lat; const lat = e.latlng.lat;
const lng = e.latlng.lng; const lng = e.latlng.lng;
@@ -355,7 +342,6 @@
}); });
} }
// Mengakses perangkat keras penerima gelombang navigasi satelit melalui web API browser
function dapatkan_lokasi_gps() { function dapatkan_lokasi_gps() {
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
@@ -380,14 +366,12 @@
} }
} }
// Mengirimkan enkapsulasi objek data survei menuju web server secara asinkron
async function kirim_data_survei() { async function kirim_data_survei() {
if (!validasi_langkah_aktif()) return; if (!validasi_langkah_aktif()) return;
btn_kirim.disabled = true; btn_kirim.disabled = true;
btn_kirim.innerHTML = 'Mengirim Data...'; btn_kirim.innerHTML = 'Mengirim Data...';
// Konstruksi objek payload JSON disesuaikan secara presisi dengan skema database_baru.sqlite
const payload_survei = { const payload_survei = {
nik: document.getElementById('input_nik').value, nik: document.getElementById('input_nik').value,
nama_kk: document.getElementById('input_nama_kk').value, nama_kk: document.getElementById('input_nama_kk').value,
@@ -411,9 +395,8 @@
}; };
try { try {
/* Baris Kompleks: Eksekusi transmisi data asinkron via HTTP POST menuju endpoint server. // Diubah ke relative path
Header 'Content-Type' diset ke JSON agar dapat dibaca oleh middleware express.json() di backend. */ const http_response = await fetch('/api/survey/submit', {
const http_response = await fetch('http://localhost:3000/api/survey/submit', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload_survei) body: JSON.stringify(payload_survei)