1// Excel Module - SheetJS Operations 2const ExcelModule = (() => { 3 // Global Excel data 4 let workbook = null; 5 let worksheet = null; 6 let excelData = []; 7 let currentFileName = ''; 8 let currentSheetName = ''; 9 10 // Initialize 11 function init() { 12 console.log('Excel module initialized'); 13 } 14 15 // Parse uploaded Excel file 16 function parseExcelFile(file, callback) { 17 if (!file) { 18 callback({ success: false, message: 'Không có file ÄÆ°á»£c chá»n' }); 19 return; 20 } 21 22 // Check file size 23 const maxSize = 50 * 1024 * 1024; // 50MB 24 if (file.size > maxSize) { 25 callback({ 26 success: false, 27 message: `File quá lá»n (${(file.size / 1024 / 1024).toFixed(2)}MB). Giá»i hạn: 50MB` 28 }); 29 return; 30 } 31 32 // Check file extension 33 const validExtensions = ['.xlsx', '.xls', '.xlsm', '.xlsb']; 34 const fileExt = '.' + file.name.split('.').pop().toLowerCase(); 35 if (!validExtensions.includes(fileExt)) { 36 callback({ 37 success: false, 38 message: `Äá»nh dạng file không hợp lá». Chá» chấp nháºn: ${validExtensions.join(', ')}` 39 }); 40 return; 41 } 42 43 const reader = new FileReader(); 44 45 reader.onload = function(e) { 46 try { 47 const data = new Uint8Array(e.target.result); 48 workbook = XLSX.read(data, { 49 type: 'array', 50 cellDates: true, 51 cellStyles: true 52 }); 53 54 // Try to find 'Data' sheet (case insensitive) 55 currentSheetName = 'Data'; 56 if (!workbook.Sheets[currentSheetName]) { 57 currentSheetName = Object.keys(workbook.Sheets).find(name => 58 name.toLowerCase() === 'data' 59 ) || workbook.SheetNames[0]; 60 } 61 62 worksheet = workbook.Sheets[currentSheetName]; 63 64 // Convert to JSON with headers 65 excelData = XLSX.utils.sheet_to_json(worksheet, { 66 header: 1, 67 defval: '', 68 blankrows: false 69 }); 70 71 // Validate data 72 if (excelData.length <= 1) { 73 callback({ 74 success: false, 75 message: 'File Excel không có dữ liá»u hoặc chá» có header' 76 }); 77 return; 78 } 79 80 // Check if column A exists (ID column) 81 if (!excelData[0] || excelData[0].length === 0) { 82 callback({ 83 success: false, 84 message: 'File không có cá»t dữ liá»u. Vui lòng kiá»m tra Äá»nh dạng file.' 85 }); 86 return; 87 } 88 89 // Update global variables 90 window.excelData = excelData; 91 currentFileName = file.name; 92 window.currentFileName = currentFileName; 93 94 // Calculate file info 95 const fileInfo = calculateFileInfo(excelData); 96 97 // ð´ PHÃT HIá»N Và Äá»C Cá»T SCAN_STATUS NẾU Cà 98 const scanStatusInfo = detectAndReadScanStatus(excelData); 99 100 callback({ 101 success: true, 102 data: excelData, 103 sheetName: currentSheetName, 104 fileName: currentFileName, 105 fileInfo: fileInfo, 106 scanStatusInfo: scanStatusInfo // ð´ THÃM THÃNG TIN NÃY 107 }); 108 109 } catch (error) { 110 console.error('Error parsing Excel:', error); 111 callback({ 112 success: false, 113 message: `Lá»i khi Äá»c file Excel: ${error.message}` 114 }); 115 } 116 }; 117 118 reader.onerror = function() { 119 callback({ 120 success: false, 121 message: 'Lá»i khi Äá»c file. Vui lòng thá» lại.' 122 }); 123 }; 124 125 reader.readAsArrayBuffer(file); 126 } 127 128 // ð´ HÃM Má»I: Phát hiá»n và Äá»c cá»t SCAN_STATUS 129 function detectAndReadScanStatus(data) { 130 if (!data || data.length <= 1) { 131 return { 132 hasScanStatus: false, 133 scanStatusColumn: -1, 134 scannedIds: [], 135 count: 0, 136 columnName: '', 137 rawData: [] 138 }; 139 } 140 141 const headers = data[0]; 142 let scanStatusColumn = -1; 143 144 // Tìm cá»t SCAN_STATUS 145 const possibleNames = [ 146 'SCAN_STATUS', 'SCAN STATUS', 'STATUS', 147 'Trạng thái quét', 'Äã quét', 'QUET_STATUS', 148 'ScanStatus', 'scan_status', 'status', 149 'TRANG_THAI_QUET', 'Trạng thái', 'Trang thai quet' 150 ]; 151 152 // Tìm theo tên cá»t
153 for (let i = 0; i < headers.length; i++) { 154 const header = String(headers[i] || '').trim().toLowerCase(); 155 for (const name of possibleNames) { 156 if (header === name.toLowerCase()) { 157 scanStatusColumn = i; 158 break; 159 } 160 } 161 if (scanStatusColumn !== -1) break; 162 } 163 164 // Nếu không tìm thấy, kiá»m tra cá»t Y (index 24 - thứ tá»± 25) 165 if (scanStatusColumn === -1 && headers.length >= 25) { 166 scanStatusColumn = 24; // Cá»t Y 167 } 168 169 // Nếu vẫn không tìm thấy, tìm cá»t cuá»i cùng 170 if (scanStatusColumn === -1) { 171 const lastColumn = headers.length - 1; 172 // Nếu cá»t cuá»i cùng trá»ng hoặc có tên Äặc biá»t 173 const lastColHeader = String(headers[lastColumn] || '').trim().toLowerCase(); 174 if (lastColHeader === '' || possibleNames.some(name => 175 lastColHeader.includes(name.toLowerCase()))) { 176 scanStatusColumn = lastColumn; 177 } 178 } 179 180 // Nếu KHÃNG tìm thấy cá»t SCAN_STATUS 181 if (scanStatusColumn === -1) { 182 return { 183 hasScanStatus: false, 184 scanStatusColumn: -1, 185 scannedIds: [], 186 count: 0, 187 columnName: '', 188 rawData: [] 189 }; 190 } 191 192 // Äá»c dữ liá»u từ cá»t SCAN_STATUS 193 const scannedIds = []; 194 const rawStatusData = []; 195 let count = 0; 196 197 // Pattern Äá» phát hiá»n Äã quét 198 const scanPatterns = [ 199 /Äã quét/i, 200 /scanned/i, 201 /Äã scan/i, 202 /completed/i, 203 /done/i, 204 /xong/i, 205 /finished/i, 206 /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/, // dd/mm/yyyy 207 /\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/, // yyyy/mm/dd 208 /\d{1,2}:\d{1,2}(:\d{1,2})?/, // hh:mm:ss 209 /\d{1,2}\s*[\/\-]\s*\d{1,2}\s*[\/\-]\s*\d{2,4}/, // dd / mm / yyyy 210 ]; 211 212 for (let i = 1; i < data.length; i++) { 213 const row = data[i]; 214 if (row && row.length > scanStatusColumn) { 215 const status = String(row[scanStatusColumn] || '').trim(); 216 const id = row[0] ? String(row[0]).trim() : null; 217 218 rawStatusData.push({ 219 rowIndex: i, 220 id: id, 221 status: status, 222 hasValue: status !== '',
223 isScanned: false 224 }); 225 226 // Kiá»m tra nếu Äã quét 227 let isScanned = false; 228 229 if (status !== '') { 230 // Kiá»m tra các pattern 231 for (const pattern of scanPatterns) { 232 if (pattern.test(status)) { 233 isScanned = true; 234 break; 235 } 236 } 237 238 // Hoặc nếu có từ "Äã" á» Äầu 239 if (status.toLowerCase().startsWith('Äã')) { 240 isScanned = true; 241 } 242 243 // Hoặc nếu có từ "Scanned" 244 if (status.toLowerCase().includes('scanned')) { 245 isScanned = true; 246 } 247 248 // Hoặc nếu Äá» dà i > 3 ký tá»± và không phải là "no", "null", "false" 249 const lowerStatus = status.toLowerCase(); 250 const notScannedWords = ['no', 'null', 'false', '0', 'không', 'chưa', '']; 251 if (status.length > 3 && !notScannedWords.includes(lowerStatus)) { 252 // Nếu có chứa sá» (có thá» là timestamp) 253 if (/\d/.test(status)) { 254 isScanned = true; 255 } 256 } 257 } 258 259 if (isScanned && id && id !== '') { 260 scannedIds.push(id); 261 count++; 262 rawStatusData[rawStatusData.length - 1].isScanned = true; 263 } 264 } 265 } 266 267 return { 268 hasScanStatus: true, 269 scanStatusColumn: scanStatusColumn, 270 scannedIds: scannedIds, 271 count: count, 272 columnName: headers[scanStatusColumn] || `Cá»t ${String.fromCharCode(65 + scanStatusColumn)}`, 273 rawData: rawStatusData, 274 totalRows: data.length - 1 275 }; 276 } 277 278 // Calculate file information 279 function calculateFileInfo(data) { 280 if (!data || data.length === 0) { 281 return null; 282 } 283 284 const totalRows = data.length - 1; 285 const totalColumns = data[0] ? data[0].length : 0; 286 287 // Estimate size 288 const estimatedSize = JSON.stringify(data).length; 289 const sizeInKB = (estimatedSize / 1024).toFixed(2); 290 const sizeInMB = (estimatedSize / 1024 / 1024).toFixed(2); 291 292 // Check if file is suitable for auto-save 293 let saveSuitability = 'good'; 294 let recommendation = ''; 295 296 if (totalRows > 50000) { 297 saveSuitability = 'poor'; 298 recommendation = 'File rất lá»n (>50,000 dòng). Không nên lưu tá»± Äá»ng tiến trình.'; 299 } else if (totalRows > 10000) { 300 saveSuitability = 'fair'; 301 recommendation = 'File lá»n (>10,000 dòng). Chế Äá» lưu nhẹ ÄÆ°á»£c kÃch hoạt.'; 302 } else { 303 saveSuitability = 'good'; 304 recommendation = 'File phù hợp. Tá»± Äá»ng lưu tiến trình Äầy Äá»§.'; 305 } 306 307 // Check for required columns 308 const headers = data[0] || []; 309 const hasIdColumn = headers.length > 0; 310 const idColumnName = headers[0] || 'Column A'; 311 312 return { 313 totalRows, 314 totalColumns, 315 sizeInKB, 316 sizeInMB, 317 saveSuitability, 318 recommendation, 319 hasIdColumn, 320 idColumnName, 321 headers: headers.slice(0, 10) 322 }; 323 } 324 325 // Find row by ID 326 function findRowById(id) { 327 if (!excelData || excelData.length === 0) { 328 return null; 329 } 330 331 // ID is in column A (index 0) 332 for (let i = 1; i < excelData.length; i++) { 333 const row = excelData[i]; 334 if (row && row[0] && row[0].toString().trim() === id.toString().trim()) { 335 return { 336 rowIndex: i, 337 rowData: row, 338 headers: excelData[0] 339 }; 340 } 341 } 342 343 return null; 344 } 345 346 // Update scan status in Excel data 347 function updateScanStatus(rowIndex, timestamp = null) { 348 if (!excelData || rowIndex < 1 || rowIndex >= excelData.length) { 349 return false;
350 } 351 352 try { 353 const headers = excelData[0]; 354 let statusColIndex = -1; 355 356 // Find SCAN_STATUS column 357 for (let i = 0; i < headers.length; i++) { 358 if (headers[i] === 'SCAN_STATUS' || headers[i] === 'Trạng thái quét') { 359 statusColIndex = i; 360 break; 361 } 362 } 363 364 // If column doesn't exist, add it 365 if (statusColIndex === -1) { 366 statusColIndex = headers.length; 367 headers[statusColIndex] = 'SCAN_STATUS'; 368 369 // Initialize for all rows 370 for (let i = 1; i < excelData.length; i++) { 371 if (!excelData[i][statusColIndex]) { 372 excelData[i][statusColIndex] = ''; 373 } 374 } 375 } 376 377 // Update status with timestamp 378 const scanTime = timestamp || new Date(); 379 const formattedTime = scanTime.toLocaleString('vi-VN'); 380 excelData[rowIndex][statusColIndex] = `Äã quét: ${formattedTime}`; 381 382 // Mark as changed 383 window.hasUnsavedChanges = true; 384 385 return true; 386 387 } catch (error) { 388 console.error('Error updating scan status:', error); 389 return false; 390 } 391 } 392 393 // Export updated Excel file 394 function exportExcel(includeAllSheets = false) { 395 if (!workbook || !excelData || excelData.length === 0) { 396 return { success: false, message: 'Không có dữ liá»u Äá» xuất' }; 397 } 398 399 try { 400 // Update worksheet with current data 401 const newWorksheet = XLSX.utils.aoa_to_sheet(excelData); 402 403 // Update workbook 404 workbook.Sheets[currentSheetName] = newWorksheet; 405 406 // Generate Excel file 407 const excelBuffer = XLSX.write(workbook, { 408 bookType: 'xlsx', 409 type: 'array', 410 bookSST: false 411 }); 412 413 // Create blob 414 const blob = new Blob([excelBuffer], { 415 type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 416 }); 417 418 // Create filename 419 const now = new Date(); 420 const dateStr = now.toISOString().slice(0, 10).replace(/-/g, ''); 421 const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, ''); 422 const originalName = currentFileName.replace(/\.[^/.]+$/, ""); 423 const exportName = `${originalName}_scanned_${dateStr}_${timeStr}.xlsx`; 424 425 return { 426 success: true, 427 blob: blob, 428 fileName: exportName, 429 size: (excelBuffer.length / 1024 / 1024).toFixed(2) + ' MB', 430 scannedCount: window.scannedIds ? window.scannedIds.size : 0 431 }; 432 433 } catch (error) { 434 console.error('Error exporting Excel:', error); 435 return { 436 success: false, 437 message: `Lá»i khi xuất file: ${error.message}` 438 }; 439 } 440 } 441 442 // Create sample data for testing 443 function createSampleData() { 444 const sampleData = [ 445 ["index", "sheet", "length", "width", "cutting-length", "cutting-width", "thickness", "grain", "material", "Code N.1", "Code N.2", "Code N.3", "label", "SCAN_STATUS"], 446 [8, 1, 2000, 350, 1998, 348, 17, "theo cạnh dà i", "17mm_101SH", "Tầng 2", "phòng ngá»§ 1", "ká» sách", "Hông Phải", "Äã quét: 15/01/2024 14:30"], 447 [39, 3, 766, 307, 766, 306, 17, "theo cạnh dà i", "17mm_101SH", "Tầng 1", "phòng ngá»§", "tá»§ kÃn", "Ká»/ Äợt Tầng Trên", ""], 448 [42, 1, 1200, 450, 1198, 448, 18, "theo cạnh ngắn", "18mm_202SH", "Tầng 3", "phòng khách", "tá»§ TV", "Mặt Trưá»c", "Äã quét: 16/01/2024 10:15"], 449 [15, 2, 800, 600, 798, 598, 15, "theo cạnh dà i", "15mm_303SH", "Tầng 1", "nhà bá
449º¿p", "tá»§ chén", "Cánh Trái", ""], 450 [27, 1, 1500, 400, 1498, 398, 16, "theo cạnh dà i", "16mm_404SH", "Tầng 2", "phòng là m viá»c", "bà n là m viá»c", "Mặt Bà n", "Scanned: 2024-01-14"] 451 ]; 452 453 excelData = sampleData; 454 window.excelData = excelData; 455 currentFileName = 'sample_data_with_status.xlsx'; 456 window.currentFileName = currentFileName; 457 458 return sampleData; 459 } 460 461 // Get current Excel data 462 function getData() { 463 return excelData; 464 } 465 466 // Set Excel data (for restoration) 467 function setData(data, fileName = '') { 468 excelData = data; 469 window.excelData = data; 470 currentFileName = fileName || currentFileName; 471 window.currentFileName = currentFileName; 472 473 // Recreate workbook from data 474 if (data && data.length > 0) { 475 worksheet = XLSX.utils.aoa_to_sheet(data); 476 workbook = XLSX.utils.book_new(); 477 currentSheetName = 'Data'; 478 XLSX.utils.book_append_sheet(workbook, worksheet, currentSheetName); 479 } 480 } 481 482 // Public API 483 return { 484 init, 485 parseExcelFile, 486 calculateFileInfo, 487 detectAndReadScanStatus, // ð´ THÃM HÃM NÃY 488 findRowById, 489 updateScanStatus, 490 exportExcel, 491 createSampleData, 492 getData, 493 setData, 494 getFileName: () => currentFileName 495 }; 496})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.