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

437 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Survei Lapangan - Dinsos Pontianak</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=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Poppins', sans-serif; }
</style>
</head>
<body class="bg-gray-100 flex flex-col min-h-screen">
<nav class="bg-emerald-600 p-4 shadow-md z-30 flex items-center gap-4 text-white sticky top-0">
<a href="index.html" class="hover:bg-emerald-700 p-2 rounded-lg transition">
<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="m15 18-6-6 6-6"/></svg>
</a>
<h1 class="text-xl font-bold">Formulir Survei Kemiskinan Kriteria BPS</h1>
</nav>
<main class="flex-grow p-4 md:p-8 flex justify-center items-start">
<div class="bg-white w-full max-w-2xl rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
<div class="bg-gray-50 border-b border-gray-200 p-6 flex justify-between items-center">
<div class="flex flex-col items-center gap-2">
<div id="indikator_1" class="w-8 h-8 rounded-full bg-emerald-600 text-white flex justify-center items-center font-bold text-sm transition-colors">1</div>
<span class="text-[10px] font-semibold text-gray-600">Identitas</span>
</div>
<div class="h-1 flex-1 bg-gray-200 mx-1 rounded">
<div id="garis_1" class="h-full bg-emerald-600 w-0 transition-all duration-300"></div>
</div>
<div class="flex flex-col items-center gap-2">
<div id="indikator_2" class="w-8 h-8 rounded-full bg-gray-300 text-gray-600 flex justify-center items-center font-bold text-sm transition-colors">2</div>
<span class="text-[10px] font-semibold text-gray-600">Rumah</span>
</div>
<div class="h-1 flex-1 bg-gray-200 mx-1 rounded">
<div id="garis_2" class="h-full bg-emerald-600 w-0 transition-all duration-300"></div>
</div>
<div class="flex flex-col items-center gap-2">
<div id="indikator_3" class="w-8 h-8 rounded-full bg-gray-300 text-gray-600 flex justify-center items-center font-bold text-sm transition-colors">3</div>
<span class="text-[10px] font-semibold text-gray-600">Ekonomi</span>
</div>
<div class="h-1 flex-1 bg-gray-200 mx-1 rounded">
<div id="garis_3" class="h-full bg-emerald-600 w-0 transition-all duration-300"></div>
</div>
<div class="flex flex-col items-center gap-2">
<div id="indikator_4" class="w-8 h-8 rounded-full bg-gray-300 text-gray-600 flex justify-center items-center font-bold text-sm transition-colors">4</div>
<span class="text-[10px] font-semibold text-gray-600">Lokasi</span>
</div>
</div>
<div class="p-6">
<div id="peringatan_error" class="hidden mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm font-medium"></div>
<form id="form_survei" onsubmit="return false;">
<div id="langkah_1" class="space-y-4">
<h2 class="text-lg font-bold text-gray-800 border-b pb-2">Informasi Legalitas Identitas</h2>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nomor Induk Kependudukan (NIK)</label>
<input type="text" id="input_nik" maxlength="16" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="16 Digit NIK KTP Kepala Keluarga">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nama Lengkap Kepala Keluarga</label>
<input type="text" id="input_nama_kk" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="Masukkan nama lengkap">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nomor Kartu Keluarga (No KK)</label>
<input type="text" id="input_no_kk" maxlength="16" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="16 Digit Nomor KK">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Jumlah Anggota Keluarga Tanggungan</label>
<input type="number" id="input_jumlah_tanggungan" min="0" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="Contoh: 4">
</div>
</div>
<div id="langkah_2" class="hidden space-y-4">
<h2 class="text-lg font-bold text-gray-800 border-b pb-2">Kondisi Fisik Rumah Tinggal</h2>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Luas Lantai Per Kapita (Per Orang)</label>
<select id="input_luas_lantai" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih ukuran luas...</option>
<option value="Kurang dari 8m2">Kurang dari 8 meter persegi per orang</option>
<option value="8m2 atau Lebih">8 meter persegi atau lebih per orang</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Material Bahan Lantai Terluas</label>
<select id="input_material_lantai" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih jenis material...</option>
<option value="Tanah / Bambu">Tanah / Bambu / Kayu Murah</option>
<option value="Semen / Keramik">Semen / Ubin / Keramik</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Material Bahan Dinding Terluas</label>
<select id="input_material_dinding" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih jenis dinding...</option>
<option value="Bambu / Rumbia / Kayu Murah">Bambu / Rumbia / Kayu Kualitas Rendah</option>
<option value="Tembok Permanen">Tembok / Bata / Plesteran</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Fasilitas Sanitasi (MCK)</label>
<select id="input_fasilitas_sanitasi" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih fasilitas sanitasi...</option>
<option value="Tidak Ada / Bersama">Tidak Ada / Menggunakan MCK Umum atau Bersama</option>
<option value="Milik Sendiri">Ada Pribadi di Dalam Rumah</option>
</select>
</div>
</div>
<div id="langkah_3" class="hidden space-y-4">
<h2 class="text-lg font-bold text-gray-800 border-b pb-2">Kondisi Ekonomi & Fasilitas Utilitas</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Sumber Air Minum Utama</label>
<select id="input_sumber_air" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih sumber air...</option>
<option value="Sumur Tak Terlindung / Sungai / Air Hujan">Sumur Tak Terlindung / Air Hujan / Sungai</option>
<option value="PDAM / Sumur Terlindung">PDAM / Sumur Bor Terlindung</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Sumber Penerangan Utama</label>
<select id="input_sumber_penerangan" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih jenis penerangan...</option>
<option value="Non-PLN / Menumpang">Bukan Listrik PLN / Menumpang Tetangga</option>
<option value="PLN Mandiri">Listrik Meteran PLN Sendiri</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Pekerjaan Utama Kepala Keluarga</label>
<input type="text" id="input_pekerjaan" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="Contoh: Buruh Harian Lepas, Pemulung">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Estimasi Pendapatan Total Per Bulan (Rp)</label>
<input type="number" id="input_pendapatan" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="Contoh: 1200000">
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Frekuensi Makan Per Hari</label>
<select id="input_frekuensi_makan" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih frekuensi...</option>
<option value="1 Kali atau Kurang">1 Kali Sehari atau Kurang</option>
<option value="2 Kali">2 Kali Sehari</option>
<option value="3 Kali atau Lebih">3 Kali Sehari atau Lebih</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Kepemilikan Aset Kendaraan</label>
<select id="input_kepemilikan_aset" class="w-full p-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">Pilih status kepemilikan...</option>
<option value="Tidak Memiliki Motor">Tidak Memiliki Sepeda Motor</option>
<option value="Memiliki Motor / Aset Lain">Memiliki Motor atau Aset Berharga Lain</option>
</select>
</div>
</div>
</div>
<div id="langkah_4" class="hidden space-y-4">
<h2 class="text-lg font-bold text-gray-800 border-b pb-2">Verifikasi Titik Lokasi Geografis</h2>
<button type="button" onclick="dapatkan_lokasi_gps()" class="bg-blue-50 text-blue-700 hover:bg-blue-100 border border-blue-200 px-4 py-3 rounded-xl text-sm font-bold transition w-full">
Ambil Koordinat Lewat GPS Perangkat
</button>
<div class="h-64 w-full rounded-xl overflow-hidden border border-gray-300 relative z-0">
<div id="wadah_peta" class="h-full w-full"></div>
</div>
<div class="flex gap-4">
<div class="flex-1">
<label class="block text-xs font-medium text-gray-500">Garis Lintang (Latitude)</label>
<input type="text" id="input_lat" class="w-full p-2 bg-gray-50 border rounded text-sm text-gray-700" readonly>
</div>
<div class="flex-1">
<label class="block text-xs font-medium text-gray-500">Garis Bujur (Longitude)</label>
<input type="text" id="input_lng" class="w-full p-2 bg-gray-50 border rounded text-sm text-gray-700" readonly>
</div>
</div>
</div>
</form>
</div>
<div class="bg-gray-50 border-t border-gray-200 p-4 flex justify-between">
<button type="button" id="btn_mundur" onclick="ubah_langkah(-1)" class="hidden px-6 py-2 rounded-lg font-bold text-gray-600 hover:bg-gray-200 transition">Sebelumnya</button>
<div class="flex-1"></div>
<button type="button" id="btn_maju" onclick="ubah_langkah(1)" class="bg-emerald-600 text-white px-8 py-2 rounded-lg font-bold hover:bg-emerald-700 transition shadow-sm">Selanjutnya</button>
<button type="button" id="btn_kirim" onclick="kirim_data_survei()" class="hidden bg-emerald-600 text-white px-8 py-2 rounded-lg font-bold hover:bg-emerald-700 transition shadow-sm">Kirim Hasil Survei</button>
</div>
</div>
</main>
<script>
// Deklarasi Status Langkah Global
let langkah_sekarang = 1;
const total_langkah = 4;
let instansi_peta = null;
let penanda_peta = null;
const btn_mundur = document.getElementById('btn_mundur');
const btn_maju = document.getElementById('btn_maju');
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)) {
tampilkan_error('NIK wajib diisi tepat 16 digit angka.');
return false;
}
if (document.getElementById('input_nama_kk').value.trim().length < 3) {
tampilkan_error('Nama lengkap kepala keluarga wajib diisi (minimal 3 karakter).');
return false;
}
if (!/^\d{16}$/.test(document.getElementById('input_no_kk').value)) {
tampilkan_error('Nomor Kartu Keluarga wajib diisi tepat 16 digit angka.');
return false;
}
if (document.getElementById('input_jumlah_tanggungan').value === '') {
tampilkan_error('Jumlah anggota keluarga tanggungan harus diisi.');
return false;
}
break;
case 2:
if (!document.getElementById('input_luas_lantai').value ||
!document.getElementById('input_material_lantai').value ||
!document.getElementById('input_material_dinding').value ||
!document.getElementById('input_fasilitas_sanitasi').value) {
tampilkan_error('Seluruh parameter kelayakan rumah tinggal wajib dipilih.');
return false;
}
break;
case 3:
if (!document.getElementById('input_sumber_air').value ||
!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.');
return false;
}
if (document.getElementById('input_pekerjaan').value.trim() === '') {
tampilkan_error('Pekerjaan utama harus diisi.');
return false;
}
const pendapatan = document.getElementById('input_pendapatan').value;
if (pendapatan === '' || Number(pendapatan) < 0) {
tampilkan_error('Estimasi nominal pendapatan harus diisi dengan angka valid.');
return false;
}
break;
case 4:
if (!document.getElementById('input_lat').value) {
tampilkan_error('Titik koordinat penanda spasial belum terekam di peta.');
return false;
}
break;
}
return true;
}
// Menggerakkan alur visual formulir multi-langkah maju atau mundur
function ubah_langkah(arah) {
if (arah === 1 && !validasi_langkah_aktif()) return;
document.getElementById(`langkah_${langkah_sekarang}`).classList.add('hidden');
langkah_sekarang += arah;
document.getElementById(`langkah_${langkah_sekarang}`).classList.remove('hidden');
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');
} else {
btn_mundur.classList.remove('hidden');
}
if (langkah_sekarang === total_langkah) {
btn_maju.classList.add('hidden');
btn_kirim.classList.remove('hidden');
setTimeout(inisialisasi_peta, 100);
} else {
btn_maju.classList.remove('hidden');
btn_kirim.classList.add('hidden');
}
for (let i = 1; i <= total_langkah; i++) {
const indikator = document.getElementById(`indikator_${i}`);
if (i <= langkah_sekarang) {
indikator.classList.replace('bg-gray-300', 'bg-emerald-600');
indikator.classList.replace('text-gray-600', 'text-white');
} else {
indikator.classList.replace('bg-emerald-600', 'bg-gray-300');
indikator.classList.replace('text-white', 'text-gray-600');
}
}
if(document.getElementById('garis_1')) document.getElementById('garis_1').style.width = langkah_sekarang >= 2 ? '100%' : '0%';
if(document.getElementById('garis_2')) document.getElementById('garis_2').style.width = langkah_sekarang >= 3 ? '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() {
if (instansi_peta !== null) {
instansi_peta.invalidateSize();
return;
}
instansi_peta = L.map('wadah_peta').setView([-0.0227, 109.3425], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; 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;
if (penanda_peta) instansi_peta.removeLayer(penanda_peta);
penanda_peta = L.marker([lat, lng]).addTo(instansi_peta);
document.getElementById('input_lat').value = lat.toFixed(6);
document.getElementById('input_lng').value = lng.toFixed(6);
sembunyikan_error();
});
}
// Mengakses perangkat keras penerima gelombang navigasi satelit melalui web API browser
function dapatkan_lokasi_gps() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(posisi) {
const lat = posisi.coords.latitude;
const lng = posisi.coords.longitude;
document.getElementById('input_lat').value = lat.toFixed(6);
document.getElementById('input_lng').value = lng.toFixed(6);
instansi_peta.setView([lat, lng], 16);
if (penanda_peta) instansi_peta.removeLayer(penanda_peta);
penanda_peta = L.marker([lat, lng]).addTo(instansi_peta);
sembunyikan_error();
},
function(error) {
tampilkan_error("Gagal menangkap koordinat satelit GPS. Pastikan izin lokasi aktif.");
}
);
} else {
tampilkan_error("Perangkat peramban tidak mendukung navigasi Geolocation.");
}
}
// 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,
no_kk: document.getElementById('input_no_kk').value,
jumlah_tanggungan: parseInt(document.getElementById('input_jumlah_tanggungan').value) || 0,
lat: parseFloat(document.getElementById('input_lat').value),
lng: parseFloat(document.getElementById('input_lng').value),
id_rumah_ibadah: null,
indikator_bps: {
luas_lantai_per_kapita: document.getElementById('input_luas_lantai').value,
material_lantai: document.getElementById('input_material_lantai').value,
material_dinding: document.getElementById('input_material_dinding').value,
fasilitas_sanitasi: document.getElementById('input_fasilitas_sanitasi').value,
sumber_air_minum: document.getElementById('input_sumber_air').value,
sumber_penerangan: document.getElementById('input_sumber_penerangan').value,
pekerjaan_utama: document.getElementById('input_pekerjaan').value,
pendapatan_bulanan: parseFloat(document.getElementById('input_pendapatan').value) || 0,
frekuensi_makan: document.getElementById('input_frekuensi_makan').value,
kepemilikan_aset: document.getElementById('input_kepemilikan_aset').value
}
};
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', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload_survei)
});
const json_result = await http_response.json();
if (!http_response.ok) throw new Error(json_result.error || 'Terjadi kesalahan internal server.');
alert('Data rekam survei kriteria baru berhasil disimpan ke basis data!');
window.location.href = 'index.html';
} catch (error_sistem) {
tampilkan_error(error_sistem.message);
btn_kirim.disabled = false;
btn_kirim.innerHTML = 'Kirim Hasil Survei';
}
}
</script>
</body>
</html>