import React, { useState, useEffect, useMemo } from 'react'; // === COMPONENT UTAMA === export default function App() { const defaultSpreadsheetUrl = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vT_ppTR-ulTolGnni4Stu6zPxEalQ-WjEDoJmSgp2THIbUBAZ7Nga2Kwyn0hHwjimRJqqpis9SVuZ6E/pub?gid=497516861&single=true&output=csv'; // State Management const [csvUrl, setCsvUrl] = useState(defaultSpreadsheetUrl); const [rawData, setRawData] = useState(''); const [dataHeaders, setDataHeaders] = useState([]); const [dataRows, setDataRows] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [dataSource, setDataSource] = useState('remote'); // 'remote', 'simulation', 'manual' // State Pencarian dan Filter const [searchTerm, setSearchTerm] = useState(''); const [selectedDesil, setSelectedDesil] = useState('Semua'); const [selectedKecamatan, setSelectedKecamatan] = useState('Semua'); const [selectedDesa, setSelectedDesa] = useState('Semua'); const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Pagination State const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); // Detail Modal State const [selectedFamily, setSelectedFamily] = useState(null); const [activeTab, setActiveTab] = useState('database'); // 'database' | 'dashboard' | 'bantuan' // Fungsi Parser CSV Kustom yang Sangat Kuat (Handal menangani koma di dalam tanda kutip) const parseCSV = (text) => { if (!text) return { headers: [], rows: [] }; const lines = []; let row = [""]; let inQuotes = false; for (let i = 0; i < text.length; i++) { const char = text[i]; const nextChar = text[i + 1]; if (char === '"') { if (inQuotes && nextChar === '"') { row[row.length - 1] += '"'; i++; } else { inQuotes = !inQuotes; } } else if (char === ',' && !inQuotes) { row.push(''); } else if ((char === '\r' || char === '\n') && !inQuotes) { if (char === '\r' && nextChar === '\n') { i++; } lines.push(row); row = ['']; } else { row[row.length - 1] += char; } } if (row.length > 1 || row[0] !== '') { lines.push(row); } if (lines.length === 0) return { headers: [], rows: [] }; // Header pembersihan karakter non-printable / BOM const headers = lines[0].map(h => h.trim().replace(/^\uFEFF/, '')); const rows = lines.slice(1) .filter(line => line.some(cell => cell.trim() !== '')) .map((line, index) => { const rowObj = { _id: index }; headers.forEach((header, colIndex) => { rowObj[header] = line[colIndex] !== undefined ? line[colIndex].trim() : ''; }); return rowObj; }); return { headers, rows }; }; // Data Simulasi Kesejahteraan (P3KE/DTKS) yang Realistis untuk Fallback Pintar const getMockData = () => { return `No,Nama Kepala Keluarga,NIK,No. KK,Alamat,RT,RW,Dusun / Desa,Kecamatan,Desil,Jumlah ART,Pekerjaan,Bantuan Sosial 1,Ahmad Subarjo,3201021405780001,3201021008120045,Jl. Kenanga No. 12,02,04,Desa Sukamakmur,Kecamatan Caringin,Desil 1 (Sangat Miskin),4,Buruh Tani,PKH & BPNT 2,Siti Aminah,3201024508820002,3201021008120099,Kampung Baru RT 03,03,01,Desa Sukamakmur,Kecamatan Caringin,Desil 2 (Miskin),5,Pedagang Kecil,BPNT 3,Bambang Pamungkas,3201021811720003,3201021203110052,RT 01 RW 02 Dusun Kidul,01,02,Desa Bojong Gede,Kecamatan Sukaraja,Desil 3 (Hampir Miskin),3,Ojek Online,KIP 4,Dewi Lestari,3201035504900004,3201031908150112,Perumahan Griya Asri B4,04,08,Desa Bojong Gede,Kecamatan Sukaraja,Desil 4 (Rentan Miskin),2,Asisten Rumah Tangga,Belum Ada 5,Joko Widodo,3201041209680005,3201041512100021,Dusun Krajan Tengah,02,02,Desa Karangrejo,Kecamatan Caringin,Desil 1 (Sangat Miskin),6,Pemulung,PBI-JK 6,Sri Wahyuni,3201034402850006,3201031104110088,Komp. Melati Blok C,01,05,Desa Karangrejo,Kecamatan Caringin,Desil 3 (Hampir Miskin),4,Buruh Cuci,PKH 7,Hendra Setiawan,3201050503800007,3201052210130004,Gg. H. Bakri No. 45,03,03,Desa Sukamakmur,Kecamatan Caringin,Desil 2 (Miskin),3,Supir Angkot,BPNT & PBI-JK 8,Kartini Sastro,3201026110550008,3201020404090023,Kampung Pasir Angin,02,01,Desa Bojong Gede,Kecamatan Sukaraja,Desil 1 (Sangat Miskin),1,Tidak Bekerja (Lansia),PKH Lansia 9,Rian Adianto,3201061902890009,3201061706140091,RT 05 RW 03 Kp. Sawah,05,03,Desa Karangrejo,Kecamatan Caringin,Desil 4 (Rentan Miskin),5,Buruh Bangunan,Belum Ada 10,Megawati Sukma,3201034101750010,3201032812110047,Jl. Garuda No. 88,01,02,Desa Sukamakmur,Kecamatan Caringin,Desil 3 (Hampir Miskin),3,Penjahit,KIP Kuliah 11,Budi Utomo,3201021408800011,3201021901110031,Kampung Babakan RT 02,02,05,Desa Bojong Gede,Kecamatan Sukaraja,Desil 1 (Sangat Miskin),5,Buruh Harian Lepas,PKH & BPNT 12,Ani Yudhoyono,3201025211830012,3201022204120054,Dusun Wetan RT 01,01,03,Desa Karangrejo,Kecamatan Caringin,Desil 2 (Miskin),4,Pedagang Gorengan,BPNT`; }; // Fetch Data dari Google Sheet CSV (Dengan Auto CORS Proxy Fallback) const fetchData = async (urlToFetch) => { setIsLoading(true); setError(null); try { let response; let text = ''; try { // Percobaan 1: Mengambil langsung (Direct Fetch) response = await fetch(urlToFetch, { cache: 'no-store' }); if (!response.ok) throw new Error(`HTTP ${response.status}`); text = await response.text(); } catch (directError) { console.warn("Direct fetch terblokir CORS, mencoba mengambil via CORS Proxy..."); // Percobaan 2: Menggunakan AllOrigins CORS Proxy secara otomatis const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(urlToFetch)}`; response = await fetch(proxyUrl); if (!response.ok) { throw new Error("Gagal terhubung baik secara langsung maupun melalui proxy."); } text = await response.text(); } if (!text || text.trim().length === 0) { throw new Error("Format Spreadsheet kosong."); } const { headers, rows } = parseCSV(text); if (headers.length === 0 || rows.length === 0) { throw new Error("Gagal mengurai kolom data CSV."); } setDataHeaders(headers); setDataRows(rows); setRawData(text); setDataSource('remote'); } catch (err) { console.error("Fetch error details:", err); setError(`Koneksi Gagal: ${err.message}. Tenang, Anda dapat menggunakan opsi 'Gunakan Data Simulasi' di bawah untuk tetap menguji aplikasi.`); } finally { setIsLoading(false); } }; // Efek Pertama Kali Load useEffect(() => { fetchData(csvUrl); }, []); // Fungsi beralih ke Data Simulasi const handleLoadSimulationData = () => { setIsLoading(true); setError(null); setTimeout(() => { const mockCsv = getMockData(); const { headers, rows } = parseCSV(mockCsv); setDataHeaders(headers); setDataRows(rows); setRawData(mockCsv); setDataSource('simulation'); setIsLoading(false); }, 400); }; // Fungsi memuat CSV manual dari Input Text const handleManualCsvSubmit = (text) => { setIsLoading(true); setError(null); try { const { headers, rows } = parseCSV(text); if (headers.length === 0 || rows.length === 0) { throw new Error("Format CSV tidak valid."); } setDataHeaders(headers); setDataRows(rows); setRawData(text); setDataSource('manual'); setActiveTab('database'); } catch (err) { setError(`Gagal mengurai CSV manual: ${err.message}`); } finally { setIsLoading(false); } }; // Deteksi kolom dinamis const getColumnMapping = useMemo(() => { const mapping = { nama: '', nik: '', kk: '', alamat: '', desa: '', kecamatan: '', desil: '', art: '', penerimaBansos: '' }; dataHeaders.forEach(header => { const hLower = header.toLowerCase(); if (hLower.includes('nama') || hLower.includes('kepala keluarga') || hLower === 'kk_nama') { if (!mapping.nama) mapping.nama = header; } else if (hLower === 'nik' || hLower.includes('n.i.k') || hLower.includes('identitas')) { if (!mapping.nik) mapping.nik = header; } else if (hLower.includes('kk') || hLower.includes('kartu keluarga') || hLower === 'no_kk') { if (!mapping.kk) mapping.kk = header; } else if (hLower.includes('alamat') || hLower.includes('jalan') || hLower === 'lokasi') { if (!mapping.alamat) mapping.alamat = header; } else if (hLower.includes('desa') || hLower.includes('kelurahan') || hLower.includes('dusun') || hLower.includes('dusun / desa')) { if (!mapping.desa) mapping.desa = header; } else if (hLower.includes('kecamatan')) { if (!mapping.kecamatan) mapping.kecamatan = header; } else if (hLower.includes('desil') || hLower.includes('kesejahteraan') || hLower.includes('p3ke')) { if (!mapping.desil) mapping.desil = header; } else if (hLower.includes('art') || hLower.includes('anggota') || hLower.includes('jumlah_keluarga')) { if (!mapping.art) mapping.art = header; } else if (hLower.includes('bansos') || hLower.includes('bantuan') || hLower.includes('pkh') || hLower.includes('program')) { if (!mapping.penerimaBansos) mapping.penerimaBansos = header; } }); // Fallbacks if (!mapping.nama && dataHeaders.length > 0) mapping.nama = dataHeaders[1] || dataHeaders[0]; if (!mapping.desil && dataHeaders.length > 0) mapping.desil = dataHeaders.find(h => h.toLowerCase().includes('desil')) || dataHeaders[dataHeaders.length - 1]; return mapping; }, [dataHeaders]); // Ekstraksi Nilai Unik untuk Filter const filterOptions = useMemo(() => { const desilSet = new Set(); const kecamatanSet = new Set(); const desaSet = new Set(); dataRows.forEach(row => { const desilCol = getColumnMapping.desil; const kecCol = getColumnMapping.kecamatan; const desaCol = getColumnMapping.desa; if (desilCol && row[desilCol]) desilSet.add(row[desilCol]); if (kecCol && row[kecCol]) kecamatanSet.add(row[kecCol]); if (desaCol && row[desaCol]) desaSet.add(row[desaCol]); }); return { desil: ['Semua', ...Array.from(desilSet).sort()], kecamatan: ['Semua', ...Array.from(kecamatanSet).sort()], desa: ['Semua', ...Array.from(desaSet).sort()] }; }, [dataRows, getColumnMapping]); const handleSort = (key) => { let direction = 'asc'; if (sortConfig.key === key && sortConfig.direction === 'asc') { direction = 'desc'; } setSortConfig({ key, direction }); }; const filteredRows = useMemo(() => { return dataRows.filter(row => { const matchSearch = searchTerm === '' || Object.values(row).some(val => String(val).toLowerCase().includes(searchTerm.toLowerCase()) ); const desilCol = getColumnMapping.desil; const matchDesil = selectedDesil === 'Semua' || (desilCol && row[desilCol] === selectedDesil); const kecCol = getColumnMapping.kecamatan; const matchKecamatan = selectedKecamatan === 'Semua' || (kecCol && row[kecCol] === selectedKecamatan); const desaCol = getColumnMapping.desa; const matchDesa = selectedDesa === 'Semua' || (desaCol && row[desaCol] === selectedDesa); return matchSearch && matchDesil && matchKecamatan && matchDesa; }).sort((a, b) => { if (!sortConfig.key) return 0; const aVal = String(a[sortConfig.key]); const bVal = String(b[sortConfig.key]); if (!isNaN(aVal) && !isNaN(bVal)) { return sortConfig.direction === 'asc' ? Number(aVal) - Number(bVal) : Number(bVal) - Number(aVal); } return sortConfig.direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); }); }, [dataRows, searchTerm, selectedDesil, selectedKecamatan, selectedDesa, sortConfig, getColumnMapping]); const handleResetFilters = () => { setSearchTerm(''); setSelectedDesil('Semua'); setSelectedKecamatan('Semua'); setSelectedDesa('Semua'); setSortConfig({ key: null, direction: 'asc' }); setCurrentPage(1); }; const paginatedRows = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return filteredRows.slice(startIndex, startIndex + itemsPerPage); }, [filteredRows, currentPage, itemsPerPage]); const totalPages = Math.ceil(filteredRows.length / itemsPerPage) || 1; useEffect(() => { setCurrentPage(1); }, [searchTerm, selectedDesil, selectedKecamatan, selectedDesa, itemsPerPage]); const stats = useMemo(() => { const total = dataRows.length; let desil1Count = 0; let desil2Count = 0; let desil3Count = 0; let desil4Count = 0; let totalART = 0; const desilCol = getColumnMapping.desil; const artCol = getColumnMapping.art; dataRows.forEach(row => { const desilVal = desilCol ? String(row[desilCol]).toLowerCase() : ''; if (desilVal.includes('desil 1') || desilVal === '1') desil1Count++; else if (desilVal.includes('desil 2') || desilVal === '2') desil2Count++; else if (desilVal.includes('desil 3') || desilVal === '3') desil3Count++; else if (desilVal.includes('desil 4') || desilVal === '4') desil4Count++; if (artCol && row[artCol]) { const val = parseInt(row[artCol]); if (!isNaN(val)) totalART += val; } }); const desilDistribution = {}; if (desilCol) { dataRows.forEach(row => { const val = row[desilCol] || 'Tidak Terdefinisi'; desilDistribution[val] = (desilDistribution[val] || 0) + 1; }); } const desaDistribution = {}; const desaCol = getColumnMapping.desa; if (desaCol) { dataRows.forEach(row => { const val = row[desaCol] || 'Lainnya'; desaDistribution[val] = (desaDistribution[val] || 0) + 1; }); } return { total, desil1Count, desil2Count, desil3Count, desil4Count, totalART, desilDistribution, desaDistribution: Object.entries(desaDistribution).sort((a,b) => b[1] - a[1]).slice(0, 5) }; }, [dataRows, getColumnMapping]); const exportToCSV = () => { if (filteredRows.length === 0) return; const csvContent = [ dataHeaders.join(','), ...filteredRows.map(row => dataHeaders.map(header => { const cell = row[header] === undefined ? '' : String(row[header]); return cell.includes(',') || cell.includes('"') || cell.includes('\n') ? `"${cell.replace(/"/g, '""')}"` : cell; }).join(',') ) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `Data_Desil_${new Date().toISOString().slice(0, 10)}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return (
{/* INJECT ANIMASI KEYFRAMES */} {/* HEADER UTAMA */}

Sistem Informasi Pencarian Data Desil

Akselerasi Pensasaran Percepatan Penghapusan Kemiskinan Ekstrem (P3KE)

{/* BANNER STATUS KONEKSI DATA */}
Sumber Data: {dataSource === 'remote' ? 'Google Spreadsheet Aktif (CORS Bypassed)' : dataSource === 'simulation' ? 'Simulasi Lokal' : 'CSV Manual'}
{dataSource !== 'remote' && ( )}
{/* HANDLING LOADING */} {isLoading && (

Sedang memproses dan menyusun data Desil...

Menghubungkan ke: {csvUrl.substring(0, 60)}...

)} {/* HANDLING ERROR */} {error && !isLoading && (

Terjadi Kendala Memuat Data

Sistem gagal menjangkau Google Spreadsheet Anda.

{error}

Solusi Cepat:

  • Klik tombol **Gunakan Data Simulasi** di bawah ini untuk melihat seluruh fitur aplikasi berjalan sempurna dengan contoh data asli P3KE.
  • Atau, pastikan spreadsheet Anda telah di-Publish di menu *File {`>`} Share {`>`} Publish to web* dan diset sebagai **CSV**.
)} {/* UTAMA JIKA BERHASIL LOAD */} {!isLoading && !error && (
{/* WIDGET SUMMARY STATS */}

Total Kepala Keluarga

{stats.total}

Kepala Keluarga terdaftar

Desil 1 (Sangat Miskin)

{stats.desil1Count}

{(stats.desil1Count / (stats.total || 1) * 100).toFixed(1)}% dari total KK

Desil 2 (Miskin)

{stats.desil2Count}

{(stats.desil2Count / (stats.total || 1) * 100).toFixed(1)}% dari total KK

Total Anggota Keluarga (ART)

{stats.totalART || '-'}

Jiwa terjangkau jaminan

{/* TAB 1: DATABASE */} {activeTab === 'database' && (
{/* FILTER PANEL */}

Pencarian dan Filter Cerdas

{/* Pencarian */}
setSearchTerm(e.target.value)} placeholder="Masukkan kata kunci pencarian..." className="w-full pl-10 pr-4 py-2 border rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-teal-600 bg-white" />
{/* Desil */}
{/* Kecamatan */}
{/* Desa */}
{/* Buttons */}
Ditemukan {filteredRows.length} dari total {dataRows.length} baris data KK.
{/* TABLE */}
{getColumnMapping.nik && ( )} {getColumnMapping.kk && ( )} {paginatedRows.length > 0 ? ( paginatedRows.map((row, idx) => { const realNo = (currentPage - 1) * itemsPerPage + idx + 1; const nameVal = row[getColumnMapping.nama] || '-'; const nikVal = getColumnMapping.nik ? row[getColumnMapping.nik] : null; const kkVal = getColumnMapping.kk ? row[getColumnMapping.kk] : null; const desaVal = row[getColumnMapping.desa] || '-'; const desilVal = row[getColumnMapping.desil] || '-'; let desilBadgeClass = "bg-slate-100 text-slate-800"; if (desilVal.includes('Desil 1') || desilVal === '1') desilBadgeClass = "bg-red-100 text-red-800 font-bold"; else if (desilVal.includes('Desil 2') || desilVal === '2') desilBadgeClass = "bg-amber-100 text-amber-800 font-semibold"; else if (desilVal.includes('Desil 3') || desilVal === '3') desilBadgeClass = "bg-sky-100 text-sky-800"; else if (desilVal.includes('Desil 4') || desilVal === '4') desilBadgeClass = "bg-emerald-100 text-emerald-800"; return ( {nikVal !== null && } {kkVal !== null && } ); }) ) : ( )}
No handleSort(getColumnMapping.nama)}> Nama Kepala Keluarga {sortConfig.key === getColumnMapping.nama ? (sortConfig.direction === 'asc' ? '▲' : '▼') : ''} handleSort(getColumnMapping.nik)}> NIK {sortConfig.key === getColumnMapping.nik ? (sortConfig.direction === 'asc' ? '▲' : '▼') : ''} handleSort(getColumnMapping.kk)}> No. KK {sortConfig.key === getColumnMapping.kk ? (sortConfig.direction === 'asc' ? '▲' : '▼') : ''} handleSort(getColumnMapping.desa)}> Desa / Kelurahan {sortConfig.key === getColumnMapping.desa ? (sortConfig.direction === 'asc' ? '▲' : '▼') : ''} handleSort(getColumnMapping.desil)}> Desil {sortConfig.key === getColumnMapping.desil ? (sortConfig.direction === 'asc' ? '▲' : '▼') : ''} Aksi
{realNo} {nameVal}{nikVal || '-'}{kkVal || '-'}{desaVal} {desilVal}

Data Tidak Ditemukan

Silakan ubah kata kunci pencarian atau bersihkan filter Anda.

{/* PAGINATION */} {filteredRows.length > 0 && (
Tampilkan data per halaman
Halaman {currentPage} dari {totalPages}
)}
)} {/* TAB 2: DASHBOARD */} {activeTab === 'dashboard' && (
{/* Desil Chart */}

Distribusi Tingkat Desil Kesejahteraan

{Object.entries(stats.desilDistribution).map(([label, count]) => { const percentage = (count / stats.total * 100).toFixed(1); let barColor = "bg-slate-500"; if (label.includes('Desil 1') || label === '1') barColor = "bg-red-500"; else if (label.includes('Desil 2') || label === '2') barColor = "bg-amber-500"; else if (label.includes('Desil 3') || label === '3') barColor = "bg-sky-500"; else if (label.includes('Desil 4') || label === '4') barColor = "bg-emerald-500"; return (
{label} {count} KK ({percentage}%)
); })}
{/* Top Desa Chart */}

Sebaran Kesejahteraan Tertinggi (Top 5 Desa)

{stats.desaDistribution.length > 0 ? ( stats.desaDistribution.map(([label, count]) => { const percentage = (count / stats.total * 100).toFixed(1); return (
{label} {count} KK ({percentage}%)
); }) ) : (

Kolom Desa/Kelurahan tidak terdeteksi untuk visualisasi.

)}

💡 Wawasan Pensasaran Program Kesejahteraan

Data di atas diklasifikasikan berdasarkan Desil (Decile) Kemiskinan. Desil 1 menunjukkan kelompok 10% terendah (Sangat Miskin/Ekstrem) secara nasional, sedangkan Desil 4 menunjukkan batas rentan kemiskinan. Pensasaran bantuan sosial idealnya diprioritaskan bagi Keluarga pada Desil 1 & Desil 2 guna menekan angka kemiskinan ekstrim hingga mendekati 0%.

)} {/* TAB 3: BANTUAN */} {activeTab === 'bantuan' && (

Ubah Link / Integrasikan Google Spreadsheet Baru

Aplikasi ini dapat disinkronkan langsung dengan Google Spreadsheet tim Anda. Caranya, masukkan URL ekspor CSV publik Anda di bawah.

setCsvUrl(e.target.value)} placeholder="Masukkan URL format CSV dari Google Spreadsheet..." className="w-full p-2.5 border rounded-xl text-sm font-mono focus:outline-none focus:ring-2 focus:ring-teal-600" />

{/* INPUT MANUAL */}

Opsi Tempel (Paste) Raw CSV Manual

Anda juga dapat langsung menempelkan baris data CSV teks di sini tanpa membutuhkan koneksi internet.

{/* LANGKAH PUBLISH */}

Cara Publish Google Spreadsheet ke Format CSV:

  1. Buka dokumen Google Spreadsheet Anda.
  2. Klik menu File di sudut kiri atas.
  3. Pilih opsi Bagikan (Share) {`>`} Publikasikan ke Web (Publish to web).
  4. Ubah pilihan "Seluruh Dokumen" menjadi Lembar/Sheet data Desil Anda.
  5. Ubah format output "Halaman Web" menjadi Nilai Terpisah Koma (.csv).
  6. Klik tombol Publikasikan (Publish).
  7. Salin link URL yang muncul dan tempelkan pada kotak kustomisasi aplikasi ini.
)}
)}
{/* FOOTER */} {/* DETAIL PROFILE DRAWER */} {selectedFamily && (
{/* Header */}

Profil Kepala Keluarga

Kartu Jaminan Sosial & Data Desil P3KE

{/* Konten */}
{/* Desil Badge */}
KLASIFIKASI KESEJAHTERAAN {selectedFamily[getColumnMapping.desil] || 'Tidak Diketahui'}
{/* Data Detail */}

Informasi Kependudukan

Nama Kepala Keluarga {selectedFamily[getColumnMapping.nama] || '-'}
Jumlah Anggota Keluarga {selectedFamily[getColumnMapping.art] || '-'} Jiwa
{getColumnMapping.nik && (
Nomor Induk Kependudukan (NIK) {selectedFamily[getColumnMapping.nik] || '-'}
)} {getColumnMapping.kk && (
Nomor Kartu Keluarga (KK) {selectedFamily[getColumnMapping.kk] || '-'}
)}
{/* Alamat */}

Lokasi & Alamat

Alamat Rumah Lengkap {selectedFamily[getColumnMapping.alamat] || '-'}
Desa / Kelurahan {selectedFamily[getColumnMapping.desa] || '-'}
Kecamatan {selectedFamily[getColumnMapping.kecamatan] || '-'}
{/* Extra Atribut dinamis */}

Atribut Tambahan Lainnya

{dataHeaders .filter(header => header !== getColumnMapping.nama && header !== getColumnMapping.nik && header !== getColumnMapping.kk && header !== getColumnMapping.desil && header !== getColumnMapping.alamat && header !== getColumnMapping.desa && header !== getColumnMapping.kecamatan && header !== getColumnMapping.art ) .map(header => (
{header} {selectedFamily[header] || '-'}
)) }
{/* Footer */}
)}
); }