import { NextRequest, NextResponse } from 'next/server'; import * as XLSX from 'xlsx'; import supabase from '@/lib/db'; export async function POST(request: NextRequest) { try { // Get form data from request const formData = await request.formData(); const file = formData.get('file') as File; if (!file) { return NextResponse.json({ message: 'File tidak ditemukan' }, { status: 400 }); } // Process file data based on file type let validData = []; let errors: string[] = []; if (file.name.endsWith('.csv') || file.type === 'text/csv') { // Process as CSV const fileContent = await file.text(); const result = await processCSVData(fileContent); validData = result.validData; errors = result.errors; } else { // Process as Excel const fileBuffer = await file.arrayBuffer(); const result = await processExcelData(fileBuffer); validData = result.validData; errors = result.errors; } if (validData.length === 0) { return NextResponse.json({ message: 'Tidak ada data valid yang ditemukan dalam file', errors }, { status: 400 }); } // Insert valid data into the database const { imported, errorCount, errorMessages } = await insertDataToDatabase(validData); // Combine all error messages const allErrors = [...errors, ...errorMessages]; return NextResponse.json({ message: 'Upload berhasil', imported, errors: errorCount, errorDetails: allErrors.length > 0 ? allErrors : undefined }); } catch (error) { console.error('Error uploading file:', error); return NextResponse.json( { message: `Terjadi kesalahan: ${(error as Error).message}` }, { status: 500 } ); } } // Function to process Excel data async function processExcelData(fileBuffer: ArrayBuffer) { try { // Parse Excel file const workbook = XLSX.read(fileBuffer, { type: 'array' }); // Get first sheet const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // Convert to JSON with proper typing const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as any[][]; if (jsonData.length === 0) { return { validData: [], errors: ['File Excel kosong'] }; } // Convert Excel data to CSV-like format for processing const headers = jsonData[0].map(h => String(h).toLowerCase()); const rows = jsonData.slice(1); // Process the data using the common function return processData(headers, rows); } catch (error) { console.error('Error processing Excel data:', error); return { validData: [], errors: [(error as Error).message] }; } } // Function to process CSV data async function processCSVData(fileContent: string) { const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== ''); if (lines.length === 0) { return { validData: [], errors: ['File CSV kosong'] }; } // Get headers from first line const headerLine = lines[0].toLowerCase(); const headers = headerLine.split(',').map(h => h.trim()); // Process data rows const rows = lines.slice(1).map(line => line.split(',').map(v => v.trim())); return processData(headers, rows); } // Common function to process data regardless of source format function processData(headers: string[], rows: any[][]) { // Define expected headers and their possible variations const expectedHeaderMap = { nim: ['nim', 'nomor induk', 'nomor mahasiswa'], nama_beasiswa: ['nama_beasiswa', 'nama beasiswa', 'namabeasiswa', 'beasiswa', 'nama'], sumber_beasiswa: ['sumber_beasiswa', 'sumber beasiswa', 'sumberbeasiswa', 'sumber'], beasiswa_status: ['beasiswa_status', 'status beasiswa', 'statusbeasiswa', 'status'], jenis_beasiswa: ['jenis_beasiswa', 'jenis beasiswa', 'jenisbeasiswa', 'jenis'] }; // Map actual headers to expected headers const headerMap: { [key: string]: number } = {}; for (const [expectedHeader, variations] of Object.entries(expectedHeaderMap)) { const index = headers.findIndex(h => variations.some(variation => h.includes(variation)) ); if (index !== -1) { headerMap[expectedHeader] = index; } } // Check required headers const requiredHeaders = ['nim', 'nama_beasiswa', 'sumber_beasiswa', 'beasiswa_status', 'jenis_beasiswa']; const missingHeaders = requiredHeaders.filter(h => headerMap[h] === undefined); if (missingHeaders.length > 0) { return { validData: [], errors: [`Kolom berikut tidak ditemukan: ${missingHeaders.join(', ')}. Pastikan file memiliki kolom: NIM, Nama Beasiswa, Sumber Beasiswa, Status Beasiswa, dan Jenis Beasiswa.`] }; } const validData = []; const errors = []; const validStatuses = ['Aktif', 'Selesai', 'Dibatalkan']; const validJenis = ['Pemerintah', 'Non-Pemerintah']; // Process data rows for (let i = 0; i < rows.length; i++) { const values = rows[i]; if (!values || values.length === 0) continue; try { // Extract values using header map const nim = String(values[headerMap.nim] || '').trim(); const nama_beasiswa = String(values[headerMap.nama_beasiswa] || '').trim(); const sumber_beasiswa = String(values[headerMap.sumber_beasiswa] || '').trim(); let beasiswa_status = String(values[headerMap.beasiswa_status] || '').trim(); let jenis_beasiswa = String(values[headerMap.jenis_beasiswa] || '').trim(); // Validate required fields if (!nim || !nama_beasiswa || !sumber_beasiswa || !beasiswa_status || !jenis_beasiswa) { errors.push(`Baris ${i+2}: Data tidak lengkap (NIM: ${nim || 'kosong'})`); continue; } // Normalize status beasiswa beasiswa_status = normalizeBeasiswaStatus(beasiswa_status); // Validate status beasiswa if (!validStatuses.includes(beasiswa_status)) { errors.push(`Baris ${i+2}: Status beasiswa tidak valid "${beasiswa_status}" untuk NIM ${nim}. Harus salah satu dari: ${validStatuses.join(', ')}`); continue; } // Normalize jenis beasiswa jenis_beasiswa = normalizeJenisBeasiswa(jenis_beasiswa); // Validate jenis beasiswa if (!validJenis.includes(jenis_beasiswa)) { errors.push(`Baris ${i+2}: Jenis beasiswa tidak valid "${jenis_beasiswa}" untuk NIM ${nim}. Harus salah satu dari: ${validJenis.join(', ')}`); continue; } // Add to valid data validData.push({ nim, nama_beasiswa, sumber_beasiswa, beasiswa_status, jenis_beasiswa }); } catch (error) { errors.push(`Baris ${i+2}: Error memproses data - ${(error as Error).message}`); } } return { validData, errors }; } // Function to normalize beasiswa status values function normalizeBeasiswaStatus(value: string): string { const lowerValue = value.toLowerCase(); if (['aktif', 'active', 'a'].includes(lowerValue)) { return 'Aktif'; } if (['selesai', 'complete', 'completed', 's', 'finish', 'finished'].includes(lowerValue)) { return 'Selesai'; } if (['dibatalkan', 'cancel', 'cancelled', 'canceled', 'batal', 'd', 'c'].includes(lowerValue)) { return 'Dibatalkan'; } return value; // Return original if no match } // Function to normalize jenis beasiswa values function normalizeJenisBeasiswa(value: string): string { const lowerValue = value.toLowerCase(); if (['pemerintah', 'government', 'p', 'gov'].includes(lowerValue)) { return 'Pemerintah'; } if (['non-pemerintah', 'non pemerintah', 'nonpemerintah', 'swasta', 'private', 'np', 'non'].includes(lowerValue)) { return 'Non-Pemerintah'; } return value; // Return original if no match } // Function to insert data into database async function insertDataToDatabase(data: any[]) { let imported = 0; let errorCount = 0; const errorMessages: string[] = []; console.log('=== DEBUG: Starting beasiswa data insertion process ==='); console.log(`Total data items to process: ${data.length}`); console.log('Sample data items:', data.slice(0, 3)); // First, validate all NIMs exist before processing const uniqueNims = [...new Set(data.map(item => item.nim))]; console.log(`Unique NIMs found: ${uniqueNims.length}`); console.log('Unique NIMs:', uniqueNims); const nimValidationMap = new Map(); // Batch check all NIMs for existence console.log('=== DEBUG: Starting NIM validation ==='); for (const nim of uniqueNims) { try { console.log(`Checking NIM: ${nim}`); const { data: mahasiswaData, error: checkError } = await supabase .from('mahasiswa') .select('id_mahasiswa, nama') .eq('nim', nim) .single(); if (checkError || !mahasiswaData) { console.log(`❌ NIM ${nim}: NOT FOUND in database`); console.log(`Error details:`, checkError); nimValidationMap.set(nim, { exists: false, error: 'Mahasiswa dengan NIM ini tidak ditemukan dalam database' }); } else { console.log(`✅ NIM ${nim}: FOUND - ID: ${mahasiswaData.id_mahasiswa}, Nama: ${mahasiswaData.nama}`); nimValidationMap.set(nim, { exists: true, id_mahasiswa: mahasiswaData.id_mahasiswa, nama: mahasiswaData.nama }); } } catch (error) { console.log(`❌ NIM ${nim}: ERROR during validation`); console.log(`Error details:`, error); nimValidationMap.set(nim, { exists: false, error: `Error checking NIM: ${(error as Error).message}` }); } } console.log('=== DEBUG: NIM validation results ==='); console.log('Validation map:', Object.fromEntries(nimValidationMap)); // Process each data item console.log('=== DEBUG: Starting beasiswa data processing ==='); for (const item of data) { try { console.log(`\n--- Processing beasiswa item: NIM ${item.nim} ---`); console.log('Item data:', item); const nimValidation = nimValidationMap.get(item.nim); console.log('NIM validation result:', nimValidation); if (!nimValidation || !nimValidation.exists) { errorCount++; const errorMsg = nimValidation?.error || `NIM ${item.nim}: Mahasiswa dengan NIM ini tidak ditemukan dalam database`; console.log(`❌ Skipping item - ${errorMsg}`); errorMessages.push(errorMsg); continue; } console.log(`✅ NIM ${item.nim} is valid, proceeding with beasiswa check/insert`); // Check if beasiswa already exists for this mahasiswa and nama_beasiswa console.log(`Checking existing beasiswa for mahasiswa ID: ${nimValidation.id_mahasiswa}, nama_beasiswa: ${item.nama_beasiswa}`); const { data: existingBeasiswa, error: beasiswaCheckError } = await supabase .from('beasiswa_mahasiswa') .select('id_beasiswa') .eq('id_mahasiswa', nimValidation.id_mahasiswa) .eq('nama_beasiswa', item.nama_beasiswa) .single(); if (beasiswaCheckError && beasiswaCheckError.code !== 'PGRST116') { console.log(`❌ Error checking existing beasiswa:`, beasiswaCheckError); } if (existingBeasiswa) { console.log(`📝 Updating existing beasiswa (ID: ${existingBeasiswa.id_beasiswa})`); // Update existing beasiswa const { error: updateError } = await supabase .from('beasiswa_mahasiswa') .update({ sumber_beasiswa: item.sumber_beasiswa, beasiswa_status: item.beasiswa_status, jenis_beasiswa: item.jenis_beasiswa }) .eq('id_beasiswa', existingBeasiswa.id_beasiswa); if (updateError) { errorCount++; const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal memperbarui beasiswa: ${updateError.message}`; console.log(`❌ Update failed: ${errorMsg}`); errorMessages.push(errorMsg); continue; } else { console.log(`✅ Beasiswa updated successfully`); } } else { console.log(`📝 Inserting new beasiswa for mahasiswa ID: ${nimValidation.id_mahasiswa}`); // Insert new beasiswa const { error: insertError } = await supabase .from('beasiswa_mahasiswa') .insert({ id_mahasiswa: nimValidation.id_mahasiswa, nama_beasiswa: item.nama_beasiswa, sumber_beasiswa: item.sumber_beasiswa, beasiswa_status: item.beasiswa_status, jenis_beasiswa: item.jenis_beasiswa }); if (insertError) { errorCount++; const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal menyimpan beasiswa: ${insertError.message}`; console.log(`❌ Insert failed: ${errorMsg}`); errorMessages.push(errorMsg); continue; } else { console.log(`✅ Beasiswa inserted successfully`); } } imported++; console.log(`✅ Item processed successfully. Imported count: ${imported}`); } catch (error) { console.error(`❌ Error processing record for NIM ${item.nim}:`, error); errorCount++; errorMessages.push(`NIM ${item.nim}: Terjadi kesalahan: ${(error as Error).message}`); } } console.log('=== DEBUG: Final results ==='); console.log(`Total imported: ${imported}`); console.log(`Total errors: ${errorCount}`); console.log(`Error messages:`, errorMessages); return { imported, errorCount, errorMessages }; }