const { useState, useRef } = React; const { Upload, FileText, CheckCircle2, AlertCircle, ShoppingCart, Calendar, User, CreditCard, ChevronRight, Copy, Check, FileCheck, RefreshCw, FileSpreadsheet } = window.lucide; function OCRScanner({ onScanComplete }) { const [dragActive, setDragActive] = useState(false); const [file, setFile] = useState(null); const [scanning, setScanning] = useState(false); const [progressMessage, setProgressMessage] = useState(''); const [scannedResults, setScannedResults] = useState(null); const [selectedResultIndex, setSelectedResultIndex] = useState(0); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const fileInputRef = useRef(null); const scannedResult = scannedResults && scannedResults.length > 0 ? scannedResults[selectedResultIndex] : null; const progressMessages = [ 'กำลังอัปโหลดไฟล์เอกสารอย่างปลอดภัย...', 'กำลังประมวลผล OCR และให้อัลกอริทึมวิเคราะห์ประเภทเอกสาร...', 'AI กำลังถอดและดึงข้อความภาษาไทยอย่างละเอียด...', 'กำลังจัดหมวดหมู่ข้อมูลอัตโนมัติ (Lazada / Shopee / TikTok)...', 'กำลังแยกชื่อสินค้า ราคา และจำนวนยอดชำระ...', 'กำลังส่งข้อมูลเข้า Google Sheets และส่งอีเมลแจ้งเตือน...', 'ใกล้เสร็จสมบูรณ์แล้ว โปรดรอสักครู่...' ]; const rotateProgressMessages = (index = 0) => { if (!scanning) return; setProgressMessage(progressMessages[index % progressMessages.length]); setTimeout(() => { rotateProgressMessages(index + 1); }, 3000); }; const handleDrag = (e) => { e.preventDefault(); e.stopPropagation(); if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true); } else if (e.type === 'dragleave') { setDragActive(false); } }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { const droppedFile = e.dataTransfer.files[0]; validateAndSetFile(droppedFile); } }; const handleFileChange = (e) => { if (e.target.files && e.target.files[0]) { validateAndSetFile(e.target.files[0]); } }; const validateAndSetFile = (selectedFile) => { setError(null); const validTypes = ['application/pdf', 'image/png', 'image/jpeg', 'image/jpg', 'image/webp']; if (!validTypes.includes(selectedFile.type)) { setError('รองรับเฉพาะไฟล์เอกสาร PDF หรือรูปภาพ (PNG, JPEG, WEBP) เท่านั้น'); return; } setFile(selectedFile); setScannedResults(null); setSelectedResultIndex(0); }; const handleScan = async () => { if (!file) return; setScanning(true); setError(null); setScannedResults(null); setSelectedResultIndex(0); rotateProgressMessages(0); try { let chunks = []; // If it's a PDF and PDFLib is available, try to chunk it if (file.type === 'application/pdf' && window.PDFLib) { try { setProgressMessage('กำลังเตรียมเอกสาร PDF (Auto-Splitting) เพื่อป้องกันเซิร์ฟเวอร์ Timeout...'); const arrayBuffer = await file.arrayBuffer(); const pdfDoc = await window.PDFLib.PDFDocument.load(arrayBuffer); const pageCount = pdfDoc.getPageCount(); if (pageCount > 5) { const CHUNK_SIZE = 5; for (let i = 0; i < pageCount; i += CHUNK_SIZE) { const newPdf = await window.PDFLib.PDFDocument.create(); const end = Math.min(i + CHUNK_SIZE, pageCount); const pageIndices = Array.from({length: end - i}, (_, idx) => i + idx); const copiedPages = await newPdf.copyPages(pdfDoc, pageIndices); copiedPages.forEach((page) => newPdf.addPage(page)); const pdfBytes = await newPdf.save(); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const chunkFile = new File([blob], `${file.name}_part${Math.floor(i/CHUNK_SIZE) + 1}.pdf`, { type: 'application/pdf' }); chunks.push(chunkFile); } } else { chunks = [file]; } } catch (pdfErr) { console.warn('PDF splitting failed, falling back to full upload:', pdfErr); chunks = [file]; } } else { chunks = [file]; } let allResults = []; for (let i = 0; i < chunks.length; i++) { const chunkFile = chunks[i]; if (chunks.length > 1) { setProgressMessage(`กำลังส่งมอบข้อมูลชุดที่ ${i + 1}/${chunks.length} ไปยังเซิร์ฟเวอร์...`); } const formData = new FormData(); formData.append('file', chunkFile); const res = await fetch('api.php?action=scan', { method: 'POST', body: formData }); if (!res.ok) { if (res.status === 504) { throw new Error(`เซิร์ฟเวอร์หมดเวลาการเชื่อมต่อ (Status 504) ในขณะส่งชุดข้อมูลที่ ${i + 1}/${chunks.length}`); } let errMsg = 'เกิดข้อผิดพลาดในการประมวลผล OCR'; try { const errText = await res.text(); try { const errData = JSON.parse(errText); errMsg = errData.error || errData.details || errMsg; } catch { if (errText.includes('GEMINI_API_KEY is not configured') || errText.includes('apiKey is not configured')) { errMsg = 'ไม่พบค่า GEMINI_API_KEY กรุณาตั้งค่า API Key ใน Settings เพื่อใช้งานระบบ AI OCR'; } else { errMsg = `เซิร์ฟเวอร์ตอบกลับด้วยรหัสข้อผิดพลาด Status ${res.status}`; } } } catch { errMsg = `เกิดข้อผิดพลาดภายนอก (HTTP Status ${res.status})`; } throw new Error(errMsg); } let results; try { const resText = await res.text(); const parsed = JSON.parse(resText); results = Array.isArray(parsed) ? parsed : [parsed]; } catch (jsonParseErr) { throw new Error('ไม่สามารถประมวลผลข้อมูลส่งกลับจากเซิร์ฟเวอร์ได้ (Invalid JSON response)'); } allResults = [...allResults, ...results]; } setScannedResults(allResults); onScanComplete(allResults); } catch (err) { console.error(err); setError(err instanceof Error ? err.message : 'เกิดข้อผิดพลาดบางประการ โปรดตรวจสอบ GEMINI_API_KEY หรือลองอีกครั้ง'); } finally { setScanning(false); } }; const handleCopyToClipboard = () => { if (!scannedResult) return; let clipboardText = "หมวดหมู่\tเลขที่สั่งซื้อ\tชื่อผู้รับ\tวันที่\tชื่อสินค้า\tราคาต่อหน่วย\tจำนวน\tยอดรวมย่อย\tยอดรวมสุทธิ\n"; scannedResult.items.forEach(item => { clipboardText += `${scannedResult.category}\t${scannedResult.orderId}\t${scannedResult.customerName}\t${new Date(scannedResult.timestamp).toLocaleDateString('th-TH')}\t${item.productName}\t${item.price}\t${item.quantity}\t${item.amount}\t${scannedResult.totalAmount}\n`; }); navigator.clipboard.writeText(clipboardText); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleReset = () => { setFile(null); setScannedResults(null); setSelectedResultIndex(0); setError(null); setProgressMessage(''); if (fileInputRef.current) fileInputRef.current.value = ''; }; const exportSingleRecordToCSV = (record) => { const headers = [ 'เลขที่คำสั่งซื้อ', 'วันที่สั่งซื้อ', 'บริษัทขนส่ง', 'หมายเลขพัสดุ', 'รหัสเส้นทาง', 'ช่องการชำระเงิน', 'ชื่อผู้ส่ง', 'ที่อยู่ผู้ส่ง', 'เบอร์ผู้ส่ง', 'ชื่อผู้รับ', 'ที่อยู่ผู้รับ', 'เบอร์ผู้รับ', 'ชื่อสินค้า', 'ราคาต่อหน่วย', 'จำนวน', 'รวมราคา' ]; const rows = []; const baseData = [ record.orderId || '', record.orderDate || '', record.shippingProvider || '', record.trackingNumber || '', record.routeCode || '', record.paymentMethod || '', record.senderName || '', (record.senderAddress || '').replace(/"/g, '""').replace(/\n/g, ' '), record.senderPhone || '', record.customerName || '', (record.receiverAddress || '').replace(/"/g, '""').replace(/\n/g, ' '), record.receiverPhone || '' ]; if (record.items && record.items.length > 0) { record.items.forEach(item => { rows.push([ ...baseData, item.productName.replace(/"/g, '""').replace(/\n/g, ' '), item.price.toString(), item.quantity.toString(), item.amount.toString() ]); }); } else { rows.push([...baseData, '', '0', '0', '0']); } const csvContent = [ headers.join(','), ...rows.map(r => r.map(val => `"${val}"`).join(',')) ].join('\n'); const blob = new Blob([new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `sheetocr_order_${record.orderId || record.id}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const exportBatchToCSV = (records) => { const headers = [ 'เลขที่คำสั่งซื้อ', 'วันที่สั่งซื้อ', 'บริษัทขนส่ง', 'หมายเลขพัสดุ', 'รหัสเส้นทาง', 'ช่องการชำระเงิน', 'ชื่อผู้ส่ง', 'ที่อยู่ผู้ส่ง', 'เบอร์ผู้ส่ง', 'ชื่อผู้รับ', 'ที่อยู่ผู้รับ', 'เบอร์ผู้รับ', 'ชื่อสินค้า', 'ราคาต่อหน่วย', 'จำนวน', 'รวมราคา' ]; const rows = []; records.forEach(record => { const baseData = [ record.orderId || '', record.orderDate || '', record.shippingProvider || '', record.trackingNumber || '', record.routeCode || '', record.paymentMethod || '', record.senderName || '', (record.senderAddress || '').replace(/"/g, '""').replace(/\n/g, ' '), record.senderPhone || '', record.customerName || '', (record.receiverAddress || '').replace(/"/g, '""').replace(/\n/g, ' '), record.receiverPhone || '' ]; if (record.items && record.items.length > 0) { record.items.forEach(item => { rows.push([ ...baseData, item.productName.replace(/"/g, '""').replace(/\n/g, ' '), item.price.toString(), item.quantity.toString(), item.amount.toString() ]); }); } else { rows.push([...baseData, '', '0', '0', '0']); } }); const csvContent = [ headers.join(','), ...rows.map(r => r.map(val => `"${val}"`).join(',')) ].join('\n'); const blob = new Blob([new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `sheetocr_batch_export_${Date.now()}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const getCategoryTheme = (category) => { switch (category) { case 'Lazada': return { bg: 'bg-blue-50 border-blue-200', text: 'text-blue-800', badge: 'bg-blue-600 text-white', accent: 'border-blue-500' }; case 'Shopee': return { bg: 'bg-orange-50 border-orange-200', text: 'text-orange-800', badge: 'bg-orange-500 text-white', accent: 'border-orange-500' }; case 'TikTok Shop': return { bg: 'bg-slate-50 border-slate-200', text: 'text-slate-800', badge: 'bg-black text-white', accent: 'border-black' }; default: return { bg: 'bg-gray-50 border-gray-200', text: 'text-gray-800', badge: 'bg-gray-600 text-white', accent: 'border-gray-500' }; } }; return (
{/* Upload Zone */}

สแกนและจัดหมวดหมู่เอกสารใหม่ (Upload & Scan)

หรือลากมาวางที่นี่

รองรับไฟล์เอกสาร PDF, รูปใบปะหน้า, รูปใบเสร็จ (Lazada, Shopee, TikTok Shop) ขนาดสูงสุด 10MB

{file && (
เตรียมพร้อมสแกน: {file.name}
)}
{error && (
{error}
)} {file && !scanning && (
)}
{/* Progress Animation Loading */} {scanning && (

ระบบ AI กำลังประมวลผลดึงข้อมูลเอกสาร...

{progressMessage}

)} {/* Extraction Result Showcase */} {scannedResult && (
{/* Multi-page batch selector if more than 1 record was scanned */} {scannedResults && scannedResults.length > 1 && (
ตรวจพบทั้งหมด {scannedResults.length} รายการ: หน้า {selectedResultIndex + 1} จาก {scannedResults.length}
{scannedResults.map((_, idx) => ( ))}
)} {/* Card Accent Header */}
{scannedResult.category}

ผลลัพธ์การดึงข้อมูลด้วย AI สำเร็จ

ไฟล์: {scannedResult.fileName}

{/* Core Info & Logistics Detail Grid */}
เลขที่สั่งซื้อ / Order ID {scannedResult.orderId || 'ไม่ระบุ'}
ชื่อผู้รับสินค้า / Customer {scannedResult.customerName || 'ไม่ระบุ'}
เวลาสแกน / Scanned At {new Date(scannedResult.timestamp).toLocaleString('th-TH')}
{/* Sub-grid of detailed logistics extraction */}
{/* Order & Logistics Metadata */}
ข้อมูลขนส่งเพิ่มเติม (Logistics Metadata)
บริษัทขนส่ง: {scannedResult.shippingProvider || 'ไม่ระบุ/LEX/SPX'}
หมายเลขพัสดุ (Tracking): {scannedResult.trackingNumber || 'ไม่ระบุ'}
รหัสเส้นทาง (Route Code): {scannedResult.routeCode || 'ไม่ระบุ'}
วันที่สั่งซื้อบนป้าย: {scannedResult.orderDate || 'ไม่ระบุ'}
ช่องทางการชำระเงิน: {scannedResult.paymentMethod || 'ไม่ระบุ'}
{/* Sender & Receiver Address Details */}
ที่อยู่ผู้ส่งและผู้รับสินค้า (Addresses)
ผู้ส่ง (Sender)
{scannedResult.senderName || 'ไม่ระบุชื่อร้านค้า'}
{scannedResult.senderPhone &&
โทร: {scannedResult.senderPhone}
} {scannedResult.senderAddress &&
{scannedResult.senderAddress}
}
ผู้รับ (Receiver)
{scannedResult.customerName || 'ไม่ระบุชื่อผู้รับ'}
{scannedResult.receiverPhone &&
โทร: {scannedResult.receiverPhone}
} {scannedResult.receiverAddress &&
{scannedResult.receiverAddress}
}
{/* Items breakdown list */}

รายการสินค้าที่พบบนเอกสาร (Extracted Products)

{scannedResult.items.map((item, index) => ( ))} {scannedResult.items.length === 0 && ( )}
ชื่อสินค้า (Product Name) ราคา/หน่วย (Price) จำนวน (Qty) จำนวนเงินรวม (Subtotal)
{item.productName} {item.price.toLocaleString('th-TH', { minimumFractionDigits: 2 })} บาท {item.quantity} {item.amount.toLocaleString('th-TH', { minimumFractionDigits: 2 })} บาท
ไม่พบรายการสินค้าที่แยกออกมา
ยอดชำระเงินสุทธิทั้งสิ้น (Net Total Paid): {scannedResult.totalAmount.toLocaleString('th-TH', { minimumFractionDigits: 2 })} บาท
{/* Sync Summary Status */}
สถานะการนำเข้า Google Sheet

{scannedResult.sheetsStatus === 'success' ? 'สำเร็จ: แถวข้อมูลถูกส่งไปเพิ่มในสเปรดชีตของคุณแล้ว' : scannedResult.sheetsStatus === 'not_configured' ? 'ไม่ได้เชื่อมต่อ: กรุณาตั้งค่า Webhook ในแท็บตั้งค่าระบบเพื่อซิงค์ข้อมูลเรียลไทม์' : 'ล้มเหลว: ไม่สามารถส่งข้อมูลผ่าน Webhook ไปยัง Sheet ได้ โปรดตรวจเช็ก URL และเปิดสิทธิ์'}

สถานะแจ้งเตือนอีเมล

{scannedResult.emailStatus === 'success' ? 'สำเร็จ: บันทึกรายงานสรุปส่งออกไปยังจดหมายพร้อมแจ้งเตือนแล้ว' : scannedResult.emailStatus === 'not_configured' ? 'ไม่ได้เปิดใช้งาน: การแจ้งเตือนทางอีเมลยังปิดอยู่ สามารถเปิดได้ในแท็บตั้งค่าระบบ' : 'ล้มเหลว: ไม่สามารถส่งอีเมลออกผ่าน SMTP ได้ ตรวจสอบได้ในประวัติ outbox'}

)}
); } window.OCRScanner = OCRScanner;