/* ============================================================ 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); } });