195 lines
11 KiB
HTML
195 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dasbor Petugas Lapangan - Dinsos Pontianak</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
body { font-family: 'Poppins', sans-serif; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 flex flex-col min-h-screen text-gray-800">
|
|
|
|
<nav class="bg-white p-4 shadow-sm z-30 flex justify-between items-center px-6 sticky top-0 border-b border-gray-100">
|
|
<div class="flex items-center gap-2">
|
|
<div class="bg-emerald-600 text-white p-2 rounded-xl">
|
|
<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="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-sm font-bold text-gray-800 leading-none">Panel Petugas</h1>
|
|
<span class="text-[10px] text-gray-500 font-medium">Dinsos Kota Pontianak</span>
|
|
</div>
|
|
</div>
|
|
<button onclick="proses_logout_petugas()" class="text-xs font-semibold text-red-600 hover:bg-red-50 px-3 py-2 rounded-xl transition">Keluar</button>
|
|
</nav>
|
|
|
|
<main class="flex-grow p-4 md:p-6 max-w-4xl mx-auto w-full space-y-6">
|
|
|
|
<section class="bg-gradient-to-br from-emerald-600 to-emerald-700 text-white p-6 rounded-2xl shadow-sm flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
<div>
|
|
<h2 class="text-xl font-bold">Selamat Datang, Petugas Lapangan</h2>
|
|
<p class="text-xs text-emerald-100 mt-1 font-light">Tetap teliti dan objektif dalam melakukan validasi indikator kemiskinan warga.</p>
|
|
</div>
|
|
<a href="survei_lapangan.html" class="bg-white text-emerald-700 hover:bg-emerald-50 px-5 py-3 rounded-xl text-sm font-bold shadow-md transition-all flex items-center gap-2 w-full sm:w-auto justify-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
|
|
Mulai Survei Baru
|
|
</a>
|
|
</section>
|
|
|
|
<section class="grid grid-cols-3 gap-3 md:gap-6">
|
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 text-center">
|
|
<span class="text-[10px] text-gray-400 font-bold uppercase tracking-wider block mb-1">Total Tugas</span>
|
|
<p class="text-2xl font-bold text-gray-800" id="stat_total">0</p>
|
|
</div>
|
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 text-center">
|
|
<span class="text-[10px] text-yellow-600 font-bold uppercase tracking-wider block mb-1">Pending</span>
|
|
<p class="text-2xl font-bold text-yellow-600" id="stat_pending">0</p>
|
|
</div>
|
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 text-center">
|
|
<span class="text-[10px] text-emerald-600 font-bold uppercase tracking-wider block mb-1">Disetujui</span>
|
|
<p class="text-2xl font-bold text-emerald-600" id="stat_verified">0</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
|
<div class="p-4 border-b border-gray-100 flex justify-between items-center bg-gray-50">
|
|
<h3 class="font-bold text-gray-800 text-sm">Daftar Antrean Survei Wilayah</h3>
|
|
<span class="text-[11px] bg-emerald-100 text-emerald-800 font-semibold px-2 py-1 rounded-full" id="label_jumlah_antrean">0 Antrean</span>
|
|
</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>
|
|
</section>
|
|
|
|
</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';
|
|
// }
|
|
}
|
|
|
|
// 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');
|
|
|
|
wadah_list.innerHTML = '';
|
|
|
|
if (daftar_warga.length === 0) {
|
|
wadah_list.innerHTML = '<div class="p-8 text-center text-sm text-gray-400">Tidak ada agenda survei aktif saat ini.</div>';
|
|
return;
|
|
}
|
|
|
|
label_antrean.textContent = `${daftar_warga.length} Rumah Tangga`;
|
|
|
|
// Variabel counter untuk menghitung jumlah status agregat data
|
|
let total_pending = 0;
|
|
let total_verified = 0;
|
|
|
|
daftar_warga.forEach(warga => {
|
|
if (warga.status_validasi.toLowerCase() === 'pending') total_pending++;
|
|
if (warga.status_validasi.toLowerCase() === 'verified') total_verified++;
|
|
|
|
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>
|
|
<p class="text-xs text-gray-500">NIK: <span class="kelas-nik"></span></p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<span class="inline-block px-2 py-1 text-[10px] font-bold rounded-full kelas-badge"></span>
|
|
<a href="survei_lapangan.html?nik=${warga.nik}" class="text-xs bg-gray-100 hover:bg-emerald-50 hover:text-emerald-700 text-gray-600 px-3 py-2 rounded-lg font-medium transition-colors border border-gray-200">
|
|
Buka Form
|
|
</a>
|
|
</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') {
|
|
badge.className += ' bg-red-50 text-red-700';
|
|
} else {
|
|
badge.className += ' bg-yellow-50 text-yellow-700';
|
|
}
|
|
|
|
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');
|
|
|
|
try {
|
|
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" }
|
|
];
|
|
|
|
msg_container.classList.add('hidden');
|
|
render_data_ke_tabel(data_warga_mock);
|
|
|
|
} catch (error_sistem) {
|
|
msg_container.classList.remove('hidden');
|
|
msg_container.textContent = 'Gagal terhubung ke server backend: ' + error_sistem.message;
|
|
}
|
|
}
|
|
|
|
// 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> |