import * as XLSX from "xlsx"; export interface ExcelPreviewData { fileName: string; fileSize: number; sheetNames: string[]; headers: string[]; rows: Array>; totalRows: number; previewRows: number; } export interface ParseOptions { maxPreviewRows?: number; sheetIndex?: number; } const DEFAULT_CONFIG = { MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB MAX_PREVIEW_ROWS: 0, // 0 = ไม่จำกัด แสดงทั้งหมด ALLOWED_EXTENSIONS: [".xlsx", ".xls"], }; function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; } function getFileExtension(fileName: string): string { return fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); } function validateFile(file: File): { message: string } | null { const extension = getFileExtension(file.name); if (!DEFAULT_CONFIG.ALLOWED_EXTENSIONS.includes(extension)) { return { message: `กรุณาเลือกไฟล์ Excel เท่านั้น (${DEFAULT_CONFIG.ALLOWED_EXTENSIONS.join(", ")})`, }; } if (file.size > DEFAULT_CONFIG.MAX_FILE_SIZE) { const fileSizeMB = (file.size / (1024 * 1024)).toFixed(2); const maxSizeMB = (DEFAULT_CONFIG.MAX_FILE_SIZE / (1024 * 1024)).toFixed(2); return { message: `ขนาดไฟล์เกิน ${maxSizeMB}MB (ไฟล์ของคุณ: ${fileSizeMB}MB)`, }; } return null; } export async function parseExcelFile( file: File, options: ParseOptions = {} ): Promise { const maxPreviewRows = options.maxPreviewRows ?? DEFAULT_CONFIG.MAX_PREVIEW_ROWS; const sheetIndex = options.sheetIndex ?? 0; // Validate file const validationError = validateFile(file); if (validationError) { throw new Error(validationError.message); } return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const data = e.target?.result; if (!data) { reject(new Error("ไม่สามารถอ่านไฟล์ได้")); return; } const workbook = XLSX.read(data, { type: "binary" }); if (workbook.SheetNames.length === 0) { reject(new Error("ไฟล์ไม่มีข้อมูล")); return; } const sheetName = workbook.SheetNames[sheetIndex]; const worksheet = workbook.Sheets[sheetName]; // ใช้ sheet_to_json กับ raw: true เพื่ออ่าน raw values ก่อน const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", raw: true, // อ่าน raw values }) as any[][]; // หลังจากได้ raw data แล้ว ต้องไปอ่านค่าจาก formula ที่ cells โดยตรง // เพราะบาง formula อาจจะยังไม่ถูกคำนวณ const range = XLSX.utils.decode_range(worksheet["!ref"] || "A1"); // อ่านค่าจาก cells ที่มี formula (column สุดท้าย) for (let row = range.s.r; row <= range.e.r; row++) { for (let col = range.s.c; col <= range.e.c; col++) { const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); const cell = worksheet[cellAddress]; // ถ้ามี formula ให้ใช้ผลลัพธ์ที่คำนวณแล้ว if (cell && cell.f && !cell.v) { // ลองใช้ cell.w (formatted value) ถ้ามี if (cell.w) { // แปลง array index ให้ถูกต้อง (row + 1 เพราะมี header row) const arrayRowIndex = row - range.s.r; if (jsonData[arrayRowIndex] && jsonData[arrayRowIndex][col] !== undefined) { jsonData[arrayRowIndex][col] = cell.w; } } } } } if (jsonData.length === 0) { reject(new Error("ไฟล์ไม่มีข้อมูล")); return; } // Extract headers from first row (ภาษาไทย) const headers = jsonData[0].map((h: any) => String(h ?? "")); // Extract data rows (skip header row) const dataRows = jsonData.slice(1).filter((row) => row.some((cell) => cell !== "")); if (dataRows.length === 0) { reject(new Error("ไฟล์ไม่มีข้อมูล")); return; } // Convert to array of objects - ใช้ header จาก Excel เป็น field names โดยตรง // ถ้า maxPreviewRows = 0 จะแสดงทั้งหมด มิฉะนั้นจะแสดงตามจำนวนที่กำหนด const rowsToProcess = maxPreviewRows === 0 ? dataRows : dataRows.slice(0, maxPreviewRows); const rows = rowsToProcess.map((row, rowIndex) => { const obj: Record = { id: `row_${rowIndex}`, }; // ใช้ header เป็น field names โดยตรง headers.forEach((header, index) => { const cellValue = row[index] ?? ""; // ใช้ค่าจาก Excel โดยตรง ไม่แปลงค่า obj[header] = cellValue; }); return obj; }); resolve({ fileName: file.name, fileSize: file.size, sheetNames: workbook.SheetNames, headers, rows, totalRows: dataRows.length, previewRows: rows.length, }); } catch (error) { reject(new Error("ไม่สามารถอ่านไฟล์ Excel ได้ กรุณาตรวจสอบไฟล์อีกครั้ง")); } }; reader.onerror = () => { reject(new Error("ไม่สามารถอ่านไฟล์ได้")); }; reader.readAsBinaryString(file); }); } export { formatFileSize };