/* ============================================================
forms.js — All modal form logic (FULLY UPDATED)
============================================================ */
'use strict';
function clientCalcPoverty(income, dependents, condition, education, landOwnership) {
let severityPoints = 0;
const indicators = [];
const members = Math.max(1, dependents);
const perCapita = income / members;
if (perCapita < 400000) { indicators.push('Pendapatan per kapita sangat rendah (< Rp 400.000)'); severityPoints += 3; }
else if (perCapita < 700000) { indicators.push('Pendapatan per kapita rendah (< Rp 700.000)'); severityPoints += 2; }
else if (perCapita < 1200000) { indicators.push('Pendapatan per kapita di bawah UMP'); severityPoints += 1; }
if (dependents >= 7) { indicators.push('Tanggungan sangat besar (≥ 7 orang)'); severityPoints += 3; }
else if (dependents >= 5) { indicators.push('Tanggungan besar (5-6 orang)'); severityPoints += 2; }
else if (dependents >= 4) { indicators.push('Tanggungan cukup besar (4 orang)'); severityPoints += 1; }
if (condition === 'tidak_layak') { indicators.push('Kondisi rumah tidak layak huni'); severityPoints += 3; }
const eduMap = { tidak_sekolah: ['Tidak pernah sekolah', 3], sd: ['Pendidikan hanya SD', 2], smp: ['Pendidikan hanya SMP', 1], sma: ['Pendidikan SMA', 0], diploma: ['Pendidikan Diploma', 0], sarjana: ['Pendidikan Sarjana', 0], pascasarjana: ['Pendidikan Pascasarjana', 0] };
if (eduMap[education] && eduMap[education][1] > 0) { indicators.push(eduMap[education][0]); severityPoints += eduMap[education][1]; }
if (landOwnership === 'numpang') { indicators.push('Tidak memiliki lahan (numpang)'); severityPoints += 2; }
else if (landOwnership === 'sewa') { indicators.push('Lahan menyewa'); severityPoints += 1; }
let status, label;
if (severityPoints >= 7) { status = 'sangat_miskin'; label = 'Sangat Miskin'; }
else if (severityPoints >= 4) { status = 'miskin'; label = 'Miskin'; }
else if (severityPoints >= 1) { status = 'rentan_miskin'; label = 'Rentan Miskin'; }
else { status = 'terdata'; label = 'Terdata'; }
return { status, label, indicators, severity: severityPoints };
}
function recalcPoverty() {
const income = parseInt(document.getElementById('houseIncome')?.value || 0);
const dependents = parseInt(document.getElementById('houseDependents')?.value || 1);
const condition = document.getElementById('houseCondition')?.value || 'layak';
const education = document.getElementById('houseEducation')?.value || 'sd';
const landOwnership = document.getElementById('houseLandOwnership')?.value || 'milik';
const { status, label, indicators } = clientCalcPoverty(income, dependents, condition, education, landOwnership);
const color = POVERTY_COLORS[status] || '#9ba4b5';
const ring = document.getElementById('povertyRing');
const icon = document.getElementById('povertyIcon');
const labelEl = document.getElementById('povertyStatusLabel');
const indicatorList = document.getElementById('povertyIndicators');
if (ring) { ring.style.borderColor = color; ring.style.boxShadow = `0 0 12px ${color}30`; }
if (icon) icon.style.color = color;
if (labelEl) { const shortLabels = { sangat_miskin: 'Sangat Miskin', miskin: 'Miskin', rentan_miskin: 'Rentan Miskin', terdata: 'Terdata' }; labelEl.textContent = shortLabels[status] || label; labelEl.style.color = color; }
if (indicatorList && indicators.length > 0) indicatorList.innerHTML = indicators.map(i => `
${i}`).join('');
else if (indicatorList) indicatorList.innerHTML = 'Tidak ada indikator kemiskinan signifikan';
}
function initFormTabs() {
document.querySelectorAll('.form-tab').forEach(tab => {
tab.addEventListener('click', () => {
const panel = tab.dataset.ftab;
document.querySelectorAll('.form-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.form-tab-panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById('ftab-' + panel)?.classList.add('active');
});
});
}
async function reverseGeocodeDetailed(lat, lng) {
try {
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&accept-language=id&addressdetails=1&zoom=18`;
const response = await fetch(url, { headers: { 'User-Agent': 'WebGIS-PovertyMapping/2.0' } });
const data = await response.json();
let rt = '', rw = '', kelurahan = '', kecamatan = '', fullAddress = '';
if (data && data.address) {
const addr = data.address;
rt = addr.quarter || addr.suburb || '';
rw = addr.neighbourhood || '';
kelurahan = addr.village || addr.suburb || addr.city_district || '';
kecamatan = addr.city || addr.town || addr.municipality || '';
const parts = [];
if (addr.road) parts.push(addr.road);
if (addr.house_number) parts.push('No. ' + addr.house_number);
if (rt) parts.push('RT ' + rt);
if (rw) parts.push('RW ' + rw);
if (kelurahan) parts.push('Kel. ' + kelurahan);
if (kecamatan) parts.push('Kec. ' + kecamatan);
if (addr.city) parts.push(addr.city);
if (addr.state) parts.push(addr.state);
fullAddress = parts.join(', ');
}
return { rt, rw, kelurahan, kecamatan, fullAddress: fullAddress || `${lat.toFixed(6)}, ${lng.toFixed(6)}` };
} catch (err) { return { rt: '', rw: '', kelurahan: '', kecamatan: '', fullAddress: `${lat.toFixed(6)}, ${lng.toFixed(6)}` }; }
}
async function autoAssignReligiousCenter(lat, lng) {
try {
const r = await ApiCenters.nearby(lat, lng, 2);
if (r.ok && r.data?.success && r.data.data.centers.length) {
const nearest = r.data.data.centers[0];
document.getElementById('managingCenterId').value = nearest.id;
document.getElementById('managingCenterName').value = nearest.name;
return nearest.id;
}
} catch (err) { console.warn('Failed to auto-assign center:', err); }
return null;
}
function openCenterModal(id = null, lat = null, lng = null, address = '') {
const isEdit = !!id;
document.getElementById('centerModalTitle').textContent = isEdit ? 'Edit Tempat Ibadah' : 'Tambah Tempat Ibadah';
document.getElementById('centerId').value = id || '';
document.getElementById('centerLat').value = lat || '';
document.getElementById('centerLng').value = lng || '';
if (!isEdit) {
document.getElementById('centerName').value = '';
document.getElementById('centerWorshipType').value = 'masjid';
document.getElementById('centerRadius').value = '300';
document.getElementById('centerRadiusSlider').value = '300';
document.getElementById('centerRadiusValue').textContent = '300m';
document.getElementById('centerContactPerson').value = '';
document.getElementById('centerContactPhone').value = '';
document.getElementById('centerNotes').value = '';
document.getElementById('centerAddress').value = address;
document.getElementById('centerLatDisplay').value = lat ? lat.toFixed(6) : '';
document.getElementById('centerLngDisplay').value = lng ? lng.toFixed(6) : '';
cancelPlacementMode();
openModal('centerModal');
return;
}
showLoading(true);
ApiCenters.show(id).then(r => {
showLoading(false);
if (!r.ok) { showToast('Gagal memuat data.', 'error'); return; }
const c = r.data.data;
document.getElementById('centerName').value = c.name;
document.getElementById('centerWorshipType').value = c.worship_type;
document.getElementById('centerRadius').value = c.radius;
document.getElementById('centerRadiusSlider').value = c.radius;
document.getElementById('centerRadiusValue').textContent = c.radius + 'm';
document.getElementById('centerContactPerson').value = c.contact_person || '';
document.getElementById('centerContactPhone').value = c.contact_phone || '';
document.getElementById('centerNotes').value = c.notes || '';
document.getElementById('centerAddress').value = c.address;
document.getElementById('centerLat').value = c.latitude;
document.getElementById('centerLng').value = c.longitude;
document.getElementById('centerLatDisplay').value = parseFloat(c.latitude).toFixed(6);
document.getElementById('centerLngDisplay').value = parseFloat(c.longitude).toFixed(6);
openModal('centerModal');
});
}
document.getElementById('centerRadiusSlider')?.addEventListener('input', function () { document.getElementById('centerRadius').value = this.value; document.getElementById('centerRadiusValue').textContent = this.value + 'm'; });
document.getElementById('centerRadius')?.addEventListener('input', function () { document.getElementById('centerRadiusSlider').value = this.value; document.getElementById('centerRadiusValue').textContent = this.value + 'm'; });
document.getElementById('centerForm')?.addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('centerId').value;
const lat = parseFloat(document.getElementById('centerLat').value);
const lng = parseFloat(document.getElementById('centerLng').value);
if (!lat || !lng) { showToast('Koordinat tidak valid.', 'error'); return; }
const body = { name: document.getElementById('centerName').value.trim(), worship_type: document.getElementById('centerWorshipType').value, radius: parseInt(document.getElementById('centerRadius').value), address: document.getElementById('centerAddress').value, latitude: lat, longitude: lng, contact_person: document.getElementById('centerContactPerson').value.trim(), contact_phone: document.getElementById('centerContactPhone').value.trim(), notes: document.getElementById('centerNotes').value.trim() };
if (!body.name) { showToast('Nama tempat ibadah wajib diisi.', 'error'); return; }
showLoading(true);
const r = id ? await ApiCenters.update(id, body) : await ApiCenters.create(body);
showLoading(false);
if (r.ok && r.data?.success) {
closeModal('centerModal');
cancelPlacementMode();
showToast(id ? 'Tempat ibadah diperbarui.' : 'Tempat ibadah ditambahkan.', 'success');
await loadAllData();
loadStats();
} else showToast(r.data?.message || 'Gagal menyimpan.', 'error');
return false;
});
async function editCenter(id) { if (MAP) MAP.closePopup(); openCenterModal(id); }
async function deleteCenter(id) {
if (!window.canDelete) { showToast('Anda tidak memiliki izin untuk menghapus data.', 'error'); return; }
if (!confirm('Hapus tempat ibadah ini? Semua data terkait akan dihapus.')) return;
showLoading(true);
const r = await ApiCenters.delete(id);
showLoading(false);
if (MAP) MAP.closePopup();
if (r.ok && r.data?.success) { showToast('Tempat ibadah dihapus.', 'success'); await loadAllData(); recountCenterHouseholds(); renderCenterList(); loadStats(); }
else showToast(r.data?.message || 'Gagal menghapus.', 'error');
}
async function openHouseModal(id = null, lat = null, lng = null, address = '') {
const isEdit = !!id;
document.getElementById('houseModalTitle').textContent = isEdit ? 'Edit Data Rumah Tangga' : 'Tambah Rumah Tangga';
document.getElementById('houseId').value = id || '';
document.getElementById('houseLat').value = lat || '';
document.getElementById('houseLng').value = lng || '';
document.getElementById('familyMembersData').value = '[]';
// Mode TAMBAH BARU (dari klik peta)
if (!isEdit && lat && lng) {
const addr = await reverseGeocodeDetailed(lat, lng);
document.getElementById('houseRt').value = addr.rt || '';
document.getElementById('houseRw').value = addr.rw || '';
document.getElementById('houseKelurahan').value = addr.kelurahan || '';
document.getElementById('houseKecamatan').value = addr.kecamatan || '';
document.getElementById('houseFullAddress').value = addr.fullAddress || address || '';
document.getElementById('houseLatDisplay').value = lat.toFixed(6);
document.getElementById('houseLngDisplay').value = lng.toFixed(6);
await autoAssignReligiousCenter(lat, lng);
// Reset form fields
document.getElementById('houseHeadName').value = '';
document.getElementById('houseNIK').value = '';
document.getElementById('houseGender').value = 'male';
document.getElementById('houseDOB').value = '';
document.getElementById('houseEducation').value = 'sd';
document.getElementById('houseIncome').value = '0';
document.getElementById('houseJob').value = '';
document.getElementById('houseCondition').value = 'layak';
document.getElementById('houseLandOwnership').value = 'milik';
document.getElementById('houseDescription').value = '';
document.getElementById('houseAidStatus').value = 'not_yet';
document.getElementById('houseEmploymentStatus').value = 'unemployed';
document.getElementById('houseJobGroup').style.display = 'none';
document.getElementById('houseInstitutionGroup').style.display = 'none';
document.getElementById('houseJob').value = '';
document.getElementById('houseInstitutionName').value = '';
// Reset family members display
const familyContainer = document.getElementById('familyMembersList');
if (familyContainer) {
familyContainer.innerHTML = 'Belum ada anggota keluarga
';
}
// Reset aid history display
renderAidHistory([]);
// Sembunyikan tombol add aid (karena household belum tersimpan)
const addAidBtn = document.getElementById('addAidBtn');
if (addAidBtn) addAidBtn.style.display = 'none';
const savedStrip = document.getElementById('houseSavedPhotoStrip');
const newStrip = document.getElementById('housePhotoStrip');
const photoInput = document.getElementById('housePhotos');
if (savedStrip) {
savedStrip.innerHTML = '';
newStrip.innerHTML = '';
if (photoInput) photoInput.value = '';
}
cancelPlacementMode();
openModal('houseModal');
recalcPoverty(); // Hitung ulang poverty preview
return;
}
// Mode TAMBAH BARU (tanpa koordinat)
if (!isEdit && !lat && !lng) {
// Reset semua form
document.getElementById('houseRt').value = '';
document.getElementById('houseRw').value = '';
document.getElementById('houseKelurahan').value = '';
document.getElementById('houseKecamatan').value = '';
document.getElementById('houseFullAddress').value = '';
document.getElementById('houseLatDisplay').value = '';
document.getElementById('houseLngDisplay').value = '';
document.getElementById('houseSavedPhotoStrip').innerHTML = '';
document.getElementById('housePhotoStrip').innerHTML = '';
document.getElementById('managingCenterId').value = '';
document.getElementById('managingCenterName').value = '';
document.getElementById('houseHeadName').value = '';
document.getElementById('houseNIK').value = '';
document.getElementById('houseGender').value = 'male';
document.getElementById('houseDOB').value = '';
document.getElementById('houseEducation').value = 'sd';
document.getElementById('houseIncome').value = '0';
document.getElementById('houseJob').value = '';
document.getElementById('houseCondition').value = 'layak';
document.getElementById('houseLandOwnership').value = 'milik';
document.getElementById('houseDescription').value = '';
document.getElementById('houseAidStatus').value = 'not_yet';
const photoInput = document.getElementById('housePhotos');
if (photoInput) photoInput.value = '';
const familyContainer = document.getElementById('familyMembersList');
if (familyContainer) {
familyContainer.innerHTML = 'Belum ada anggota keluarga
';
}
renderAidHistory([]);
const addAidBtn = document.getElementById('addAidBtn');
if (addAidBtn) addAidBtn.style.display = 'none';
cancelPlacementMode();
openModal('houseModal');
recalcPoverty();
return;
}
// ================================================================
// EDIT MODE: Load data dari API
// ================================================================
if (isEdit) {
showLoading(true);
const r = await ApiHouses.show(id);
showLoading(false);
if (!r.ok || !r.data?.success) {
showToast('Gagal memuat data rumah tangga.', 'error');
return;
}
const h = r.data.data;
// Informasi Alamat
if (document.getElementById('houseRt'))
document.getElementById('houseRt').value = h.rt || '';
if (document.getElementById('houseRw'))
document.getElementById('houseRw').value = h.rw || '';
if (document.getElementById('houseKelurahan'))
document.getElementById('houseKelurahan').value = h.kelurahan || '';
if (document.getElementById('houseKecamatan'))
document.getElementById('houseKecamatan').value = h.kecamatan || '';
if (document.getElementById('houseFullAddress')) {
document.getElementById('houseFullAddress').value = h.full_address || h.address || '';
// Hapus readonly attribute jika ada
document.getElementById('houseFullAddress').removeAttribute('readonly');
// Set background ke surface (tidak readonly)
document.getElementById('houseFullAddress').style.background = 'var(--surface)';
}
// Koordinat
if (document.getElementById('houseLat'))
document.getElementById('houseLat').value = h.latitude;
if (document.getElementById('houseLng'))
document.getElementById('houseLng').value = h.longitude;
if (document.getElementById('houseLatDisplay'))
document.getElementById('houseLatDisplay').value = parseFloat(h.latitude).toFixed(6);
if (document.getElementById('houseLngDisplay'))
document.getElementById('houseLngDisplay').value = parseFloat(h.longitude).toFixed(6);
// Pusat Pengelola
if (document.getElementById('managingCenterId'))
document.getElementById('managingCenterId').value = h.managing_center_id || '';
if (document.getElementById('managingCenterName'))
document.getElementById('managingCenterName').value = h.center_name || '';
// Kondisi Rumah
if (document.getElementById('houseCondition'))
document.getElementById('houseCondition').value = h.house_condition || 'layak';
// Kepemilikan Lahan
if (document.getElementById('houseLandOwnership'))
document.getElementById('houseLandOwnership').value = h.land_ownership || 'milik';
// Keterangan
if (document.getElementById('houseDescription'))
document.getElementById('houseDescription').value = h.notes || h.description || '';
// ⭐ KEPALA KELUARGA (Head of Household)
if (document.getElementById('houseHeadName'))
document.getElementById('houseHeadName').value = h.head_name || '';
if (document.getElementById('houseNIK'))
document.getElementById('houseNIK').value = h.head_nik || h.nik || '';
if (document.getElementById('houseGender'))
document.getElementById('houseGender').value = h.head_gender || h.gender || 'male';
if (document.getElementById('houseDOB'))
document.getElementById('houseDOB').value = h.head_date_of_birth || h.date_of_birth || '';
if (document.getElementById('houseEducation'))
document.getElementById('houseEducation').value = h.head_education || h.education || 'sd';
// Pekerjaan dan Pendapatan
if (document.getElementById('houseJob'))
document.getElementById('houseJob').value = h.head_job_name || h.job || '';
if (document.getElementById('houseIncome'))
document.getElementById('houseIncome').value = h.head_monthly_income || h.income || 0;
if (document.getElementById('houseEmploymentStatus')) {
const empStatus = h.head_employment_status || 'unemployed';
document.getElementById('houseEmploymentStatus').value = empStatus;
toggleHouseEmploymentFields();
if (empStatus === 'working') {
document.getElementById('houseJob').value = h.head_job_name || '';
} else if (empStatus === 'studying') {
document.getElementById('houseInstitutionName').value = h.head_institution_name || '';
}
}
// Status Bantuan
if (document.getElementById('houseAidStatus')) {
// Jika ada aid_history, set status ke 'received'
const hasAidHistory = (h.aid_history && h.aid_history.length > 0);
document.getElementById('houseAidStatus').value = hasAidHistory ? 'received' : (h.aid_status || 'not_yet');
}
// ⭐ ANGGOTA KELUARGA (Household Members)
if (h.household_members && h.household_members.length) {
document.getElementById('familyMembersData').value = JSON.stringify(h.household_members);
renderFamilyMembers(h.household_members);
} else if (h.family_members && h.family_members.length) {
document.getElementById('familyMembersData').value = JSON.stringify(h.family_members);
renderFamilyMembers(h.family_members);
} else {
document.getElementById('familyMembersData').value = '[]';
const familyContainer = document.getElementById('familyMembersList');
if (familyContainer) {
familyContainer.innerHTML = 'Belum ada anggota keluarga
';
}
}
// ⭐ RIWAYAT BANTUAN (Aid History)
if (h.aid_history && h.aid_history.length) {
renderAidHistory(h.aid_history);
} else {
renderAidHistory([]);
}
const savedStrip = document.getElementById('houseSavedPhotoStrip');
const newStrip = document.getElementById('housePhotoStrip');
const photoInput = document.getElementById('housePhotos');
if (savedStrip) {
savedStrip.innerHTML = '';
newStrip.innerHTML = '';
if (photoInput) photoInput.value = '';
const photos = JSON.parse(h.house_photos || '[]');
if (photos.length) {
const hId = id; // capture for closure
savedStrip.appendChild(
PhotoUpload.buildSavedStrip(
photos,
'uploads/houses/',
window.canDelete ?? false, // removable only for admin/officer
(filename, wrap) => deleteHousePhoto(hId, filename, wrap)
)
);
}
}
// Tampilkan tombol add aid jika household sudah ada di database
const addAidBtn = document.getElementById('addAidBtn');
if (addAidBtn) addAidBtn.style.display = 'inline-flex';
// Hitung ulang poverty preview berdasarkan data yang diisi
setTimeout(() => recalcPoverty(), 100);
openModal('houseModal');
}
}
function renderFamilyMembers(members) {
const container = document.getElementById('familyMembersList');
if (!container) return;
if (!members || members.length === 0) { container.innerHTML = 'Belum ada anggota keluarga
'; return; }
container.innerHTML = '';
members.forEach((member, index) => {
const row = document.createElement('div');
row.className = 'family-member-item';
row.innerHTML = `${escapeHtml(member.name)}${member.relationship || 'lainnya'}${member.employment_status === 'working' ? `💼 ${escapeHtml(member.job_name || 'Bekerja')}` : ''}${member.employment_status === 'studying' ? `📚 ${escapeHtml(member.institution_name || 'Sekolah')}` : ''}
`;
container.appendChild(row);
});
}
let currentEditingMemberIndex = -1;
function openFamilyMemberModal(index = -1) {
currentEditingMemberIndex = index;
const title = document.getElementById('familyMemberModalTitle');
if (index >= 0) {
title.textContent = 'Edit Anggota Keluarga';
const members = JSON.parse(document.getElementById('familyMembersData').value || '[]');
const member = members[index];
document.getElementById('memberName').value = member.name || '';
document.getElementById('memberNik').value = member.nik || '';
document.getElementById('memberGender').value = member.gender || 'male';
document.getElementById('memberDob').value = member.date_of_birth || '';
document.getElementById('memberEducation').value = member.education || 'sd';
document.getElementById('memberRelationship').value = member.relationship || 'lainnya';
document.getElementById('memberEmploymentStatus').value = member.employment_status || 'unemployed';
const jobGroup = document.getElementById('memberJobGroup');
const institutionGroup = document.getElementById('memberInstitutionGroup');
if (member.employment_status === 'working') { if (jobGroup) jobGroup.style.display = 'block'; if (institutionGroup) institutionGroup.style.display = 'none'; document.getElementById('memberJobName').value = member.job_name || ''; }
else if (member.employment_status === 'studying') { if (jobGroup) jobGroup.style.display = 'none'; if (institutionGroup) institutionGroup.style.display = 'block'; document.getElementById('memberInstitutionName').value = member.institution_name || ''; }
else { if (jobGroup) jobGroup.style.display = 'none'; if (institutionGroup) institutionGroup.style.display = 'none'; }
document.getElementById('memberIncome').value = member.monthly_income || 0;
} else {
title.textContent = 'Tambah Anggota Keluarga';
document.getElementById('memberForm').reset();
document.getElementById('memberGender').value = 'male';
document.getElementById('memberEducation').value = 'sd';
document.getElementById('memberRelationship').value = 'lainnya';
document.getElementById('memberEmploymentStatus').value = 'unemployed';
if (document.getElementById('memberJobGroup')) document.getElementById('memberJobGroup').style.display = 'none';
if (document.getElementById('memberInstitutionGroup')) document.getElementById('memberInstitutionGroup').style.display = 'none';
document.getElementById('memberIncome').value = '0';
}
openModal('familyMemberModal');
}
function saveFamilyMember() {
const member = { name: document.getElementById('memberName').value.trim(), nik: document.getElementById('memberNik').value.trim(), gender: document.getElementById('memberGender').value, date_of_birth: document.getElementById('memberDob').value, education: document.getElementById('memberEducation').value, relationship: document.getElementById('memberRelationship').value, employment_status: document.getElementById('memberEmploymentStatus').value, job_name: document.getElementById('memberJobName').value.trim(), institution_name: document.getElementById('memberInstitutionName').value.trim(), monthly_income: parseInt(document.getElementById('memberIncome').value) || 0 };
if (!member.name) { showToast('Nama anggota keluarga harus diisi.', 'error'); return; }
let members = JSON.parse(document.getElementById('familyMembersData').value || '[]');
if (currentEditingMemberIndex >= 0) members[currentEditingMemberIndex] = member;
else members.push(member);
document.getElementById('familyMembersData').value = JSON.stringify(members);
renderFamilyMembers(members);
closeModal('familyMemberModal');
}
function editFamilyMember(index) { openFamilyMemberModal(index); }
function deleteFamilyMember(index) {
if (!confirm('Hapus anggota keluarga ini?')) return;
let members = JSON.parse(document.getElementById('familyMembersData').value || '[]');
members.splice(index, 1);
document.getElementById('familyMembersData').value = JSON.stringify(members);
renderFamilyMembers(members);
}
function toggleMemberEmploymentFields() {
const status = document.getElementById('memberEmploymentStatus')?.value;
const jobGroup = document.getElementById('memberJobGroup');
const institutionGroup = document.getElementById('memberInstitutionGroup');
if (status === 'working') { if (jobGroup) jobGroup.style.display = 'block'; if (institutionGroup) institutionGroup.style.display = 'none'; }
else if (status === 'studying') { if (jobGroup) jobGroup.style.display = 'none'; if (institutionGroup) institutionGroup.style.display = 'block'; }
else { if (jobGroup) jobGroup.style.display = 'none'; if (institutionGroup) institutionGroup.style.display = 'none'; }
}
function toggleHouseEmploymentFields() {
const status = document.getElementById('houseEmploymentStatus')?.value;
const jobGroup = document.getElementById('houseJobGroup');
const institutionGroup = document.getElementById('houseInstitutionGroup');
const jobField = document.getElementById('houseJob');
const institutionField = document.getElementById('houseInstitutionName');
if (status === 'working') {
if (jobGroup) jobGroup.style.display = 'block';
if (institutionGroup) institutionGroup.style.display = 'none';
if (jobField) jobField.required = false;
if (institutionField) institutionField.required = false;
} else if (status === 'studying') {
if (jobGroup) jobGroup.style.display = 'none';
if (institutionGroup) institutionGroup.style.display = 'block';
if (jobField) jobField.required = false;
if (institutionField) institutionField.required = false;
} else {
if (jobGroup) jobGroup.style.display = 'none';
if (institutionGroup) institutionGroup.style.display = 'none';
if (jobField) jobField.required = false;
if (institutionField) institutionField.required = false;
}
}
async function editHouse(id) { if (MAP) MAP.closePopup(); await openHouseModal(id); }
async function deleteHouse(id) {
if (!window.canDelete) { showToast('Anda tidak memiliki izin untuk menghapus data.', 'error'); return; }
if (!confirm('Hapus data rumah ini? Semua data terkait akan dihapus.')) return;
showLoading(true);
const r = await ApiHouses.delete(id);
showLoading(false);
if (MAP) MAP.closePopup();
if (r.ok && r.data?.success) {
showToast('Data rumah dihapus.', 'success');
await loadAllData();
// ⭐ HITUNG ULANG CENTER HOUSEHOLDS
recountCenterHouseholds();
renderCenterList();
renderHouseList();
updateLayerCounts();
loadStats();
} else {
showToast(r.data?.message || 'Gagal menghapus.', 'error');
}
}
function openAidModalForHouse(householdId) { if (MAP) MAP.closePopup(); document.getElementById('aidHouseholdId').value = householdId; document.getElementById('aidDate').value = new Date().toISOString().slice(0, 10); document.getElementById('aidType').value = 'sembako'; document.getElementById('aidAmount').value = '0'; document.getElementById('aidNotes').value = ''; openModal('aidModal'); }
// assets/js/forms.js - Update event listener aidForm
document.getElementById('aidForm')?.addEventListener('submit', async (e) => {
e.preventDefault();
const editId = document.getElementById('aidForm').dataset.editId;
const body = {
household_id: parseInt(document.getElementById('aidHouseholdId').value),
aid_type: document.getElementById('aidType').value,
aid_date: document.getElementById('aidDate').value,
amount: parseInt(document.getElementById('aidAmount').value || 0),
notes: document.getElementById('aidNotes').value.trim()
};
showLoading(true);
const r = editId ? await ApiAid.update(editId, body) : await ApiAid.create(body);
showLoading(false);
if (r.ok && r.data?.success) {
closeModal('aidModal');
document.getElementById('aidForm').dataset.editId = '';
document.querySelector('#aidModal .modal-header h2').textContent = 'Catat Bantuan';
showToast(editId ? 'Bantuan berhasil diperbarui.' : 'Bantuan berhasil dicatat.', 'success');
const houseId = document.getElementById('houseId').value;
if (houseId) {
// ⭐ REFRESH DATA RUMAH SETELAH TAMBAH BANTUAN
showLoading(true);
const houseData = await ApiHouses.show(houseId);
showLoading(false);
if (houseData.ok && houseData.data?.data) {
const freshData = houseData.data.data;
// Update aid history display in modal
renderAidHistory(freshData.aid_history || []);
document.getElementById('houseAidStatus').value = 'received';
// ⭐ UPDATE POPUP DI PETA - REFRESH MARKER
const existingHouse = State.houses.find(h => h.id == houseId);
if (existingHouse && existingHouse._marker) {
// Update data rumah di State
existingHouse.aid_history = freshData.aid_history || [];
existingHouse.aid_status = 'received';
existingHouse._marker._houseData = freshData;
// Jika popup sedang terbuka, refresh dengan data baru
if (existingHouse._marker.isPopupOpen()) {
await showHousePopup(existingHouse._marker, freshData);
}
}
// Reload stats untuk update counter
await loadStats();
}
}
} else {
showToast(r.data?.message || 'Gagal menyimpan.', 'error');
}
return false;
});
async function refreshHousePopup(householdId) {
try {
const r = await ApiHouses.show(householdId);
if (r.ok && r.data?.success) {
const freshData = r.data.data;
// Update data di State
const index = State.houses.findIndex(h => h.id == householdId);
if (index !== -1) {
State.houses[index] = freshData;
// Update marker popup
if (State.houses[index]._marker) {
showHousePopup(State.houses[index]._marker, freshData);
}
}
return freshData;
}
} catch (err) {
console.error('Failed to refresh house popup:', err);
}
return null;
}
// Export fungsi untuk digunakan di file lain
window.refreshHousePopup = refreshHousePopup;
document.getElementById('addAidBtn')?.addEventListener('click', () => { const hhId = document.getElementById('houseId').value; if (hhId) openAidModalForHouse(hhId); });
function renderAidHistory(list) {
const el = document.getElementById('aidHistoryList');
if (!el) return;
if (!list || !list.length) {
el.innerHTML = 'Belum ada riwayat bantuan
';
return;
}
el.innerHTML = list.map((a, idx) => {
// Gunakan label yang sudah ada atau mapping dari AID_LABELS
const typeLabel = a.aid_type_label || AID_LABELS[a.aid_type] || a.aid_type || 'Bantuan';
const amount = a.amount || 0;
const notes = a.description || a.notes || '';
return `
${escapeHtml(typeLabel)}
${formatDate(a.aid_date)}
${amount ? formatRp(amount) : '—'}
`;
}).join('');
}
async function editAidRecord(aidId) {
showLoading(true);
const r = await ApiAid.show(aidId);
showLoading(false);
if (!r.ok || !r.data?.data) { showToast('Gagal memuat data bantuan.', 'error'); return; }
const a = r.data.data;
document.getElementById('aidHouseholdId').value = a.household_id;
document.getElementById('aidDate').value = a.aid_date;
document.getElementById('aidType').value = a.aid_type;
document.getElementById('aidAmount').value = a.amount || 0;
document.getElementById('aidNotes').value = a.notes || '';
document.getElementById('aidForm').dataset.editId = aidId;
document.querySelector('#aidModal .modal-header h2').textContent = 'Edit Bantuan';
openModal('aidModal');
}
async function deleteAidRecord(aidId) {
if (!confirm('Hapus riwayat bantuan ini?')) return;
showLoading(true);
const r = await ApiAid.delete(aidId);
showLoading(false);
if (r.ok && r.data?.success) { showToast('Riwayat bantuan dihapus.', 'success'); const item = document.getElementById('aid-item-' + aidId); if (item) item.remove(); const houseId = document.getElementById('houseId').value; if (houseId) { const houseData = await ApiHouses.show(houseId); if (houseData.ok && houseData.data?.data) { renderAidHistory(houseData.data.data.aid_history || []); document.getElementById('houseAidStatus').value = houseData.data.data.aid_status; } } }
else showToast(r.data?.message || 'Gagal menghapus.', 'error');
}
function openReportModal(householdId) { showToast('Gunakan halaman lapor.html untuk melaporkan warga.', 'success', 3000); }
// Helper: show error message below field
function showFieldError(fieldId, errorId, message) {
const errorEl = document.getElementById(errorId);
const fieldEl = document.getElementById(fieldId);
if (errorEl) {
errorEl.innerHTML = ` ${message}`;
errorEl.classList.add('show');
}
if (fieldEl) fieldEl.classList.add('error');
}
// Helper: clear error for a field
function clearFieldError(fieldId, errorId) {
const errorEl = document.getElementById(errorId);
const fieldEl = document.getElementById(fieldId);
if (errorEl) errorEl.classList.remove('show');
if (fieldEl) fieldEl.classList.remove('error');
}
// Validate house form before submit
function validateHouseForm() {
let isValid = true;
const employmentStatus = document.getElementById('houseEmploymentStatus')?.value;
// Required fields
const requiredFields = [
{ id: 'houseHeadName', errId: 'errHouseHeadName', label: 'Nama Kepala Keluarga' },
{ id: 'houseNIK', errId: 'errHouseNIK', label: 'NIK KK', validate: (val) => /^\d{16}$/.test(val) },
{ id: 'houseGender', errId: 'errHouseGender', label: 'Jenis Kelamin' },
{ id: 'houseDOB', errId: 'errHouseDOB', label: 'Tanggal Lahir' },
{ id: 'houseEducation', errId: 'errHouseEducation', label: 'Pendidikan' },
{ id: 'houseIncome', errId: 'errHouseIncome', label: 'Pendapatan' },
{ id: 'houseCondition', errId: 'errHouseCondition', label: 'Kondisi Rumah' },
{ id: 'houseLandOwnership', errId: 'errHouseLandOwnership', label: 'Kepemilikan Lahan' },
{ id: 'houseRt', errId: 'errHouseRt', label: 'RT' },
{ id: 'houseRw', errId: 'errHouseRw', label: 'RW' },
{ id: 'houseKelurahan', errId: 'errHouseKelurahan', label: 'Kelurahan' },
{ id: 'houseKecamatan', errId: 'errHouseKecamatan', label: 'Kecamatan' },
{ id: 'houseFullAddress', errId: 'errHouseAddress', label: 'Alamat Lengkap' }
];
requiredFields.forEach(field => {
const el = document.getElementById(field.id);
let value = el ? el.value.trim() : '';
if (!value || value === '') {
showFieldError(field.id, field.errId, `${field.label} wajib diisi.`);
isValid = false;
} else if (field.validate && !field.validate(value)) {
showFieldError(field.id, field.errId, `${field.label} tidak valid.`);
isValid = false;
} else {
clearFieldError(field.id, field.errId);
}
});
if (employmentStatus === 'working') {
const jobValue = document.getElementById('houseJob')?.value.trim();
if (!jobValue || jobValue === '') {
showFieldError('houseJob', 'errHouseJob', 'Nama pekerjaan wajib diisi.');
isValid = false;
} else {
clearFieldError('houseJob', 'errHouseJob');
}
} else if (employmentStatus === 'studying') {
const institutionValue = document.getElementById('houseInstitutionName')?.value.trim();
if (!institutionValue || institutionValue === '') {
showFieldError('houseInstitutionName', 'errHouseInstitution', 'Nama sekolah/universitas wajib diisi.');
isValid = false;
} else {
clearFieldError('houseInstitutionName', 'errHouseInstitution');
}
} else {
// Status unemployed: tidak perlu validasi pekerjaan/institusi
clearFieldError('houseJob', 'errHouseJob');
clearFieldError('houseInstitutionName', 'errHouseInstitution');
}
// Koordinat validation
const lat = document.getElementById('houseLat')?.value;
const lng = document.getElementById('houseLng')?.value;
if (!lat || !lng || lat === '' || lng === '') {
showFieldError('houseLatDisplay', 'errHouseCoord', 'Pilih lokasi pada peta terlebih dahulu.');
isValid = false;
} else {
clearFieldError('houseLatDisplay', 'errHouseCoord');
}
// Family members validation (at least 1 member? optional)
const familyData = document.getElementById('familyMembersData')?.value;
// Tidak wajib, hanya peringatan jika kosong
if (familyData && familyData !== '[]') {
const members = JSON.parse(familyData);
if (members.length === 0) {
// Optional warning
}
}
return isValid;
}
// Tambahkan event listener untuk real-time validation clearing
function initHouseFormValidation() {
const fields = ['houseHeadName', 'houseNIK', 'houseGender', 'houseDOB', 'houseEducation',
'houseIncome', 'houseJob', 'houseCondition', 'houseLandOwnership',
'houseRt', 'houseRw', 'houseKelurahan', 'houseKecamatan', 'houseFullAddress'];
fields.forEach(fieldId => {
const el = document.getElementById(fieldId);
if (el) {
el.addEventListener('input', () => {
const errId = 'err' + fieldId.charAt(0).toUpperCase() + fieldId.slice(1);
clearFieldError(fieldId, errId);
});
el.addEventListener('change', () => {
const errId = 'err' + fieldId.charAt(0).toUpperCase() + fieldId.slice(1);
clearFieldError(fieldId, errId);
});
}
});
// Coordinate clear on map click
const coordFields = ['houseLat', 'houseLng'];
coordFields.forEach(fieldId => {
const el = document.getElementById(fieldId);
if (el) {
el.addEventListener('change', () => {
clearFieldError('houseLatDisplay', 'errHouseCoord');
});
}
});
}
/* ================================================================
Photo upload — houseModal wiring
================================================================ */
/* ================================================================
deleteHousePhoto — called from the × button on saved photo thumbs
================================================================ */
async function deleteHousePhoto(householdId, filename, thumbWrap) {
if (!confirm('Hapus foto ini secara permanen?')) return;
// Optimistic UI: dim the thumb immediately
if (thumbWrap) thumbWrap.style.opacity = '0.4';
const r = await PhotoUpload.deletePhoto('house', householdId, filename);
if (r.ok && r.data?.success) {
// Remove thumb from DOM
thumbWrap?.remove();
showToast('Foto dihapus.', 'success');
} else {
// Restore opacity on failure
if (thumbWrap) thumbWrap.style.opacity = '1';
showToast(r.data?.message || 'Gagal menghapus foto.', 'error');
}
}
(function() {
const zone = document.getElementById('housePhotoZone');
const input = document.getElementById('housePhotos');
if (!zone || !input) return;
const strip = document.getElementById('housePhotoStrip');
const errEl = document.getElementById('housePhotoError');
const savedEl = document.getElementById('houseSavedPhotoStrip');
function showErr(msg) { errEl.textContent = msg; errEl.classList.add('show'); }
function clearErr() { errEl.classList.remove('show'); }
function currentSavedCount() {
return savedEl ? savedEl.querySelectorAll('div').length : 0;
}
function handleFiles(fileList) {
clearErr();
const v = PhotoUpload.validate(fileList, currentSavedCount());
if (!v.valid) { showErr(v.message); input.value = ''; return; }
strip.innerHTML = '';
strip.appendChild(PhotoUpload.buildPreviewStrip(fileList));
}
input.addEventListener('change', () => handleFiles(input.files));
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('dragover'); });
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.classList.remove('dragover');
try {
const dt = new DataTransfer();
Array.from(e.dataTransfer.files).forEach(f => dt.items.add(f));
input.files = dt.files;
handleFiles(input.files);
} catch(_) {
showErr('Drag & drop tidak didukung. Klik untuk memilih file.');
}
});
})();
// Modify the houseForm submit handler
document.getElementById('houseForm')?.addEventListener('submit', async (e) => {
e.preventDefault();
// VALIDATE FORM FIRST
if (!validateHouseForm()) {
const firstError = document.querySelector('.field-error.show');
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
showToast('Periksa kembali isian yang belum lengkap.', 'error');
return;
}
const id = document.getElementById('houseId').value;
const lat = parseFloat(document.getElementById('houseLat').value);
const lng = parseFloat(document.getElementById('houseLng').value);
if (!lat || !lng || isNaN(lat) || isNaN(lng)) {
showToast('Koordinat tidak valid. Klik peta terlebih dahulu.', 'error');
return;
}
const members = JSON.parse(document.getElementById('familyMembersData').value || '[]');
const body = {
rt: document.getElementById('houseRt')?.value || '',
rw: document.getElementById('houseRw')?.value || '',
kelurahan: document.getElementById('houseKelurahan')?.value || '',
kecamatan: document.getElementById('houseKecamatan')?.value || '',
full_address: document.getElementById('houseFullAddress')?.value || '',
latitude: lat,
longitude: lng,
house_condition: document.getElementById('houseCondition').value,
managing_center_id: document.getElementById('managingCenterId')?.value || null,
head_name: document.getElementById('houseHeadName').value.trim(),
head_nik: document.getElementById('houseNIK').value.trim(),
head_gender: document.getElementById('houseGender').value,
head_date_of_birth: document.getElementById('houseDOB').value,
head_education: document.getElementById('houseEducation').value,
head_employment_status: document.getElementById('houseEmploymentStatus').value,
head_job_name: document.getElementById('houseEmploymentStatus').value === 'working' ? document.getElementById('houseJob').value.trim() || null : null,
head_institution_name: document.getElementById('houseEmploymentStatus').value === 'studying' ? document.getElementById('houseInstitutionName').value.trim() || null : null,
head_monthly_income: parseInt(document.getElementById('houseIncome').value) || 0,
land_ownership: document.getElementById('houseLandOwnership').value,
description: document.getElementById('houseDescription').value.trim(),
aid_status: document.getElementById('houseAidStatus').value,
household_members: members,
notes: document.getElementById('houseDescription').value.trim()
};
if (!body.head_name) { showToast('Nama Kepala Keluarga wajib diisi.', 'error'); return; }
if (!body.head_nik || body.head_nik.length !== 16) { showToast('NIK KK harus 16 digit.', 'error'); return; }
if (!body.head_date_of_birth) { showToast('Tanggal Lahir KK wajib diisi.', 'error'); return; }
showLoading(true);
try {
const r = id ? await ApiHouses.update(id, body) : await ApiHouses.create(body);
if (r.ok && r.data?.success) {
// ── Upload photos ─────────────────────────────────────
const savedId = id || r.data.data?.id;
const photoInput = document.getElementById('housePhotos');
if (savedId && photoInput?.files?.length) {
const upRes = await PhotoUpload.upload('house', savedId, photoInput.files);
if (!upRes.ok || !upRes.data?.success) {
showToast('Data disimpan, tapi foto gagal diunggah: ' + (upRes.data?.message || ''), 'error', 5000);
}
photoInput.value = '';
}
// ── End upload ────────────────────────────────────────
closeModal('houseModal');
cancelPlacementMode();
showToast(id ? 'Data rumah diperbarui.' : 'Rumah berhasil ditambahkan.', 'success');
await loadAllData();
recountCenterHouseholds();
renderCenterList();
renderHouseList();
updateLayerCounts();
loadStats();
} else {
showToast(r.data?.message || 'Gagal menyimpan.', 'error');
}
} catch (err) {
showToast('Terjadi kesalahan: ' + err.message, 'error');
} finally {
showLoading(false);
}
});