1571 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Plaintext
		
	
	
			
		
		
	
	
			1571 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Plaintext
		
	
	
@{
 | 
						|
    Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
 | 
						|
    ViewData["Title"] = "Submit Struk";
 | 
						|
}
 | 
						|
 | 
						|
@section Styles {
 | 
						|
    <link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
 | 
						|
}
 | 
						|
 | 
						|
<div class="max-w-sm mx-auto bg-white min-h-screen">
 | 
						|
    <div class="bg-cyan-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
 | 
						|
        <div class="flex items-center justify-between">
 | 
						|
            <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
 | 
						|
                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
						|
            </a>
 | 
						|
            <h1 class="text-lg font-bold">Unggah Struk</h1>
 | 
						|
            <div class="w-8"></div>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <div class="px-8 py-4">
 | 
						|
        <div class="mb-6">
 | 
						|
            <div class="flex flex-col items-center space-y-2 mb-4">
 | 
						|
                <div class="bg-cyan-100 rounded-full p-3">
 | 
						|
                    <i data-lucide="camera" class="w-7 h-7 text-cyan-500"></i>
 | 
						|
                </div>
 | 
						|
                <h2 class="text-xl font-bold text-cyan-500">Scan Struk Otomatis</h2>
 | 
						|
                <p class="text-sm text-gray-500 text-center">Arahkan kamera ke struk atau upload foto struk untuk membaca data secara otomatis.</p>
 | 
						|
        </div>
 | 
						|
 | 
						|
            <div id="ocr-processing" class="hidden bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
 | 
						|
                <div class="flex items-center">
 | 
						|
                    <div class="loading-spinner-small mr-2"></div>
 | 
						|
                    <span class="text-yellow-800 text-sm">Memproses teks dari struk...</span>
 | 
						|
                </div>
 | 
						|
            </div>  
 | 
						|
            
 | 
						|
 | 
						|
            <div id="scan-success" class="hidden bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
 | 
						|
                <div class="flex items-center">
 | 
						|
                    <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
						|
                    <span class="text-green-800 font-medium">Data struk berhasil diisi otomatis!</span>
 | 
						|
                </div>
 | 
						|
                <p class="text-green-700 text-sm mt-1">Scanning dihentikan. Periksa form di bawah dan lengkapi data jika diperlukan.</p>
 | 
						|
                <p class="text-green-700 text-xs mt-1">💡 Untuk scan ulang, "Upload Foto Struk"</p>
 | 
						|
            </div>
 | 
						|
 | 
						|
 | 
						|
            <div class="scanner-container mb-4" style="height: 300px;">
 | 
						|
                <div id="scanner-container" class="w-full h-full relative bg-gray-900 rounded-lg overflow-hidden">
 | 
						|
                    <div id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
 | 
						|
                        <div class="text-center text-white">
 | 
						|
                            <div class="loading-spinner mx-auto mb-2"></div>
 | 
						|
                            <p class="text-sm">Memuat scanner...</p>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
 | 
						|
            <div class="space-y-3 mb-4">
 | 
						|
                @* <button id="start-scanner" type="button" class="w-full bg-cyan-500 hover:bg-cyan-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner">
 | 
						|
                    <i class="w-5 h-5 inline mr-2" data-lucide="camera"></i>
 | 
						|
                    Mulai Scan Struk
 | 
						|
                </button> *@
 | 
						|
                
 | 
						|
                <button id="stop-scanner" type="button" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner hidden">
 | 
						|
                    <i class="w-5 h-5 inline mr-2" data-lucide="camera-off"></i>
 | 
						|
                    Hentikan Scan
 | 
						|
                </button>
 | 
						|
 | 
						|
               
 | 
						|
                    @* <div class="flex items-center">
 | 
						|
                        <div class="flex-1 border-t border-gray-200"></div>
 | 
						|
                        <span class="px-3 text-sm text-gray-500 bg-white">atau</span>
 | 
						|
                        <div class="flex-1 border-t border-gray-200"></div>
 | 
						|
                    </div> *@
 | 
						|
                    <label for="file-upload" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-4 rounded-lg transition-colors cursor-pointer flex items-center justify-center gap-2 upload-label">
 | 
						|
                        <i class="w-5 h-5" data-lucide="upload"></i>
 | 
						|
                        Upload Foto Struk
 | 
						|
                    </label>
 | 
						|
                    <input 
 | 
						|
                        type="file" 
 | 
						|
                        id="file-upload" 
 | 
						|
                        accept="image/*" 
 | 
						|
                        class="hidden"
 | 
						|
                    />
 | 
						|
                    <p class="text-xs text-gray-500 mt-2 text-center">
 | 
						|
                        Pilih foto struk dari galeri atau ambil foto baru
 | 
						|
                    </p>
 | 
						|
         
 | 
						|
 | 
						|
                <div id="permission-info" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-3">
 | 
						|
                    <div class="flex items-start">
 | 
						|
                        <i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i>
 | 
						|
                        <div class="text-blue-800 text-sm">
 | 
						|
                            <p class="font-medium mb-1">📸 Meminta Akses Kamera...</p>
 | 
						|
                            <p class="mb-2">Browser akan meminta izin akses kamera. Pastikan untuk:</p>
 | 
						|
                            <ul class="text-xs space-y-1 list-disc list-inside">
 | 
						|
                                <li>Klik tombol <strong>"Allow"</strong> atau <strong>"Izinkan"</strong></li>
 | 
						|
                                <li>Jika popup tidak muncul, cek address bar browser</li>
 | 
						|
                                <li>Pastikan kamera tidak sedang digunakan aplikasi lain</li>
 | 
						|
                            </ul>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
 | 
						|
                <div id="permission-denied" class="hidden bg-red-50 border border-red-200 rounded-lg p-3">
 | 
						|
                    <div class="flex items-start">
 | 
						|
                        <i class="w-5 h-5 text-red-600 mr-2 mt-0.5" data-lucide="alert-triangle"></i>
 | 
						|
                        <div class="text-red-800 text-sm">
 | 
						|
                            <p class="font-medium mb-1">Akses Kamera Ditolak</p>
 | 
						|
                            <p class="mb-2">Untuk menggunakan scanner, aktifkan akses kamera:</p>
 | 
						|
                            <ol class="list-decimal list-inside space-y-1 text-xs">
 | 
						|
                                <li>Klik ikon kunci/kamera di address bar browser</li>
 | 
						|
                                <li>Pilih "Allow" atau "Izinkan" untuk kamera</li>
 | 
						|
                                <li>Refresh halaman dan coba lagi</li>
 | 
						|
                            </ol>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
 | 
						|
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div class="flex items-center my-6">
 | 
						|
            <div class="flex-1 border-t border-gray-200"></div>
 | 
						|
            <span class="px-3 text-sm text-gray-500 bg-white">Hasil Scan</span>
 | 
						|
            <div class="flex-1 border-t border-gray-200"></div>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
 | 
						|
     <form action="@Url.Action("Struk", "SpjDriver")" method="post" class="px-8 py-4 space-y-6 bg-white">
 | 
						|
            <div class="flex flex-col items-center space-y-2">
 | 
						|
                <div class="bg-cyan-100 rounded-full p-3">
 | 
						|
                    <i data-lucide="edit-3" class="w-7 h-7 text-cyan-500"></i>
 | 
						|
                </div>
 | 
						|
                <h2 class="text-xl font-bold text-cyan-500">Data Struk</h2>
 | 
						|
                <p class="text-sm text-gray-500 text-center">Periksa dan lengkapi data struk sebelum submit.</p>
 | 
						|
            </div>
 | 
						|
            
 | 
						|
            <div class="grid grid-cols-1 gap-4">
 | 
						|
                <!-- Nomor Struk -->
 | 
						|
                <div>
 | 
						|
                    <label for="NomorStruk" class="block text-sm font-medium text-gray-700 mb-1">Nomor Struk</label>
 | 
						|
                    <input 
 | 
						|
                        type="text" 
 | 
						|
                        id="NomorStruk" 
 | 
						|
                        name="NomorStruk" 
 | 
						|
                        class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                        required 
 | 
						|
                        placeholder="8001441" 
 | 
						|
                    />
 | 
						|
                    <p class="text-xs text-gray-500 mt-1">Nomor tanpa prefix (contoh: 8001441)</p>
 | 
						|
                </div>
 | 
						|
 | 
						|
                <!-- Nomor Polisi -->
 | 
						|
                <div>
 | 
						|
                    <label for="NomorPolisi" class="block text-sm font-medium text-gray-700 mb-1">Nomor Polisi</label>
 | 
						|
                    <input 
 | 
						|
                        type="text" 
 | 
						|
                        id="NomorPolisi" 
 | 
						|
                        name="NomorPolisi" 
 | 
						|
                        class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                        placeholder="B 9125 PJA" 
 | 
						|
                    />
 | 
						|
                </div>
 | 
						|
 | 
						|
                <!-- Penugasan -->
 | 
						|
                <div>
 | 
						|
                    <label for="Penugasan" class="block text-sm font-medium text-gray-700 mb-1">Penugasan</label>
 | 
						|
                    <input 
 | 
						|
                        type="text" 
 | 
						|
                        id="Penugasan" 
 | 
						|
                        name="Penugasan" 
 | 
						|
                        class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                        placeholder="JAKARTA BARAT" 
 | 
						|
                    />
 | 
						|
                </div>
 | 
						|
 | 
						|
                <!-- Waktu Masuk dan Keluar -->
 | 
						|
                <div class="grid grid-cols-1 gap-3">
 | 
						|
                    <div>
 | 
						|
                        <label for="WaktuMasuk" class="block text-sm font-medium text-gray-700 mb-1">Masuk</label>
 | 
						|
                        <input 
 | 
						|
                            type="text" 
 | 
						|
                            id="WaktuMasuk" 
 | 
						|
                            name="WaktuMasuk" 
 | 
						|
                            class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                            placeholder="2025-08-04, 08:13:51" 
 | 
						|
                        />
 | 
						|
                    </div>
 | 
						|
                    <div>
 | 
						|
                        <label for="WaktuKeluar" class="block text-sm font-medium text-gray-700 mb-1">Keluar</label>
 | 
						|
                        <input 
 | 
						|
                            type="text" 
 | 
						|
                            id="WaktuKeluar" 
 | 
						|
                            name="WaktuKeluar" 
 | 
						|
                            class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                            placeholder="2025-08-04, 14:35:10" 
 | 
						|
                        />
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
 | 
						|
                <!-- Berat -->
 | 
						|
                <div class="grid grid-cols-2 gap-3">
 | 
						|
                    <div>
 | 
						|
                        <label for="BeratMasuk" class="block text-sm font-medium text-gray-700 mb-1">Berat Masuk (kg)</label>
 | 
						|
                        <input 
 | 
						|
                            type="number" 
 | 
						|
                            id="BeratMasuk" 
 | 
						|
                            name="BeratMasuk" 
 | 
						|
                            class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                            placeholder="23280" 
 | 
						|
                        />
 | 
						|
                    </div>
 | 
						|
                    <div>
 | 
						|
                        <label for="BeratKeluar" class="block text-sm font-medium text-gray-700 mb-1">Berat Keluar (kg)</label>
 | 
						|
                        <input 
 | 
						|
                            type="number" 
 | 
						|
                            id="BeratKeluar" 
 | 
						|
                            name="BeratKeluar" 
 | 
						|
                            class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                            placeholder="13540" 
 | 
						|
                        />
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
 | 
						|
                <!-- Berat Nett -->
 | 
						|
                <div>
 | 
						|
                    <label for="BeratNett" class="block text-sm font-medium text-gray-700 mb-1">Berat Nett (kg)</label>
 | 
						|
                    <input 
 | 
						|
                        type="number" 
 | 
						|
                        id="BeratNett" 
 | 
						|
                        name="BeratNett" 
 | 
						|
                        class="mt-1 block w-full rounded-lg border border-cyan-300 shadow-sm focus:border-cyan-500 focus:ring-2 focus:ring-cyan-200 transition-all duration-150 px-4 py-2" 
 | 
						|
                        required 
 | 
						|
                        placeholder="9740" 
 | 
						|
                    />
 | 
						|
                    <p class="text-xs text-gray-500 mt-1">Berat Nett wajib diisi</p>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
            
 | 
						|
            <button type="submit" class="w-full bg-gradient-to-r from-cyan-500 to-cyan-400 text-white py-3 rounded-lg font-semibold shadow hover:from-cyan-600 hover:to-cyan-500 transition-all duration-150 flex items-center justify-center gap-2">
 | 
						|
                <i data-lucide="send" class="w-5 h-5"></i>
 | 
						|
                Submit Data Struk
 | 
						|
            </button>
 | 
						|
        </form>
 | 
						|
    
 | 
						|
        <partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
 | 
						|
 | 
						|
</div>
 | 
						|
 | 
						|
<register-block dynamic-section="scripts" key="jsSubmitStruk">
 | 
						|
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.1.1/dist/tesseract.min.js"></script>
 | 
						|
<script>
 | 
						|
    document.addEventListener('DOMContentLoaded', function() {
 | 
						|
        const nomorStrukInput = document.getElementById('NomorStruk');
 | 
						|
        const nomorPolisiInput = document.getElementById('NomorPolisi');
 | 
						|
        const penugasanInput = document.getElementById('Penugasan');
 | 
						|
        const waktuMasukInput = document.getElementById('WaktuMasuk');
 | 
						|
        const waktuKeluarInput = document.getElementById('WaktuKeluar');
 | 
						|
        const beratMasukInput = document.getElementById('BeratMasuk');
 | 
						|
        const beratKeluarInput = document.getElementById('BeratKeluar');
 | 
						|
        const beratNettInput = document.getElementById('BeratNett');
 | 
						|
 | 
						|
        nomorStrukInput.addEventListener('input', function() {
 | 
						|
            this.value = this.value.replace(/[^0-9]/g, '');
 | 
						|
        });
 | 
						|
 | 
						|
        beratMasukInput.addEventListener('input', function() {
 | 
						|
            this.value = this.value.replace(/[^0-9]/g, '');
 | 
						|
        });
 | 
						|
 | 
						|
        beratKeluarInput.addEventListener('input', function() {
 | 
						|
            this.value = this.value.replace(/[^0-9]/g, '');
 | 
						|
        });
 | 
						|
 | 
						|
        beratNettInput.addEventListener('input', function() {
 | 
						|
            this.value = this.value.replace(/[^0-9]/g, '');
 | 
						|
        });
 | 
						|
 | 
						|
        class ReceiptScanner {
 | 
						|
            constructor() {
 | 
						|
                this.isScanning = false;
 | 
						|
                this.stream = null;
 | 
						|
                this.video = null;
 | 
						|
                this.canvas = null;
 | 
						|
                this.ctx = null;
 | 
						|
                this.detectedData = {
 | 
						|
                    receiptNumber: '',
 | 
						|
                    truckNumber: '',
 | 
						|
                    assignment: '',
 | 
						|
                    entryTime: '',
 | 
						|
                    exitTime: '',
 | 
						|
                    weightIn: '',
 | 
						|
                    weightOut: '',
 | 
						|
                    weightNett: ''
 | 
						|
                };
 | 
						|
                this.ocrTimeout = null;
 | 
						|
                this.initializeElements();
 | 
						|
                this.bindEvents();
 | 
						|
                this.checkBrowserSupport();
 | 
						|
            }
 | 
						|
 | 
						|
            initializeElements() {
 | 
						|
                console.log('Initializing elements...');
 | 
						|
                
 | 
						|
                this.startBtn = document.getElementById('start-scanner');
 | 
						|
                this.stopBtn = document.getElementById('stop-scanner');
 | 
						|
                this.loadingDiv = document.getElementById('loading-scanner');
 | 
						|
                this.ocrProcessing = document.getElementById('ocr-processing');
 | 
						|
                this.ocrResult = document.getElementById('ocr-result');
 | 
						|
                this.permissionInfo = document.getElementById('permission-info');
 | 
						|
                this.permissionDenied = document.getElementById('permission-denied');
 | 
						|
                this.scanSuccess = document.getElementById('scan-success');
 | 
						|
 | 
						|
                this.scannerContainer = document.getElementById('scanner-container');
 | 
						|
                this.fileUpload = document.getElementById('file-upload');
 | 
						|
 | 
						|
                console.log('Elements found:', {
 | 
						|
                    startBtn: !!this.startBtn,
 | 
						|
                    stopBtn: !!this.stopBtn,
 | 
						|
                    loadingDiv: !!this.loadingDiv,
 | 
						|
                    ocrProcessing: !!this.ocrProcessing,
 | 
						|
                    permissionInfo: !!this.permissionInfo,
 | 
						|
                    permissionDenied: !!this.permissionDenied,
 | 
						|
                    scanSuccess: !!this.scanSuccess,
 | 
						|
                    scannerContainer: !!this.scannerContainer,
 | 
						|
                    fileUpload: !!this.fileUpload
 | 
						|
                });
 | 
						|
 | 
						|
                if (!this.fileUpload) {
 | 
						|
                    console.error('CRITICAL: File upload input not found! Looking for #file-upload');
 | 
						|
                    console.log('Available file inputs:', document.querySelectorAll('input[type="file"]'));
 | 
						|
                    console.log('All elements with file-upload in ID:', document.querySelectorAll('[id*="file-upload"]'));
 | 
						|
                    console.log('DOM ready state:', document.readyState);
 | 
						|
                    
 | 
						|
                    const altFileInput = document.querySelector('input[type="file"]');
 | 
						|
                    if (altFileInput) {
 | 
						|
                        console.log('Found file input via type selector:', altFileInput);
 | 
						|
                        this.fileUpload = altFileInput;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (!this.scannerContainer) {
 | 
						|
                    console.error('CRITICAL: Scanner container not found! Looking for #scanner-container');
 | 
						|
                    console.log('Available containers:', document.querySelectorAll('[id*="scanner"]'));
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            bindEvents() {
 | 
						|
                console.log('Binding events...');
 | 
						|
                
 | 
						|
                if (this.startBtn) {
 | 
						|
                    this.startBtn.addEventListener('click', () => this.startScanner());
 | 
						|
                    console.log('Start scanner button bound');
 | 
						|
                } else {
 | 
						|
                    console.error('Start scanner button not found!');
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (this.stopBtn) {
 | 
						|
                    this.stopBtn.addEventListener('click', () => this.stopScanner());
 | 
						|
                    console.log('Stop scanner button bound');
 | 
						|
                } else {
 | 
						|
                    console.error('Stop scanner button not found!');
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (this.fileUpload) {
 | 
						|
                    console.log('File upload element found, binding change event...');
 | 
						|
                    this.fileUpload.addEventListener('change', (e) => {
 | 
						|
                        console.log('File upload change event triggered!');
 | 
						|
                        this.handleFileUpload(e);
 | 
						|
                    });
 | 
						|
                    console.log('File upload input bound successfully');
 | 
						|
                } else {
 | 
						|
                    console.error('CRITICAL: File upload input not found! Element ID: file-upload');
 | 
						|
                    const fileInput = document.querySelector('#file-upload');
 | 
						|
                    if (fileInput) {
 | 
						|
                        console.log('Found file input via querySelector, binding now...');
 | 
						|
                        this.fileUpload = fileInput;
 | 
						|
                        this.fileUpload.addEventListener('change', (e) => {
 | 
						|
                            console.log('File upload change event triggered via querySelector!');
 | 
						|
                            this.handleFileUpload(e);
 | 
						|
                        });
 | 
						|
                    } else {
 | 
						|
                        console.error('File upload input still not found via querySelector');
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            checkBrowserSupport() {
 | 
						|
                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
 | 
						|
                    this.disableScanner('Browser Tidak Didukung');
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
 | 
						|
                    this.showWarning('Scanner berfungsi optimal dengan koneksi HTTPS yang aman.');
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            disableScanner(message) {
 | 
						|
                this.startBtn.disabled = true;
 | 
						|
                this.startBtn.innerHTML = `<i class="w-5 h-5 inline mr-2" data-lucide="x-circle"></i>${message}`;
 | 
						|
                this.startBtn.classList.remove('bg-cyan-500', 'hover:bg-cyan-600');
 | 
						|
                this.startBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
 | 
						|
            }
 | 
						|
 | 
						|
            async startScanner() {
 | 
						|
                try {
 | 
						|
                    this.showLoading();
 | 
						|
                    this.hideMessages();
 | 
						|
                    this.permissionInfo.classList.remove('hidden');
 | 
						|
 | 
						|
                    this.stream = await navigator.mediaDevices.getUserMedia({
 | 
						|
                        video: { 
 | 
						|
                            facingMode: 'environment',
 | 
						|
                            width: { ideal: 1280 },
 | 
						|
                            height: { ideal: 720 }
 | 
						|
                        }
 | 
						|
                    });
 | 
						|
 | 
						|
                    this.setupVideoElement();
 | 
						|
                    this.isScanning = true;
 | 
						|
                    this.startBtn.classList.add('hidden');
 | 
						|
                    this.stopBtn.classList.remove('hidden');
 | 
						|
                    this.hideLoading();
 | 
						|
                    this.hideMessages();
 | 
						|
 | 
						|
                    this.startContinuousCapture();
 | 
						|
 | 
						|
                    if (this.ocrTimeout) clearTimeout(this.ocrTimeout);
 | 
						|
                    this.ocrTimeout = setTimeout(() => {
 | 
						|
                        if (this.isScanning && !this.hasDetectedData()) {
 | 
						|
                            this.stopScanner();
 | 
						|
                            this.showError('Gagal scan struk. Silakan input data secara manual.');
 | 
						|
                        }
 | 
						|
                    }, 30000);
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    this.handleCameraError(error);
 | 
						|
                    this.hideLoading();
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            hasDetectedData() {
 | 
						|
                const d = this.detectedData;
 | 
						|
                return d && (d.receiptNumber || d.truckNumber || d.assignment || d.entryTime || d.exitTime || d.weightIn || d.weightOut || d.weightNett);
 | 
						|
            }
 | 
						|
 | 
						|
            setupVideoElement() {
 | 
						|
                this.video = document.createElement('video');
 | 
						|
                this.video.autoplay = true;
 | 
						|
                this.video.playsInline = true;
 | 
						|
                this.video.muted = true;
 | 
						|
                this.video.srcObject = this.stream;
 | 
						|
                
 | 
						|
                this.video.className = 'w-full h-full object-cover rounded-lg';
 | 
						|
                
 | 
						|
                this.scannerContainer.innerHTML = '';
 | 
						|
                this.scannerContainer.appendChild(this.video);
 | 
						|
 | 
						|
                this.canvas = document.createElement('canvas');
 | 
						|
                this.ctx = this.canvas.getContext('2d');
 | 
						|
            }
 | 
						|
 | 
						|
            startContinuousCapture() {
 | 
						|
                const captureInterval = setInterval(() => {
 | 
						|
                    if (!this.isScanning) {
 | 
						|
                        clearInterval(captureInterval);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                    this.captureAndProcessFrame();
 | 
						|
                }, 3000);
 | 
						|
            }
 | 
						|
 | 
						|
            async captureAndProcessFrame() {
 | 
						|
                if (!this.video || !this.canvas || !this.isScanning) return;
 | 
						|
 | 
						|
                try {
 | 
						|
                    this.canvas.width = this.video.videoWidth;
 | 
						|
                    this.canvas.height = this.video.videoHeight;
 | 
						|
                    
 | 
						|
                    this.ctx.drawImage(this.video, 0, 0);
 | 
						|
                    
 | 
						|
                    this.canvas.toBlob(async (blob) => {
 | 
						|
                        await this.processImageWithOCR(blob);
 | 
						|
                    }, 'image/jpeg', 0.8);
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    console.error('Error capturing frame:', error);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            setupVideoElement() {
 | 
						|
                this.video = document.createElement('video');
 | 
						|
                this.video.autoplay = true;
 | 
						|
                this.video.playsInline = true;
 | 
						|
                this.video.muted = true;
 | 
						|
                this.video.srcObject = this.stream;
 | 
						|
                
 | 
						|
                this.video.className = 'w-full h-full object-cover rounded-lg';
 | 
						|
                
 | 
						|
                this.scannerContainer.innerHTML = '';
 | 
						|
                this.scannerContainer.appendChild(this.video);
 | 
						|
 | 
						|
                this.canvas = document.createElement('canvas');
 | 
						|
                this.ctx = this.canvas.getContext('2d');
 | 
						|
            }
 | 
						|
 | 
						|
            startContinuousCapture() {
 | 
						|
                const captureInterval = setInterval(() => {
 | 
						|
                    if (!this.isScanning) {
 | 
						|
                        clearInterval(captureInterval);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                    this.captureAndProcessFrame();
 | 
						|
                }, 3000);
 | 
						|
            }
 | 
						|
 | 
						|
            async captureAndProcessFrame() {
 | 
						|
                if (!this.video || !this.canvas || !this.isScanning) return;
 | 
						|
 | 
						|
                try {
 | 
						|
                    this.canvas.width = this.video.videoWidth;
 | 
						|
                    this.canvas.height = this.video.videoHeight;
 | 
						|
                    
 | 
						|
                    this.ctx.drawImage(this.video, 0, 0);
 | 
						|
                    
 | 
						|
                    this.canvas.toBlob(async (blob) => {
 | 
						|
                        await this.processImageWithOCR(blob);
 | 
						|
                    }, 'image/jpeg', 0.8);
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    console.error('Error capturing frame:', error);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            async processImageWithOCR(imageInput) {
 | 
						|
                try {
 | 
						|
                    this.showOcrProcessing();
 | 
						|
 | 
						|
                    const { data: { text } } = await Tesseract.recognize(
 | 
						|
                        imageInput,
 | 
						|
                        'ind+eng',
 | 
						|
                        {
 | 
						|
                            logger: m => {
 | 
						|
                                if (m.status === 'recognizing text') {
 | 
						|
                                }
 | 
						|
                            },
 | 
						|
                            tessedit_pageseg_mode: Tesseract.PSM.AUTO,
 | 
						|
                            tessedit_char_whitelist: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz :.,/-_()kg',
 | 
						|
                        }
 | 
						|
                    );
 | 
						|
 | 
						|
                    await this.extractDataFromText(text);
 | 
						|
                    this.hideOcrProcessing();
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    this.hideOcrProcessing();
 | 
						|
                    this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.');
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            async extractDataFromText(text) {
 | 
						|
                const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
 | 
						|
                
 | 
						|
                let receiptNumber = '';
 | 
						|
                let truckNumber = '';
 | 
						|
                let assignment = '';
 | 
						|
                let entryTime = '';
 | 
						|
                let exitTime = '';
 | 
						|
                let weightIn = '';
 | 
						|
                let weightOut = '';
 | 
						|
                let weightNett = '';
 | 
						|
 | 
						|
                const receiptPatterns = [
 | 
						|
                    /(\d{2})_(\d{6,})/gi,                    
 | 
						|
                    /(\d{2})\s+(\d{6,})/gi,             
 | 
						|
                    /(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{6,})/gi, 
 | 
						|
                    /(?:^|\s)(\d{6,10})(?:\s|$)/g,         
 | 
						|
                ];
 | 
						|
 | 
						|
                for (const line of lines) {
 | 
						|
                    console.log(`Processing line for receipt: "${line}"`);
 | 
						|
                    
 | 
						|
                    const monthUnderscoreMatch = line.match(/(\d{2})_(\d{6,})/);
 | 
						|
                    if (monthUnderscoreMatch && monthUnderscoreMatch[2]) {
 | 
						|
                        receiptNumber = monthUnderscoreMatch[2]; 
 | 
						|
                        console.log(`Found receipt number: ${receiptNumber} using month-underscore pattern (removed prefix: ${monthUnderscoreMatch[1]}_)`);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    const monthSpaceMatch = line.match(/(\d{2})\s+(\d{6,})/);
 | 
						|
                    if (monthSpaceMatch && monthSpaceMatch[2]) {
 | 
						|
                        receiptNumber = monthSpaceMatch[2]; // Take ONLY the number after space
 | 
						|
                        console.log(`Found receipt number: ${receiptNumber} using month-space pattern (removed prefix: ${monthSpaceMatch[1]} )`);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    for (const pattern of receiptPatterns.slice(2)) {
 | 
						|
                        const matches = [...line.matchAll(pattern)];
 | 
						|
                        for (const match of matches) {
 | 
						|
                            if (match[1] && match[1].length >= 6) {
 | 
						|
                                receiptNumber = match[1];
 | 
						|
                                console.log(`Found receipt number: ${receiptNumber} using context pattern: ${pattern}`);
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (receiptNumber) break;
 | 
						|
                    }
 | 
						|
                    if (receiptNumber) break;
 | 
						|
                }
 | 
						|
 | 
						|
                const truckPatterns = [
 | 
						|
                    /([A-Z]\s+\d{1,4}\s+[A-Z]{2,3})/gi, // "B 9125 PJA" (with spaces)
 | 
						|
                    /([A-Z]\d{1,4}\s+[A-Z]{2,3})/gi, // "B9125 PJA" (no space before number)
 | 
						|
                    /([A-Z]{1,2}\s*\d{1,4}\s*[A-Z]{1,3})/gi, // Flexible spacing
 | 
						|
                    
 | 
						|
                    /(?:no.*pol|nopol|nomor.*polisi|no.*truk|nomor.*truk)[\s.:]*([A-Z]{1,2}\s*\d{1,4}\s*[A-Z]{1,3})/gi,
 | 
						|
                    
 | 
						|
                    /([A-Z]{1,2}\d{3,4}[A-Z]{2,3})/gi, // No spaces at all
 | 
						|
                    
 | 
						|
                    /([A-Z])\s+(\d{3,4})\s+([A-Z]{2,3})/gi, // Separate capture groups
 | 
						|
                ];
 | 
						|
 | 
						|
                for (const line of lines) {
 | 
						|
                    const lowerLine = line.toLowerCase();
 | 
						|
                    console.log(`Processing line for truck: "${line}"`);
 | 
						|
                    
 | 
						|
                    if (lowerLine.includes('nopol') || 
 | 
						|
                        lowerLine.includes('no pol') ||
 | 
						|
                        lowerLine.includes('nomor polisi') ||
 | 
						|
                        lowerLine.includes('no truk') ||
 | 
						|
                        lowerLine.includes('nomor truk') ||
 | 
						|
                        lowerLine.includes('polisi')) {
 | 
						|
                        console.log('Found truck context line:', line);
 | 
						|
                        
 | 
						|
                        for (const pattern of truckPatterns) {
 | 
						|
                            const match = line.match(pattern);
 | 
						|
                            if (match) {
 | 
						|
                                let foundTruck = '';
 | 
						|
                                
 | 
						|
                                if (match.length === 4 && match[1] && match[2] && match[3]) {
 | 
						|
                                    foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
 | 
						|
                                } else if (match[1]) {
 | 
						|
                                    foundTruck = match[1].trim();
 | 
						|
                                }
 | 
						|
                                
 | 
						|
                                if (foundTruck) {
 | 
						|
                                    foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3');
 | 
						|
                                    foundTruck = foundTruck.replace(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
 | 
						|
                                    foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
 | 
						|
                                    
 | 
						|
                                    if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
 | 
						|
                                        truckNumber = foundTruck;
 | 
						|
                                        console.log(`Found truck number: "${truckNumber}" using context pattern`);
 | 
						|
                                        break;
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (truckNumber) break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (!truckNumber) {
 | 
						|
                    console.log('No truck number found in context lines, trying all lines...');
 | 
						|
                    for (const line of lines) {
 | 
						|
                        console.log(`Scanning line for truck pattern: "${line}"`);
 | 
						|
                        
 | 
						|
                        if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) {
 | 
						|
                            console.log('Potential truck pattern found:', line);
 | 
						|
                            
 | 
						|
                            for (const pattern of truckPatterns) {
 | 
						|
                                const match = line.match(pattern);
 | 
						|
                                if (match) {
 | 
						|
                                    let foundTruck = '';
 | 
						|
                                    
 | 
						|
                                    if (match.length === 4 && match[1] && match[2] && match[3]) {
 | 
						|
                                        foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
 | 
						|
                                    } else if (match[1]) {
 | 
						|
                                        foundTruck = match[1].trim();
 | 
						|
                                    }
 | 
						|
                                    
 | 
						|
                                    if (foundTruck) {
 | 
						|
                                        foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3');
 | 
						|
                                        foundTruck = foundTruck.replace(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
 | 
						|
                                        foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
 | 
						|
                                        
 | 
						|
                                        if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
 | 
						|
                                            truckNumber = foundTruck;
 | 
						|
                                            console.log(`Found truck number: "${truckNumber}" using general pattern`);
 | 
						|
                                            break;
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            if (truckNumber) break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (!truckNumber) {
 | 
						|
                    console.log('Still no truck number, trying loose patterns...');
 | 
						|
                    for (const line of lines) {
 | 
						|
                        const looseMatch = line.match(/([A-Z]{1,2})\s*(\d{3,4})\s*([A-Z]{2,3})/i);
 | 
						|
                        if (looseMatch && looseMatch[1] && looseMatch[2] && looseMatch[3]) {
 | 
						|
                            const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`;
 | 
						|
                            console.log(`Found potential truck number with loose pattern: "${candidate}"`);
 | 
						|
                            
 | 
						|
                            if (looseMatch[1].length <= 2 && 
 | 
						|
                                looseMatch[2].length >= 3 && looseMatch[2].length <= 4 &&
 | 
						|
                                looseMatch[3].length >= 2 && looseMatch[3].length <= 3) {
 | 
						|
                                truckNumber = candidate;
 | 
						|
                                console.log(`Accepted truck number: "${truckNumber}"`);
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                console.log('Truck detection results:', { truckNumber });
 | 
						|
                
 | 
						|
                console.log('Truck detection result:', truckNumber);
 | 
						|
 | 
						|
                const assignmentPatterns = [
 | 
						|
                    /(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi,
 | 
						|
                    /(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas
 | 
						|
                    /(BANDUNG|SURABAYA|MEDAN|SEMARANG|PALEMBANG|MAKASSAR)[\s\w]*/gi,
 | 
						|
                ];
 | 
						|
 | 
						|
                for (const line of lines) {
 | 
						|
                    console.log(`Processing line for assignment: "${line}"`);
 | 
						|
                    
 | 
						|
                    if (line.toLowerCase().includes('penugasan')) {
 | 
						|
                        console.log('Found penugasan line:', line);
 | 
						|
                        
 | 
						|
                        const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i);
 | 
						|
                        if (assignmentMatch && assignmentMatch[1]) {
 | 
						|
                            assignment = assignmentMatch[1].trim();
 | 
						|
                            console.log(`Found assignment: ${assignment}`);
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) {
 | 
						|
                        assignment = 'JAKARTA BARAT';
 | 
						|
                        console.log(`Found assignment: ${assignment} from Jakarta Barat line`);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    for (const pattern of assignmentPatterns) {
 | 
						|
                        const match = line.match(pattern);
 | 
						|
                        if (match && match[1]) {
 | 
						|
                            assignment = match[1].trim();
 | 
						|
                            console.log(`Found assignment: ${assignment} using pattern: ${pattern}`);
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    if (assignment) break;
 | 
						|
                }
 | 
						|
                
 | 
						|
                console.log('Assignment detection result:', assignment);
 | 
						|
 | 
						|
                // Enhanced time patterns
 | 
						|
                const timePatterns = [
 | 
						|
                    /(\d{1,2}\s+\w{3}\s+\d{4},\s*\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025, 08:13:51
 | 
						|
                    /(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025 08:13:51
 | 
						|
                    /(\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04/08/2025 08:13:51
 | 
						|
                    /(\d{1,2}-\d{1,2}-\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04-08-2025 08:13:51
 | 
						|
                    /(\d{4}-\d{1,2}-\d{1,2},\s*\d{1,2}:\d{2}:\d{2})/gi, // 2025-08-04, 08:13:51
 | 
						|
                    /(\d{4}\/\d{1,2}\/\d{1,2}\s+\d{1,2}:\d{2}:\d{2})/gi, // 2025/08/04 08:13:51
 | 
						|
                ];
 | 
						|
 | 
						|
                const convertToStandardFormat = (dateTimeString) => {
 | 
						|
                    if (!dateTimeString) return '';
 | 
						|
                    
 | 
						|
                    console.log('Converting date string:', dateTimeString);
 | 
						|
                    
 | 
						|
                    const monthNamePattern = /(\d{1,2})\s+(\w{3})\s+(\d{4})[,\s]+(\d{1,2}:\d{2}:\d{2})/i;
 | 
						|
                    const monthNameMatch = dateTimeString.match(monthNamePattern);
 | 
						|
                    if (monthNameMatch) {
 | 
						|
                        const day = monthNameMatch[1].padStart(2, '0');
 | 
						|
                        const monthName = monthNameMatch[2].toLowerCase();
 | 
						|
                        const year = monthNameMatch[3];
 | 
						|
                        const time = monthNameMatch[4];
 | 
						|
                        
 | 
						|
                        const monthMap = {
 | 
						|
                            'jan': '01', 'feb': '02', 'mar': '03', 'apr': '04',
 | 
						|
                            'may': '05', 'jun': '06', 'jul': '07', 'aug': '08',
 | 
						|
                            'sep': '09', 'oct': '10', 'nov': '11', 'dec': '12'
 | 
						|
                        };
 | 
						|
                        
 | 
						|
                        const month = monthMap[monthName] || '01';
 | 
						|
                        const converted = `${year}-${month}-${day}, ${time}`;
 | 
						|
                        console.log('Converted month name format:', converted);
 | 
						|
                        return converted;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    const ddmmyyyyPattern = /(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}:\d{2}:\d{2})/;
 | 
						|
                    const ddmmyyyyMatch = dateTimeString.match(ddmmyyyyPattern);
 | 
						|
                    if (ddmmyyyyMatch) {
 | 
						|
                        const day = ddmmyyyyMatch[1].padStart(2, '0');
 | 
						|
                        const month = ddmmyyyyMatch[2].padStart(2, '0');
 | 
						|
                        const year = ddmmyyyyMatch[3];
 | 
						|
                        const time = ddmmyyyyMatch[4];
 | 
						|
                        const converted = `${year}-${month}-${day}, ${time}`;
 | 
						|
                        console.log('Converted DD/MM/YYYY format:', converted);
 | 
						|
                        return converted;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    const ddmmyyyyDashPattern = /(\d{1,2})-(\d{1,2})-(\d{4})\s+(\d{1,2}:\d{2}:\d{2})/;
 | 
						|
                    const ddmmyyyyDashMatch = dateTimeString.match(ddmmyyyyDashPattern);
 | 
						|
                    if (ddmmyyyyDashMatch) {
 | 
						|
                        const day = ddmmyyyyDashMatch[1].padStart(2, '0');
 | 
						|
                        const month = ddmmyyyyDashMatch[2].padStart(2, '0');
 | 
						|
                        const year = ddmmyyyyDashMatch[3];
 | 
						|
                        const time = ddmmyyyyDashMatch[4];
 | 
						|
                        const converted = `${year}-${month}-${day}, ${time}`;
 | 
						|
                        console.log('Converted DD-MM-YYYY format:', converted);
 | 
						|
                        return converted;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    const yyyymmddSlashPattern = /(\d{4})\/(\d{1,2})\/(\d{1,2})\s+(\d{1,2}:\d{2}:\d{2})/;
 | 
						|
                    const yyyymmddSlashMatch = dateTimeString.match(yyyymmddSlashPattern);
 | 
						|
                    if (yyyymmddSlashMatch) {
 | 
						|
                        const year = yyyymmddSlashMatch[1];
 | 
						|
                        const month = yyyymmddSlashMatch[2].padStart(2, '0');
 | 
						|
                        const day = yyyymmddSlashMatch[3].padStart(2, '0');
 | 
						|
                        const time = yyyymmddSlashMatch[4];
 | 
						|
                        const converted = `${year}-${month}-${day}, ${time}`;
 | 
						|
                        console.log('Converted YYYY/MM/DD format:', converted);
 | 
						|
                        return converted;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    const standardPattern = /(\d{4}-\d{1,2}-\d{1,2}),?\s*(\d{1,2}:\d{2}:\d{2})/;
 | 
						|
                    const standardMatch = dateTimeString.match(standardPattern);
 | 
						|
                    if (standardMatch) {
 | 
						|
                        const datePart = standardMatch[1];
 | 
						|
                        const timePart = standardMatch[2];
 | 
						|
                        
 | 
						|
                        const [year, month, day] = datePart.split('-');
 | 
						|
                        const paddedMonth = month.padStart(2, '0');
 | 
						|
                        const paddedDay = day.padStart(2, '0');
 | 
						|
                        
 | 
						|
                        const converted = `${year}-${paddedMonth}-${paddedDay}, ${timePart}`;
 | 
						|
                        console.log('Standardized YYYY-MM-DD format:', converted);
 | 
						|
                        return converted;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    console.log('No date pattern matched, returning original:', dateTimeString);
 | 
						|
                    return dateTimeString;
 | 
						|
                };
 | 
						|
 | 
						|
                for (const line of lines) {
 | 
						|
                    console.log(`Processing line for time: "${line}"`);
 | 
						|
                    
 | 
						|
                    // Entry time - look for "Masuk :" pattern
 | 
						|
                    if (line.toLowerCase().includes('masuk') && line.includes(':')) {
 | 
						|
                        console.log('Found masuk line:', line);
 | 
						|
                        for (const pattern of timePatterns) {
 | 
						|
                            const match = line.match(pattern);
 | 
						|
                            if (match && match[1]) {
 | 
						|
                                entryTime = convertToStandardFormat(match[1].trim());
 | 
						|
                                console.log(`Found entry time: ${entryTime}`);
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        // Also try to extract after "Masuk : "
 | 
						|
                        const masukMatch = line.match(/masuk\s*:\s*(.+)/i);
 | 
						|
                        if (masukMatch && masukMatch[1] && !entryTime) {
 | 
						|
                            entryTime = convertToStandardFormat(masukMatch[1].trim());
 | 
						|
                            console.log(`Found entry time via masuk pattern: ${entryTime}`);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    // Exit time - look for "Keluar :" pattern  
 | 
						|
                    if (line.toLowerCase().includes('keluar') && line.includes(':')) {
 | 
						|
                        console.log('Found keluar line:', line);
 | 
						|
                        for (const pattern of timePatterns) {
 | 
						|
                            const match = line.match(pattern);
 | 
						|
                            if (match && match[1]) {
 | 
						|
                                exitTime = convertToStandardFormat(match[1].trim());
 | 
						|
                                console.log(`Found exit time: ${exitTime}`);
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        // Also try to extract after "Keluar : "
 | 
						|
                        const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
 | 
						|
                        if (keluarMatch && keluarMatch[1] && !exitTime) {
 | 
						|
                            exitTime = convertToStandardFormat(keluarMatch[1].trim());
 | 
						|
                            console.log(`Found exit time via keluar pattern: ${exitTime}`);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                console.log('Time detection results:', { entryTime, exitTime });
 | 
						|
 | 
						|
                // Enhanced weight patterns - more specific for Indonesian receipts
 | 
						|
                const weightPatterns = [
 | 
						|
                    /berat\s+masuk\s*:\s*(\d+)\s*kg/gi, // Berat Masuk : 23280 kg
 | 
						|
                    /berat\s+keluar\s*:\s*(\d+)\s*kg/gi, // Berat Keluar : 13540 kg
 | 
						|
                    /berat\s+nett?\s*:\s*(\d+)\s*kg/gi, // Berat Nett : 9740 kg
 | 
						|
                    /:\s*(\d{3,6})\s*kg/gi, // : 23280 kg
 | 
						|
                    /(\d{3,6})\s*kg/gi, // 23280 kg
 | 
						|
                    /:\s*(\d{3,6})/g, // : 23280
 | 
						|
                ];
 | 
						|
 | 
						|
                for (const line of lines) {
 | 
						|
                    const lowerLine = line.toLowerCase();
 | 
						|
                    console.log(`Processing line for weight: "${line}"`);
 | 
						|
                    
 | 
						|
                    // Berat Masuk - look for exact pattern "Berat Masuk : 23280 kg"
 | 
						|
                    if (lowerLine.includes('berat masuk')) {
 | 
						|
                        console.log('Found berat masuk line:', line);
 | 
						|
                        
 | 
						|
                        // Try specific pattern first
 | 
						|
                        const specificMatch = line.match(/berat\s+masuk\s*:\s*(\d+)\s*kg/gi);
 | 
						|
                        if (specificMatch) {
 | 
						|
                            const numbers = specificMatch[0].match(/(\d+)/);
 | 
						|
                            if (numbers && numbers[1]) {
 | 
						|
                                weightIn = numbers[1];
 | 
						|
                                console.log(`Found weight in via specific pattern: ${weightIn} kg`);
 | 
						|
                            }
 | 
						|
                        } else {
 | 
						|
                            // Try general patterns
 | 
						|
                            for (const pattern of weightPatterns) {
 | 
						|
                                const match = line.match(pattern);
 | 
						|
                                if (match && match[1] && parseInt(match[1]) > 1000) {
 | 
						|
                                    weightIn = match[1];
 | 
						|
                                    console.log(`Found weight in via general pattern: ${weightIn} kg`);
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Berat Keluar - look for exact pattern "Berat Keluar : 13540 kg"
 | 
						|
                    if (lowerLine.includes('berat keluar')) {
 | 
						|
                        console.log('Found berat keluar line:', line);
 | 
						|
                        
 | 
						|
                        // Try specific pattern first
 | 
						|
                        const specificMatch = line.match(/berat\s+keluar\s*:\s*(\d+)\s*kg/gi);
 | 
						|
                        if (specificMatch) {
 | 
						|
                            const numbers = specificMatch[0].match(/(\d+)/);
 | 
						|
                            if (numbers && numbers[1]) {
 | 
						|
                                weightOut = numbers[1];
 | 
						|
                                console.log(`Found weight out via specific pattern: ${weightOut} kg`);
 | 
						|
                            }
 | 
						|
                        } else {
 | 
						|
                            // Try general patterns
 | 
						|
                            for (const pattern of weightPatterns) {
 | 
						|
                                const match = line.match(pattern);
 | 
						|
                                if (match && match[1] && parseInt(match[1]) > 1000) {
 | 
						|
                                    weightOut = match[1];
 | 
						|
                                    console.log(`Found weight out via general pattern: ${weightOut} kg`);
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Berat Nett - look for exact pattern "Berat Nett : 9740 kg"
 | 
						|
                    if ((lowerLine.includes('berat nett') || lowerLine.includes('berat net'))) {
 | 
						|
                        console.log('Found berat nett line:', line);
 | 
						|
                        
 | 
						|
                        // Try specific pattern first
 | 
						|
                        const specificMatch = line.match(/berat\s+nett?\s*:\s*(\d+)\s*kg/gi);
 | 
						|
                        if (specificMatch) {
 | 
						|
                            const numbers = specificMatch[0].match(/(\d+)/);
 | 
						|
                            if (numbers && numbers[1]) {
 | 
						|
                                weightNett = numbers[1];
 | 
						|
                                console.log(`Found weight nett via specific pattern: ${weightNett} kg`);
 | 
						|
                            }
 | 
						|
                        } else {
 | 
						|
                            // Try general patterns
 | 
						|
                            for (const pattern of weightPatterns) {
 | 
						|
                                const match = line.match(pattern);
 | 
						|
                                if (match && match[1] && parseInt(match[1]) > 100) {
 | 
						|
                                    weightNett = match[1];
 | 
						|
                                    console.log(`Found weight nett via general pattern: ${weightNett} kg`);
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                console.log('Weight detection results:', { weightIn, weightOut, weightNett });
 | 
						|
 | 
						|
                console.log('Final Detected Data before assignment:', {
 | 
						|
                    receiptNumber, truckNumber, assignment, entryTime, exitTime, 
 | 
						|
                    weightIn, weightOut, weightNett
 | 
						|
                }); // Debug log
 | 
						|
 | 
						|
                // Update detected data with explicit logging
 | 
						|
                this.detectedData = {
 | 
						|
                    receiptNumber: receiptNumber || '',
 | 
						|
                    truckNumber: truckNumber || '',
 | 
						|
                    assignment: assignment || '',
 | 
						|
                    entryTime: entryTime || '',
 | 
						|
                    exitTime: exitTime || '',
 | 
						|
                    weightIn: weightIn || '',
 | 
						|
                    weightOut: weightOut || '',
 | 
						|
                    weightNett: weightNett || ''
 | 
						|
                };
 | 
						|
 | 
						|
                console.log('Final Detected Data after assignment:', this.detectedData);
 | 
						|
 | 
						|
                // If we found any data, apply directly to form
 | 
						|
                if (receiptNumber || truckNumber || assignment || weightNett) {
 | 
						|
                    console.log('Data found, applying directly to form');
 | 
						|
                    this.applyDetectedDataDirectly();
 | 
						|
                    this.playSuccessSound();
 | 
						|
                    this.vibrate();
 | 
						|
                    
 | 
						|
                    // Show success message
 | 
						|
                    this.showScanSuccess();
 | 
						|
                    
 | 
						|
                    // Auto-stop scanning after successful detection
 | 
						|
                    console.log('Auto-stopping scanner after successful data detection');
 | 
						|
                    setTimeout(() => {
 | 
						|
                        if (this.isScanning) {
 | 
						|
                            console.log('Scanner stopped automatically. User can click "Mulai Scan Struk" to scan again.');
 | 
						|
                            this.stopScanner();
 | 
						|
                        }
 | 
						|
                    }, 1000);
 | 
						|
                    
 | 
						|
                    // Scroll to form to show filled data
 | 
						|
                    setTimeout(() => {
 | 
						|
                        const formSection = document.querySelector('form');
 | 
						|
                        if (formSection) {
 | 
						|
                            formSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
 | 
						|
                        }
 | 
						|
                    }, 1500);
 | 
						|
                } else {
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            applyDetectedDataDirectly() {
 | 
						|
                console.log('=== APPLYING DATA DIRECTLY TO FORM ===');
 | 
						|
                console.log('Data to apply:', this.detectedData);
 | 
						|
                
 | 
						|
                // Get input elements by ID directly
 | 
						|
                const nomorStrukInput = document.getElementById('NomorStruk');
 | 
						|
                const nomorPolisiInput = document.getElementById('NomorPolisi');
 | 
						|
                const penugasanInput = document.getElementById('Penugasan');
 | 
						|
                const waktuMasukInput = document.getElementById('WaktuMasuk');
 | 
						|
                const waktuKeluarInput = document.getElementById('WaktuKeluar');
 | 
						|
                const beratMasukInput = document.getElementById('BeratMasuk');
 | 
						|
                const beratKeluarInput = document.getElementById('BeratKeluar');
 | 
						|
                const beratNettInput = document.getElementById('BeratNett');
 | 
						|
 | 
						|
                let fieldsUpdated = 0;
 | 
						|
 | 
						|
                // Apply Receipt Number
 | 
						|
                if (this.detectedData.receiptNumber && nomorStrukInput) {
 | 
						|
                    console.log('Setting receipt number:', this.detectedData.receiptNumber);
 | 
						|
                    nomorStrukInput.value = this.detectedData.receiptNumber;
 | 
						|
                    nomorStrukInput.classList.add('auto-filled');
 | 
						|
                    nomorStrukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => nomorStrukInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Truck Number
 | 
						|
                if (this.detectedData.truckNumber && nomorPolisiInput) {
 | 
						|
                    console.log('Setting truck number:', this.detectedData.truckNumber);
 | 
						|
                    nomorPolisiInput.value = this.detectedData.truckNumber;
 | 
						|
                    nomorPolisiInput.classList.add('auto-filled');
 | 
						|
                    nomorPolisiInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => nomorPolisiInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Assignment
 | 
						|
                if (this.detectedData.assignment && penugasanInput) {
 | 
						|
                    console.log('Setting assignment:', this.detectedData.assignment);
 | 
						|
                    penugasanInput.value = this.detectedData.assignment;
 | 
						|
                    penugasanInput.classList.add('auto-filled');
 | 
						|
                    penugasanInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => penugasanInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Entry Time
 | 
						|
                if (this.detectedData.entryTime && waktuMasukInput) {
 | 
						|
                    console.log('Setting entry time:', this.detectedData.entryTime);
 | 
						|
                    waktuMasukInput.value = this.detectedData.entryTime;
 | 
						|
                    waktuMasukInput.classList.add('auto-filled');
 | 
						|
                    waktuMasukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => waktuMasukInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Exit Time
 | 
						|
                if (this.detectedData.exitTime && waktuKeluarInput) {
 | 
						|
                    console.log('Setting exit time:', this.detectedData.exitTime);
 | 
						|
                    waktuKeluarInput.value = this.detectedData.exitTime;
 | 
						|
                    waktuKeluarInput.classList.add('auto-filled');
 | 
						|
                    waktuKeluarInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => waktuKeluarInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight In
 | 
						|
                if (this.detectedData.weightIn && beratMasukInput) {
 | 
						|
                    console.log('Setting weight in:', this.detectedData.weightIn);
 | 
						|
                    beratMasukInput.value = this.detectedData.weightIn;
 | 
						|
                    beratMasukInput.classList.add('auto-filled');
 | 
						|
                    beratMasukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratMasukInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight Out  
 | 
						|
                if (this.detectedData.weightOut && beratKeluarInput) {
 | 
						|
                    console.log('Setting weight out:', this.detectedData.weightOut);
 | 
						|
                    beratKeluarInput.value = this.detectedData.weightOut;
 | 
						|
                    beratKeluarInput.classList.add('auto-filled');
 | 
						|
                    beratKeluarInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratKeluarInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight Nett
 | 
						|
                if (this.detectedData.weightNett && beratNettInput) {
 | 
						|
                    console.log('Setting weight nett:', this.detectedData.weightNett);
 | 
						|
                    beratNettInput.value = this.detectedData.weightNett;
 | 
						|
                    beratNettInput.classList.add('auto-filled');
 | 
						|
                    beratNettInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratNettInput.classList.remove('auto-filled'), 3000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                }
 | 
						|
 | 
						|
                console.log(`=== DIRECT APPLY COMPLETED: ${fieldsUpdated} fields updated ===`);
 | 
						|
                return fieldsUpdated;
 | 
						|
            }
 | 
						|
 | 
						|
            applyDetectedData() {
 | 
						|
                console.log('=== APPLY DETECTED DATA CALLED ===');
 | 
						|
                console.log('ApplyDetectedData called with:', this.detectedData);
 | 
						|
                
 | 
						|
                // Get input elements by ID directly
 | 
						|
                const nomorStrukInput = document.getElementById('NomorStruk');
 | 
						|
                const nomorPolisiInput = document.getElementById('NomorPolisi');
 | 
						|
                const penugasanInput = document.getElementById('Penugasan');
 | 
						|
                const waktuMasukInput = document.getElementById('WaktuMasuk');
 | 
						|
                const waktuKeluarInput = document.getElementById('WaktuKeluar');
 | 
						|
                const beratMasukInput = document.getElementById('BeratMasuk');
 | 
						|
                const beratKeluarInput = document.getElementById('BeratKeluar');
 | 
						|
                const beratNettInput = document.getElementById('BeratNett');
 | 
						|
 | 
						|
                console.log('Input elements found:', {
 | 
						|
                    nomorStrukInput: !!nomorStrukInput,
 | 
						|
                    nomorPolisiInput: !!nomorPolisiInput,
 | 
						|
                    penugasanInput: !!penugasanInput,
 | 
						|
                    waktuMasukInput: !!waktuMasukInput,
 | 
						|
                    waktuKeluarInput: !!waktuKeluarInput,
 | 
						|
                    beratMasukInput: !!beratMasukInput,
 | 
						|
                    beratKeluarInput: !!beratKeluarInput,
 | 
						|
                    beratNettInput: !!beratNettInput
 | 
						|
                });
 | 
						|
 | 
						|
                // Debug: Check if elements exist and are visible
 | 
						|
                if (nomorStrukInput) {
 | 
						|
                    console.log('NomorStruk element properties:', {
 | 
						|
                        id: nomorStrukInput.id,
 | 
						|
                        type: nomorStrukInput.type,
 | 
						|
                        disabled: nomorStrukInput.disabled,
 | 
						|
                        readonly: nomorStrukInput.readOnly,
 | 
						|
                        currentValue: nomorStrukInput.value
 | 
						|
                    });
 | 
						|
                }
 | 
						|
 | 
						|
                let fieldsUpdated = 0;
 | 
						|
 | 
						|
                // Apply Receipt Number
 | 
						|
                if (this.detectedData.receiptNumber && nomorStrukInput) {
 | 
						|
                    console.log('Setting receipt number:', this.detectedData.receiptNumber);
 | 
						|
                    nomorStrukInput.value = this.detectedData.receiptNumber;
 | 
						|
                    nomorStrukInput.classList.add('auto-filled');
 | 
						|
                    // Trigger input event to ensure any validation runs
 | 
						|
                    nomorStrukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => nomorStrukInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Receipt number field updated. New value:', nomorStrukInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Truck Number
 | 
						|
                if (this.detectedData.truckNumber && nomorPolisiInput) {
 | 
						|
                    console.log('Setting truck number:', this.detectedData.truckNumber);
 | 
						|
                    nomorPolisiInput.value = this.detectedData.truckNumber;
 | 
						|
                    nomorPolisiInput.classList.add('auto-filled');
 | 
						|
                    nomorPolisiInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => nomorPolisiInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Truck number field updated. New value:', nomorPolisiInput.value);
 | 
						|
                } else {
 | 
						|
                    console.log('Truck number not applied. Data:', this.detectedData.truckNumber, 'Input exists:', !!nomorPolisiInput);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Assignment
 | 
						|
                if (this.detectedData.assignment && penugasanInput) {
 | 
						|
                    console.log('Setting assignment:', this.detectedData.assignment);
 | 
						|
                    penugasanInput.value = this.detectedData.assignment;
 | 
						|
                    penugasanInput.classList.add('auto-filled');
 | 
						|
                    penugasanInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => penugasanInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Assignment field updated. New value:', penugasanInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Entry Time
 | 
						|
                if (this.detectedData.entryTime && waktuMasukInput) {
 | 
						|
                    console.log('Setting entry time:', this.detectedData.entryTime);
 | 
						|
                    waktuMasukInput.value = this.detectedData.entryTime;
 | 
						|
                    waktuMasukInput.classList.add('auto-filled');
 | 
						|
                    waktuMasukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => waktuMasukInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Entry time field updated. New value:', waktuMasukInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Exit Time
 | 
						|
                if (this.detectedData.exitTime && waktuKeluarInput) {
 | 
						|
                    console.log('Setting exit time:', this.detectedData.exitTime);
 | 
						|
                    waktuKeluarInput.value = this.detectedData.exitTime;
 | 
						|
                    waktuKeluarInput.classList.add('auto-filled');
 | 
						|
                    waktuKeluarInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => waktuKeluarInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Exit time field updated. New value:', waktuKeluarInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight In
 | 
						|
                if (this.detectedData.weightIn && beratMasukInput) {
 | 
						|
                    console.log('Setting weight in:', this.detectedData.weightIn);
 | 
						|
                    beratMasukInput.value = this.detectedData.weightIn;
 | 
						|
                    beratMasukInput.classList.add('auto-filled');
 | 
						|
                    beratMasukInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratMasukInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Weight in field updated. New value:', beratMasukInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight Out  
 | 
						|
                if (this.detectedData.weightOut && beratKeluarInput) {
 | 
						|
                    console.log('Setting weight out:', this.detectedData.weightOut);
 | 
						|
                    beratKeluarInput.value = this.detectedData.weightOut;
 | 
						|
                    beratKeluarInput.classList.add('auto-filled');
 | 
						|
                    beratKeluarInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratKeluarInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Weight out field updated. New value:', beratKeluarInput.value);
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Apply Weight Nett
 | 
						|
                if (this.detectedData.weightNett && beratNettInput) {
 | 
						|
                    console.log('Setting weight nett:', this.detectedData.weightNett);
 | 
						|
                    beratNettInput.value = this.detectedData.weightNett;
 | 
						|
                    beratNettInput.classList.add('auto-filled');
 | 
						|
                    beratNettInput.dispatchEvent(new Event('input', { bubbles: true }));
 | 
						|
                    setTimeout(() => beratNettInput.classList.remove('auto-filled'), 2000);
 | 
						|
                    fieldsUpdated++;
 | 
						|
                    console.log('Weight nett field updated. New value:', beratNettInput.value);
 | 
						|
                }
 | 
						|
 | 
						|
                console.log(`=== APPLY COMPLETED: ${fieldsUpdated} fields updated ===`);
 | 
						|
                
 | 
						|
                this.hideOcrResult();
 | 
						|
                
 | 
						|
                // Show success message and scroll to form
 | 
						|
                this.showSuccess(`Data dari scan telah diisi ke ${fieldsUpdated} field!`);
 | 
						|
                
 | 
						|
                // Scroll to form section to show the filled data
 | 
						|
                const formSection = document.querySelector('form');
 | 
						|
                if (formSection) {
 | 
						|
                    formSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Removed unused functions - auto-fill is now automatic
 | 
						|
 | 
						|
            async handleFileUpload(event) {
 | 
						|
                console.log('=== FILE UPLOAD TRIGGERED ===');
 | 
						|
                console.log('Event:', event);
 | 
						|
                console.log('Event target:', event.target);
 | 
						|
                console.log('Files:', event.target.files);
 | 
						|
                
 | 
						|
                const file = event.target.files[0];
 | 
						|
                console.log('Selected file:', file);
 | 
						|
                
 | 
						|
                if (!file) {
 | 
						|
                    console.log('No file selected');
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                console.log('File details:', {
 | 
						|
                    name: file.name,
 | 
						|
                    type: file.type,
 | 
						|
                    size: file.size,
 | 
						|
                    lastModified: file.lastModified
 | 
						|
                });
 | 
						|
 | 
						|
                // Validate file type
 | 
						|
                if (!file.type.startsWith('image/')) {
 | 
						|
                    console.error('Invalid file type:', file.type);
 | 
						|
                    this.showError('Harap pilih file gambar (JPG, PNG, etc.)');
 | 
						|
                    event.target.value = ''; // Reset input
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                // Validate file size (max 10MB)
 | 
						|
                if (file.size > 10 * 1024 * 1024) {
 | 
						|
                    console.error('File too large:', file.size);
 | 
						|
                    this.showError('Ukuran file terlalu besar. Maksimal 10MB.');
 | 
						|
                    event.target.value = ''; // Reset input
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                try {
 | 
						|
                    console.log('Starting file processing...');
 | 
						|
                    this.hideMessages();
 | 
						|
                    this.showOcrProcessing();
 | 
						|
 | 
						|
                    // Stop any active scanning first
 | 
						|
                    if (this.isScanning) {
 | 
						|
                        await this.stopScanner();
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Show uploaded image in scanner container
 | 
						|
                    console.log('Displaying uploaded image...');
 | 
						|
                    this.displayUploadedImage(file);
 | 
						|
 | 
						|
                    await this.processImageWithOCR(file);
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    console.error('Upload processing error:', error);
 | 
						|
                    this.hideOcrProcessing();
 | 
						|
                    this.showError('Gagal memproses gambar yang diupload.');
 | 
						|
                } finally {
 | 
						|
                    // Reset file input for next upload
 | 
						|
                    event.target.value = '';
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            displayUploadedImage(file) {
 | 
						|
                console.log('Displaying uploaded image...');
 | 
						|
                const reader = new FileReader();
 | 
						|
                reader.onload = (e) => {
 | 
						|
                    console.log('FileReader loaded, creating image element...');
 | 
						|
                    const img = document.createElement('img');
 | 
						|
                    img.src = e.target.result;
 | 
						|
                    img.className = 'w-full h-full object-contain rounded-lg';
 | 
						|
                    img.style.backgroundColor = '#f3f4f6';
 | 
						|
                    img.style.maxHeight = '300px';
 | 
						|
                    
 | 
						|
                    // Add loading indicator
 | 
						|
                    img.onload = () => {
 | 
						|
                        console.log('Image loaded successfully in container');
 | 
						|
                    };
 | 
						|
                    
 | 
						|
                    img.onerror = () => {
 | 
						|
                        console.error('Error loading image in container');
 | 
						|
                        this.showError('Gagal menampilkan gambar yang diupload.');
 | 
						|
                    };
 | 
						|
                    
 | 
						|
                    this.scannerContainer.innerHTML = '';
 | 
						|
                    this.scannerContainer.appendChild(img);
 | 
						|
                    console.log('Image added to scanner container');
 | 
						|
                };
 | 
						|
                reader.onerror = () => {
 | 
						|
                    console.error('FileReader error');
 | 
						|
                    this.showError('Gagal membaca file gambar.');
 | 
						|
                };
 | 
						|
                reader.readAsDataURL(file);
 | 
						|
            }
 | 
						|
 | 
						|
            async processImageWithOCR(imageInput) {
 | 
						|
                try {
 | 
						|
                    this.showOcrProcessing();
 | 
						|
 | 
						|
                    const { data: { text } } = await Tesseract.recognize(
 | 
						|
                        imageInput,
 | 
						|
                        'ind+eng',
 | 
						|
                        {
 | 
						|
                            logger: m => {
 | 
						|
                                if (m.status === 'recognizing text') {
 | 
						|
                                }
 | 
						|
                            },
 | 
						|
                            tessedit_pageseg_mode: Tesseract.PSM.AUTO,
 | 
						|
                            tessedit_char_whitelist: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz :.,/-_()kg',
 | 
						|
                        }
 | 
						|
                    );
 | 
						|
 | 
						|
                    await this.extractDataFromText(text);
 | 
						|
                    this.hideOcrProcessing();
 | 
						|
 | 
						|
                } catch (error) {
 | 
						|
                    this.hideOcrProcessing();
 | 
						|
                    this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.');
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            async stopScanner() {
 | 
						|
                if (this.stream) {
 | 
						|
                    this.stream.getTracks().forEach(track => track.stop());
 | 
						|
                    this.stream = null;
 | 
						|
                }
 | 
						|
                
 | 
						|
                this.isScanning = false;
 | 
						|
                this.startBtn.classList.remove('hidden');
 | 
						|
                this.stopBtn.classList.add('hidden');
 | 
						|
                this.hideLoading();
 | 
						|
                
 | 
						|
                // Only hide permission and processing messages
 | 
						|
                this.permissionInfo.classList.add('hidden');
 | 
						|
                this.permissionDenied.classList.add('hidden');
 | 
						|
                this.ocrProcessing.classList.add('hidden');
 | 
						|
                // Keep this.ocrResult visible if it contains data
 | 
						|
 | 
						|
                if (this.ocrTimeout) {
 | 
						|
                    clearTimeout(this.ocrTimeout);
 | 
						|
                    this.ocrTimeout = null;
 | 
						|
                }
 | 
						|
 | 
						|
                // Reset file upload
 | 
						|
                if (this.fileUpload) {
 | 
						|
                    this.fileUpload.value = '';
 | 
						|
                }
 | 
						|
                
 | 
						|
                // Reset scanner container
 | 
						|
                this.scannerContainer.innerHTML = `
 | 
						|
                    <div id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
 | 
						|
                        <div class="text-center text-white">
 | 
						|
                            <div class="loading-spinner mx-auto mb-2"></div>
 | 
						|
                            <p class="text-sm">Memuat scanner...</p>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                `;
 | 
						|
                this.loadingDiv = document.getElementById('loading-scanner');
 | 
						|
            }
 | 
						|
 | 
						|
            handleCameraError(error) {
 | 
						|
                if (error.name === 'NotAllowedError') {
 | 
						|
                    this.permissionDenied.classList.remove('hidden');
 | 
						|
                } else if (error.name === 'NotFoundError') {
 | 
						|
                    this.showError('Kamera tidak ditemukan pada perangkat ini.');
 | 
						|
                } else if (error.name === 'NotReadableError') {
 | 
						|
                    this.showError('Kamera sedang digunakan aplikasi lain. Tutup aplikasi lain dan coba lagi.');
 | 
						|
                } else {
 | 
						|
                    this.showError('Gagal mengakses kamera: ' + error.message);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Helper methods
 | 
						|
            showLoading() {
 | 
						|
                this.loadingDiv.classList.remove('hidden');
 | 
						|
            }
 | 
						|
 | 
						|
            hideLoading() {
 | 
						|
                this.loadingDiv.classList.add('hidden');
 | 
						|
            }
 | 
						|
 | 
						|
            showOcrProcessing() {
 | 
						|
                this.ocrProcessing.classList.remove('hidden');
 | 
						|
            }
 | 
						|
 | 
						|
            hideOcrProcessing() {
 | 
						|
                this.ocrProcessing.classList.add('hidden');
 | 
						|
            }
 | 
						|
 | 
						|
            hideMessages() {
 | 
						|
                this.permissionInfo.classList.add('hidden');
 | 
						|
                this.permissionDenied.classList.add('hidden');
 | 
						|
                this.ocrProcessing.classList.add('hidden');
 | 
						|
                this.scanSuccess.classList.add('hidden');
 | 
						|
            }
 | 
						|
            
 | 
						|
            showScanSuccess() {
 | 
						|
                this.scanSuccess.classList.remove('hidden');
 | 
						|
                // Auto-hide after 5 seconds (longer to let user read the instructions)
 | 
						|
                setTimeout(() => {
 | 
						|
                    this.scanSuccess.classList.add('hidden');
 | 
						|
                }, 5000);
 | 
						|
            }
 | 
						|
 | 
						|
            showError(message) {
 | 
						|
                // Create temporary error message
 | 
						|
                const errorDiv = document.createElement('div');
 | 
						|
                errorDiv.className = 'bg-red-50 border border-red-200 rounded-lg p-3 mt-2';
 | 
						|
                errorDiv.innerHTML = `
 | 
						|
                    <div class="flex items-center">
 | 
						|
                        <i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
 | 
						|
                        <span class="text-red-800 text-sm">${message}</span>
 | 
						|
                    </div>
 | 
						|
                `;
 | 
						|
                this.scannerContainer.parentNode.insertBefore(errorDiv, this.scannerContainer.nextSibling);
 | 
						|
                
 | 
						|
                setTimeout(() => {
 | 
						|
                    errorDiv.remove();
 | 
						|
                }, 5000);
 | 
						|
            }
 | 
						|
 | 
						|
            showSuccess(message) {
 | 
						|
                // Create temporary success message
 | 
						|
                const successDiv = document.createElement('div');
 | 
						|
                successDiv.className = 'bg-green-50 border border-green-200 rounded-lg p-3 mt-2';
 | 
						|
                successDiv.innerHTML = `
 | 
						|
                    <div class="flex items-center">
 | 
						|
                        <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
						|
                        <span class="text-green-800 text-sm">${message}</span>
 | 
						|
                    </div>
 | 
						|
                `;
 | 
						|
                this.scannerContainer.parentNode.insertBefore(successDiv, this.scannerContainer.nextSibling);
 | 
						|
                
 | 
						|
                setTimeout(() => {
 | 
						|
                    successDiv.remove();
 | 
						|
                }, 3000);
 | 
						|
            }
 | 
						|
 | 
						|
            playSuccessSound() {
 | 
						|
                try {
 | 
						|
                    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
 | 
						|
                    const oscillator = audioContext.createOscillator();
 | 
						|
                    const gainNode = audioContext.createGain();
 | 
						|
                    
 | 
						|
                    oscillator.connect(gainNode);
 | 
						|
                    gainNode.connect(audioContext.destination);
 | 
						|
                    
 | 
						|
                    oscillator.frequency.value = 800;
 | 
						|
                    oscillator.type = 'square';
 | 
						|
                    
 | 
						|
                    gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
 | 
						|
                    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
 | 
						|
                    
 | 
						|
                    oscillator.start(audioContext.currentTime);
 | 
						|
                    oscillator.stop(audioContext.currentTime + 0.2);
 | 
						|
                } catch (e) {
 | 
						|
                    // Silent fail
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            vibrate() {
 | 
						|
                if ('vibrate' in navigator) {
 | 
						|
                    navigator.vibrate([200]);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Initialize the receipt scanner
 | 
						|
        const scanner = new ReceiptScanner();
 | 
						|
        
 | 
						|
        // Additional fallback for file upload if not bound during initialization
 | 
						|
        setTimeout(() => {
 | 
						|
            const fileUploadFallback = document.getElementById('file-upload');
 | 
						|
            if (fileUploadFallback && !fileUploadFallback.hasAttribute('data-bound')) {
 | 
						|
                console.log('Setting up fallback file upload listener...');
 | 
						|
                fileUploadFallback.addEventListener('change', (e) => {
 | 
						|
                    console.log('Fallback file upload triggered!');
 | 
						|
                    scanner.handleFileUpload(e);
 | 
						|
                });
 | 
						|
                fileUploadFallback.setAttribute('data-bound', 'true');
 | 
						|
            }
 | 
						|
        }, 1000);
 | 
						|
    });
 | 
						|
</script>
 | 
						|
</register-block> |