/**
* 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 = `
<div class="simple-qr-selector">
<h3><i class="bi bi-qr-code-scan"></i> สแกน QR Code จากสลิป</h3>
<!-- การเลือกธนาคาร -->
<div class="bank-selector">
<h4><i class="bi bi-bank"></i> เลือกธนาคาร:</h4>
<div class="bank-buttons">
<button class="bank-btn" data-bank="auto">อัตโนมัติ</button>
<button class="bank-btn" data-bank="SCB">ไทยพาณิชย์</button>
<button class="bank-btn" data-bank="KBANK">กสิกรไทย</button>
<button class="bank-btn" data-bank="BBL">กรุงเทพ</button>
<button class="bank-btn" data-bank="KTB">กรุงไทย</button>
<button class="bank-btn" data-bank="TMB">ทหารไทย</button>
<button class="bank-btn" data-bank="GSB">ออมสิน</button>
<button class="bank-btn" data-bank="other">อื่นๆ</button>
</div>
<p class="selected-bank">เลือก: <span id="selected-bank-text">อัตโนมัติ</span></p>
</div>
<!-- การเลือกตำแหน่ง QR -->
<div class="qr-position-selector">
<h4><i class="bi bi-bullseye"></i> ตำแหน่ง QR Code ในสลิป:</h4>
<div class="position-grid">
<button class="position-btn" data-position="top-left">บนซ้าย</button>
<button class="position-btn" data-position="top-center">บนกลาง</button>
<button class="position-btn" data-position="top-right">บนขวา</button>
<button class="position-btn" data-position="middle-left">กลางซ้าย</button>
<button class="position-btn" data-position="middle-center">กลางกลาง</button>
<button class="position-btn" data-position="middle-right">กลางขวา</button>
<button class="position-btn" data-position="bottom-left">ล่างซ้าย</button>
<button class="position-btn" data-position="bottom-center">ล่างกลาง</button>
<button class="position-btn" data-position="bottom-right">ล่างขวา</button>
</div>
<p class="selected-position">เลือก: <span id="selected-position-text">กลางกลาง</span></p>
</div>
<!-- การอัปโหลดรูป -->
<div class="qr-upload-section">
<h4><i class="bi bi-upload"></i> อัปโหลดรูปสลิป:</h4>
<input type="file" id="simple-qr-file" accept="image/*" style="display: none;">
<button class="btn btn-primary" id="simple-upload-btn">
<i class="bi bi-upload"></i> เลือกรูปภาพ
</button>
<div class="upload-info">
<p>รองรับ JPG, PNG, WEBP | ขนาดไม่เกิน 5MB</p>
</div>
</div>
<!-- แสดงรูปและพื้นที่ QR -->
<div class="qr-preview-section" id="simple-qr-preview" style="display: none;">
<h4><i class="bi bi-crop"></i> รูปภาพและพื้นที่ QR:</h4>
<div class="preview-container">
<div class="image-with-overlay">
<img id="simple-preview-image" src="" alt="Preview">
<div class="qr-overlay" id="simple-qr-overlay">
<div class="qr-highlight-box"></div>
</div>
</div>
</div>
<div class="scan-controls">
<button class="btn btn-success" id="simple-scan-btn">
<i class="bi bi-search"></i> สแกน QR Code
</button>
<button class="btn btn-secondary" id="simple-adjust-btn">
<i class="bi bi-arrows-move"></i> ปรับตำแหน่ง
</button>
</div>
</div>
<!-- ปุ่มควบคุม -->
<div class="qr-controls">
<button class="btn btn-secondary" id="qr-cancel-btn">
<i class="bi bi-x"></i> ยกเลิก
</button>
</div>
</div>
`;
// ตั้งค่าเริ่มต้น
this.selectedBankType = 'auto';
this.selectedQRPosition = 'middle-center';
console.log('📋 HTML Created, binding events...');
// ผูก events (รอ DOM อัปเดต)
setTimeout(() => {
this.bindSimpleQREvents();
}, 100);
} /**
* ผูก events สำหรับระบบ QR แบบง่าย
*/
bindSimpleQREvents() {
console.log('🔗 Binding Simple QR Events...');
// Bank selection
const bankButtons = document.querySelectorAll('.bank-btn');
console.log('Found bank buttons:', bankButtons.length);
bankButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
console.log('Bank button clicked:', e.target.dataset.bank);
// ลบ active class จากปุ่มอื่น
bankButtons.forEach(b => b.classList.remove('active'));
// เพิ่ม active class ให้ปุ่มที่เลือก
e.target.classList.add('active');
this.selectedBankType = e.target.dataset.bank;
const selectedText = document.getElementById('selected-bank-text');
if (selectedText) {
selectedText.textContent = e.target.textContent;
}
console.log('Selected bank:', this.selectedBankType);
this.showToast(`เลือก: ${e.target.textContent}`, 'success');
});
});
// Position selection
const positionButtons = document.querySelectorAll('.position-btn');
console.log('Found position buttons:', positionButtons.length);
positionButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
console.log('Position button clicked:', e.target.dataset.position);
// ลบ active class จากปุ่มอื่น
positionButtons.forEach(b => b.classList.remove('active'));
// เพิ่ม active class ให้ปุ่มที่เลือก
e.target.classList.add('active');
this.selectedQRPosition = e.target.dataset.position;
const selectedText = document.getElementById('selected-position-text');
if (selectedText) {
selectedText.textContent = e.target.textContent;
}
// อัปเดตตำแหน่งในรูป
this.updateQROverlayPosition();
console.log('Selected position:', this.selectedQRPosition);
this.showToast(`ตำแหน่ง QR: ${e.target.textContent}`, 'success');
});
});
// File upload
const uploadBtn = document.getElementById('simple-upload-btn');
const fileInput = document.getElementById('simple-qr-file');
console.log('Upload button:', uploadBtn ? 'found' : 'not found');
console.log('File input:', fileInput ? 'found' : 'not found');
if (uploadBtn && fileInput) {
uploadBtn.addEventListener('click', () => {
console.log('Upload button clicked');
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
console.log('File selected:', e.target.files.length);
if (e.target.files.length > 0) {
this.loadSimpleQRImage(e.target.files[0]);
}
});
}
// Scan button
const scanBtn = document.getElementById('simple-scan-btn');
console.log('Scan button:', scanBtn ? 'found' : 'not found');
if (scanBtn) {
scanBtn.addEventListener('click', () => {
console.log('Scan button clicked');
this.scanSimpleQR();
});
}
// Adjust button
const adjustBtn = document.getElementById('simple-adjust-btn');
if (adjustBtn) {
adjustBtn.addEventListener('click', () => {
console.log('Adjust button clicked');
this.showPositionAdjustment();
});
}
// Cancel button
const cancelBtn = document.getElementById('qr-cancel-btn');
console.log('Cancel button:', cancelBtn ? 'found' : 'not found');
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
console.log('Cancel button clicked');
this.stopQRScanner();
});
}
// Set default selections
setTimeout(() => {
const defaultBank = document.querySelector('.bank-btn[data-bank="auto"]');
const defaultPosition = document.querySelector('.position-btn[data-position="middle-center"]');
console.log('Setting defaults...');
console.log('Default bank:', defaultBank ? 'found' : 'not found');
console.log('Default position:', defaultPosition ? 'found' : 'not found');
if (defaultBank) {
defaultBank.classList.add('active');
console.log('Added active class to default bank');
}
if (defaultPosition) {
defaultPosition.classList.add('active');
console.log('Added active class to default position');
}
}, 200);
}
/**
* โหลดรูปภาพ QR แบบง่าย
*/
loadSimpleQRImage(file) {
if (!file.type.startsWith('image/')) {
this.showToast('กรุณาเลือกไฟล์รูปภาพ', 'error');
return;
}
if (file.size > 5 * 1024 * 1024) {
this.showToast('ขนาดไฟล์เกิน 5MB', 'error');
return;
}
this.currentQRImageFile = file;
const reader = new FileReader();
reader.onload = (e) => {
const previewImg = document.getElementById('simple-preview-image');
const previewSection = document.getElementById('simple-qr-preview');
previewImg.src = e.target.result;
previewSection.style.display = 'block';
// อัปเดตตำแหน่ง QR overlay
previewImg.onload = () => {
this.updateQROverlayPosition();
};
};
reader.readAsDataURL(file);
this.showToast('โหลดรูปภาพสำเร็จ', 'success');
}
/**
* อัปเดตตำแหน่ง QR overlay
*/
updateQROverlayPosition() {
const overlay = document.getElementById('simple-qr-overlay');
const highlightBox = overlay.querySelector('.qr-highlight-box');
if (!highlightBox) return;
// กำหนดตำแหน่งตาม position ที่เลือก
const positions = {
'top-left': {top: '10%', left: '10%', width: '30%', height: '30%'},
'top-center': {top: '10%', left: '35%', width: '30%', height: '30%'},
'top-right': {top: '10%', left: '60%', width: '30%', height: '30%'},
'middle-left': {top: '35%', left: '10%', width: '30%', height: '30%'},
'middle-center': {top: '35%', left: '35%', width: '30%', height: '30%'},
'middle-right': {top: '35%', left: '60%', width: '30%', height: '30%'},
'bottom-left': {top: '60%', left: '10%', width: '30%', height: '30%'},
'bottom-center': {top: '60%', left: '35%', width: '30%', height: '30%'},
'bottom-right': {top: '60%', left: '60%', width: '30%', height: '30%'}
};
const pos = positions[this.selectedQRPosition] || positions['middle-center'];
highlightBox.style.top = pos.top;
highlightBox.style.left = pos.left;
highlightBox.style.width = pos.width;
highlightBox.style.height = pos.height;
}
/**
* สแกน QR แบบง่าย
*/
async scanSimpleQR() {
if (!this.currentQRImageFile) {
this.showToast('กรุณาเลือกรูปภาพก่อน', 'error');
return;
}
try {
this.showToast('กำลังสแกน QR Code...', 'info');
// สร้าง canvas จากรูปภาพ
const img = document.getElementById('simple-preview-image');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
// คำนวณพื้นที่ที่ต้องการสแกน
const cropArea = this.calculateCropArea(canvas.width, canvas.height);
// ตัดรูปตามพื้นที่ที่เลือก
const croppedCanvas = document.createElement('canvas');
const croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = cropArea.width;
croppedCanvas.height = cropArea.height;
croppedCtx.drawImage(
canvas,
cropArea.x, cropArea.y, cropArea.width, cropArea.height,
0, 0, cropArea.width, cropArea.height
);
// แสดงรูปที่ตัดใน debug mode
if (this.debug) {
console.log('Crop area:', cropArea);
console.log('Cropped image:', croppedCanvas.toDataURL());
}
// สแกน QR จากรูปที่ตัด
const imageData = croppedCtx.getImageData(0, 0, croppedCanvas.width, croppedCanvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
// พบ QR Code
this.currentQRData = code.data;
// แสดงรูปที่ใช้สแกนใน debug
if (this.debug) {
this.showQRDebugImages(this.currentQRImageFile, {
canvas: croppedCanvas,
imageData: imageData
});
}
this.showToast('พบ QR Code แล้ว!', 'success');
// ประมวลผลข้อมูล QR
this.processQRData(code.data);
} else {
this.showToast('ไม่พบ QR Code ในตำแหน่งที่เลือก', 'error');
if (this.debug) {
console.log('ไม่พบ QR Code ในพื้นที่:', cropArea);
}
}
} catch (error) {
console.error('QR scan error:', error);
this.showToast('เกิดข้อผิดพลาดในการสแกน QR', 'error');
}
}
/**
* คำนวณพื้นที่ crop ตามตำแหน่งที่เลือก
*/
calculateCropArea(imageWidth, imageHeight) {
const positions = {
'top-left': {x: 0.1, y: 0.1, w: 0.3, h: 0.3},
'top-center': {x: 0.35, y: 0.1, w: 0.3, h: 0.3},
'top-right': {x: 0.6, y: 0.1, w: 0.3, h: 0.3},
'middle-left': {x: 0.1, y: 0.35, w: 0.3, h: 0.3},
'middle-center': {x: 0.35, y: 0.35, w: 0.3, h: 0.3},
'middle-right': {x: 0.6, y: 0.35, w: 0.3, h: 0.3},
'bottom-left': {x: 0.1, y: 0.6, w: 0.3, h: 0.3},
'bottom-center': {x: 0.35, y: 0.6, w: 0.3, h: 0.3},
'bottom-right': {x: 0.6, y: 0.6, w: 0.3, h: 0.3}
};
const pos = positions[this.selectedQRPosition] || positions['middle-center'];
return {
x: Math.round(imageWidth * pos.x),
y: Math.round(imageHeight * pos.y),
width: Math.round(imageWidth * pos.w),
height: Math.round(imageHeight * pos.h)
};
} /**
* แสดง/ซ่อน section ต่างๆ พร้อม animation
*/
showSection(sectionName) {
console.log(`🔄 Switching to section: ${sectionName}`);
// อัปเดต progress steps และ description
this.updateProgressSteps(sectionName);
this.updateHeaderDescription(sectionName);
const sections = [
'upload-section',
'camera-section',
'image-preview-section',
'qr-section',
'processing-section',
'results-section',
'error-section'
];
const targetSection = document.getElementById(`${sectionName}-section`);
if (!targetSection) {
console.error(`Section not found: ${sectionName}-section`);
return;
}
// ซ่อน sections อื่นๆ ด้วย animation
sections.forEach(sectionId => {
const section = document.getElementById(sectionId);
if (section && sectionId !== `${sectionName}-section`) {
if (section.style.display !== 'none') {
section.classList.add('section-exit');
setTimeout(() => {
section.style.display = 'none';
section.classList.remove('section-exit');
}, 300);
}
}
});
// แสดง section ที่ต้องการด้วย animation
setTimeout(() => {
targetSection.style.display = 'block';
targetSection.classList.add('section-enter');
setTimeout(() => {
targetSection.classList.remove('section-enter');
}, 400);
}, 100);
}
/**
* แสดงข้อความ Toast
*/
showToast(message, type = 'info') {
console.log(`🍞 Toast [${type}]: ${message}`);
const toast = document.getElementById('toast');
const toastMessage = toast?.querySelector('.toast-message');
const toastIcon = toast?.querySelector('.toast-icon');
if (!toast || !toastMessage || !toastIcon) {
console.warn('Toast elements not found');
return;
}
// ตั้งค่าไอคอนและสี
const toastConfig = {
success: {icon: 'bi-check-circle-fill', class: 'success'},
error: {icon: 'bi-exclamation-triangle-fill', class: 'error'},
warning: {icon: 'bi-exclamation-triangle-fill', class: 'warning'},
info: {icon: 'bi-info-circle-fill', class: 'info'}
};
const config = toastConfig[type] || toastConfig.info;
toastIcon.className = `toast-icon bi ${config.icon}`;
toastMessage.textContent = message;
toast.className = `toast ${config.class}`;
// แสดง toast
toast.style.display = 'block';
// ซ่อนหลัง 3 วินาที
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
/**
* แสดงการแจ้งเตือน (alias สำหรับ showToast)
*/
showNotification(message, type = 'info') {
this.showToast(message, type);
}
/**
* ทำลายและล้างข้อมูลทั้งหมด
*/
async destroy() {
console.log('🗑️ Destroying SlipVerifier...');
try {
// หยุดกล้องและ QR Scanner
if (this.stopCamera && typeof this.stopCamera === 'function') {
this.stopCamera();
}
if (this.stopQRScanner && typeof this.stopQRScanner === 'function') {
this.stopQRScanner();
}
// ทำลาย crop selector
if (this.qrCropSelector && this.qrCropSelector.destroy) {
this.qrCropSelector.destroy();
this.qrCropSelector = null;
}
// ล้างข้อมูลทั้งหมด
this.currentSlipData = null;
this.currentQRData = null;
this.currentQRImageFile = null;
this.qrResult = null;
this.isProcessing = false;
this.currentCamera = null;
// ล้าง intervals/timeouts (ถ้ามี)
if (this.processingInterval) {
clearInterval(this.processingInterval);
this.processingInterval = null;
}
if (this.qrScanInterval) {
clearInterval(this.qrScanInterval);
this.qrScanInterval = null;
}
console.log('✅ SlipVerifier destroyed successfully');
} catch (error) {
console.error('❌ Error during destroy:', error);
}
}
/**
* จัดการเลือกรูป QR
*/
handleQRImageSelect(e) {
console.log('📁 QR Image selected');
const files = e.target.files;
if (files.length > 0) {
this.loadSimpleQRImage(files[0]);
}
}
/**
* จัดการ drag over สำหรับ QR upload
*/
handleQRDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('drag-over');
}
/**
* จัดการ drag leave สำหรับ QR upload
*/
handleQRDragLeave(e) {
e.preventDefault();
e.currentTarget.classList.remove('drag-over');
}
/**
* จัดการ drop สำหรับ QR upload
*/
handleQRDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('drag-over');
const files = e.dataTransfer.files;
if (files.length > 0) {
this.loadSimpleQRImage(files[0]);
}
}
/**
* หยุดกล้อง QR (ถ้ามี)
*/
stopQRCamera() {
console.log('📱 Stopping QR camera...');
// หยุด QR video stream ถ้ามี
const qrVideo = document.getElementById('qr-video');
if (qrVideo && qrVideo.srcObject) {
const stream = qrVideo.srcObject;
stream.getTracks().forEach(track => track.stop());
qrVideo.srcObject = null;
console.log('📱 QR camera stream stopped');
}
// ล้าง QR scanning interval ถ้ามี
if (this.qrScanInterval) {
clearInterval(this.qrScanInterval);
this.qrScanInterval = null;
console.log('📱 QR scan interval cleared');
}
}
/**
* อัปเดตสถานะการประมวลผล
*/
updateProcessingStatus(message, percentage = 0) {
console.log(`⚙️ Processing: ${message} (${percentage}%)`);
// อัปเดตข้อความ
const processingText = document.getElementById('processing-text');
if (processingText) {
processingText.textContent = message;
}
// อัปเดต progress bar
const progressFill = document.getElementById('progress-fill');
if (progressFill) {
progressFill.style.width = `${percentage}%`;
}
}
/**
* ประมวลผลผลลัพธ์จาก OCR และ QR
*/
async processResults(ocrResult, qrResult, qrCroppedImageDataUrl = null, hasQrCrop = false) {
console.log('📊 Processing results...', {
ocrResult,
qrResult,
hasQrCroppedImage: !!qrCroppedImageDataUrl,
hasQrCrop
});
try {
let parsedData = null;
let dataSource = 'none';
// รวมข้อมูลจาก QR และ OCR (ถ้ามี)
let qrData = null;
if (qrResult && qrResult.data) {
try {
// แสดงข้อมูลดิบ QR Code เท่านั้น
qrData = {
raw_data: qrResult.data,
type: 'raw',
message: 'ข้อมูลดิบ QR Code',
length: qrResult.data.length,
preview: qrResult.data.length > 100 ? qrResult.data.substring(0, 100) + '...' : qrResult.data,
success: true
};
console.log('📱 QR raw data result:', qrData);
} catch (qrError) {
console.warn('⚠️ Failed to process QR data:', qrError);
qrData = null;
}
}
// ถ้ามี OCR ให้ parse ด้วย
let ocrData = null;
if (ocrResult) {
if (ocrResult && ocrResult.length > 10) {
try {
ocrData = this.slipParser.parse(ocrResult);
console.log('📝 OCR parse result:', ocrData);
} catch (ocrError) {
console.warn('⚠️ Failed to parse OCR data:', ocrError);
ocrData = this.createBasicSlipData(ocrResult);
console.log('📋 Created basic data from OCR:', ocrData);
}
}
}
// รวมข้อมูลโดยให้ OCR มีความสำคัญสูงกว่า (เพราะมีข้อมูลครบกว่า)
if (ocrData) {
parsedData = {...ocrData};
dataSource = 'ocr';
// เติมข้อมูลจาก QR ที่ขาดหาย
if (qrData) {
if (!parsedData.reference && qrData.reference) {
parsedData.reference = qrData.reference;
}
if (!parsedData.receiver && qrData.receiver) {
parsedData.receiver = qrData.receiver;
}
dataSource = 'combined';
}
} else if (qrData) {
// ใช้ QR เป็นหลักถ้าไม่มี OCR
const hasUsefulQrData = qrData.amount || qrData.reference || qrData.sender || qrData.receiver || qrData.datetime;
if (hasUsefulQrData) {
parsedData = qrData;
dataSource = 'qr';
}
}
// ถ้ายังไม่มีข้อมูล ให้สร้างข้อมูลเปล่า
if (!parsedData) {
console.log('⚠️ No parseable data found, creating empty data');
parsedData = this.createEmptySlipData(ocrResult, qrResult);
dataSource = 'empty';
}
console.log('✅ Final parsed data:', parsedData, 'Source:', dataSource);
// เก็บข้อมูล
this.currentSlipData = parsedData;
if (qrResult && qrResult.data) {
this.currentQRData = qrResult.data;
}
// แสดงผลลัพธ์
this.showResults(parsedData, ocrResult, qrResult, dataSource, qrCroppedImageDataUrl, hasQrCrop);
} catch (error) {
console.error('Error processing results:', error);
throw error;
}
}
/**
* สร้างข้อมูลพื้นฐานจากข้อความ OCR
*/
createBasicSlipData(ocrText) {
const data = {
amount: 'ไม่สามารถอ่านได้',
datetime: 'ไม่สามารถอ่านได้',
sender: 'ไม่สามารถอ่านได้',
receiver: 'ไม่สามารถอ่านได้',
reference: 'ไม่สามารถอ่านได้',
bank: 'ไม่ทราบ',
type: 'transfer'
};
// พยายามหาจำนวนเงินจากข้อความ
const amountMatch = ocrText.match(/(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)\s*บาท/);
if (amountMatch) {
data.amount = amountMatch[1] + ' บาท';
}
// พยายามหาวันที่
const dateMatch = ocrText.match(/(\d{1,2})\s*[\/\-\.]\s*(\d{1,2})\s*[\/\-\.]\s*(\d{2,4})/);
if (dateMatch) {
data.datetime = `${dateMatch[1]}/${dateMatch[2]}/${dateMatch[3]}`;
}
return data;
}
/**
* สร้างข้อมูลเปล่าเมื่อไม่สามารถอ่านได้
*/
createEmptySlipData(ocrResult, qrResult) {
return {
amount: 'ไม่สามารถอ่านได้',
datetime: 'ไม่สามารถอ่านได้',
sender: 'ไม่สามารถอ่านได้',
receiver: 'ไม่สามารถอ่านได้',
reference: 'ไม่สามารถอ่านได้',
bank: 'ไม่ทราบ',
type: 'unknown',
note: 'ข้อมูลไม่สมบูรณ์ - กรุณาตรวจสอบรูปภาพและลองใหม่'
};
}
/**
* แสดงผลลัพธ์
*/
showResults(slipData, ocrResult, qrResult, dataSource = 'unknown', qrCroppedImageDataUrl = null, hasQrCrop = false) {
console.log('📋 Showing results...', {
dataSource,
hasQrCroppedImage: !!qrCroppedImageDataUrl,
hasQrCrop
});
this.showSection('results');
// ตั้งสถานะการตรวจสอบ
const verificationStatus = document.getElementById('verification-status');
const verificationText = document.getElementById('verification-text');
if (verificationStatus && verificationText) {
switch (dataSource) {
case 'qr':
verificationStatus.className = 'verification-status verified';
verificationText.textContent = 'ตรวจสอบด้วย QR แล้ว';
break;
case 'ocr':
verificationStatus.className = 'verification-status partial';
verificationText.textContent = 'ตรวจสอบด้วย OCR เท่านั้น';
break;
case 'ocr-basic':
verificationStatus.className = 'verification-status warning';
verificationText.textContent = 'อ่านข้อมูลได้บางส่วน';
break;
default:
verificationStatus.className = 'verification-status error';
verificationText.textContent = 'ข้อมูลไม่สมบูรณ์';
}
}
// แสดงข้อมูลสลิป
this.displaySlipData(slipData);
// ซ่อน/แสดง sections ตามข้อมูลที่มี
this.toggleRelevantSections(qrResult, ocrResult, dataSource);
// แสดงข้อมูล QR (ถ้ามีและอ่านสำเร็จ)
if (qrResult && qrResult.data && qrResult.success) {
this.displayQRData(slipData, qrResult.data);
if (hasQrCrop) {
console.log('✅ QR Code successfully read from selected crop area');
this.showNotification('อ่าน QR Code จากพื้นที่ที่เลือกสำเร็จ', 'success');
}
} else if (hasQrCrop) {
console.log('⚠️ No QR Code found in selected crop area');
this.showNotification('ไม่พบ QR Code ในพื้นที่ที่เลือก', 'warning');
// แสดงข้อมูลจาก OCR แทน
if (dataSource === 'ocr' && slipData) {
const warningMessage = `ใช้ข้อมูลจาก OCR เท่านั้น: จำนวน ${slipData.amount ? slipData.amount.toLocaleString() : 'ไม่ระบุ'} บาท`;
console.log('ℹ️ Using OCR data only:', warningMessage);
}
}
// แสดงข้อมูล OCR ดิบ
this.displayRawData(slipData, qrResult);
}
/**
* ซ่อน/แสดง sections ตามความเกี่ยวข้อง
*/
toggleRelevantSections(qrResult, ocrResult, dataSource) {
// QR Verification Section - แสดงเฉพาะเมื่ออ่าน QR สำเร็จ
const qrVerification = document.getElementById('qr-verification');
if (qrVerification) {
qrVerification.style.display = (qrResult && qrResult.data && qrResult.success) ? 'block' : 'none';
}
// Raw Data Section - แสดงเฉพาะในกรณีจำเป็น
const rawDataContent = document.getElementById('raw-data-content');
const toggleRawData = document.getElementById('toggle-raw-data');
if (rawDataContent && toggleRawData) {
const shouldShow = this.debug || dataSource === 'ocr-basic' || dataSource === 'empty';
if (shouldShow) {
rawDataContent.style.display = 'block';
toggleRawData.style.display = 'inline';
} else {
rawDataContent.style.display = 'none';
toggleRawData.style.display = 'none';
}
}
console.log('🔀 Toggled sections:', {
showQR: !!(qrResult && qrResult.data && qrResult.success),
showRawData: this.debug || dataSource === 'ocr-basic' || dataSource === 'empty'
});
}
/**
* ซ่อนส่วน Raw Data
*/
hideRawDataSection() {
const rawDataSection = document.querySelector('.ocr-raw-data');
if (rawDataSection) {
rawDataSection.style.display = 'none';
}
}
}