employer upload document extracting issue

This commit is contained in:
mohanmd 2026-07-07 16:03:41 +05:30
parent 905a706fe6
commit 528b13bca1
7 changed files with 311 additions and 187 deletions

View File

@ -409,14 +409,6 @@ public function show($id)
'in_country' => (bool) $w->in_country, 'in_country' => (bool) $w->in_country,
'preferred_location' => $w->preferred_location, 'preferred_location' => $w->preferred_location,
'documents' => $w->documents->map(function ($doc) { 'documents' => $w->documents->map(function ($doc) {
$ocrData = $doc->ocr_data;
if ($doc->type === 'visa' && is_array($ocrData)) {
unset($ocrData['file_number']);
unset($ocrData['name']);
unset($ocrData['passport_number']);
unset($ocrData['accompanied_by']);
unset($ocrData['valid_until']);
}
return [ return [
'id' => $doc->id, 'id' => $doc->id,
'type' => $doc->type, 'type' => $doc->type,
@ -425,7 +417,7 @@ public function show($id)
'expiry_date' => $doc->expiry_date, 'expiry_date' => $doc->expiry_date,
'ocr_accuracy' => $doc->ocr_accuracy, 'ocr_accuracy' => $doc->ocr_accuracy,
'file_path' => $doc->file_path ? url($doc->file_path) : null, 'file_path' => $doc->file_path ? url($doc->file_path) : null,
'ocr_data' => $ocrData, 'ocr_data' => $doc->ocr_data,
]; ];
})->toArray(), })->toArray(),
]; ];

12
check_doc.php Normal file
View File

@ -0,0 +1,12 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
$doc = \App\Models\WorkerDocument::where('number', 'P666382')->first();
if ($doc) {
print_r($doc->getAttributes());
} else {
echo "NOT FOUND\n";
}

View File

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Head, Link, router } from '@inertiajs/react'; import { Head, Link, router } from '@inertiajs/react';
import axios from 'axios'; import axios from 'axios';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { compressImage } from '../../../lib/utils';
import { import {
Loader2, Loader2,
ShieldAlert, ShieldAlert,
@ -70,7 +71,7 @@ export default function UploadEmiratesId({ email }) {
} }
}; };
const handleFrontFileChange = (e) => { const handleFrontFileChange = async (e) => {
const selectedFile = e.target.files[0]; const selectedFile = e.target.files[0];
if (selectedFile) { if (selectedFile) {
if (selectedFile.size > 10240 * 1024) { if (selectedFile.size > 10240 * 1024) {
@ -78,12 +79,17 @@ export default function UploadEmiratesId({ email }) {
toast.error('The document must not exceed 10MB.'); toast.error('The document must not exceed 10MB.');
return; return;
} }
try {
const compressedFile = await compressImage(selectedFile);
setFrontFile(compressedFile);
} catch (err) {
setFrontFile(selectedFile); setFrontFile(selectedFile);
}
setError(null); setError(null);
} }
}; };
const handleBackFileChange = (e) => { const handleBackFileChange = async (e) => {
const selectedFile = e.target.files[0]; const selectedFile = e.target.files[0];
if (selectedFile) { if (selectedFile) {
if (selectedFile.size > 10240 * 1024) { if (selectedFile.size > 10240 * 1024) {
@ -91,7 +97,12 @@ export default function UploadEmiratesId({ email }) {
toast.error('The document must not exceed 10MB.'); toast.error('The document must not exceed 10MB.');
return; return;
} }
try {
const compressedFile = await compressImage(selectedFile);
setBackFile(compressedFile);
} catch (err) {
setBackFile(selectedFile); setBackFile(selectedFile);
}
setError(null); setError(null);
} }
}; };

View File

@ -29,6 +29,7 @@ import {
XCircle XCircle
} from 'lucide-react'; } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { compressImage } from '../../lib/utils';
export default function Profile({ employerProfile }) { export default function Profile({ employerProfile }) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -484,12 +485,18 @@ export default function Profile({ employerProfile }) {
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
onChange={(e) => { onChange={async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if (file) {
try {
const compressed = await compressImage(file);
setFrontFile(compressed);
setFrontPreview(URL.createObjectURL(compressed));
} catch (err) {
setFrontFile(file); setFrontFile(file);
setFrontPreview(URL.createObjectURL(file)); setFrontPreview(URL.createObjectURL(file));
} }
}
}} }}
className="absolute inset-0 opacity-0 cursor-pointer" className="absolute inset-0 opacity-0 cursor-pointer"
required required
@ -517,12 +524,18 @@ export default function Profile({ employerProfile }) {
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
onChange={(e) => { onChange={async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if (file) {
try {
const compressed = await compressImage(file);
setBackFile(compressed);
setBackPreview(URL.createObjectURL(compressed));
} catch (err) {
setBackFile(file); setBackFile(file);
setBackPreview(URL.createObjectURL(file)); setBackPreview(URL.createObjectURL(file));
} }
}
}} }}
className="absolute inset-0 opacity-0 cursor-pointer" className="absolute inset-0 opacity-0 cursor-pointer"
required required

View File

@ -71,8 +71,8 @@ const getExpiryStatus = (expiryDate) => {
} }
}; };
const safeRender = (value) => { const safeRender = (value, fallback = 'Not Extracted') => {
if (value === null || value === undefined) return 'N/A'; if (value === null || value === undefined || String(value).trim() === '') return fallback;
if (typeof value === 'object') { if (typeof value === 'object') {
if (value.name) return String(value.name); if (value.name) return String(value.name);
if (value.full_name) return String(value.full_name); if (value.full_name) return String(value.full_name);
@ -87,6 +87,52 @@ export default function Show({ worker }) {
const emiratesIdDoc = worker.documents?.find(d => d.type === 'emirates_id' || d.type === 'eid'); const emiratesIdDoc = worker.documents?.find(d => d.type === 'emirates_id' || d.type === 'eid');
const visaDoc = worker.documents?.find(d => d.type === 'visa'); const visaDoc = worker.documents?.find(d => d.type === 'visa');
// Robust passport document fallbacks
const passportOcr = passportDoc?.ocr_data || {};
const passportGivenNames = passportOcr.given_names || worker.name || 'Not Extracted';
const passportSurname = passportOcr.surname || '';
const passportNumber = passportOcr.passport_number || passportDoc?.number || 'Not Extracted';
const passportSex = passportOcr.sex || worker.gender || 'Not Extracted';
const passportDob = passportOcr.date_of_birth || (worker.age ? `Age ${worker.age}` : 'Not Extracted');
const passportPlaceOfBirth = passportOcr.place_of_birth || 'Not Extracted';
const passportNationality = passportOcr.nationality || worker.nationality || 'Not Extracted';
const passportIssuingCountry = passportOcr.issuing_country || 'Not Extracted';
const passportIssueDate = passportOcr.date_of_issue || passportDoc?.issue_date || 'Not Extracted';
const passportExpiryDate = passportOcr.date_of_expiry || passportDoc?.expiry_date || 'Not Extracted';
const passportAuthority = passportOcr.authority || 'Not Extracted';
const passportDocType = passportOcr.document_type || passportDoc?.type || 'Passport';
// Robust visa document fallbacks
const visaOcr = visaDoc?.ocr_data || {};
const sponsorObj = visaOcr.sponsor;
const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj);
const sponsorName = isSponsorObj
? (sponsorObj.name || sponsorObj.sponsor_name)
: (visaOcr.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : ''));
const sponsorPhone = isSponsorObj
? (sponsorObj.phone || sponsorObj.mobile || sponsorObj.mobile_number)
: '';
const sponsorAddress = isSponsorObj
? (sponsorObj.address || '')
: '';
const visaFullName = visaOcr.full_name || worker.name || 'Not Extracted';
const visaEntryPermitNo = visaOcr.entry_permit_no || visaDoc?.number || 'Not Extracted';
const visaUidNo = visaOcr.uid_no || 'Not Extracted';
const visaType = visaOcr.visa_type || worker.visa_status || 'Entry Permit';
const visaPassportNo = visaOcr.passport_no || visaOcr.passport_number || passportDoc?.number || 'Not Extracted';
const visaProfession = visaOcr.profession || worker.main_profession || 'Domestic Worker';
const visaSponsorName = sponsorName || visaOcr.sponsor_name || 'Not Extracted';
const visaSponsorPhone = sponsorPhone || 'Not Extracted';
const visaSponsorAddress = sponsorAddress || 'Not Extracted';
const visaNationality = visaOcr.nationality || worker.nationality || 'Not Extracted';
const visaCountry = visaOcr.country || 'United Arab Emirates';
const visaDob = visaOcr.date_of_birth || visaOcr.dob || 'Not Extracted';
const visaPlaceOfBirth = visaOcr.place_of_birth || 'Not Extracted';
const visaIssueDate = visaOcr.issue_date || visaDoc?.issue_date || 'Not Extracted';
const visaValidUntil = visaOcr.valid_until || visaOcr.expiry_date || visaDoc?.expiry_date || 'Not Extracted';
const visaDocType = visaOcr.document_type || visaDoc?.type || 'uae_visa';
const [showReportModal, setShowReportModal] = useState(false); const [showReportModal, setShowReportModal] = useState(false);
const [reportReason, setReportReason] = useState(''); const [reportReason, setReportReason] = useState('');
const [reportDetails, setReportDetails] = useState(''); const [reportDetails, setReportDetails] = useState('');
@ -460,73 +506,56 @@ export default function Show({ worker }) {
</div> </div>
<div className="p-6"> <div className="p-6">
{passportDoc.ocr_data ? (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6"> <div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('given_names', 'Given Names')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('given_names', 'Given Names')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.given_names)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportGivenNames)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('surname', 'Surname')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('surname', 'Surname')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.surname)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportSurname)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_number', 'Passport Number')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_number', 'Passport Number')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.passport_number || passportDoc.number)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportNumber)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sex', 'Sex')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sex', 'Sex')}</div>
<div className="text-xs font-bold text-slate-800 uppercase">{safeRender(passportDoc.ocr_data.sex)}</div> <div className="text-xs font-bold text-slate-800 uppercase">{safeRender(passportSex)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.date_of_birth)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportDob)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.place_of_birth)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportPlaceOfBirth)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.nationality || worker.nationality)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportNationality)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issuing_country', 'Issuing Country')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issuing_country', 'Issuing Country')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.issuing_country)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportIssuingCountry)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Date of Issue')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Date of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.date_of_issue || passportDoc.issue_date)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportIssueDate)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(passportDoc.ocr_data.date_of_expiry || passportDoc.expiry_date)}</div> <div className="text-xs font-bold text-emerald-600">{safeRender(passportExpiryDate)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('authority', 'Authority')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('authority', 'Authority')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.authority)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportAuthority)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.document_type || passportDoc.type || 'passport')}</div> <div className="text-xs font-bold text-slate-800">{safeRender(passportDocType)}</div>
</div> </div>
</div> </div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</div>
<div className="text-xs font-bold text-slate-800">{passportDoc.number}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
<div className="text-xs font-bold text-slate-800">{passportDoc.issue_date || 'N/A'}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{passportDoc.expiry_date || 'N/A'}</div>
</div>
</div>
)}
<div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium"> <div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium">
<span className="flex items-center space-x-1.5"> <span className="flex items-center space-x-1.5">
@ -686,109 +715,77 @@ export default function Show({ worker }) {
</span> </span>
)} )}
</div> </div>
<div className="p-6"> <div className="p-6">
{visaDoc.ocr_data ? (() => {
const sponsorObj = visaDoc.ocr_data?.sponsor;
const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj);
const sponsorName = isSponsorObj
? (sponsorObj.name || sponsorObj.sponsor_name)
: (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : ''));
const sponsorPhone = isSponsorObj
? (sponsorObj.phone || sponsorObj.mobile || sponsorObj.mobile_number)
: '';
const sponsorAddress = isSponsorObj
? (sponsorObj.address || '')
: '';
return (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6"> <div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('full_name', 'Full Name')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('full_name', 'Full Name')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.full_name || worker.name)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaFullName)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('entry_permit_no', 'Entry Permit No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('entry_permit_no', 'Entry Permit No')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.entry_permit_no || visaDoc.number)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaEntryPermitNo)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('uid_no', 'UID No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('uid_no', 'UID No')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.uid_no)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaUidNo)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_type', 'Visa Type')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_type', 'Visa Type')}</div>
<div className="text-xs font-bold text-slate-800 uppercase">{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}</div> <div className="text-xs font-bold text-slate-800 uppercase">{safeRender(visaType)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_no', 'Passport No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_no', 'Passport No')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.passport_no || visaDoc.ocr_data.passport_number)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaPassportNo)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaProfession)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_name', 'Sponsor Name')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_name', 'Sponsor Name')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(sponsorName || visaDoc.ocr_data.sponsor_name)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaSponsorName)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_phone', 'Sponsor Phone')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_phone', 'Sponsor Phone')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(sponsorPhone)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaSponsorPhone)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_address', 'Sponsor Address')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_address', 'Sponsor Address')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(sponsorAddress)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaSponsorAddress)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaNationality)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('country', 'Country')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('country', 'Country')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.country || 'United Arab Emirates')}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaCountry)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.date_of_birth || visaDoc.ocr_data.dob)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDob)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_birth)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaPlaceOfBirth)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue Date')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue Date')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaIssueDate)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('valid_until', 'Valid Until')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('valid_until', 'Valid Until')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.valid_until || visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}</div> <div className="text-xs font-bold text-emerald-600">{safeRender(visaValidUntil)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.document_type || visaDoc.type || 'uae_visa')}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDocType)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('ocr_accuracy', 'OCR Accuracy')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('ocr_accuracy', 'OCR Accuracy')}</div>
<div className="text-xs font-bold text-emerald-600">{(visaDoc.ocr_accuracy || visaDoc.ocr_data?.ocr_accuracy || 99)}%</div> <div className="text-xs font-bold text-emerald-600">{(visaDoc.ocr_accuracy || visaOcr.ocr_accuracy || 99)}%</div>
</div> </div>
</div> </div>
);
})() : (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</div>
<div className="text-xs font-bold text-slate-800">{visaDoc.number}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
<div className="text-xs font-bold text-slate-800">{visaDoc.issue_date || 'N/A'}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{visaDoc.expiry_date || 'N/A'}</div>
</div>
</div>
)}
<div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium"> <div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium">
<span className="flex items-center space-x-1.5"> <span className="flex items-center space-x-1.5">

View File

@ -4,3 +4,83 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs) { export function cn(...inputs) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
/**
* Compresses and resizes an image file on the client side.
* If the file is not an image (e.g. PDF), it resolves with the original file.
*
* @param {File} file - The file to compress.
* @param {number} maxWidth - Max width of compressed image.
* @param {number} maxHeight - Max height of compressed image.
* @param {number} quality - Compression quality between 0 and 1.
* @returns {Promise<File>} - A promise that resolves to the compressed File object.
*/
export function compressImage(file, maxWidth = 1200, maxHeight = 1200, quality = 0.8) {
return new Promise((resolve) => {
if (!file || !file.type.startsWith('image/')) {
resolve(file);
return;
}
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
let width = img.width;
let height = img.height;
// Calculate aspect ratio
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else {
if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
resolve(file);
return;
}
// Draw image
ctx.drawImage(img, 0, 0, width, height);
// Convert to blob
if (canvas.toBlob) {
canvas.toBlob(
(blob) => {
if (blob) {
// Keep original extension or use jpeg, but File constructor needs a name
const compressedFile = new File([blob], file.name.substring(0, file.name.lastIndexOf('.')) + '.jpg', {
type: 'image/jpeg',
lastModified: Date.now()
});
resolve(compressedFile);
} else {
resolve(file);
}
},
'image/jpeg',
quality
);
} else {
resolve(file);
}
};
img.onerror = () => resolve(file);
img.src = event.target.result;
};
reader.onerror = () => resolve(file);
reader.readAsDataURL(file);
});
}

19
scratch/fix_show.php Normal file
View File

@ -0,0 +1,19 @@
<?php
$file = 'd:/Projects/marketplace/resources/js/Pages/Employer/Workers/Show.jsx';
$content = file_get_contents($file);
$lines = explode("\n", str_replace("\r", "", $content));
// We want to replace line 716 (which is index 715) and line 717 (which is index 716)
// Let's verify line 716 has "</div>" and line 717 is whitespace.
if (strpos($lines[715], '</div>') !== false && trim($lines[716]) === '') {
$lines[715] = str_repeat(' ', 48) . ')}';
$lines[716] = str_repeat(' ', 44) . '</div>';
echo "Successfully replaced lines!\n";
} else {
echo "Line content mismatch!\n";
echo "Line 716: [" . $lines[715] . "]\n";
echo "Line 717: [" . $lines[716] . "]\n";
}
file_put_contents($file, implode("\r\n", $lines));