update : Struk

main
marszayn 2025-08-05 15:59:43 +07:00
parent 15023967a1
commit b9431a67b1
2 changed files with 32 additions and 138 deletions

View File

@ -5,37 +5,9 @@
@section Styles { @section Styles {
<link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" /> <link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
<style>
.auto-filled {
background-color: #dcfce7 !important;
border-color: #16a34a !important;
transition: all 0.3s ease;
}
.ocr-success {
animation: slideInUp 0.5s ease-out;
}
@@keyframes slideInUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.upload-label:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
</style>
} }
<div class="max-w-sm mx-auto bg-white min-h-screen"> <div class="max-w-sm mx-auto bg-white min-h-screen">
<!-- Header with Orange Background -->
<div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12"> <div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors"> <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
@ -47,7 +19,6 @@
</div> </div>
<div class="px-8 py-4"> <div class="px-8 py-4">
<!-- Camera Scanner Section -->
<div class="mb-6"> <div class="mb-6">
<div class="flex flex-col items-center space-y-2 mb-4"> <div class="flex flex-col items-center space-y-2 mb-4">
<div class="bg-orange-100 rounded-full p-3"> <div class="bg-orange-100 rounded-full p-3">
@ -58,7 +29,13 @@
</div> </div>
<!-- Scanner Container --> <div id="ocr-processing" class="hidden bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<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 class="scanner-container mb-4" style="height: 300px;"> <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="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 id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
@ -70,7 +47,6 @@
</div> </div>
</div> </div>
<!-- Scanner Controls -->
<div class="space-y-3 mb-4"> <div class="space-y-3 mb-4">
<button id="start-scanner" type="button" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner"> <button id="start-scanner" type="button" class="w-full bg-orange-500 hover:bg-orange-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> <i class="w-5 h-5 inline mr-2" data-lucide="camera"></i>
@ -82,7 +58,6 @@
Hentikan Scan Hentikan Scan
</button> </button>
<!-- Upload File Option -->
<div class="flex items-center"> <div class="flex items-center">
<div class="flex-1 border-t border-gray-200"></div> <div class="flex-1 border-t border-gray-200"></div>
@ -104,7 +79,6 @@
</p> </p>
<!-- Permission Messages -->
<div id="permission-info" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-3"> <div id="permission-info" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-3">
<div class="flex items-start"> <div class="flex items-start">
<i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i> <i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i>
@ -135,15 +109,8 @@
</div> </div>
</div> </div>
<!-- OCR Processing Message -->
<div id="ocr-processing" class="hidden bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<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>
<!-- Success Message (will be shown temporarily) -->
<div id="scan-success" class="hidden bg-green-50 border border-green-200 rounded-lg p-3"> <div id="scan-success" class="hidden bg-green-50 border border-green-200 rounded-lg p-3">
<div class="flex items-center"> <div class="flex items-center">
<i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i> <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
@ -281,7 +248,6 @@
</button> </button>
</form> </form>
<!-- Bottom Navigation -->
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" /> <partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
</div> </div>
@ -299,9 +265,7 @@
const beratKeluarInput = document.getElementById('BeratKeluar'); const beratKeluarInput = document.getElementById('BeratKeluar');
const beratNettInput = document.getElementById('BeratNett'); const beratNettInput = document.getElementById('BeratNett');
// Input validation for manual entry
nomorStrukInput.addEventListener('input', function() { nomorStrukInput.addEventListener('input', function() {
// Only allow numbers for receipt number (remove 08_ prefix automatically)
this.value = this.value.replace(/[^0-9]/g, ''); this.value = this.value.replace(/[^0-9]/g, '');
}); });
@ -317,7 +281,6 @@
this.value = this.value.replace(/[^0-9]/g, ''); this.value = this.value.replace(/[^0-9]/g, '');
}); });
// Initialize Receipt Scanner with OCR
class ReceiptScanner { class ReceiptScanner {
constructor() { constructor() {
this.isScanning = false; this.isScanning = false;
@ -335,6 +298,7 @@
weightOut: '', weightOut: '',
weightNett: '' weightNett: ''
}; };
this.ocrTimeout = null;
this.initializeElements(); this.initializeElements();
this.bindEvents(); this.bindEvents();
this.checkBrowserSupport(); this.checkBrowserSupport();
@ -367,14 +331,12 @@
fileUpload: !!this.fileUpload fileUpload: !!this.fileUpload
}); });
// Check for missing critical elements
if (!this.fileUpload) { if (!this.fileUpload) {
console.error('CRITICAL: File upload input not found! Looking for #file-upload'); console.error('CRITICAL: File upload input not found! Looking for #file-upload');
console.log('Available file inputs:', document.querySelectorAll('input[type="file"]')); 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('All elements with file-upload in ID:', document.querySelectorAll('[id*="file-upload"]'));
console.log('DOM ready state:', document.readyState); console.log('DOM ready state:', document.readyState);
// Try alternative selectors
const altFileInput = document.querySelector('input[type="file"]'); const altFileInput = document.querySelector('input[type="file"]');
if (altFileInput) { if (altFileInput) {
console.log('Found file input via type selector:', altFileInput); console.log('Found file input via type selector:', altFileInput);
@ -405,9 +367,6 @@
console.error('Stop scanner button not found!'); console.error('Stop scanner button not found!');
} }
// No additional buttons needed - auto-fill is automatic
// Fix file upload binding - ensure element exists and bind properly
if (this.fileUpload) { if (this.fileUpload) {
console.log('File upload element found, binding change event...'); console.log('File upload element found, binding change event...');
this.fileUpload.addEventListener('change', (e) => { this.fileUpload.addEventListener('change', (e) => {
@ -417,7 +376,6 @@
console.log('File upload input bound successfully'); console.log('File upload input bound successfully');
} else { } else {
console.error('CRITICAL: File upload input not found! Element ID: file-upload'); console.error('CRITICAL: File upload input not found! Element ID: file-upload');
// Try to find it again
const fileInput = document.querySelector('#file-upload'); const fileInput = document.querySelector('#file-upload');
if (fileInput) { if (fileInput) {
console.log('Found file input via querySelector, binding now...'); console.log('Found file input via querySelector, binding now...');
@ -456,7 +414,6 @@
this.hideMessages(); this.hideMessages();
this.permissionInfo.classList.remove('hidden'); this.permissionInfo.classList.remove('hidden');
// Get camera access
this.stream = await navigator.mediaDevices.getUserMedia({ this.stream = await navigator.mediaDevices.getUserMedia({
video: { video: {
facingMode: 'environment', facingMode: 'environment',
@ -472,37 +429,44 @@
this.hideLoading(); this.hideLoading();
this.hideMessages(); this.hideMessages();
// Start continuous capture for OCR
this.startContinuousCapture(); 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) { } catch (error) {
this.handleCameraError(error); this.handleCameraError(error);
this.hideLoading(); 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() { setupVideoElement() {
// Create video element
this.video = document.createElement('video'); this.video = document.createElement('video');
this.video.autoplay = true; this.video.autoplay = true;
this.video.playsInline = true; this.video.playsInline = true;
this.video.muted = true; this.video.muted = true;
this.video.srcObject = this.stream; this.video.srcObject = this.stream;
// Style video element
this.video.className = 'w-full h-full object-cover rounded-lg'; this.video.className = 'w-full h-full object-cover rounded-lg';
// Clear container and add video
this.scannerContainer.innerHTML = ''; this.scannerContainer.innerHTML = '';
this.scannerContainer.appendChild(this.video); this.scannerContainer.appendChild(this.video);
// Create canvas for capture
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d'); this.ctx = this.canvas.getContext('2d');
} }
startContinuousCapture() { startContinuousCapture() {
// Capture frame every 3 seconds for OCR processing
const captureInterval = setInterval(() => { const captureInterval = setInterval(() => {
if (!this.isScanning) { if (!this.isScanning) {
clearInterval(captureInterval); clearInterval(captureInterval);
@ -516,14 +480,11 @@
if (!this.video || !this.canvas || !this.isScanning) return; if (!this.video || !this.canvas || !this.isScanning) return;
try { try {
// Set canvas size to video size
this.canvas.width = this.video.videoWidth; this.canvas.width = this.video.videoWidth;
this.canvas.height = this.video.videoHeight; this.canvas.height = this.video.videoHeight;
// Draw video frame to canvas
this.ctx.drawImage(this.video, 0, 0); this.ctx.drawImage(this.video, 0, 0);
// Convert to blob for OCR
this.canvas.toBlob(async (blob) => { this.canvas.toBlob(async (blob) => {
await this.processImageWithOCR(blob); await this.processImageWithOCR(blob);
}, 'image/jpeg', 0.8); }, 'image/jpeg', 0.8);
@ -534,27 +495,22 @@
} }
setupVideoElement() { setupVideoElement() {
// Create video element
this.video = document.createElement('video'); this.video = document.createElement('video');
this.video.autoplay = true; this.video.autoplay = true;
this.video.playsInline = true; this.video.playsInline = true;
this.video.muted = true; this.video.muted = true;
this.video.srcObject = this.stream; this.video.srcObject = this.stream;
// Style video element
this.video.className = 'w-full h-full object-cover rounded-lg'; this.video.className = 'w-full h-full object-cover rounded-lg';
// Clear container and add video
this.scannerContainer.innerHTML = ''; this.scannerContainer.innerHTML = '';
this.scannerContainer.appendChild(this.video); this.scannerContainer.appendChild(this.video);
// Create canvas for capture
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d'); this.ctx = this.canvas.getContext('2d');
} }
startContinuousCapture() { startContinuousCapture() {
// Capture frame every 3 seconds for OCR processing
const captureInterval = setInterval(() => { const captureInterval = setInterval(() => {
if (!this.isScanning) { if (!this.isScanning) {
clearInterval(captureInterval); clearInterval(captureInterval);
@ -568,14 +524,11 @@
if (!this.video || !this.canvas || !this.isScanning) return; if (!this.video || !this.canvas || !this.isScanning) return;
try { try {
// Set canvas size to video size
this.canvas.width = this.video.videoWidth; this.canvas.width = this.video.videoWidth;
this.canvas.height = this.video.videoHeight; this.canvas.height = this.video.videoHeight;
// Draw video frame to canvas
this.ctx.drawImage(this.video, 0, 0); this.ctx.drawImage(this.video, 0, 0);
// Convert to blob for OCR
this.canvas.toBlob(async (blob) => { this.canvas.toBlob(async (blob) => {
await this.processImageWithOCR(blob); await this.processImageWithOCR(blob);
}, 'image/jpeg', 0.8); }, 'image/jpeg', 0.8);
@ -589,15 +542,12 @@
try { try {
this.showOcrProcessing(); this.showOcrProcessing();
// Enhanced OCR configuration for better Indonesian text recognition
const { data: { text } } = await Tesseract.recognize( const { data: { text } } = await Tesseract.recognize(
imageInput, imageInput,
'ind+eng', 'ind+eng',
{ {
logger: m => { logger: m => {
// Show OCR progress
if (m.status === 'recognizing text') { if (m.status === 'recognizing text') {
console.log(`OCR Progress: ${Math.round(m.progress * 100)}%`);
} }
}, },
tessedit_pageseg_mode: Tesseract.PSM.AUTO, tessedit_pageseg_mode: Tesseract.PSM.AUTO,
@ -609,7 +559,6 @@
this.hideOcrProcessing(); this.hideOcrProcessing();
} catch (error) { } catch (error) {
console.error('OCR Error:', error);
this.hideOcrProcessing(); this.hideOcrProcessing();
this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.'); this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.');
} }
@ -617,8 +566,6 @@
async extractDataFromText(text) { async extractDataFromText(text) {
const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0); const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
console.log('OCR Text Lines:', lines); // Debug log
console.log('Full OCR Text:', text); // Debug log
let receiptNumber = ''; let receiptNumber = '';
let truckNumber = ''; let truckNumber = '';
@ -629,19 +576,16 @@
let weightOut = ''; let weightOut = '';
let weightNett = ''; let weightNett = '';
// Enhanced patterns for better detection
// Extract receipt number (multiple patterns)
const receiptPatterns = [ const receiptPatterns = [
/(\d{2})\s+(\d{7,})/gi, // Pattern like "08 8001441" - take second group /(\d{2})\s+(\d{7,})/gi,
/(\d{2})_(\d{7,})/gi, // Pattern like "08_8001441" - take second group /(\d{2})_(\d{7,})/gi,
/(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{7,})/gi, // Near "nomor struk" /(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{7,})/gi,
/(?:^|\s)(\d{7,10})(?:\s|$)/g, // Standalone 7-10 digit numbers /(?:^|\s)(\d{7,10})(?:\s|$)/g,
]; ];
for (const line of lines) { for (const line of lines) {
console.log(`Processing line for receipt: "${line}"`); console.log(`Processing line for receipt: "${line}"`);
// Special handling for "08 8001441" or "08_8001441" format
const monthNumberMatch = line.match(/(\d{2})\s+(\d{7,})/); const monthNumberMatch = line.match(/(\d{2})\s+(\d{7,})/);
if (monthNumberMatch && monthNumberMatch[2]) { if (monthNumberMatch && monthNumberMatch[2]) {
receiptNumber = monthNumberMatch[2]; // Take the number after space/underscore receiptNumber = monthNumberMatch[2]; // Take the number after space/underscore
@ -671,30 +615,22 @@
if (receiptNumber) break; if (receiptNumber) break;
} }
// Enhanced truck number patterns - more comprehensive for Indonesian plates
const truckPatterns = [ const truckPatterns = [
// Standard Indonesian plate patterns
/([A-Z]\s+\d{1,4}\s+[A-Z]{2,3})/gi, // "B 9125 PJA" (with spaces) /([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]\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 /([A-Z]{1,2}\s*\d{1,4}\s*[A-Z]{1,3})/gi, // Flexible spacing
// Patterns with context keywords
/(?:no.*pol|nopol|nomor.*polisi|no.*truk|nomor.*truk)[\s.:]*([A-Z]{1,2}\s*\d{1,4}\s*[A-Z]{1,3})/gi, /(?:no.*pol|nopol|nomor.*polisi|no.*truk|nomor.*truk)[\s.:]*([A-Z]{1,2}\s*\d{1,4}\s*[A-Z]{1,3})/gi,
// More flexible patterns for OCR errors
/([A-Z]\s*\d{3,4}\s*[A-Z]{2,3})/gi, // Allow for OCR spacing issues
/([A-Z]{1,2}\d{3,4}[A-Z]{2,3})/gi, // No spaces at all /([A-Z]{1,2}\d{3,4}[A-Z]{2,3})/gi, // No spaces at all
// Pattern for when OCR reads as separate words
/([A-Z])\s+(\d{3,4})\s+([A-Z]{2,3})/gi, // Separate capture groups /([A-Z])\s+(\d{3,4})\s+([A-Z]{2,3})/gi, // Separate capture groups
]; ];
// First pass - look for lines with truck number context
for (const line of lines) { for (const line of lines) {
const lowerLine = line.toLowerCase(); const lowerLine = line.toLowerCase();
console.log(`Processing line for truck: "${line}"`); console.log(`Processing line for truck: "${line}"`);
// Check for context keywords first
if (lowerLine.includes('nopol') || if (lowerLine.includes('nopol') ||
lowerLine.includes('no pol') || lowerLine.includes('no pol') ||
lowerLine.includes('nomor polisi') || lowerLine.includes('nomor polisi') ||
@ -703,27 +639,22 @@
lowerLine.includes('polisi')) { lowerLine.includes('polisi')) {
console.log('Found truck context line:', line); console.log('Found truck context line:', line);
// Try to extract truck number from this line
for (const pattern of truckPatterns) { for (const pattern of truckPatterns) {
const match = line.match(pattern); const match = line.match(pattern);
if (match) { if (match) {
let foundTruck = ''; let foundTruck = '';
// Handle different match groups
if (match.length === 4 && match[1] && match[2] && match[3]) { if (match.length === 4 && match[1] && match[2] && match[3]) {
// Separate groups: "B", "9125", "PJA"
foundTruck = `${match[1]} ${match[2]} ${match[3]}`; foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
} else if (match[1]) { } else if (match[1]) {
foundTruck = match[1].trim(); foundTruck = match[1].trim();
} }
if (foundTruck) { if (foundTruck) {
// Normalize the truck number format
foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3'); 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(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
foundTruck = foundTruck.replace(/\s+/g, ' ').trim(); foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
// Validate format (should be like "B 9125 PJA")
if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) { if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
truckNumber = foundTruck; truckNumber = foundTruck;
console.log(`Found truck number: "${truckNumber}" using context pattern`); console.log(`Found truck number: "${truckNumber}" using context pattern`);
@ -736,13 +667,11 @@
} }
} }
// Second pass - look for any line that might contain truck number pattern
if (!truckNumber) { if (!truckNumber) {
console.log('No truck number found in context lines, trying all lines...'); console.log('No truck number found in context lines, trying all lines...');
for (const line of lines) { for (const line of lines) {
console.log(`Scanning line for truck pattern: "${line}"`); console.log(`Scanning line for truck pattern: "${line}"`);
// Check if line contains potential truck number pattern
if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) { if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) {
console.log('Potential truck pattern found:', line); console.log('Potential truck pattern found:', line);
@ -758,12 +687,10 @@
} }
if (foundTruck) { if (foundTruck) {
// Normalize format
foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3'); 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(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
foundTruck = foundTruck.replace(/\s+/g, ' ').trim(); foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
// Validate format
if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) { if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
truckNumber = foundTruck; truckNumber = foundTruck;
console.log(`Found truck number: "${truckNumber}" using general pattern`); console.log(`Found truck number: "${truckNumber}" using general pattern`);
@ -777,17 +704,14 @@
} }
} }
// Third pass - try to find truck number in any format and clean it up
if (!truckNumber) { if (!truckNumber) {
console.log('Still no truck number, trying loose patterns...'); console.log('Still no truck number, trying loose patterns...');
for (const line of lines) { for (const line of lines) {
// Very loose pattern - any letter followed by numbers followed by letters
const looseMatch = line.match(/([A-Z]{1,2})\s*(\d{3,4})\s*([A-Z]{2,3})/i); 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]) { if (looseMatch && looseMatch[1] && looseMatch[2] && looseMatch[3]) {
const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`; const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`;
console.log(`Found potential truck number with loose pattern: "${candidate}"`); console.log(`Found potential truck number with loose pattern: "${candidate}"`);
// Additional validation - check if it looks like Indonesian plate
if (looseMatch[1].length <= 2 && if (looseMatch[1].length <= 2 &&
looseMatch[2].length >= 3 && looseMatch[2].length <= 4 && looseMatch[2].length >= 3 && looseMatch[2].length <= 4 &&
looseMatch[3].length >= 2 && looseMatch[3].length <= 3) { looseMatch[3].length >= 2 && looseMatch[3].length <= 3) {
@ -803,7 +727,6 @@
console.log('Truck detection result:', truckNumber); console.log('Truck detection result:', truckNumber);
// Enhanced assignment patterns
const assignmentPatterns = [ const assignmentPatterns = [
/(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi, /(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi,
/(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas /(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas
@ -813,11 +736,9 @@
for (const line of lines) { for (const line of lines) {
console.log(`Processing line for assignment: "${line}"`); console.log(`Processing line for assignment: "${line}"`);
// Special check for the exact format from OCR
if (line.toLowerCase().includes('penugasan')) { if (line.toLowerCase().includes('penugasan')) {
console.log('Found penugasan line:', line); console.log('Found penugasan line:', line);
// Extract everything after "Penugasan :"
const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i); const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i);
if (assignmentMatch && assignmentMatch[1]) { if (assignmentMatch && assignmentMatch[1]) {
assignment = assignmentMatch[1].trim(); assignment = assignmentMatch[1].trim();
@ -826,7 +747,6 @@
} }
} }
// Check if line contains JAKARTA BARAT specifically
if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) { if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) {
assignment = 'JAKARTA BARAT'; assignment = 'JAKARTA BARAT';
console.log(`Found assignment: ${assignment} from Jakarta Barat line`); console.log(`Found assignment: ${assignment} from Jakarta Barat line`);
@ -1036,7 +956,6 @@
} }
}, 1500); }, 1500);
} else { } else {
console.log('No significant data found in OCR text');
} }
} }
@ -1271,7 +1190,6 @@
console.log(`=== APPLY COMPLETED: ${fieldsUpdated} fields updated ===`); console.log(`=== APPLY COMPLETED: ${fieldsUpdated} fields updated ===`);
// Hide OCR result after applying data
this.hideOcrResult(); this.hideOcrResult();
// Show success message and scroll to form // Show success message and scroll to form
@ -1337,8 +1255,6 @@
console.log('Displaying uploaded image...'); console.log('Displaying uploaded image...');
this.displayUploadedImage(file); this.displayUploadedImage(file);
// Process with OCR
console.log('Starting OCR processing...');
await this.processImageWithOCR(file); await this.processImageWithOCR(file);
} catch (error) { } catch (error) {
@ -1387,15 +1303,12 @@
try { try {
this.showOcrProcessing(); this.showOcrProcessing();
// Enhanced OCR configuration for better Indonesian text recognition
const { data: { text } } = await Tesseract.recognize( const { data: { text } } = await Tesseract.recognize(
imageInput, imageInput,
'ind+eng', 'ind+eng',
{ {
logger: m => { logger: m => {
// Show OCR progress
if (m.status === 'recognizing text') { if (m.status === 'recognizing text') {
console.log(`OCR Progress: ${Math.round(m.progress * 100)}%`);
} }
}, },
tessedit_pageseg_mode: Tesseract.PSM.AUTO, tessedit_pageseg_mode: Tesseract.PSM.AUTO,
@ -1407,7 +1320,6 @@
this.hideOcrProcessing(); this.hideOcrProcessing();
} catch (error) { } catch (error) {
console.error('OCR Error:', error);
this.hideOcrProcessing(); this.hideOcrProcessing();
this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.'); this.showError('Gagal membaca teks dari gambar. Coba dengan pencahayaan yang lebih baik.');
} }
@ -1424,13 +1336,17 @@
this.stopBtn.classList.add('hidden'); this.stopBtn.classList.add('hidden');
this.hideLoading(); this.hideLoading();
// DON'T hide OCR results when stopping scanner
// Only hide permission and processing messages // Only hide permission and processing messages
this.permissionInfo.classList.add('hidden'); this.permissionInfo.classList.add('hidden');
this.permissionDenied.classList.add('hidden'); this.permissionDenied.classList.add('hidden');
this.ocrProcessing.classList.add('hidden'); this.ocrProcessing.classList.add('hidden');
// Keep this.ocrResult visible if it contains data // Keep this.ocrResult visible if it contains data
if (this.ocrTimeout) {
clearTimeout(this.ocrTimeout);
this.ocrTimeout = null;
}
// Reset file upload // Reset file upload
if (this.fileUpload) { if (this.fileUpload) {
this.fileUpload.value = ''; this.fileUpload.value = '';

View File

@ -30,7 +30,6 @@
--color-amber-600: oklch(66.6% 0.179 58.318); --color-amber-600: oklch(66.6% 0.179 58.318);
--color-amber-700: oklch(55.5% 0.163 48.998); --color-amber-700: oklch(55.5% 0.163 48.998);
--color-yellow-50: oklch(98.7% 0.026 102.212); --color-yellow-50: oklch(98.7% 0.026 102.212);
--color-yellow-100: oklch(97.3% 0.071 103.193);
--color-yellow-200: oklch(94.5% 0.129 101.54); --color-yellow-200: oklch(94.5% 0.129 101.54);
--color-yellow-400: oklch(85.2% 0.199 91.936); --color-yellow-400: oklch(85.2% 0.199 91.936);
--color-yellow-500: oklch(79.5% 0.184 86.047); --color-yellow-500: oklch(79.5% 0.184 86.047);
@ -39,18 +38,14 @@
--color-green-50: oklch(98.2% 0.018 155.826); --color-green-50: oklch(98.2% 0.018 155.826);
--color-green-100: oklch(96.2% 0.044 156.743); --color-green-100: oklch(96.2% 0.044 156.743);
--color-green-200: oklch(92.5% 0.084 155.995); --color-green-200: oklch(92.5% 0.084 155.995);
--color-green-300: oklch(87.1% 0.15 154.449);
--color-green-400: oklch(79.2% 0.209 151.711); --color-green-400: oklch(79.2% 0.209 151.711);
--color-green-500: oklch(72.3% 0.219 149.579); --color-green-500: oklch(72.3% 0.219 149.579);
--color-green-600: oklch(62.7% 0.194 149.214); --color-green-600: oklch(62.7% 0.194 149.214);
--color-green-700: oklch(52.7% 0.154 150.069); --color-green-700: oklch(52.7% 0.154 150.069);
--color-green-800: oklch(44.8% 0.119 151.328); --color-green-800: oklch(44.8% 0.119 151.328);
--color-emerald-50: oklch(97.9% 0.021 166.113); --color-emerald-50: oklch(97.9% 0.021 166.113);
--color-emerald-200: oklch(90.5% 0.093 164.15);
--color-emerald-400: oklch(76.5% 0.177 163.223); --color-emerald-400: oklch(76.5% 0.177 163.223);
--color-emerald-600: oklch(59.6% 0.145 163.225);
--color-emerald-700: oklch(50.8% 0.118 165.612); --color-emerald-700: oklch(50.8% 0.118 165.612);
--color-teal-50: oklch(98.4% 0.014 180.72);
--color-blue-50: oklch(97% 0.014 254.604); --color-blue-50: oklch(97% 0.014 254.604);
--color-blue-100: oklch(93.2% 0.032 255.585); --color-blue-100: oklch(93.2% 0.032 255.585);
--color-blue-200: oklch(88.2% 0.059 254.128); --color-blue-200: oklch(88.2% 0.059 254.128);
@ -61,11 +56,8 @@
--color-blue-800: oklch(42.4% 0.199 265.638); --color-blue-800: oklch(42.4% 0.199 265.638);
--color-indigo-50: oklch(96.2% 0.018 272.314); --color-indigo-50: oklch(96.2% 0.018 272.314);
--color-indigo-100: oklch(93% 0.034 272.788); --color-indigo-100: oklch(93% 0.034 272.788);
--color-indigo-200: oklch(87% 0.065 274.039);
--color-indigo-300: oklch(78.5% 0.115 274.713); --color-indigo-300: oklch(78.5% 0.115 274.713);
--color-purple-50: oklch(97.7% 0.014 308.299); --color-purple-50: oklch(97.7% 0.014 308.299);
--color-purple-100: oklch(94.6% 0.033 307.174);
--color-purple-400: oklch(71.4% 0.203 305.504);
--color-purple-500: oklch(62.7% 0.265 303.9); --color-purple-500: oklch(62.7% 0.265 303.9);
--color-purple-600: oklch(55.8% 0.288 302.321); --color-purple-600: oklch(55.8% 0.288 302.321);
--color-rose-50: oklch(96.9% 0.015 12.422); --color-rose-50: oklch(96.9% 0.015 12.422);
@ -73,7 +65,6 @@
--color-slate-100: oklch(96.8% 0.007 247.896); --color-slate-100: oklch(96.8% 0.007 247.896);
--color-slate-200: oklch(92.9% 0.013 255.508); --color-slate-200: oklch(92.9% 0.013 255.508);
--color-slate-400: oklch(70.4% 0.04 256.788); --color-slate-400: oklch(70.4% 0.04 256.788);
--color-slate-500: oklch(55.4% 0.046 257.417);
--color-slate-600: oklch(44.6% 0.043 257.281); --color-slate-600: oklch(44.6% 0.043 257.281);
--color-slate-700: oklch(37.2% 0.044 257.287); --color-slate-700: oklch(37.2% 0.044 257.287);
--color-slate-800: oklch(27.9% 0.041 260.031); --color-slate-800: oklch(27.9% 0.041 260.031);
@ -91,7 +82,6 @@
--color-black: #000; --color-black: #000;
--color-white: #fff; --color-white: #fff;
--spacing: 0.25rem; --spacing: 0.25rem;
--container-xs: 20rem;
--container-sm: 24rem; --container-sm: 24rem;
--text-xs: 0.75rem; --text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75); --text-xs--line-height: calc(1 / 0.75);
@ -103,8 +93,6 @@
--text-lg--line-height: calc(1.75 / 1.125); --text-lg--line-height: calc(1.75 / 1.125);
--text-xl: 1.25rem; --text-xl: 1.25rem;
--text-xl--line-height: calc(1.75 / 1.25); --text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-semibold: 600; --font-weight-semibold: 600;
--font-weight-bold: 700; --font-weight-bold: 700;
@ -1392,9 +1380,6 @@
.bg-orange-500 { .bg-orange-500 {
background-color: var(--color-orange-500); background-color: var(--color-orange-500);
} }
.bg-purple-500 {
background-color: var(--color-purple-500);
}
.bg-red-50 { .bg-red-50 {
background-color: var(--color-red-50); background-color: var(--color-red-50);
} }
@ -2244,13 +2229,6 @@
} }
} }
} }
.hover\:bg-purple-600 {
&:hover {
@media (hover: hover) {
background-color: var(--color-purple-600);
}
}
}
.hover\:bg-red-50 { .hover\:bg-red-50 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {