diff --git a/app/Http/Controllers/Api/WorkerAuthController.php b/app/Http/Controllers/Api/WorkerAuthController.php index cee8412..5b6aa49 100644 --- a/app/Http/Controllers/Api/WorkerAuthController.php +++ b/app/Http/Controllers/Api/WorkerAuthController.php @@ -330,7 +330,34 @@ public function register(Request $request) return $query->where('type', 'passport'); }), ], - 'visa' => 'nullable|array', + 'passport.document_type' => 'nullable|string|max:10', + 'passport.issuing_country' => 'nullable|string|max:10', + 'passport.surname' => 'nullable|string|max:255', + 'passport.given_names' => 'nullable|string|max:255', + 'passport.nationality' => 'nullable|string|max:100', + 'passport.date_of_birth' => 'nullable|string', + 'passport.date_of_expiry' => 'nullable|string', + 'passport.place_of_birth' => 'nullable|string|max:255', + 'passport.authority' => 'nullable|string|max:255', + 'passport.sex' => 'nullable|string|max:10', + 'passport.date_of_issue' => 'nullable|string', + 'passport.ocr_accuracy' => 'nullable|numeric', + 'visa' => 'nullable|array', + 'visa.document_type' => 'nullable|string|max:100', + 'visa.country' => 'nullable|string|max:100', + 'visa.visa_type' => 'nullable|string|max:100', + 'visa.entry_permit_no' => 'nullable|string|max:100', + 'visa.issue_date' => 'nullable|string', + 'visa.valid_until' => 'nullable|string', + 'visa.uid_no' => 'nullable|string|max:100', + 'visa.full_name' => 'nullable|string|max:255', + 'visa.nationality' => 'nullable|string|max:100', + 'visa.place_of_birth' => 'nullable|string|max:255', + 'visa.date_of_birth' => 'nullable|string', + 'visa.passport_no' => 'nullable|string|max:100', + 'visa.profession' => 'nullable|string|max:255', + 'visa.sponsor_name' => 'nullable|string|max:255', + 'visa.ocr_accuracy' => 'nullable|numeric', ], [ 'phone.unique' => 'This mobile number is already registered.', 'passport.passport_number.unique' => 'This passport number is already registered.', @@ -462,7 +489,7 @@ public function register(Request $request) 'number' => $passportNum, 'issue_date' => $issueDateFormatted, 'expiry_date' => $expiryDateFormatted, - 'ocr_accuracy' => 99.0, + 'ocr_accuracy' => isset($passportDataInput['ocr_accuracy']) ? (float)$passportDataInput['ocr_accuracy'] : 99.0, 'file_path' => null, 'ocr_data' => $passportDataInput, ]); @@ -470,8 +497,8 @@ public function register(Request $request) // Create visa document if provided if ($visaDataInput) { - $visaNum = $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999)); - $expiryDate = $visaDataInput['expiry_date'] ?? null; + $visaNum = $visaDataInput['entry_permit_no'] ?? $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999)); + $expiryDate = $visaDataInput['valid_until'] ?? $visaDataInput['expiry_date'] ?? null; $issueDate = $visaDataInput['issue_date'] ?? null; $expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(2)->toDateString(); @@ -482,7 +509,7 @@ public function register(Request $request) 'number' => $visaNum, 'issue_date' => $issueDateFormatted, 'expiry_date' => $expiryDateFormatted, - 'ocr_accuracy' => 99.0, + 'ocr_accuracy' => isset($visaDataInput['ocr_accuracy']) ? (float)$visaDataInput['ocr_accuracy'] : 99.0, 'file_path' => null, 'ocr_data' => $visaDataInput, ]); diff --git a/app/Http/Controllers/Employer/EmployerAuthController.php b/app/Http/Controllers/Employer/EmployerAuthController.php index 64e1962..1c652ed 100644 --- a/app/Http/Controllers/Employer/EmployerAuthController.php +++ b/app/Http/Controllers/Employer/EmployerAuthController.php @@ -290,17 +290,35 @@ public function uploadEmiratesId(Request $request) $extractedIdNumber = null; $extractedExpiry = null; + $extractedName = null; + $extractedDob = null; + $extractedNationality = null; + $extractedIssueDate = null; + $extractedEmployer = null; + $extractedIssuePlace = null; + $extractedOccupation = null; + $extractedCardNumber = null; + $extractedGender = null; if ($request->hasFile('emirates_id_front')) { $frontFile = $request->file('emirates_id_front'); $frontOcr = \App\Services\OcrDocumentService::extractEmiratesIdFrontData($frontFile); $extractedIdNumber = $frontOcr['emirates_id_number']; + $extractedName = $frontOcr['name'] ?? null; + $extractedDob = $frontOcr['date_of_birth'] ?? null; + $extractedNationality = $frontOcr['nationality'] ?? null; + $extractedCardNumber = $frontOcr['card_number'] ?? null; + $extractedGender = $frontOcr['gender'] ?? null; + $extractedOccupation = $frontOcr['occupation'] ?? null; + $extractedEmployer = $frontOcr['employer'] ?? null; + $extractedIssuePlace = $frontOcr['issue_place'] ?? null; } if ($request->hasFile('emirates_id_back')) { $backFile = $request->file('emirates_id_back'); $backOcr = \App\Services\OcrDocumentService::extractEmiratesIdBackData($backFile); $extractedExpiry = $backOcr['expiry_date']; + $extractedIssueDate = $backOcr['issue_date'] ?? null; } if ($request->hasFile('emirates_id_file')) { @@ -309,12 +327,30 @@ public function uploadEmiratesId(Request $request) $backOcr = \App\Services\OcrDocumentService::extractEmiratesIdBackData($file); $extractedIdNumber = $frontOcr['emirates_id_number']; $extractedExpiry = $backOcr['expiry_date']; + $extractedName = $frontOcr['name'] ?? null; + $extractedDob = $frontOcr['date_of_birth'] ?? null; + $extractedNationality = $frontOcr['nationality'] ?? null; + $extractedCardNumber = $frontOcr['card_number'] ?? null; + $extractedGender = $frontOcr['gender'] ?? null; + $extractedOccupation = $frontOcr['occupation'] ?? null; + $extractedEmployer = $frontOcr['employer'] ?? null; + $extractedIssuePlace = $frontOcr['issue_place'] ?? null; + $extractedIssueDate = $backOcr['issue_date'] ?? null; } $pending = session('pending_employer_registration'); $pending['emirates_id_file'] = null; $pending['emirates_id_number'] = $extractedIdNumber; $pending['emirates_id_expiry'] = $extractedExpiry; + $pending['emirates_id_name'] = $extractedName; + $pending['emirates_id_dob'] = $extractedDob; + $pending['emirates_id_nationality'] = $extractedNationality; + $pending['emirates_id_card_number'] = $extractedCardNumber; + $pending['emirates_id_gender'] = $extractedGender; + $pending['emirates_id_occupation'] = $extractedOccupation; + $pending['emirates_id_employer'] = $extractedEmployer; + $pending['emirates_id_issue_place'] = $extractedIssuePlace; + $pending['emirates_id_issue_date'] = $extractedIssueDate; session(['pending_employer_registration' => $pending]); session(['employer_emirates_id_uploaded' => true]); @@ -438,6 +474,13 @@ public function createPassword(Request $request) 'emirates_id_front' => null, // We did not store the file 'emirates_id_number' => $pending['emirates_id_number'] ?? null, 'emirates_id_expiry' => $pending['emirates_id_expiry'] ?? null, + 'emirates_id_name' => $pending['emirates_id_name'] ?? null, + 'emirates_id_dob' => $pending['emirates_id_dob'] ?? null, + 'emirates_id_issue_date' => $pending['emirates_id_issue_date'] ?? null, + 'emirates_id_employer' => $pending['emirates_id_employer'] ?? null, + 'emirates_id_issue_place' => $pending['emirates_id_issue_place'] ?? null, + 'emirates_id_occupation' => $pending['emirates_id_occupation'] ?? null, + 'nationality' => $pending['emirates_id_nationality'] ?? null, 'address' => $pending['address'] ?? null, ]); @@ -459,6 +502,15 @@ public function createPassword(Request $request) 'last_login_at' => now(), 'emirates_id_file' => null, // We did not store the file 'address' => $pending['address'] ?? null, + 'emirates_id' => $pending['emirates_id_number'] ?? null, + 'emirates_id_name' => $pending['emirates_id_name'] ?? null, + 'emirates_id_dob' => $pending['emirates_id_dob'] ?? null, + 'emirates_id_issue_date' => $pending['emirates_id_issue_date'] ?? null, + 'emirates_id_expiry' => $pending['emirates_id_expiry'] ?? null, + 'emirates_id_employer' => $pending['emirates_id_employer'] ?? null, + 'emirates_id_issue_place' => $pending['emirates_id_issue_place'] ?? null, + 'emirates_id_occupation' => $pending['emirates_id_occupation'] ?? null, + 'nationality' => $pending['emirates_id_nationality'] ?? null, ]); // Create active subscription in database diff --git a/app/Http/Controllers/Employer/ProfileController.php b/app/Http/Controllers/Employer/ProfileController.php index 3b2198f..147ea82 100644 --- a/app/Http/Controllers/Employer/ProfileController.php +++ b/app/Http/Controllers/Employer/ProfileController.php @@ -42,15 +42,15 @@ private function buildProfileData($user, $profile) 'email' => $user->email, 'phone' => $profile->phone ?? '+971509990001', 'emirates_id' => [ - 'emirates_id_number' => $profile->emirates_id_number ?? '784-1988-5310327-2', + 'emirates_id_number' => $profile->emirates_id_number ?? '784-1987-5493842-5', 'name' => $profile->emirates_id_name ?? $user->name, - 'date_of_birth' => $profile->emirates_id_dob ?? '1990-01-01', - 'nationality' => $profile->nationality ?? 'Emirati', - 'issue_date' => $profile->emirates_id_issue_date ?? '2023-04-11', - 'expiry_date' => $profile->emirates_id_expiry ?? '2028-04-11', - 'employer' => $profile->emirates_id_employer ?? 'Federal Authority', - 'issue_place' => $profile->emirates_id_issue_place ?? 'Abu Dhabi', - 'occupation' => $profile->emirates_id_occupation ?? 'Manager', + 'date_of_birth' => $profile->emirates_id_dob ?? '1987-04-14', + 'nationality' => $profile->nationality ?? 'Bangladesh', + 'issue_date' => $profile->emirates_id_issue_date ?? '2025-12-12', + 'expiry_date' => $profile->emirates_id_expiry ?? '2027-12-11', + 'employer' => $profile->emirates_id_employer ?? 'Msj International Technical Services L.L.C UAE', + 'issue_place' => $profile->emirates_id_issue_place ?? 'Dubai', + 'occupation' => $profile->emirates_id_occupation ?? 'Electrician', ], 'fcm_token' => $user->fcm_token ?? 'fcm_token_example', 'address' => $profile->address ?? 'Villa 45, Street 12', @@ -74,15 +74,15 @@ public function index(Request $request) $profile = EmployerProfile::create([ 'user_id' => $user->id, 'phone' => '+971509990001', - 'emirates_id_number' => '784-1988-5310327-2', - 'emirates_id_name' => 'Ahmad Bin Ahmed', - 'emirates_id_dob' => '1990-01-01', - 'nationality' => 'Emirati', - 'emirates_id_issue_date' => '2023-04-11', - 'emirates_id_expiry' => '2028-04-11', - 'emirates_id_employer' => 'Federal Authority', - 'emirates_id_issue_place' => 'Abu Dhabi', - 'emirates_id_occupation' => 'Manager', + 'emirates_id_number' => '784-1987-5493842-5', + 'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam', + 'emirates_id_dob' => '1987-04-14', + 'nationality' => 'Bangladesh', + 'emirates_id_issue_date' => '2025-12-12', + 'emirates_id_expiry' => '2027-12-11', + 'emirates_id_employer' => 'Msj International Technical Services L.L.C UAE', + 'emirates_id_issue_place' => 'Dubai', + 'emirates_id_occupation' => 'Electrician', 'address' => 'Villa 45, Street 12', ]); } @@ -106,15 +106,15 @@ public function edit(Request $request) $profile = EmployerProfile::create([ 'user_id' => $user->id, 'phone' => '+971509990001', - 'emirates_id_number' => '784-1988-5310327-2', - 'emirates_id_name' => 'Ahmad Bin Ahmed', - 'emirates_id_dob' => '1990-01-01', - 'nationality' => 'Emirati', - 'emirates_id_issue_date' => '2023-04-11', - 'emirates_id_expiry' => '2028-04-11', - 'emirates_id_employer' => 'Federal Authority', - 'emirates_id_issue_place' => 'Abu Dhabi', - 'emirates_id_occupation' => 'Manager', + 'emirates_id_number' => '784-1987-5493842-5', + 'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam', + 'emirates_id_dob' => '1987-04-14', + 'nationality' => 'Bangladesh', + 'emirates_id_issue_date' => '2025-12-12', + 'emirates_id_expiry' => '2027-12-11', + 'emirates_id_employer' => 'Msj International Technical Services L.L.C UAE', + 'emirates_id_issue_place' => 'Dubai', + 'emirates_id_occupation' => 'Electrician', 'address' => 'Villa 45, Street 12', ]); } diff --git a/app/Services/OcrDocumentService.php b/app/Services/OcrDocumentService.php index de9eba3..081f038 100644 --- a/app/Services/OcrDocumentService.php +++ b/app/Services/OcrDocumentService.php @@ -316,6 +316,11 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array 'name' => null, 'nationality' => null, 'date_of_birth' => null, + 'card_number' => null, + 'gender' => null, + 'occupation' => null, + 'employer' => null, + 'issue_place' => null, 'ocr_accuracy' => 0.0, 'success' => false, ]; @@ -376,6 +381,12 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array if (!empty($data['date_of_birth'])) { $extracted['date_of_birth'] = self::normaliseDate($data['date_of_birth']); } + + $extracted['card_number'] = $data['card_number'] ?? null; + $extracted['gender'] = $data['gender'] ?? $data['sex'] ?? null; + $extracted['occupation'] = $data['occupation'] ?? null; + $extracted['employer'] = $data['employer'] ?? $data['company_name'] ?? null; + $extracted['issue_place'] = $data['issue_place'] ?? $data['issuing_place'] ?? $data['place_of_issue'] ?? null; } else { Log::warning('Emirates ID Front OCR API success=false', ['response' => $json]); } @@ -392,13 +403,31 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array // Fallbacks if (empty($extracted['emirates_id_number'])) { - $extracted['emirates_id_number'] = '784-' . rand(1975, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9); + $extracted['emirates_id_number'] = '784-1987-5493842-5'; } if (empty($extracted['name'])) { - $extracted['name'] = 'KRISHNA PRASAD'; + $extracted['name'] = 'Mohammad Jobaier Mohammad Abul Kalam'; } if (empty($extracted['nationality'])) { - $extracted['nationality'] = 'NPL'; + $extracted['nationality'] = 'Bangladesh'; + } + if (empty($extracted['date_of_birth'])) { + $extracted['date_of_birth'] = '1987-04-14'; + } + if (empty($extracted['card_number'])) { + $extracted['card_number'] = '151023946'; + } + if (empty($extracted['gender'])) { + $extracted['gender'] = 'M'; + } + if (empty($extracted['occupation'])) { + $extracted['occupation'] = 'Electrician'; + } + if (empty($extracted['employer'])) { + $extracted['employer'] = 'Msj International Technical Services L.L.C UAE'; + } + if (empty($extracted['issue_place'])) { + $extracted['issue_place'] = 'Dubai'; } return $extracted; @@ -517,10 +546,15 @@ private static function emiratesIdFrontTestFallback(): array { return [ 'success' => true, - 'emirates_id_number' => '784-1988-5310327-2', - 'name' => 'KRISHNA PRASAD', - 'nationality' => 'NPL', - 'date_of_birth' => '1988-03-22', + 'emirates_id_number' => '784-1987-5493842-5', + 'name' => 'Mohammad Jobaier Mohammad Abul Kalam', + 'nationality' => 'Bangladesh', + 'date_of_birth' => '1987-04-14', + 'card_number' => '151023946', + 'gender' => 'M', + 'occupation' => 'Electrician', + 'employer' => 'Msj International Technical Services L.L.C UAE', + 'issue_place' => 'Dubai', 'ocr_accuracy' => 98.5, ]; } @@ -529,8 +563,8 @@ private static function emiratesIdBackTestFallback(): array { return [ 'success' => true, - 'expiry_date' => '2028-04-11', - 'issue_date' => '2023-04-11', + 'expiry_date' => '2027-12-11', + 'issue_date' => '2025-12-12', 'ocr_accuracy' => 98.5, ]; } diff --git a/public/swagger.json b/public/swagger.json index 1c41e89..fae42c6 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -743,6 +743,11 @@ "nationality": { "type": "string" }, + "ocr_accuracy": { + "type": "number", + "format": "float", + "example": 99.0 + }, "passport_number": { "type": "string" }, @@ -767,6 +772,22 @@ "accompanied_by": { "type": "string" }, + "country": { + "type": "string", + "example": "United Arab Emirates" + }, + "date_of_birth": { + "type": "string", + "example": "25-JUN-1999" + }, + "document_type": { + "type": "string", + "example": "uae_visa" + }, + "entry_permit_no": { + "type": "string", + "example": "77003098 / 2019 / 204" + }, "expiry_date": { "type": "string", "example": "2024-02-15" @@ -774,6 +795,10 @@ "file_number": { "type": "string" }, + "full_name": { + "type": "string", + "example": "Mr.MUHAMMAD NADEEM RASHEED" + }, "id_number": { "type": "string" }, @@ -784,17 +809,50 @@ "name": { "type": "string" }, + "nationality": { + "type": "string", + "example": "PAKISTAN" + }, + "ocr_accuracy": { + "type": "number", + "format": "float", + "example": 99.0 + }, + "passport_no": { + "type": "string", + "example": "EN9458281" + }, "passport_number": { "type": "string" }, + "place_of_birth": { + "type": "string", + "example": "SIALKOT PAK" + }, "place_of_issue": { "type": "string" }, "profession": { - "type": "string" + "type": "string", + "example": "SALES REPRESENTATIVE" }, "sponsor": { "type": "string" + }, + "sponsor_name": { + "type": "string" + }, + "uid_no": { + "type": "string", + "example": "207404887" + }, + "valid_until": { + "type": "string", + "example": "04-MAR-2019" + }, + "visa_type": { + "type": "string", + "example": "ENTRY PERMIT" } } } diff --git a/resources/js/Layouts/AdminLayout.jsx b/resources/js/Layouts/AdminLayout.jsx index 1c1dc68..13f255d 100644 --- a/resources/js/Layouts/AdminLayout.jsx +++ b/resources/js/Layouts/AdminLayout.jsx @@ -40,6 +40,7 @@ export default function AdminLayout({ children, title = 'Dashboard' }) { { name: 'Support Tickets', href: '/admin/tickets', icon: LifeBuoy }, { name: 'Frequently Asked Questions', href: '/admin/faqs', icon: HelpCircle }, { name: 'Payments', href: '/admin/payments', icon: BadgeDollarSign }, + { name: 'Plan Configuration', href: '/admin/subscriptions', icon: CreditCard }, { name: 'Reports & Analytics', href: '/admin/analytics', icon: BarChart3 }, { name: 'System Audit Logs', href: '/admin/audit-logs', icon: History }, { name: 'Charity Events', href: '/admin/events', icon: Heart }, diff --git a/resources/js/Pages/Admin/Subscriptions/Index.jsx b/resources/js/Pages/Admin/Subscriptions/Index.jsx index 3a9934c..c180173 100644 --- a/resources/js/Pages/Admin/Subscriptions/Index.jsx +++ b/resources/js/Pages/Admin/Subscriptions/Index.jsx @@ -43,19 +43,78 @@ export default function SubscriptionsIndex({ plans: initialPlans }) { const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingPlan, setEditingPlan] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + + // Form states + const [name, setName] = useState(''); + const [price, setPrice] = useState(''); + const [duration, setDuration] = useState('Monthly'); + const [features, setFeatures] = useState(''); + + const handleCreateClick = () => { + setEditingPlan(null); + setName(''); + setPrice(''); + setDuration('Monthly'); + setFeatures(''); + setIsDialogOpen(true); + }; const handleEdit = (plan) => { setEditingPlan(plan); + setName(plan.name); + setPrice(plan.price); + setDuration(plan.duration); + setFeatures(plan.features.join('\n')); setIsDialogOpen(true); }; const handleDelete = (id) => { if (confirm('Are you sure you want to delete this plan?')) { - // router.delete(`/admin/subscriptions/plans/${id}`); setPlans(plans.filter(p => p.id !== id)); } }; + const handleSave = (e) => { + e.preventDefault(); + if (!name.trim() || !price) { + alert('Please fill out Name and Price.'); + return; + } + + const featuresArray = features + .split('\n') + .map(f => f.trim()) + .filter(Boolean); + + if (editingPlan) { + setPlans(plans.map(p => p.id === editingPlan.id ? { + ...p, + name, + price: Number(price), + duration, + features: featuresArray + } : p)); + } else { + const newId = name.toLowerCase().replace(/\s+/g, '-'); + setPlans([...plans, { + id: newId || `plan-${Date.now()}`, + name, + price: Number(price), + duration, + features: featuresArray, + status: 'Active' + }]); + } + setIsDialogOpen(false); + }; + + const filteredPlans = plans.filter(plan => + plan.name.toLowerCase().includes(searchQuery.toLowerCase()) || + plan.duration.toLowerCase().includes(searchQuery.toLowerCase()) || + plan.features.some(f => f.toLowerCase().includes(searchQuery.toLowerCase())) + ); + return ( @@ -68,11 +127,13 @@ export default function SubscriptionsIndex({ plans: initialPlans }) { setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-white border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-teal-500/10 focus:border-teal-500 outline-none transition-all" /> - - + {filteredPlans.length === 0 ? ( + + + No plans found - ))} + ) : ( + filteredPlans.map((plan) => ( + + +
+
+ {plan.id === 'basic' ? : + plan.id === 'premium' ? : + } +
+ {plan.name} +
+
+ {plan.price} + {plan.duration} + +
+ {plan.features.map((f, i) => ( + + {f} + + ))} +
+
+ + + {plan.status} + + + +
+ + +
+
+
+ )) + )} - {/* Plan Modal (Placeholder UI) */} + {/* Plan Modal */} @@ -163,44 +232,70 @@ export default function SubscriptionsIndex({ plans: initialPlans }) { -
-
-
- - +
+
+
+
+ + setName(e.target.value)} + className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none" + placeholder="e.g. Pro Search" + /> +
+
+ + setPrice(e.target.value)} + className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none" + placeholder="299" + /> +
- - + + +
+
+ +