218 lines
12 KiB
JavaScript
218 lines
12 KiB
JavaScript
const Penduduk = (() => {
|
||
let all = [], filtered = [], editNIK = null;
|
||
|
||
const DEMO = [
|
||
{ nik:'6171010101010001', nama_lengkap:'Ahmad Suryana', umur:44, status_keluarga:'kepala', pekerjaan:'Buruh Harian', penghasilan:900000, status_ekonomi:'miskin', kondisi_kesehatan:'sehat', pendidikan_terakhir:'SMP', no_kk:'6171010101010001', kecamatan:'Pontianak Timur' },
|
||
{ nik:'6171010101010002', nama_lengkap:'Siti Rahma', umur:39, status_keluarga:'istri', pekerjaan:'Ibu RT', penghasilan:0, status_ekonomi:'miskin', kondisi_kesehatan:'sakit_ringan',pendidikan_terakhir:'SD', no_kk:'6171010101010001', kecamatan:'Pontianak Timur' },
|
||
{ nik:'6171010101010003', nama_lengkap:'Budi Suryana', umur:12, status_keluarga:'anak', pekerjaan:'Pelajar', penghasilan:0, status_ekonomi:'miskin', kondisi_kesehatan:'sehat', pendidikan_terakhir:'SMP', no_kk:'6171010101010001', kecamatan:'Pontianak Timur' },
|
||
{ nik:'6171010101010004', nama_lengkap:'Samsul Bahri', umur:49, status_keluarga:'kepala', pekerjaan:'Pedagang Kaki Lima',penghasilan:1200000,status_ekonomi:'rentan', kondisi_kesehatan:'sehat', pendidikan_terakhir:'SMA_SMK', no_kk:'6171010101010002', kecamatan:'Pontianak Kota' },
|
||
{ nik:'6171010101010005', nama_lengkap:'Dewi Kartika', umur:46, status_keluarga:'istri', pekerjaan:'Buruh Cuci', penghasilan:500000, status_ekonomi:'rentan', kondisi_kesehatan:'sehat', pendidikan_terakhir:'SD', no_kk:'6171010101010002', kecamatan:'Pontianak Kota' },
|
||
{ nik:'6171010101010006', nama_lengkap:'Maria Magdalena', umur:54, status_keluarga:'kepala', pekerjaan:'Penjual Gorengan',penghasilan:800000, status_ekonomi:'miskin', kondisi_kesehatan:'sakit_berat', pendidikan_terakhir:'SD', no_kk:'6171010101010003', kecamatan:'Pontianak Selatan' },
|
||
{ nik:'6171010101010007', nama_lengkap:'Petrus Yulius', umur:19, status_keluarga:'anak', pekerjaan:'Pelajar', penghasilan:0, status_ekonomi:'miskin', kondisi_kesehatan:'sehat', pendidikan_terakhir:'SMA_SMK', no_kk:'6171010101010003', kecamatan:'Pontianak Selatan' },
|
||
{ nik:'6171010101010010', nama_lengkap:'Lim Ah Kow', umur:59, status_keluarga:'kepala', pekerjaan:'Tidak Bekerja', penghasilan:0, status_ekonomi:'miskin', kondisi_kesehatan:'sakit_berat', pendidikan_terakhir:'SD', no_kk:'6171010101010005', kecamatan:'Pontianak Kota' },
|
||
{ nik:'6171010101010012', nama_lengkap:'Mulyadi', umur:45, status_keluarga:'kepala', pekerjaan:'PNS Gol. III', penghasilan:4500000, status_ekonomi:'mampu', kondisi_kesehatan:'sehat', pendidikan_terakhir:'S1', no_kk:'6171010101010006', kecamatan:'Pontianak Timur' },
|
||
{ nik:'6171010101010014', nama_lengkap:'Tan Eng Huat', umur:65, status_keluarga:'kepala', pekerjaan:'Pemulung', penghasilan:600000, status_ekonomi:'miskin', kondisi_kesehatan:'disabilitas', pendidikan_terakhir:'SD', no_kk:'6171010101010008', kecamatan:'Pontianak Kota' },
|
||
];
|
||
|
||
async function load() {
|
||
try { all = await Api.get('/penduduk'); } catch { all = DEMO; }
|
||
filtered = [...all];
|
||
render(filtered);
|
||
ge('pf-count').textContent = `${all.length} total`;
|
||
}
|
||
|
||
function search(q) { applyFilter(q); }
|
||
function applyFilter(qOverride) {
|
||
const q = qOverride ?? gv('pf-search');
|
||
const ek = gv('pf-ekonomi');
|
||
const ke = gv('pf-kesehatan');
|
||
filtered = all.filter(p =>
|
||
(!q || p.nama_lengkap?.toLowerCase().includes(q.toLowerCase()) || p.nik?.includes(q)) &&
|
||
(!ek || p.status_ekonomi === ek) &&
|
||
(!ke || p.kondisi_kesehatan === ke)
|
||
);
|
||
render(filtered);
|
||
ge('pf-count').textContent = `${filtered.length} / ${all.length}`;
|
||
}
|
||
|
||
const pendidikanLabel = { tidak_sekolah:'Tdk Sekolah', SD:'SD', SMP:'SMP', SMA_SMK:'SMA/SMK', D1_D3:'D3', S1:'S1', S2_S3:'S2/S3' };
|
||
|
||
function render(data) {
|
||
const tb = ge('tbody-penduduk');
|
||
if (!data.length) { tb.innerHTML = '<tr><td colspan="10"><div class="empty-state"><div class="es-icon">🔍</div><h3>Tidak ada data</h3><p>Coba ubah filter pencarian</p></div></td></tr>'; return; }
|
||
tb.innerHTML = data.map(p => `
|
||
<tr class="row-${p.status_ekonomi}">
|
||
<td class="mono">${p.nik}</td>
|
||
<td><strong>${p.nama_lengkap}</strong><div style="font-size:10px;color:var(--muted)">${p.kecamatan||''}</div></td>
|
||
<td>${p.umur||'?'} th</td>
|
||
<td>${cap(p.status_keluarga)}</td>
|
||
<td>${p.pekerjaan||'—'}</td>
|
||
<td style="white-space:nowrap;font-family:'JetBrains Mono',monospace;font-size:11px">${rp(p.penghasilan)}</td>
|
||
<td>${bdg(p.status_ekonomi,'')}</td>
|
||
<td>${bdg(p.kondisi_kesehatan,'')}</td>
|
||
<td>${pendidikanLabel[p.pendidikan_terakhir]||p.pendidikan_terakhir||'—'}</td>
|
||
<td>
|
||
<div style="display:flex;gap:4px">
|
||
<button class="btn btn-secondary btn-xs" onclick="Penduduk.showDetail('${p.nik}')" title="Detail">👁</button>
|
||
<button class="btn btn-secondary btn-xs" onclick="Penduduk.showForm('${p.nik}')" title="Edit" ${!App.canEdit()?'style="display:none"':''}>✏️</button>
|
||
<button class="btn btn-danger btn-xs" onclick="Penduduk.hapus('${p.nik}')" title="Hapus" ${!App.canEdit()?'style="display:none"':''}>🗑</button>
|
||
</div>
|
||
</td>
|
||
</tr>`).join('');
|
||
}
|
||
|
||
async function showForm(nik = null) {
|
||
editNIK = nik;
|
||
ge('mp-title').textContent = nik ? `✏️ Edit — ${nik}` : '➕ Tambah Penduduk Baru';
|
||
ge('fp-nik').readOnly = !!nik;
|
||
await loadKKDropdown();
|
||
if (nik) {
|
||
try {
|
||
const p = await Api.get(`/penduduk/${nik}`);
|
||
fillForm(p);
|
||
} catch {
|
||
const p = all.find(x => x.nik === nik);
|
||
if (p) fillForm(p);
|
||
}
|
||
} else {
|
||
['fp-nik','fp-nama','fp-tempat','fp-pekerjaan','fp-ket-sehat'].forEach(id => sv(id,''));
|
||
sv('fp-penghasilan','0'); sv('fp-umur','');
|
||
ge('fp-warn').classList.add('hidden');
|
||
}
|
||
App.showModal('modal-penduduk');
|
||
}
|
||
|
||
function fillForm(p) {
|
||
sv('fp-nik', p.nik); sv('fp-nama', p.nama_lengkap);
|
||
sv('fp-jkel', p.jenis_kelamin); sv('fp-tgl', p.tanggal_lahir?.substring(0,10));
|
||
sv('fp-umur', p.umur ? `${p.umur} tahun` : '');
|
||
sv('fp-tempat', p.tempat_lahir); sv('fp-agama', p.agama);
|
||
sv('fp-status-kk', p.status_keluarga); sv('fp-kawin', p.status_perkawinan);
|
||
sv('fp-hidup', p.status_hidup); sv('fp-ekonomi', p.status_ekonomi);
|
||
sv('fp-pekerjaan', p.pekerjaan); sv('fp-penghasilan', p.penghasilan || 0);
|
||
sv('fp-pendidikan', p.pendidikan_terakhir); sv('fp-stat-pend', p.status_pendidikan);
|
||
sv('fp-kesehatan', p.kondisi_kesehatan); sv('fp-ket-sehat', p.keterangan_kesehatan);
|
||
if (p.id_keluarga) sv('fp-kk', p.id_keluarga);
|
||
}
|
||
|
||
async function loadKKDropdown() {
|
||
const sel = ge('fp-kk');
|
||
try {
|
||
const list = await Api.get('/keluarga');
|
||
sel.innerHTML = list.map(k => `<option value="${k.id}">${k.no_kk} — ${k.nama_kepala}</option>`).join('');
|
||
} catch {
|
||
sel.innerHTML = '<option value="1">Demo — Ahmad Suryana</option>';
|
||
}
|
||
}
|
||
|
||
function hitungUmur() {
|
||
const tgl = gv('fp-tgl');
|
||
if (!tgl) return;
|
||
const umur = Math.floor((Date.now() - new Date(tgl)) / (365.25*24*3600*1000));
|
||
sv('fp-umur', `${umur} tahun`);
|
||
// Validasi
|
||
const pend = gv('fp-pendidikan'), stat = gv('fp-stat-pend');
|
||
const warns = [];
|
||
if (umur >= 7 && umur <= 15 && stat === 'tidak_sekolah')
|
||
warns.push(`⚠️ Usia ${umur} tahun — seharusnya masih wajib belajar (SD/SMP)`);
|
||
if (umur >= 16 && umur <= 18 && stat === 'tidak_sekolah' && ['tidak_sekolah','SD'].includes(pend))
|
||
warns.push(`⚠️ Usia ${umur} tahun — disarankan melanjutkan ke SMA/SMK`);
|
||
const wEl = ge('fp-warn');
|
||
if (warns.length) { wEl.innerHTML = warns.join('<br>'); wEl.classList.remove('hidden'); }
|
||
else wEl.classList.add('hidden');
|
||
}
|
||
|
||
async function save() {
|
||
const nik = gv('fp-nik');
|
||
if (!nik || nik.length !== 16 || !/^\d+$/.test(nik)) { App.toast('NIK harus 16 digit angka','error'); return; }
|
||
if (!gv('fp-nama')) { App.toast('Nama lengkap wajib diisi','error'); return; }
|
||
if (!gv('fp-tgl')) { App.toast('Tanggal lahir wajib diisi','error'); return; }
|
||
const body = {
|
||
nik, id_keluarga: gv('fp-kk'),
|
||
nama_lengkap: gv('fp-nama'), jenis_kelamin: gv('fp-jkel'),
|
||
tanggal_lahir: gv('fp-tgl'), tempat_lahir: gv('fp-tempat'),
|
||
agama: gv('fp-agama'), status_keluarga: gv('fp-status-kk'),
|
||
status_perkawinan: gv('fp-kawin'), status_hidup: gv('fp-hidup'),
|
||
pekerjaan: gv('fp-pekerjaan'), penghasilan: +gv('fp-penghasilan')||0,
|
||
status_ekonomi: gv('fp-ekonomi'), pendidikan_terakhir: gv('fp-pendidikan'),
|
||
status_pendidikan: gv('fp-stat-pend'), kondisi_kesehatan: gv('fp-kesehatan'),
|
||
keterangan_kesehatan: gv('fp-ket-sehat'),
|
||
};
|
||
try {
|
||
if (editNIK) { await Api.put(`/penduduk/${editNIK}`, body); App.toast('Data diperbarui ✅','success'); }
|
||
else { await Api.post('/penduduk', body); App.toast('Penduduk ditambahkan ✅','success'); }
|
||
App.closeModal('modal-penduduk');
|
||
await load();
|
||
App.updateHeaderStats();
|
||
} catch(e) { App.toast(e.message,'error'); }
|
||
}
|
||
|
||
async function hapus(nik) {
|
||
if (!confirm(`Hapus data penduduk NIK ${nik}?\nTindakan ini tidak dapat dibatalkan.`)) return;
|
||
try {
|
||
await Api.delete(`/penduduk/${nik}`);
|
||
App.toast('Data dihapus','success');
|
||
load(); App.updateHeaderStats();
|
||
} catch(e) { App.toast(e.message,'error'); }
|
||
}
|
||
|
||
async function showDetail(nik) {
|
||
ge('dp-title').textContent = 'Memuat…';
|
||
ge('detail-panel').classList.add('open');
|
||
let p;
|
||
try { p = await Api.get(`/penduduk/${nik}`); }
|
||
catch { p = all.find(x => x.nik === nik); }
|
||
if (!p) { App.toast('Gagal memuat detail','error'); return; }
|
||
ge('dp-title').textContent = p.nama_lengkap;
|
||
const histori = p.histori_bantuan || [];
|
||
ge('dp-body').innerHTML = `
|
||
<div class="dp-section">
|
||
<div class="dp-section-title">Identitas</div>
|
||
${field('NIK', `<span class="mono">${p.nik}</span>`)}
|
||
${field('Jenis Kelamin', p.jenis_kelamin === 'L' ? 'Laki-laki' : 'Perempuan')}
|
||
${field('Umur', `${p.umur||'?'} tahun`)}
|
||
${field('Tempat Lahir', p.tempat_lahir||'—')}
|
||
${field('Agama', p.agama||'—')}
|
||
${field('Status KK', cap(p.status_keluarga))}
|
||
${field('Perkawinan', cap(p.status_perkawinan))}
|
||
</div>
|
||
<div class="dp-section">
|
||
<div class="dp-section-title">Sosial Ekonomi</div>
|
||
${field('Pekerjaan', p.pekerjaan||'—')}
|
||
${field('Penghasilan', rp(p.penghasilan))}
|
||
${field('Status Ekonomi', bdg(p.status_ekonomi,''))}
|
||
${field('Pendidikan', p.pendidikan_terakhir?.replace('_','/')||'—')}
|
||
${field('Kesehatan', bdg(p.kondisi_kesehatan,''))}
|
||
</div>
|
||
<div class="dp-section">
|
||
<div class="dp-section-title">Lokasi</div>
|
||
${field('Kelurahan', p.kelurahan||'—')}
|
||
${field('Kecamatan', p.kecamatan||'—')}
|
||
${field('Alamat', p.alamat||'—')}
|
||
${field('Rumah Ibadah', p.nama_ri||'—')}
|
||
</div>
|
||
<div class="dp-section">
|
||
<div class="dp-section-title">Riwayat Bantuan (${histori.length}x)</div>
|
||
${histori.length ? histori.map(h=>`
|
||
<div style="background:var(--bg4);border-radius:8px;padding:8px 10px;margin-bottom:6px;font-size:12px">
|
||
<strong>${h.nama_bantuan}</strong><span style="color:var(--muted);font-size:11px;float:right">${h.tanggal?.substring(0,10)}</span>
|
||
<div style="color:var(--muted);font-size:11px">${h.nama_ri||''} · ${h.jumlah} ${h.satuan||''}</div>
|
||
</div>`).join('') : '<p style="color:var(--muted);font-size:12px">Belum pernah menerima bantuan</p>'}
|
||
</div>
|
||
${App.canEdit() ? `
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-primary" style="flex:1" onclick="Bantuan.quickSalur('${p.nik}','${p.nama_lengkap}')">📦 Salurkan Bantuan</button>
|
||
<button class="btn btn-secondary" onclick="Penduduk.showForm('${p.nik}')">✏️ Edit</button>
|
||
</div>` : ''}
|
||
`;
|
||
}
|
||
|
||
function field(lbl, val) {
|
||
return `<div class="dp-field"><span class="dp-field-lbl">${lbl}</span><span class="dp-field-val">${val}</span></div>`;
|
||
}
|
||
|
||
function closeDetail() { ge('detail-panel').classList.remove('open'); }
|
||
|
||
return { load, search, applyFilter, showForm, hitungUmur, save, hapus, showDetail, closeDetail };
|
||
})();
|