diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php
index 41abe6e..13a6b7d 100644
--- a/app/Http/Controllers/Employer/WorkerController.php
+++ b/app/Http/Controllers/Employer/WorkerController.php
@@ -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(),
];
diff --git a/check_doc.php b/check_doc.php
new file mode 100644
index 0000000..fdeaac5
--- /dev/null
+++ b/check_doc.php
@@ -0,0 +1,12 @@
+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";
+}
diff --git a/resources/js/Pages/Employer/Auth/UploadEmiratesId.jsx b/resources/js/Pages/Employer/Auth/UploadEmiratesId.jsx
index 56a1a7c..b611549 100644
--- a/resources/js/Pages/Employer/Auth/UploadEmiratesId.jsx
+++ b/resources/js/Pages/Employer/Auth/UploadEmiratesId.jsx
@@ -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;
}
- setFrontFile(selectedFile);
+ 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;
}
- setBackFile(selectedFile);
+ try {
+ const compressedFile = await compressImage(selectedFile);
+ setBackFile(compressedFile);
+ } catch (err) {
+ setBackFile(selectedFile);
+ }
setError(null);
}
};
diff --git a/resources/js/Pages/Employer/Profile.jsx b/resources/js/Pages/Employer/Profile.jsx
index ba7d0cb..7fbc7a4 100644
--- a/resources/js/Pages/Employer/Profile.jsx
+++ b/resources/js/Pages/Employer/Profile.jsx
@@ -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,11 +485,17 @@ export default function Profile({ employerProfile }) {
{
+ onChange={async (e) => {
const file = e.target.files[0];
if (file) {
- setFrontFile(file);
- setFrontPreview(URL.createObjectURL(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"
@@ -517,11 +524,17 @@ export default function Profile({ employerProfile }) {
{
+ onChange={async (e) => {
const file = e.target.files[0];
if (file) {
- setBackFile(file);
- setBackPreview(URL.createObjectURL(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"
diff --git a/resources/js/Pages/Employer/Workers/Show.jsx b/resources/js/Pages/Employer/Workers/Show.jsx
index 42148d3..1abf6d7 100644
--- a/resources/js/Pages/Employer/Workers/Show.jsx
+++ b/resources/js/Pages/Employer/Workers/Show.jsx
@@ -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 }) {
- {passportDoc.ocr_data ? (
-
-
-
{t('given_names', 'Given Names')}
-
{safeRender(passportDoc.ocr_data.given_names)}
-
-
-
{t('surname', 'Surname')}
-
{safeRender(passportDoc.ocr_data.surname)}
-
-
-
{t('passport_number', 'Passport Number')}
-
{safeRender(passportDoc.ocr_data.passport_number || passportDoc.number)}
-
-
-
{t('sex', 'Sex')}
-
{safeRender(passportDoc.ocr_data.sex)}
-
-
-
{t('date_of_birth', 'Date of Birth')}
-
{safeRender(passportDoc.ocr_data.date_of_birth)}
-
-
-
{t('place_of_birth', 'Place of Birth')}
-
{safeRender(passportDoc.ocr_data.place_of_birth)}
-
-
-
{t('nationality', 'Nationality')}
-
{safeRender(passportDoc.ocr_data.nationality || worker.nationality)}
-
-
-
{t('issuing_country', 'Issuing Country')}
-
{safeRender(passportDoc.ocr_data.issuing_country)}
-
-
-
{t('issue_date', 'Date of Issue')}
-
{safeRender(passportDoc.ocr_data.date_of_issue || passportDoc.issue_date)}
-
-
-
{t('expiry_date', 'Date of Expiry')}
-
{safeRender(passportDoc.ocr_data.date_of_expiry || passportDoc.expiry_date)}
-
-
-
{t('authority', 'Authority')}
-
{safeRender(passportDoc.ocr_data.authority)}
-
-
-
{t('document_type', 'Document Type')}
-
{safeRender(passportDoc.ocr_data.document_type || passportDoc.type || 'passport')}
-
+
+
+
{t('given_names', 'Given Names')}
+
{safeRender(passportGivenNames)}
- ) : (
-
-
-
{t('document_id', 'Document ID')}
-
{passportDoc.number}
-
-
-
{t('issue_date', 'Issue')}
-
{passportDoc.issue_date || 'N/A'}
-
-
-
{t('expiry_date', 'Expiry')}
-
{passportDoc.expiry_date || 'N/A'}
-
+
+
{t('surname', 'Surname')}
+
{safeRender(passportSurname)}
- )}
+
+
{t('passport_number', 'Passport Number')}
+
{safeRender(passportNumber)}
+
+
+
{t('sex', 'Sex')}
+
{safeRender(passportSex)}
+
+
+
{t('date_of_birth', 'Date of Birth')}
+
{safeRender(passportDob)}
+
+
+
{t('place_of_birth', 'Place of Birth')}
+
{safeRender(passportPlaceOfBirth)}
+
+
+
{t('nationality', 'Nationality')}
+
{safeRender(passportNationality)}
+
+
+
{t('issuing_country', 'Issuing Country')}
+
{safeRender(passportIssuingCountry)}
+
+
+
{t('issue_date', 'Date of Issue')}
+
{safeRender(passportIssueDate)}
+
+
+
{t('expiry_date', 'Date of Expiry')}
+
{safeRender(passportExpiryDate)}
+
+
+
{t('authority', 'Authority')}
+
{safeRender(passportAuthority)}
+
+
+
{t('document_type', 'Document Type')}
+
{safeRender(passportDocType)}
+
+
@@ -686,109 +715,77 @@ export default function Show({ worker }) {
)}
-
- {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 (
-
-
-
{t('full_name', 'Full Name')}
-
{safeRender(visaDoc.ocr_data.full_name || worker.name)}
-
-
-
{t('entry_permit_no', 'Entry Permit No')}
-
{safeRender(visaDoc.ocr_data.entry_permit_no || visaDoc.number)}
-
-
-
{t('uid_no', 'UID No')}
-
{safeRender(visaDoc.ocr_data.uid_no)}
-
-
-
{t('visa_type', 'Visa Type')}
-
{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}
-
-
-
{t('passport_no', 'Passport No')}
-
{safeRender(visaDoc.ocr_data.passport_no || visaDoc.ocr_data.passport_number)}
-
-
-
{t('profession', 'Profession')}
-
{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}
-
-
-
{t('sponsor_name', 'Sponsor Name')}
-
{safeRender(sponsorName || visaDoc.ocr_data.sponsor_name)}
-
-
-
{t('sponsor_phone', 'Sponsor Phone')}
-
{safeRender(sponsorPhone)}
-
-
-
{t('sponsor_address', 'Sponsor Address')}
-
{safeRender(sponsorAddress)}
-
-
-
{t('nationality', 'Nationality')}
-
{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}
-
-
-
{t('country', 'Country')}
-
{safeRender(visaDoc.ocr_data.country || 'United Arab Emirates')}
-
-
-
{t('date_of_birth', 'Date of Birth')}
-
{safeRender(visaDoc.ocr_data.date_of_birth || visaDoc.ocr_data.dob)}
-
-
-
{t('place_of_birth', 'Place of Birth')}
-
{safeRender(visaDoc.ocr_data.place_of_birth)}
-
-
-
{t('issue_date', 'Issue Date')}
-
{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}
-
-
-
{t('valid_until', 'Valid Until')}
-
{safeRender(visaDoc.ocr_data.valid_until || visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}
-
-
-
{t('document_type', 'Document Type')}
-
{safeRender(visaDoc.ocr_data.document_type || visaDoc.type || 'uae_visa')}
-
-
-
{t('ocr_accuracy', 'OCR Accuracy')}
-
{(visaDoc.ocr_accuracy || visaDoc.ocr_data?.ocr_accuracy || 99)}%
-
-
- );
- })() : (
-
-
-
{t('document_id', 'Document ID')}
-
{visaDoc.number}
-
-
-
{t('issue_date', 'Issue')}
-
{visaDoc.issue_date || 'N/A'}
-
-
-
{t('expiry_date', 'Expiry')}
-
{visaDoc.expiry_date || 'N/A'}
-
+
+
+
{t('full_name', 'Full Name')}
+
{safeRender(visaFullName)}
- )}
+
+
{t('entry_permit_no', 'Entry Permit No')}
+
{safeRender(visaEntryPermitNo)}
+
+
+
{t('uid_no', 'UID No')}
+
{safeRender(visaUidNo)}
+
+
+
{t('visa_type', 'Visa Type')}
+
{safeRender(visaType)}
+
+
+
{t('passport_no', 'Passport No')}
+
{safeRender(visaPassportNo)}
+
+
+
{t('profession', 'Profession')}
+
{safeRender(visaProfession)}
+
+
+
{t('sponsor_name', 'Sponsor Name')}
+
{safeRender(visaSponsorName)}
+
+
+
{t('sponsor_phone', 'Sponsor Phone')}
+
{safeRender(visaSponsorPhone)}
+
+
+
{t('sponsor_address', 'Sponsor Address')}
+
{safeRender(visaSponsorAddress)}
+
+
+
{t('nationality', 'Nationality')}
+
{safeRender(visaNationality)}
+
+
+
{t('country', 'Country')}
+
{safeRender(visaCountry)}
+
+
+
{t('date_of_birth', 'Date of Birth')}
+
{safeRender(visaDob)}
+
+
+
{t('place_of_birth', 'Place of Birth')}
+
{safeRender(visaPlaceOfBirth)}
+
+
+
{t('issue_date', 'Issue Date')}
+
{safeRender(visaIssueDate)}
+
+
+
{t('valid_until', 'Valid Until')}
+
{safeRender(visaValidUntil)}
+
+
+
{t('document_type', 'Document Type')}
+
{safeRender(visaDocType)}
+
+
+
{t('ocr_accuracy', 'OCR Accuracy')}
+
{(visaDoc.ocr_accuracy || visaOcr.ocr_accuracy || 99)}%
+
+
diff --git a/resources/js/lib/utils.js b/resources/js/lib/utils.js
index b20bf01..9da77a1 100644
--- a/resources/js/lib/utils.js
+++ b/resources/js/lib/utils.js
@@ -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} - 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);
+ });
+}
+
diff --git a/scratch/fix_show.php b/scratch/fix_show.php
new file mode 100644
index 0000000..e610095
--- /dev/null
+++ b/scratch/fix_show.php
@@ -0,0 +1,19 @@
+" and line 717 is whitespace.
+if (strpos($lines[715], '
') !== false && trim($lines[716]) === '') {
+ $lines[715] = str_repeat(' ', 48) . ')}';
+ $lines[716] = str_repeat(' ', 44) . '
';
+ 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));