/** * SlipVerifier.js * ระบบหลักที่ประสานงานทุกส่วน * จัดการการตรวจสอบสลิปการโอนเงินแบบครบวงจร */ class SlipVerifier { constructor(debug = false) { this.ocrProcessor = new OCRProcessor(debug); this.slipParser = new SlipParser(); this.qrScanner = new QRScanner(debug); this.qrCropSelector = null; // จะสร้างเมื่อมีรูปภาพ this.imagePreviewManager = new ImagePreviewManager(debug); // เพิ่ม Image Preview Manager this.isProcessing = false; this.currentCamera = null; this.currentQRData = null; // เก็บข้อมูล QR สำหรับการยืนยัน this.currentSlipData = null; // เก็บข้อมูลสลิปปัจจุบัน this.qrResult = null; // เก็บผลการสแกน QR แบบละเอียด this.currentQRMode = 'camera'; // camera หรือ image this.selectedBankType = 'auto'; // ธนาคารที่เลือก this.qrImageFile = null; // ไฟล์รูปภาพ QR this.currentImageFile = null; // ไฟล์รูปภาพปัจจุบัน this.currentImageDataUrl = null; // Data URL ของรูปภาพปัจจุบัน this.currentCropArea = null; // พื้นที่ crop ปัจจุบัน this.debug = debug; this.init(); } /** * เปิด/ปิด debug mode */ setDebug(enabled) { this.debug = enabled; this.ocrProcessor.setDebug(enabled); this.qrScanner.setDebug(enabled); this.imagePreviewManager.debug = enabled; } /** * เริ่มต้นระบบ */ init() { this.bindEvents(); this.showSection('upload'); // ให้ bindQREvents ทำงานหลังจาก DOM พร้อมเสมอ setTimeout(() => { this.bindQREvents(); }, 100); console.log('Slip Verifier initialized'); } /** * ผูก Event Listeners */ bindEvents() { // Upload events const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); const fileBtn = document.getElementById('file-btn'); const cameraBtn = document.getElementById('camera-btn'); const qrBtn = document.getElementById('qr-btn'); // Drag & Drop uploadArea.addEventListener('dragover', this.handleDragOver.bind(this)); uploadArea.addEventListener('dragleave', this.handleDragLeave.bind(this)); uploadArea.addEventListener('drop', this.handleDrop.bind(this)); uploadArea.addEventListener('click', () => fileInput.click()); // File selection fileInput.addEventListener('change', this.handleFileSelect.bind(this)); fileBtn.addEventListener('click', (e) => { e.stopPropagation(); fileInput.click(); }); // Camera cameraBtn.addEventListener('click', (e) => { e.stopPropagation(); this.startCamera(); }); // QR Scanner qrBtn.addEventListener('click', (e) => { e.stopPropagation(); this.startQRScanner(); }); // Camera controls const captureBtn = document.getElementById('capture-btn'); const cameraCancelBtn = document.getElementById('camera-cancel-btn'); if (captureBtn) { captureBtn.addEventListener('click', this.capturePhoto.bind(this)); } if (cameraCancelBtn) { cameraCancelBtn.addEventListener('click', this.stopCamera.bind(this)); } // QR Scanner controls const qrCancelBtn = document.getElementById('qr-cancel-btn'); const qrScanAgainBtn = document.getElementById('qr-scan-again-btn'); const qrCopyDataBtn = document.getElementById('qr-copy-data-btn'); if (qrCancelBtn) { qrCancelBtn.addEventListener('click', this.stopQRScanner.bind(this)); } if (qrScanAgainBtn) { qrScanAgainBtn.addEventListener('click', this.restartQRScan.bind(this)); } if (qrCopyDataBtn) { qrCopyDataBtn.addEventListener('click', this.copyQRData.bind(this)); } // Result actions const copyDataBtn = document.getElementById('copy-data-btn'); if (copyDataBtn) { copyDataBtn.addEventListener('click', this.copySlipData.bind(this)); } // เพิ่ม event listener สำหรับปุ่มคัดลอกข้อมูลดิบ const copyRawBtn = document.getElementById('copy-raw-btn'); if (copyRawBtn) { copyRawBtn.addEventListener('click', this.copyRawData.bind(this)); } const resetBtn = document.getElementById('reset-btn'); if (resetBtn) { resetBtn.addEventListener('click', this.reset.bind(this)); } const errorRetryBtn = document.getElementById('error-retry-btn'); if (errorRetryBtn) { errorRetryBtn.addEventListener('click', this.reset.bind(this)); } // เพิ่ม toggle สำหรับ raw data const toggleRawData = document.getElementById('toggle-raw-data'); if (toggleRawData) { toggleRawData.addEventListener('click', this.toggleRawDataVisibility.bind(this)); } } /** * ผูก Event Listeners สำหรับระบบ QR */ bindQREvents() { console.log('🔗 Binding QR Events (checking elements...)'); // QR mode buttons (ถ้ามี) const qrCameraModeBtn = document.getElementById('qr-camera-mode-btn'); const qrImageModeBtn = document.getElementById('qr-image-mode-btn'); if (qrCameraModeBtn) { qrCameraModeBtn.addEventListener('click', () => this.switchQRMode('camera')); console.log('✅ QR camera mode button bound'); } if (qrImageModeBtn) { qrImageModeBtn.addEventListener('click', () => this.switchQRMode('image')); console.log('✅ QR image mode button bound'); } // QR file input (ถ้ามี) const qrFileInput = document.getElementById('qr-file-input'); const qrUploadArea = document.getElementById('qr-upload-area'); if (qrFileInput && qrUploadArea) { qrUploadArea.addEventListener('click', () => qrFileInput.click()); qrFileInput.addEventListener('change', this.handleQRImageSelect.bind(this)); // Drag & Drop for QR upload if (this.handleQRDragOver && this.handleQRDragLeave && this.handleQRDrop) { qrUploadArea.addEventListener('dragover', this.handleQRDragOver.bind(this)); qrUploadArea.addEventListener('dragleave', this.handleQRDragLeave.bind(this)); qrUploadArea.addEventListener('drop', this.handleQRDrop.bind(this)); } console.log('✅ QR file upload events bound'); } // QR crop controls (ถ้ามี) const qrScanSelectedBtn = document.getElementById('qr-scan-selected-btn'); const qrResetSelectionBtn = document.getElementById('qr-reset-selection-btn'); const qrSavePositionBtn = document.getElementById('qr-save-position-btn'); if (qrScanSelectedBtn && this.scanSelectedQRArea) { qrScanSelectedBtn.addEventListener('click', this.scanSelectedQRArea.bind(this)); console.log('✅ QR scan selected button bound'); } if (qrResetSelectionBtn && this.resetQRSelection) { qrResetSelectionBtn.addEventListener('click', this.resetQRSelection.bind(this)); console.log('✅ QR reset selection button bound'); } if (qrSavePositionBtn && this.saveQRPosition) { qrSavePositionBtn.addEventListener('click', this.saveQRPosition.bind(this)); console.log('✅ QR save position button bound'); } console.log('📋 QR Events binding completed'); } /** * จัดการ Drag Over */ handleDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('drag-over'); } /** * จัดการ Drag Leave */ handleDragLeave(e) { e.currentTarget.classList.remove('drag-over'); } /** * จัดการ Drop ไฟล์ */ async handleDrop(event) { event.preventDefault(); event.currentTarget.classList.remove('drag-over'); const files = event.dataTransfer.files; if (files.length > 0) { const file = files[0]; if (!this.validateFile(file)) return; this.currentImageFile = file; try { // สร้าง data URL สำหรับแสดงตัวอย่าง const dataUrl = await this.fileToDataUrl(file); this.currentImageDataUrl = dataUrl; // แสดงหน้า preview this.imagePreviewManager.showPreview(file, dataUrl); } catch (error) { console.error('Error processing dropped file:', error); this.showError('ไม่สามารถอ่านไฟล์ได้'); } } } /** * จัดการเลือกไฟล์ */ async handleFileSelect(event) { const file = event.target.files[0]; if (!file) return; if (!this.validateFile(file)) return; this.currentImageFile = file; try { // สร้าง data URL สำหรับแสดงตัวอย่าง const dataUrl = await this.fileToDataUrl(file); this.currentImageDataUrl = dataUrl; // แสดงหน้า preview this.imagePreviewManager.showPreview(file, dataUrl); } catch (error) { console.error('Error processing file:', error); this.showError('ไม่สามารถอ่านไฟล์ได้'); } } /** * แปลงไฟล์เป็น Data URL */ fileToDataUrl(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); }); } /** * แปลง Blob เป็น Data URL */ blobToDataUrl(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } /** * ประมวลผลรูปภาพพร้อม crop area */ async processImageWithCrop(imageFile, imageDataUrl, cropArea, selectedBank) { if (this.isProcessing) { this.showNotification('กำลังประมวลผลอยู่', 'warning'); return; } this.isProcessing = true; this.currentImageFile = imageFile; this.currentImageDataUrl = imageDataUrl; this.currentCropArea = cropArea; this.selectedBankType = selectedBank; try { this.showSection('processing'); // Debug: ตรวจสอบ function binding console.log('🔧 Checking updateProcessingStatus:', typeof this.updateProcessingStatus); if (typeof this.updateProcessingStatus !== 'function') { throw new Error('updateProcessingStatus is not a function'); } this.updateProcessingStatus('กำลังเตรียมรูปภาพ...', 10); // แสดงรูปภาพใน processing section const processingImage = document.getElementById('processing-image'); if (processingImage) { processingImage.src = imageDataUrl; } // ขั้นตอนที่ 1: ประมวลผล OCR จากรูปภาพเต็ม this.updateProcessingStatus('กำลังอ่านข้อความจากสลิป...', 20); const ocrResult = await this.ocrProcessor.processImage(imageFile); console.log('📝 OCR processing completed'); // ขั้นตอนที่ 2: ประมวลผล QR จากพื้นที่ที่เลือก (ถ้ามี) this.updateProcessingStatus('กำลังสแกน QR Code...', 50); let qrResult = null; let qrCroppedImageDataUrl = null; if (cropArea) { console.log('� Processing QR from crop area:', cropArea); // ตัดรูปเฉพาะพื้นที่ QR ที่เลือก const qrCroppedBlob = await this.cropImage(imageDataUrl, cropArea); qrCroppedImageDataUrl = await this.blobToDataUrl(qrCroppedBlob); console.log('✂️ QR area cropped successfully'); // อ่าน QR จากรูปที่ตัดแล้ว qrResult = await this.qrScanner.scanImageBlob(qrCroppedBlob); if (qrResult && qrResult.data) { console.log('✅ QR Code detected from cropped area'); } else { console.log('⚠️ No QR Code found in cropped area'); } } else { console.log('ℹ️ No crop area selected, skipping QR scan'); } // ขั้นตอนที่ 3: วิเคราะห์และรวมข้อมูล this.updateProcessingStatus('กำลังวิเคราะห์ข้อมูล...', 80); await this.processResults(ocrResult, qrResult, qrCroppedImageDataUrl, cropArea ? true : false); this.updateProcessingStatus('เสร็จสิ้น', 100); } catch (error) { console.error('Processing error:', error); this.showError(error.message || 'เกิดข้อผิดพลาดในการประมวลผล'); } finally { this.isProcessing = false; } } /** * ตัดรูปภาพตาม crop area */ async cropImage(imageDataUrl, cropArea) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = cropArea.width; canvas.height = cropArea.height; ctx.drawImage( img, cropArea.x, cropArea.y, cropArea.width, cropArea.height, 0, 0, cropArea.width, cropArea.height ); canvas.toBlob(resolve, 'image/jpeg', 0.9); }; img.src = imageDataUrl; }); } /** * เริ่มต้นกล้อง */ async startCamera() { console.log('📷 Starting camera...'); try { const stream = await navigator.mediaDevices.getUserMedia({ video: {facingMode: 'environment'} }); const video = document.getElementById('camera-video'); if (video) { video.srcObject = stream; this.currentCamera = stream; this.showSection('camera'); this.showToast('กล้องเริ่มทำงานแล้ว', 'success'); } } catch (error) { console.error('Camera error:', error); this.showToast('ไม่สามารถเปิดกล้องได้', 'error'); } } /** * หยุดกล้อง */ stopCamera() { if (this.currentCamera) { this.currentCamera.getTracks().forEach(track => track.stop()); this.currentCamera = null; } const video = document.getElementById('camera-video'); if (video) { video.srcObject = null; } // กลับไปหน้าหลัก this.showSection('upload'); console.log('📷 Camera stopped and returned to upload'); } /** * ถ่ายรูป */ capturePhoto() { const video = document.getElementById('camera-video'); const canvas = document.getElementById('camera-canvas'); if (!video || !canvas) { this.showToast('ไม่พบกล้องหรือ canvas', 'error'); return; } const ctx = canvas.getContext('2d'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0); // แปลง canvas เป็น blob canvas.toBlob(async (blob) => { if (blob) { this.stopCamera(); // สร้าง File object จาก blob const file = new File([blob], 'camera-capture.jpg', {type: 'image/jpeg'}); const dataUrl = await this.fileToDataUrl(file); // แสดงหน้า preview this.imagePreviewManager.showPreview(file, dataUrl); } }, 'image/jpeg', 0.9); } /** * เริ่มต้น QR Scanner */ async startQRScanner() { console.log('🔍 Starting QR Scanner...'); this.showSection('qr'); try { // เริ่ม QR Camera const video = document.getElementById('qr-video'); const canvas = document.getElementById('qr-canvas'); if (!video || !canvas) { throw new Error('ไม่พบ video หรือ canvas element'); } // ขอสิทธิ์เข้าถึงกล้อง const stream = await navigator.mediaDevices.getUserMedia({ video: {facingMode: 'environment'} }); video.srcObject = stream; this.qrCameraStream = stream; // เริ่มสแกน QR this.startQRScanLoop(video, canvas); this.showNotification('เริ่มสแกน QR Code แล้ว', 'success'); } catch (error) { console.error('QR Scanner error:', error); this.showNotification('ไม่สามารถเปิดกล้องได้', 'error'); } } /** * เริ่ม loop การสแกน QR */ startQRScanLoop(video, canvas) { const scanQR = () => { if (video.readyState === video.HAVE_ENOUGH_DATA) { const ctx = canvas.getContext('2d'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { this.handleQRDetected(code); return; // หยุดการสแกนเมื่อพบ QR } } // ต่อการสแกน this.qrScanTimer = requestAnimationFrame(scanQR); }; scanQR(); } /** * จัดการเมื่อตรวจพบ QR Code */ handleQRDetected(qrCode) { console.log('📱 QR Code detected:', qrCode.data); // หยุดการสแกน if (this.qrScanTimer) { cancelAnimationFrame(this.qrScanTimer); this.qrScanTimer = null; } // แสดงผลลัพธ์ this.displayQRResults(qrCode); this.showNotification('พบ QR Code แล้ว!', 'success'); } /** * แสดงผลลัพธ์ QR */ displayQRResults(qrCode) { // แสดงข้อมูลดิบ const rawDisplay = document.getElementById('qr-raw-display'); if (rawDisplay) { rawDisplay.value = qrCode.data; } // ถอดรหัสข้อมูล const parsedData = this.parseQRData(qrCode.data); this.displayParsedQRData(parsedData); // แสดงส่วนผลลัพธ์ const resultsSection = document.getElementById('qr-results'); if (resultsSection) { resultsSection.style.display = 'block'; } // เก็บข้อมูลสำหรับการคัดลอก this.currentQRData = { raw: qrCode.data, parsed: parsedData }; } /** * แสดงข้อมูล QR ที่ถอดรหัสแล้ว */ displayParsedQRData(data) { const elements = { 'qr-amount-display': data.amount || '-', 'qr-datetime-display': data.datetime || '-', 'qr-sender-display': data.sender || '-', 'qr-receiver-display': data.receiver || '-', 'qr-reference-display': data.reference || '-' }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); } /** * แสดงข้อมูลดิบ QR Code */ parseQRData(rawData) { try { // แสดงข้อมูลดิบ QR Code เท่านั้น return { raw_data: rawData, type: 'raw', message: 'ข้อมูลดิบ QR Code', length: rawData ? rawData.length : 0, preview: rawData && rawData.length > 100 ? rawData.substring(0, 100) + '...' : rawData, success: !!(rawData && rawData.length > 0) }; } catch (error) { console.warn('Failed to parse QR data:', error); return { raw_data: null, type: 'error', message: 'ไม่สามารถอ่าน QR Code ได้', success: false }; } } /** * เริ่มสแกน QR ใหม่ */ restartQRScan() { // ซ่อนผลลัพธ์ const resultsSection = document.getElementById('qr-results'); if (resultsSection) { resultsSection.style.display = 'none'; } // เริ่มสแกนใหม่ const video = document.getElementById('qr-video'); const canvas = document.getElementById('qr-canvas'); if (video && canvas) { this.startQRScanLoop(video, canvas); } this.showNotification('เริ่มสแกนใหม่', 'info'); } /** * หยุด QR Scanner */ stopQRScanner() { // หยุดการสแกน if (this.qrScanTimer) { cancelAnimationFrame(this.qrScanTimer); this.qrScanTimer = null; } // หยุดกล้อง if (this.qrCameraStream) { this.qrCameraStream.getTracks().forEach(track => track.stop()); this.qrCameraStream = null; } // กลับไปหน้าหลัก this.showSection('upload'); this.showNotification('หยุด QR Scanner แล้ว', 'info'); } /** * คัดลอกข้อมูล QR */ async copyQRData() { if (!this.currentQRData) { this.showNotification('ไม่มีข้อมูล QR ให้คัดลอก', 'warning'); return; } const data = this.currentQRData.parsed; const text = `จำนวนเงิน: ${data.amount} วันที่-เวลา: ${data.datetime} ผู้โอน: ${data.sender} ผู้รับ: ${data.receiver} รหัสอ้างอิง: ${data.reference} ข้อมูลดิบ: ${this.currentQRData.raw}`; try { await navigator.clipboard.writeText(text); this.showNotification('คัดลอกข้อมูล QR แล้ว', 'success'); } catch (error) { console.error('Copy failed:', error); this.showNotification('ไม่สามารถคัดลอกได้', 'error'); } } /** * จัดการเมื่อตรวจพบ QR Code */ handleQRDetected(result) { this.stopQRScanner(); this.showToast('ตรวจพบ QR Code แล้ว', 'success'); // เก็บผลการสแกน QR this.qrResult = result; this.currentQRData = result.rawData; if (this.debug) { console.log('=== QR Detection Result ==='); console.log('Raw Data:', result.rawData); console.log('Parsed Data:', result.parsedData); console.log('Type:', result.parsedData?.type); console.log('Is Payment Slip:', result.parsedData?.isPaymentSlip); console.log('============================'); } // ประมวลผลข้อมูล QR this.processQRData(result.rawData); } /** * จัดการข้อผิดพลาด QR */ handleQRError(errorMessage) { this.stopQRScanner(); this.showToast(errorMessage, 'error'); } /** * ประมวลผลข้อมูล QR */ async processQRData(qrData) { console.log('🔍 Processing QR Data:', qrData); try { // แสดงข้อมูลดิบ QR Code เท่านั้น const qrResult = { raw_data: qrData, type: 'raw', message: 'ข้อมูลดิบ QR Code', length: qrData ? qrData.length : 0, preview: qrData && qrData.length > 100 ? qrData.substring(0, 100) + '...' : qrData, success: !!(qrData && qrData.length > 0) }; console.log('QR Raw Data:', qrResult); if (qrResult.success) { this.currentQRData = qrResult; this.showToast('อ่าน QR Code สำเร็จ!', 'success'); // ไปหน้าผลลัพธ์ this.showQRResults(qrResult, qrData); } else { this.showToast('ไม่สามารถอ่าน QR Code ได้', 'error'); console.log('Raw QR data:', qrData); } } catch (error) { console.error('QR processing error:', error); this.showToast('เกิดข้อผิดพลาดในการประมวลผล QR', 'error'); } } /** * แสดงผลลัพธ์ QR */ showQRResults(parsedData, rawData) { console.log('📊 Showing QR Results'); // แสดงหน้าผลลัพธ์ this.showSection('results'); // ตั้งสถานะเป็น verified const verificationStatus = document.getElementById('verification-status'); const verificationText = document.getElementById('verification-text'); if (verificationStatus && verificationText) { verificationStatus.className = 'verification-status verified'; verificationText.textContent = 'ตรวจสอบด้วย QR แล้ว'; } // แสดงข้อมูลสลิป this.displaySlipData(parsedData); // แสดงข้อมูล QR this.displayQRData(parsedData, rawData); } /** * แสดงข้อมูลสลิป (เฉพาะข้อมูลจาก OCR เท่านั้น) */ displaySlipData(data) { // ถ้าเป็นข้อมูลจาก QR ให้แสดงว่าไม่มีข้อมูลสลิป if (data.type === 'raw') { const elements = { 'slip-amount': '-', 'slip-datetime': '-', 'slip-sender': '-', 'slip-receiver': '-', 'slip-reference': '-' }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); return; } // แสดงข้อมูลสลิปปกติ (จาก OCR) const elements = { 'slip-amount': data.amount || '-', 'slip-datetime': data.datetime || '-', 'slip-sender': data.sender || '-', 'slip-receiver': data.receiver || '-', 'slip-reference': data.reference || '-' }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); } /** * แสดงข้อมูล QR Code (ข้อมูลดิบเท่านั้น) */ displayQRData(parsedData, rawData) { // แสดงส่วน QR verification const qrVerification = document.getElementById('qr-verification'); if (qrVerification) { qrVerification.style.display = 'block'; } // แสดงข้อมูลดิบของ QR Code const qrRawData = parsedData.raw_data || rawData || ''; const qrElements = { 'qr-amount': `QR Code ความยาว: ${qrRawData.length} อักขระ`, 'qr-datetime': parsedData.message || 'ข้อมูลดิบ QR Code', 'qr-sender': `ประเภท: ${parsedData.type || 'raw'}`, 'qr-receiver': `สถานะ: ${parsedData.success ? 'อ่านสำเร็จ' : 'อ่านไม่สำเร็จ'}`, 'qr-reference': parsedData.preview || '-', 'qr-raw-text': qrRawData }; Object.entries(qrElements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); // แสดงข้อมูล statistics const stats = { 'ocr-confidence': 'N/A (QR only)', 'processing-time': '< 1 วินาที', 'thai-chars': 'N/A', 'qr-status': parsedData.success ? 'อ่าน QR Code สำเร็จ ✓' : 'ไม่สามารถอ่าน QR Code ได้ ✗', 'qr-match-score': parsedData.success ? '100%' : '0%' }; Object.entries(stats).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); } /** * ตรวจสอบไฟล์ */ validateFile(file) { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; const maxSize = 5 * 1024 * 1024; // 5MB if (!allowedTypes.includes(file.type)) { this.showNotification('รองรับเฉพาะไฟล์ JPG, PNG, WEBP เท่านั้น', 'error'); return false; } if (file.size > maxSize) { this.showNotification('ขนาดไฟล์เกิน 5MB', 'error'); return false; } return true; } /** * แสดงพรีวิวรูปภาพ */ showImagePreview(file) { const img = document.getElementById('processing-image'); const reader = new FileReader(); reader.onload = (e) => { img.src = e.target.result; }; reader.readAsDataURL(file); } /** * ประมวลผลไฟล์รูปภาพ */ async processFile(file) { console.log('📁 Processing file:', file.name); if (!file.type.startsWith('image/')) { this.showToast('กรุณาเลือกไฟล์รูปภาพ', 'error'); return; } if (file.size > 10 * 1024 * 1024) { this.showToast('ขนาดไฟล์เกิน 10MB', 'error'); return; } try { // แสดงหน้าประมวลผล this.showSection('processing'); // แสดงรูปตัวอย่าง const reader = new FileReader(); reader.onload = (e) => { const processingImage = document.getElementById('processing-image'); if (processingImage) { processingImage.src = e.target.result; } }; reader.readAsDataURL(file); // อัปเดต progress และข้อความ this.updateProgress(20, 'กำลังอ่านไฟล์...'); await this.delay(500); this.updateProgress(50, 'กำลังประมวลผลด้วย OCR...'); // ประมวลผล OCR const ocrResult = await this.ocrProcessor.processImage(file); this.updateProgress(80, 'กำลังแปลข้อมูล...'); // แปลข้อมูลสลิป const slipData = this.slipParser.parse(ocrResult.text); this.updateProgress(100, 'เสร็จสิ้น!'); // เก็บข้อมูล this.currentSlipData = { ...slipData, ocrResult: ocrResult, imageFile: file }; await this.delay(500); // แสดงผลลัพธ์ this.showResults(this.currentSlipData); } catch (error) { console.error('File processing error:', error); this.showError('เกิดข้อผิดพลาดในการประมวลผล: ' + error.message); } } /** * อัปเดต progress bar */ updateProgress(percent, text) { const progressFill = document.getElementById('progress-fill'); const processingText = document.getElementById('processing-text'); if (progressFill) { progressFill.style.width = percent + '%'; } if (processingText) { processingText.textContent = text; } } /** * อัปเดต progress steps */ updateProgressSteps(currentStep) { const stepMapping = { 'upload': 0, 'camera': 0, 'qr': 0, 'image-preview': 1, 'processing': 2, 'results': 3, 'error': -1 // แสดงสถานะ error โดยไม่เปลี่ยน step }; const stepIndex = stepMapping[currentStep]; // ถ้าเป็น error ไม่ต้องเปลี่ยน progress if (stepIndex === -1) return; const steps = document.querySelectorAll('.step'); const connectors = document.querySelectorAll('.step-connector'); if (steps.length === 0) return; steps.forEach((step, index) => { step.classList.remove('active', 'completed'); if (index < stepIndex) { step.classList.add('completed'); } else if (index === stepIndex) { step.classList.add('active'); } }); connectors.forEach((connector, index) => { connector.classList.remove('completed'); if (index < stepIndex) { connector.classList.add('completed'); } }); if (this.debug) { console.log(`📊 Progress updated: ${currentStep} (step ${stepIndex})`); } } /** * อัปเดตข้อความอธิบายใน header */ updateHeaderDescription(sectionName) { const descriptions = { 'upload': 'เลือกวิธีการอัปโหลดสลิปการโอนเงินของคุณ', 'camera': 'ถ่ายรูปสลิปด้วยกล้อง', 'qr': 'สแกน QR Code จากสลิป', 'image-preview': 'ตรวจสอบรูปภาพและเลือกพื้นที่ QR Code', 'processing': 'กำลังประมวลผลและอ่านข้อมูลจากสลิป', 'results': 'ผลการตรวจสอบสลิปการโอนเงิน', 'error': 'เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง' }; const headerDescription = document.getElementById('header-description'); if (headerDescription && descriptions[sectionName]) { headerDescription.textContent = descriptions[sectionName]; // เพิ่ม animation headerDescription.style.opacity = '0'; setTimeout(() => { headerDescription.style.opacity = '1'; }, 200); } } /** * แสดงข้อมูลดิบ */ displayRawData(slipData, qrResult) { // OCR raw text const rawOcrText = document.getElementById('raw-ocr-text'); if (rawOcrText && slipData) { rawOcrText.value = slipData.raw_text || ''; } } /** * แสดงข้อผิดพลาด */ showError(message) { console.error('❌ Error:', message); this.showSection('error'); const errorMessage = document.getElementById('error-message'); if (errorMessage) { errorMessage.textContent = message; } } /** * Delay helper function */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * แสดงข้อมูลตัวอย่างใน debug mode */ showDebugExample() { this.showToast('ตัวอย่าง debug - ฟีเจอร์นี้จะพัฒนาในเวอร์ชันถัดไป', 'info'); } /** * คัดลอกข้อมูลสลิป */ copySlipData() { if (!this.currentSlipData) { this.showToast('ไม่มีข้อมูลให้คัดลอก', 'warning'); return; } const data = this.currentSlipData; const copyText = ` ข้อมูลสลิปการโอนเงิน จำนวนเงิน: ${data.amount || '-'} วันที่-เวลา: ${data.datetime || '-'} ผู้โอน: ${data.sender || '-'} ผู้รับ: ${data.receiver || '-'} รหัสอ้างอิง: ${data.reference || '-'} `.trim(); this.copyToClipboard(copyText); } /** * คัดลอกข้อมูลดิบ */ copyRawData() { if (!this.currentSlipData || !this.currentSlipData.ocrResult) { this.showToast('ไม่มีข้อมูลดิบให้คัดลอก', 'warning'); return; } const rawText = this.currentSlipData.ocrResult.text || ''; this.copyToClipboard(rawText); } /** * คัดลอกข้อความไปยัง clipboard */ async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); this.showToast('คัดลอกข้อมูลแล้ว', 'success'); } catch (error) { console.error('Copy failed:', error); this.showToast('ไม่สามารถคัดลอกได้', 'error'); } } /** * รีเซ็ตระบบ */ reset() { console.log('🔄 Resetting system...'); // ล้างข้อมูล this.currentSlipData = null; this.currentQRData = null; this.currentQRImageFile = null; this.currentCropArea = null; // รีเซ็ต crop area this.qrResult = null; this.isProcessing = false; // หยุดกล้องและ QR Scanner this.stopCamera(); this.stopQRScanner(); // กลับไปหน้าแรก this.showSection('upload'); this.showToast('รีเซ็ตระบบแล้ว', 'info'); } /** * Toggle การแสดงข้อมูลดิบ */ toggleRawDataVisibility() { const rawDataContent = document.getElementById('raw-data-content'); const toggleIcon = document.querySelector('#toggle-raw-data .bi'); if (rawDataContent && toggleIcon) { const isVisible = rawDataContent.style.display !== 'none'; rawDataContent.style.display = isVisible ? 'none' : 'block'; toggleIcon.className = isVisible ? 'bi bi-chevron-down' : 'bi bi-chevron-up'; } } /** * สร้างระบบเลือกธนาคารและตำแหน่ง QR แบบง่าย */ createSimpleBankQRSelector() { console.log('🏦 Creating Simple Bank QR Selector...'); const qrSection = document.getElementById('qr-section'); if (!qrSection) { console.error('QR Section not found!'); return; } // สร้าง UI แบบง่าย qrSection.innerHTML = `
เลือก: อัตโนมัติ
เลือก: กลางกลาง
รองรับ JPG, PNG, WEBP | ขนาดไม่เกิน 5MB