diff --git a/src/FileListComponent.js b/src/FileListComponent.js index 168f5e7..dfed338 100644 --- a/src/FileListComponent.js +++ b/src/FileListComponent.js @@ -17,6 +17,69 @@ const FileListComponent = ({ const [successMessage, setSuccessMessage] = useState(""); const [selectedDocumentType, setSelectedDocumentType] = useState(""); + // Helper function to convert snake_case to Title Case + const formatKeyToLabel = (key) => { + return key + .replace(/_/g, ' ') + .replace(/\b\w/g, l => l.toUpperCase()); + }; + + // Helper function to check if value is a date string and convert it + const formatValue = (key, value) => { + if (value === null || value === undefined || value === '') { + return null; + } + + // Check if the value looks like a date + if (typeof value === 'string' && + (key.includes('tanggal') || key.includes('lahir') || key.includes('berlaku') || key.includes('pembuatan') || key.includes('created_at'))) { + const date = new Date(value); + if (!isNaN(date.getTime())) { + return date; + } + } + + return value; + }; + + // Dynamic function to process data for Excel export + const processDataForExcel = (data) => { + if (!data || data.length === 0) return []; + + return data.map((item) => { + const processedItem = {}; + + Object.entries(item).forEach(([key, value]) => { + // Skip null, undefined, or empty string values + if (value === null || value === undefined || value === '') { + return; + } + + // Skip certain keys that are not needed in export + const excludedKeys = ['id', 'document_type', 'created_at', 'data', 'foto_url']; + if (excludedKeys.includes(key)) { + return; + } + + // Format the key as label + const label = formatKeyToLabel(key); + + // Format the value + const formattedValue = formatValue(key, value); + + processedItem[label] = formattedValue; + }); + + return processedItem; + }); + }; + + // Dynamic function to get unique document types + const getUniqueDocumentTypes = (data) => { + const types = [...new Set(data.map(item => item.document_type).filter(Boolean))]; + return types; + }; + useEffect(() => { const fetchFiles = async () => { const token = localStorage.getItem("token"); @@ -47,7 +110,7 @@ const FileListComponent = ({ const today = new Date().toISOString().slice(0, 10); const totalToday = fileData.filter((f) => - f.created_at.startsWith(today) + f.created_at && f.created_at.startsWith(today) ).length; setTotalFilesSentToday(totalToday); @@ -55,6 +118,7 @@ const FileListComponent = ({ const currentMonth = now.getMonth(); const currentYear = now.getFullYear(); const totalThisMonth = fileData.filter((f) => { + if (!f.created_at) return false; const d = new Date(f.created_at); return ( d.getMonth() === currentMonth && d.getFullYear() === currentYear @@ -64,7 +128,10 @@ const FileListComponent = ({ setTotalFilesSentOverall(fileData.length); - const dateObjects = fileData.map((item) => new Date(item.created_at)); + const dateObjects = fileData + .filter(item => item.created_at) + .map((item) => new Date(item.created_at)); + if (dateObjects.length > 0) { const minDate = new Date(Math.min(...dateObjects)); const maxDate = new Date(Math.max(...dateObjects)); @@ -81,14 +148,16 @@ const FileListComponent = ({ current.setMonth(current.getMonth() + 1); } - fileData.forEach((item) => { - const d = new Date(item.created_at); - const monthKey = `${d.getFullYear()}-${String( - d.getMonth() + 1 - ).padStart(2, "0")}`; - if (monthlyDataMap[monthKey] !== undefined) - monthlyDataMap[monthKey]++; - }); + fileData + .filter(item => item.created_at) + .forEach((item) => { + const d = new Date(item.created_at); + const monthKey = `${d.getFullYear()}-${String( + d.getMonth() + 1 + ).padStart(2, "0")}`; + if (monthlyDataMap[monthKey] !== undefined) + monthlyDataMap[monthKey]++; + }); const performanceArray = Object.entries(monthlyDataMap).map( ([month, count]) => { @@ -133,7 +202,7 @@ const FileListComponent = ({ try { const response = await fetch( `https://bot.kediritechnopark.com/webhook/solid-data/merged?nama_lengkap=${encodeURIComponent( - file.nama_lengkap + file.nama_lengkap || '' )}`, { method: "GET", @@ -155,6 +224,9 @@ const FileListComponent = ({ return; } + console.log("Data received from merged API:", data[0]); // Debug log + console.log("All keys in data:", Object.keys(data[0])); // Debug log + console.log("Non-null values:", Object.entries(data[0]).filter(([k,v]) => v !== null)); // Debug log setSelectedFile(data[0]); } catch (error) { console.error("Gagal mengambil detail:", error.message); @@ -177,38 +249,21 @@ const FileListComponent = ({ phone?.replace(/(\d{4})(\d{4})(\d{4})/, "$1-$2-$3"); const exportToExcel = (data) => { - const modifiedData = data.map((item) => ({ - ID: item.id, - Petugas_ID: item.petugas_id, - Petugas: item.username, - NIK: item.nik, - Nama_Lengkap: item.nama_lengkap, - Tempat_Lahir: item.tempat_lahir, - Tanggal_Lahir: new Date(item.tanggal_lahir), - Jenis_Kelamin: item.jenis_kelamin, - Alamat: item.alamat, - RT: item.rt, - RW: item.rw, - Kel_Desa: item.kel_desa, - Kecamatan: item.kecamatan, - Agama: item.agama, - Status_Perkawinan: item.status_perkawinan, - Pekerjaan: item.pekerjaan, - Kewarganegaraan: item.kewarganegaraan, - No_HP: item.no_hp, - Email: item.email, - Berlaku_Hingga: new Date(item.berlaku_hingga), - Pembuatan: new Date(item.pembuatan), - Kota_Pembuatan: item.kota_pembuatan, - Created_At: new Date(item.created_at), - })); + const processedData = processDataForExcel(data); + if (processedData.length === 0) { + alert("Tidak ada data untuk diekspor."); + return; + } - const worksheet = XLSX.utils.json_to_sheet(modifiedData); + const worksheet = XLSX.utils.json_to_sheet(processedData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Data"); XLSX.writeFile(workbook, "data-export.xlsx"); }; + // Get unique document types for dropdown + const documentTypes = getUniqueDocumentTypes(files); + return (
| {label} | -{value} | -
| {formatKeyToLabel(key)} | +
+ {value.map((member, idx) => (
+
+ {Object.entries(member)
+ .filter(([_, memberValue]) => {
+ if (memberValue === null || memberValue === undefined) return false;
+ if (typeof memberValue === 'string' && memberValue.trim() === '') return false;
+ return true;
+ })
+ .map(([memberKey, memberValue]) => (
+
+ ))}
+
+ {formatKeyToLabel(memberKey)}: {memberValue}
+
+ ))}
+ |
+
| {formatKeyToLabel(key)} | +{displayValue} | +