employer upload document extracting issue
This commit is contained in:
parent
905a706fe6
commit
528b13bca1
@ -409,14 +409,6 @@ public function show($id)
|
||||
'in_country' => (bool) $w->in_country,
|
||||
'preferred_location' => $w->preferred_location,
|
||||
'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 [
|
||||
'id' => $doc->id,
|
||||
'type' => $doc->type,
|
||||
@ -425,7 +417,7 @@ public function show($id)
|
||||
'expiry_date' => $doc->expiry_date,
|
||||
'ocr_accuracy' => $doc->ocr_accuracy,
|
||||
'file_path' => $doc->file_path ? url($doc->file_path) : null,
|
||||
'ocr_data' => $ocrData,
|
||||
'ocr_data' => $doc->ocr_data,
|
||||
];
|
||||
})->toArray(),
|
||||
];
|
||||
|
||||
12
check_doc.php
Normal file
12
check_doc.php
Normal 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";
|
||||
}
|
||||
@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Head, Link, router } from '@inertiajs/react';
|
||||
import axios from 'axios';
|
||||
import { toast } from 'sonner';
|
||||
import { compressImage } from '../../../lib/utils';
|
||||
import {
|
||||
Loader2,
|
||||
ShieldAlert,
|
||||
@ -70,7 +71,7 @@ export default function UploadEmiratesId({ email }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleFrontFileChange = (e) => {
|
||||
const handleFrontFileChange = async (e) => {
|
||||
const selectedFile = e.target.files[0];
|
||||
if (selectedFile) {
|
||||
if (selectedFile.size > 10240 * 1024) {
|
||||
@ -78,12 +79,17 @@ export default function UploadEmiratesId({ email }) {
|
||||
toast.error('The document must not exceed 10MB.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const compressedFile = await compressImage(selectedFile);
|
||||
setFrontFile(compressedFile);
|
||||
} catch (err) {
|
||||
setFrontFile(selectedFile);
|
||||
}
|
||||
setError(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackFileChange = (e) => {
|
||||
const handleBackFileChange = async (e) => {
|
||||
const selectedFile = e.target.files[0];
|
||||
if (selectedFile) {
|
||||
if (selectedFile.size > 10240 * 1024) {
|
||||
@ -91,7 +97,12 @@ export default function UploadEmiratesId({ email }) {
|
||||
toast.error('The document must not exceed 10MB.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const compressedFile = await compressImage(selectedFile);
|
||||
setBackFile(compressedFile);
|
||||
} catch (err) {
|
||||
setBackFile(selectedFile);
|
||||
}
|
||||
setError(null);
|
||||
}
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
XCircle
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { compressImage } from '../../lib/utils';
|
||||
|
||||
export default function Profile({ employerProfile }) {
|
||||
const { t } = useTranslation();
|
||||
@ -484,12 +485,18 @@ export default function Profile({ employerProfile }) {
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
try {
|
||||
const compressed = await compressImage(file);
|
||||
setFrontFile(compressed);
|
||||
setFrontPreview(URL.createObjectURL(compressed));
|
||||
} catch (err) {
|
||||
setFrontFile(file);
|
||||
setFrontPreview(URL.createObjectURL(file));
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="absolute inset-0 opacity-0 cursor-pointer"
|
||||
required
|
||||
@ -517,12 +524,18 @@ export default function Profile({ employerProfile }) {
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
try {
|
||||
const compressed = await compressImage(file);
|
||||
setBackFile(compressed);
|
||||
setBackPreview(URL.createObjectURL(compressed));
|
||||
} catch (err) {
|
||||
setBackFile(file);
|
||||
setBackPreview(URL.createObjectURL(file));
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="absolute inset-0 opacity-0 cursor-pointer"
|
||||
required
|
||||
|
||||
@ -71,8 +71,8 @@ const getExpiryStatus = (expiryDate) => {
|
||||
}
|
||||
};
|
||||
|
||||
const safeRender = (value) => {
|
||||
if (value === null || value === undefined) return 'N/A';
|
||||
const safeRender = (value, fallback = 'Not Extracted') => {
|
||||
if (value === null || value === undefined || String(value).trim() === '') return fallback;
|
||||
if (typeof value === 'object') {
|
||||
if (value.name) return String(value.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 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 [reportReason, setReportReason] = useState('');
|
||||
const [reportDetails, setReportDetails] = useState('');
|
||||
@ -460,73 +506,56 @@ export default function Show({ worker }) {
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
{passportDoc.ocr_data ? (
|
||||
<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('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 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 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 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 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 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 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 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 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 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 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 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 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">
|
||||
<span className="flex items-center space-x-1.5">
|
||||
@ -686,109 +715,77 @@ export default function Show({ worker }) {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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">
|
||||
<span className="flex items-center space-x-1.5">
|
||||
|
||||
@ -4,3 +4,83 @@ import { twMerge } from "tailwind-merge"
|
||||
export function cn(...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
19
scratch/fix_show.php
Normal 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));
|
||||
Loading…
x
Reference in New Issue
Block a user