feat : fix fetch bug
This commit is contained in:
+65
-22
@@ -74,8 +74,33 @@
|
||||
|
||||
</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>
|
||||
// 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) {
|
||||
const tbody = document.getElementById('tabel_body_mustahik');
|
||||
const pesan_kosong = document.getElementById('pesan_tabel_kosong');
|
||||
@@ -100,9 +125,6 @@
|
||||
const baris = document.createElement('tr');
|
||||
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 = `
|
||||
<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>
|
||||
@@ -119,7 +141,6 @@
|
||||
</td>
|
||||
`;
|
||||
|
||||
// Menyuntikkan nilai teks string secara terisolasi dan aman
|
||||
baris.querySelector('.kolom-nama').textContent = warga.nama_kk;
|
||||
baris.querySelector('.kolom-nik').textContent = warga.nik;
|
||||
baris.querySelector('.kolom-nokk').textContent = warga.no_kk || '-';
|
||||
@@ -139,7 +160,6 @@
|
||||
tbody.appendChild(baris);
|
||||
});
|
||||
|
||||
// Perbarui papan indikator agregat atas secara asinkron
|
||||
document.getElementById('label_total_baris').textContent = `${daftar_mustahik.length} Rekaman`;
|
||||
document.getElementById('stat_total_warga').textContent = daftar_mustahik.length;
|
||||
document.getElementById('stat_pending_warga').textContent = hitung_pending;
|
||||
@@ -147,13 +167,13 @@
|
||||
document.getElementById('stat_rejected_warga').textContent = hitung_rejected;
|
||||
}
|
||||
|
||||
// Meminta daftar seluruh entitas warga dari sistem peladen Express
|
||||
async function ambil_data_admin_mustahik() {
|
||||
const banner_error = document.getElementById('banner_error_admin');
|
||||
try {
|
||||
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();
|
||||
|
||||
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
|
||||
async function ubah_status_verifikasi(id_warga, status_baru) {
|
||||
const banner_error = document.getElementById('banner_error_admin');
|
||||
function ubah_status_verifikasi(id_warga, status_baru) {
|
||||
antrean_id_warga = id_warga;
|
||||
antrean_status_baru = status_baru;
|
||||
|
||||
if (!confirm(`Apakah Anda yakin ingin mengubah status data ini menjadi ${status_baru}?`)) {
|
||||
return;
|
||||
const teks_pesan = document.getElementById('teks_pesan_modal');
|
||||
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 {
|
||||
/* Baris Kompleks: Melakukan transmisi parsial status data menggunakan metode HTTP PATCH
|
||||
menuju endpoint verifikasi admin, membawa payload status terenkapsulasi JSON
|
||||
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}`, {
|
||||
// Diubah ke relative path
|
||||
const http_response = await fetch(`/api/admin/verifikasi/${antrean_id_warga}`, {
|
||||
method: 'PATCH',
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
} catch (error_sistem) {
|
||||
tutup_modal();
|
||||
banner_error.textContent = 'Gagal memproses mutasi data: ' + error_sistem.message;
|
||||
banner_error.classList.remove('hidden');
|
||||
} finally {
|
||||
tombol_setuju.disabled = false;
|
||||
tombol_setuju.textContent = 'Ya, Lanjutkan';
|
||||
}
|
||||
}
|
||||
|
||||
// Mengakhiri sesi masuk administrator utama
|
||||
function eksekusi_logout_admin() {
|
||||
localStorage.removeItem('token_admin');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
// Inisialisasi awal penarikan data saat halaman terbuka sempurna
|
||||
ambil_data_admin_mustahik();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+66
-18
@@ -85,27 +85,49 @@
|
||||
|
||||
<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>
|
||||
<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="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>
|
||||
|
||||
</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>
|
||||
// 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;
|
||||
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) {
|
||||
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;
|
||||
|
||||
@@ -119,9 +141,7 @@
|
||||
.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;
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
// Diubah ke relative path
|
||||
const http_response = await fetch(`/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.');
|
||||
@@ -177,29 +194,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
function ubah_status_verifikasi(id_warga, status_baru) {
|
||||
antrean_id_warga = id_warga;
|
||||
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 {
|
||||
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',
|
||||
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();
|
||||
if (!http_response.ok) throw new Error(json_result.error || 'Gagal mengubah status.');
|
||||
|
||||
tutup_modal();
|
||||
alert('Status otorisasi data berhasil diperbarui!');
|
||||
window.location.reload();
|
||||
|
||||
} catch (error_sistem) {
|
||||
tutup_modal();
|
||||
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();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+2
-9
@@ -33,7 +33,6 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mengirimkan data otentikasi admin menuju API backend server secara asinkron
|
||||
async function eksekusi_login_admin() {
|
||||
const username_val = document.getElementById('input_username').value;
|
||||
const password_val = document.getElementById('input_password').value;
|
||||
@@ -51,10 +50,8 @@
|
||||
tombol_login.textContent = 'Otorisasi Sistem...';
|
||||
|
||||
try {
|
||||
/* Baris Kompleks: Mengirimkan request POST JSON payload kredensial admin ke server port 3000.
|
||||
Jika kredensial terverifikasi sah oleh database, string token otorisasi disimpan ke dalam localStorage
|
||||
peramban sebelum mengalihkan hak akses visual secara penuh ke halaman dasbor admin. */
|
||||
const http_response = await fetch('http://localhost:3000/api/auth/login-admin', {
|
||||
// Diubah ke relative path
|
||||
const http_response = await fetch('/api/auth/login-admin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -71,10 +68,7 @@
|
||||
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);
|
||||
|
||||
// Redireksi antarmuka secara instan menuju halaman dasbor pusat kontrol utama
|
||||
window.location.href = 'admin_dasbor.html';
|
||||
|
||||
} 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) {
|
||||
if (e.key === 'Enter') {
|
||||
eksekusi_login_admin();
|
||||
|
||||
+4
-10
@@ -105,22 +105,17 @@
|
||||
|
||||
let map_marker = null;
|
||||
|
||||
// Fungsi baru untuk memuat statistik secara asinkron dari basis data
|
||||
async function muat_statistik_publik() {
|
||||
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');
|
||||
|
||||
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_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 waktu_terformat_wib = objek_waktu_sekarang.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
@@ -150,7 +145,8 @@
|
||||
tombol_cari.textContent = 'Mencari...';
|
||||
|
||||
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) {
|
||||
throw new Error('Data tidak ditemukan');
|
||||
@@ -186,9 +182,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Mengeksekusi penarikan data statistik tepat saat halaman selesai dimuat
|
||||
muat_statistik_publik();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+6
-35
@@ -68,21 +68,12 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Mengambil token autentikasi petugas untuk validasi sesi REST API
|
||||
const token_petugas = localStorage.getItem('token_petugas');
|
||||
|
||||
// Fungsi pengecekan proteksi halaman (RBAC) saat pertama kali dimuat
|
||||
function periksa_sesi_petugas() {
|
||||
/* Sesi dummy dinonaktifkan sementara untuk mempermudah pengujian UAS.
|
||||
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';
|
||||
// }
|
||||
// Sesi dinonaktifkan sementara untuk mempermudah testing
|
||||
}
|
||||
|
||||
// Fungsi merender list data antrean warga ke elemen DOM secara aman (XSS Prevention)
|
||||
function render_data_ke_tabel(daftar_warga) {
|
||||
const wadah_list = document.getElementById('wadah_list_antrean');
|
||||
const label_antrean = document.getElementById('label_jumlah_antrean');
|
||||
@@ -96,7 +87,6 @@
|
||||
|
||||
label_antrean.textContent = `${daftar_warga.length} Rumah Tangga`;
|
||||
|
||||
// Variabel counter untuk menghitung jumlah status agregat data
|
||||
let total_pending = 0;
|
||||
let total_verified = 0;
|
||||
|
||||
@@ -107,9 +97,6 @@
|
||||
const item_div = document.createElement('div');
|
||||
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 = `
|
||||
<div class="space-y-1">
|
||||
<h4 class="font-bold text-gray-800 text-sm kelas-nama"></h4>
|
||||
@@ -123,14 +110,12 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Menyuntikkan nilai data secara aman menggunakan textContent
|
||||
item_div.querySelector('.kelas-nama').textContent = warga.nama_kk;
|
||||
item_div.querySelector('.kelas-nik').textContent = warga.nik;
|
||||
|
||||
const badge = item_div.querySelector('.kelas-badge');
|
||||
badge.textContent = warga.status_validasi;
|
||||
|
||||
// Menentukan warna badge visual berdasarkan status validasi dari database
|
||||
if (warga.status_validasi.toLowerCase() === 'verified') {
|
||||
badge.className += ' bg-emerald-50 text-emerald-700';
|
||||
} else if (warga.status_validasi.toLowerCase() === 'rejected') {
|
||||
@@ -142,13 +127,11 @@
|
||||
wadah_list.appendChild(item_div);
|
||||
});
|
||||
|
||||
// Memperbarui indikator angka papan KPI atas
|
||||
document.getElementById('stat_total').textContent = daftar_warga.length;
|
||||
document.getElementById('stat_pending').textContent = total_pending;
|
||||
document.getElementById('stat_verified').textContent = total_verified;
|
||||
}
|
||||
|
||||
// Mengambil data seluruh antrean mustahik dari server backend Node.js
|
||||
async function ambil_antrean_survei() {
|
||||
const msg_container = document.getElementById('status_message_container');
|
||||
|
||||
@@ -156,22 +139,13 @@
|
||||
msg_container.classList.remove('hidden');
|
||||
msg_container.textContent = 'Memuat daftar antrean wilayah...';
|
||||
|
||||
/* Contoh implementasi fetch asinkron ke server lokal port 3000.
|
||||
Gunakan endpoint yang sesuai (misal mengambil seluruh data MUSTAHIK untuk petugas) */
|
||||
// const respons = await fetch('http://localhost:3000/api/survey/list-antrean');
|
||||
// if (!respons.ok) throw new Error('Gagal mengambil data dari server.');
|
||||
// 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" }
|
||||
];
|
||||
// Diubah ke relative path dan diaktifkan untuk mengambil data aslinya
|
||||
const respons = await fetch('/api/admin/mustahik');
|
||||
if (!respons.ok) throw new Error('Gagal mengambil data dari server.');
|
||||
const data_warga = await respons.json();
|
||||
|
||||
msg_container.classList.add('hidden');
|
||||
render_data_ke_tabel(data_warga_mock);
|
||||
render_data_ke_tabel(data_warga);
|
||||
|
||||
} catch (error_sistem) {
|
||||
msg_container.classList.remove('hidden');
|
||||
@@ -179,17 +153,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi menghapus token autentikasi sesi saat petugas keluar aplikasi
|
||||
function proses_logout_petugas() {
|
||||
localStorage.removeItem('token_petugas');
|
||||
alert('Sesi berhasil diakhiri.');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
// Eksekusi trigger inisialisasi awal saat halaman selesai dimuat oleh browser
|
||||
periksa_sesi_petugas();
|
||||
ambil_antrean_survei();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+2
-10
@@ -28,14 +28,12 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mengirimkan data otentikasi menuju API backend server
|
||||
async function eksekusi_login_petugas() {
|
||||
const username_val = document.getElementById('input_username').value;
|
||||
const password_val = document.getElementById('input_password').value;
|
||||
const wadah_error = document.getElementById('pesan_error');
|
||||
const tombol_login = document.getElementById('btn_submit_login');
|
||||
|
||||
// Validasi masukan dasar pada sisi klien sebelum memicu lalu lintas jaringan
|
||||
if (!username_val || !password_val) {
|
||||
wadah_error.textContent = 'Kolom username dan kata sandi tidak boleh kosong.';
|
||||
wadah_error.classList.remove('hidden');
|
||||
@@ -47,10 +45,8 @@
|
||||
tombol_login.textContent = 'Memverifikasi...';
|
||||
|
||||
try {
|
||||
/* Baris Kompleks: Melakukan transmisi payload data sensitif terenkripsi via jaringan menggunakan HTTP POST
|
||||
menuju peladen port 3000, serta menyimpan string token autentikasi ke media penyimpanan lokal peramban
|
||||
(localStorage) jika validasi kredensial dinyatakan sah oleh server database. */
|
||||
const http_response = await fetch('http://localhost:3000/api/auth/login-petugas', {
|
||||
// Diubah ke relative path
|
||||
const http_response = await fetch('/api/auth/login-petugas', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -67,10 +63,7 @@
|
||||
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);
|
||||
|
||||
// Mengalihkan fokus antarmuka menuju halaman dasbor manajemen tugas lapangan
|
||||
window.location.href = 'petugas_dasbor.html';
|
||||
|
||||
} 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) {
|
||||
if (e.key === 'Enter') {
|
||||
eksekusi_login_petugas();
|
||||
|
||||
+3
-20
@@ -196,7 +196,6 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Deklarasi Status Langkah Global
|
||||
let langkah_sekarang = 1;
|
||||
const total_langkah = 4;
|
||||
|
||||
@@ -208,25 +207,19 @@
|
||||
const btn_kirim = document.getElementById('btn_kirim');
|
||||
const peringatan_error = document.getElementById('peringatan_error');
|
||||
|
||||
// Menampilkan pesan notifikasi kegagalan validasi ke antarmuka pengguna
|
||||
function tampilkan_error(pesan) {
|
||||
peringatan_error.textContent = pesan;
|
||||
peringatan_error.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Menyembunyikan wadah notifikasi kegagalan
|
||||
function sembunyikan_error() {
|
||||
peringatan_error.classList.add('hidden');
|
||||
peringatan_error.textContent = '';
|
||||
}
|
||||
|
||||
// Memastikan kelayakan data masukan di setiap bagian formulir sebelum melangkah
|
||||
function validasi_langkah_aktif() {
|
||||
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) {
|
||||
case 1:
|
||||
if (!/^\d{16}$/.test(document.getElementById('input_nik').value)) {
|
||||
@@ -260,7 +253,7 @@
|
||||
!document.getElementById('input_sumber_penerangan').value ||
|
||||
!document.getElementById('input_frekuensi_makan').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;
|
||||
}
|
||||
if (document.getElementById('input_pekerjaan').value.trim() === '') {
|
||||
@@ -283,7 +276,6 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
// Menggerakkan alur visual formulir multi-langkah maju atau mundur
|
||||
function ubah_langkah(arah) {
|
||||
if (arah === 1 && !validasi_langkah_aktif()) return;
|
||||
|
||||
@@ -294,7 +286,6 @@
|
||||
perbarui_antarmuka_wizard();
|
||||
}
|
||||
|
||||
// Sinkronisasi status bar kemajuan dan kontrol tombol navigasi bawah
|
||||
function perbarui_antarmuka_wizard() {
|
||||
if (langkah_sekarang === 1) {
|
||||
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%';
|
||||
}
|
||||
|
||||
// Inisialisasi komponen GIS Leaflet untuk penentuan lokasi rumah warga
|
||||
function inisialisasi_peta() {
|
||||
if (instansi_peta !== null) {
|
||||
instansi_peta.invalidateSize();
|
||||
@@ -339,9 +329,6 @@
|
||||
attribution: '© OpenStreetMap'
|
||||
}).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) {
|
||||
const lat = e.latlng.lat;
|
||||
const lng = e.latlng.lng;
|
||||
@@ -355,7 +342,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Mengakses perangkat keras penerima gelombang navigasi satelit melalui web API browser
|
||||
function dapatkan_lokasi_gps() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
@@ -380,14 +366,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Mengirimkan enkapsulasi objek data survei menuju web server secara asinkron
|
||||
async function kirim_data_survei() {
|
||||
if (!validasi_langkah_aktif()) return;
|
||||
|
||||
btn_kirim.disabled = true;
|
||||
btn_kirim.innerHTML = 'Mengirim Data...';
|
||||
|
||||
// Konstruksi objek payload JSON disesuaikan secara presisi dengan skema database_baru.sqlite
|
||||
const payload_survei = {
|
||||
nik: document.getElementById('input_nik').value,
|
||||
nama_kk: document.getElementById('input_nama_kk').value,
|
||||
@@ -411,9 +395,8 @@
|
||||
};
|
||||
|
||||
try {
|
||||
/* Baris Kompleks: Eksekusi transmisi data asinkron via HTTP POST menuju endpoint server.
|
||||
Header 'Content-Type' diset ke JSON agar dapat dibaca oleh middleware express.json() di backend. */
|
||||
const http_response = await fetch('http://localhost:3000/api/survey/submit', {
|
||||
// Diubah ke relative path
|
||||
const http_response = await fetch('/api/survey/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload_survei)
|
||||
|
||||
Reference in New Issue
Block a user