diff --git a/app/Http/Controllers/Admin/AdminExtraController.php b/app/Http/Controllers/Admin/AdminExtraController.php
index 595a151..7e5ef60 100644
--- a/app/Http/Controllers/Admin/AdminExtraController.php
+++ b/app/Http/Controllers/Admin/AdminExtraController.php
@@ -440,7 +440,7 @@ public function auditLogs(Request $request)
}
/**
- * Announcements management
+ * Charity Events & Drives management
*/
public function announcements()
{
@@ -456,15 +456,38 @@ public function announcements()
$organization = optional($ann->employer->employerProfile)->company_name ?? 'Employer';
}
+ // Decode charity details if they exist in json
+ $charityDetails = null;
+ $content = $ann->body;
+ if (strpos($ann->body, '{"type":"Charity"') === 0) {
+ $decoded = json_decode($ann->body, true);
+ if ($decoded) {
+ $charityDetails = $decoded;
+ $content = $decoded['content'] ?? $ann->body;
+ }
+ } else {
+ // Fallback structured details if plain text existed previously
+ $charityDetails = [
+ 'type' => 'Charity',
+ 'provided_items' => 'Free Medical Checks & Food Supplies',
+ 'event_date' => $ann->created_at->addDays(2)->format('Y-m-d'),
+ 'event_time' => '9:00 AM - 3:00 PM',
+ 'location_details' => 'Al Quoz Community Center, Dubai',
+ 'location_pin' => 'https://maps.google.com',
+ 'content' => $ann->body,
+ ];
+ }
+
return [
'id' => $ann->id,
'title' => $ann->title,
- 'content' => $ann->body,
- 'type' => $ann->type,
+ 'content' => $content,
+ 'type' => $ann->type ?? 'Charity',
'posted_by' => $postedBy,
'organization' => $organization,
'status' => $ann->status ?? 'pending',
'created_at' => $ann->created_at ? $ann->created_at->format('M d, Y h:i A') : 'Just now',
+ 'charityDetails' => $charityDetails,
];
});
@@ -474,56 +497,70 @@ public function announcements()
}
/**
- * Store admin announcement
+ * Store admin charity event
*/
public function storeAnnouncement(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
- 'type' => 'nullable|string',
+ 'provided_items' => 'required|string',
+ 'event_date' => 'required|string',
+ 'event_time' => 'required|string',
+ 'location_details' => 'required|string',
+ 'location_pin' => 'required|string|url',
+ ]);
+
+ $body = json_encode([
+ 'type' => 'Charity',
+ 'provided_items' => $request->provided_items,
+ 'event_date' => $request->event_date,
+ 'event_time' => $request->event_time,
+ 'location_details' => $request->location_details,
+ 'location_pin' => $request->location_pin,
+ 'content' => $request->content,
]);
\App\Models\Announcement::create([
'title' => $request->title,
- 'body' => $request->content,
- 'type' => $request->type ?? 'info',
+ 'body' => $body,
+ 'type' => 'Charity',
'status' => 'approved',
]);
- return back()->with('success', 'Announcement broadcasted successfully.');
+ return back()->with('success', 'Charity Event created successfully.');
}
/**
- * Approve announcement
+ * Approve charity event
*/
public function approveAnnouncement(Request $request, $id)
{
$ann = \App\Models\Announcement::findOrFail($id);
$ann->update(['status' => 'approved']);
- return back()->with('success', 'Announcement approved successfully.');
+ return back()->with('success', 'Charity Event approved successfully.');
}
/**
- * Reject announcement
+ * Reject charity event
*/
public function rejectAnnouncement(Request $request, $id)
{
$ann = \App\Models\Announcement::findOrFail($id);
$ann->update(['status' => 'rejected']);
- return back()->with('success', 'Announcement rejected successfully.');
+ return back()->with('success', 'Charity Event rejected successfully.');
}
/**
- * Delete announcement
+ * Delete charity event
*/
public function deleteAnnouncement(Request $request, $id)
{
$ann = \App\Models\Announcement::findOrFail($id);
$ann->delete();
- return back()->with('success', 'Announcement deleted successfully.');
+ return back()->with('success', 'Charity Event deleted successfully.');
}
}
diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php
index 8b1becd..d7da971 100644
--- a/app/Http/Controllers/Admin/DashboardController.php
+++ b/app/Http/Controllers/Admin/DashboardController.php
@@ -19,6 +19,9 @@ public function index()
$inactiveWorkers = $totalWorkers - $activeWorkers;
$totalSponsors = \App\Models\Sponsor::count();
+ $activeSponsors = \App\Models\Sponsor::where('status', 'active')->count();
+ $inactiveSponsors = $totalSponsors - $activeSponsors;
+
$activeSubs = \App\Models\Sponsor::where('subscription_status', 'active')->count();
$expiredSubs = \App\Models\Sponsor::where('subscription_status', 'expired')->count();
$newSponsors = \App\Models\Sponsor::where('created_at', '>=', now()->subDays(7))->count();
@@ -83,6 +86,8 @@ public function index()
'active_workers' => $activeWorkers,
'inactive_workers' => $inactiveWorkers,
'total_employers' => $totalSponsors,
+ 'active_employers' => $activeSponsors,
+ 'inactive_employers' => $inactiveSponsors,
'active_subscriptions' => $activeSubs,
'revenue_this_month_aed' => $revenueSum,
'new_employers_this_week' => $newSponsors,
diff --git a/app/Http/Controllers/Admin/SupportTicketController.php b/app/Http/Controllers/Admin/SupportTicketController.php
index f803e49..2e3f8cf 100644
--- a/app/Http/Controllers/Admin/SupportTicketController.php
+++ b/app/Http/Controllers/Admin/SupportTicketController.php
@@ -112,6 +112,7 @@ public function reply(Request $request, $id)
$request->validate([
'message' => 'required|string',
'is_developer_response' => 'nullable|boolean',
+ 'close_ticket' => 'nullable|boolean',
]);
SupportTicketReply::create([
@@ -121,9 +122,11 @@ public function reply(Request $request, $id)
'is_developer_response' => $request->is_developer_response ?? false,
]);
- // Auto transition status to resolved if developer replies, or keep as in_progress
+ // Auto transition status to resolved/closed or keep as in_progress
$newStatus = $ticket->status;
- if ($ticket->status === 'open') {
+ if ($request->close_ticket) {
+ $newStatus = 'closed';
+ } else if ($ticket->status === 'open') {
$newStatus = 'in_progress';
}
$ticket->update(['status' => $newStatus]);
diff --git a/app/Http/Controllers/Api/EmployerAnnouncementController.php b/app/Http/Controllers/Api/EmployerAnnouncementController.php
index 79920ef..3b70148 100644
--- a/app/Http/Controllers/Api/EmployerAnnouncementController.php
+++ b/app/Http/Controllers/Api/EmployerAnnouncementController.php
@@ -111,6 +111,7 @@ public function createAnnouncement(Request $request)
'body' => $bodyText,
'type' => $request->type ?? 'info',
'employer_id' => $employer->id,
+ 'status' => 'pending',
]);
return response()->json([
diff --git a/app/Http/Controllers/Api/EmployerMessageController.php b/app/Http/Controllers/Api/EmployerMessageController.php
index affbe04..854326a 100644
--- a/app/Http/Controllers/Api/EmployerMessageController.php
+++ b/app/Http/Controllers/Api/EmployerMessageController.php
@@ -267,32 +267,6 @@ public function sendMessage(Request $request, $id)
// Touch conversation updated_at for sorting
$conv->touch();
-
- // Check if employer sent predefined message "are you looking job?"
- $textLower = strtolower($request->text);
- if (
- str_contains($textLower, 'looking job') ||
- str_contains($textLower, 'looking for a job') ||
- str_contains($textLower, 'looking for job') ||
- str_contains($textLower, 'are you looking')
- ) {
- // Automatically simulate worker response 'Yes' (triggers the S6 hired flow)
- $workerResponseText = 'Yes';
-
- // Create the message in database
- Message::create([
- 'conversation_id' => $conv->id,
- 'sender_type' => 'worker',
- 'sender_id' => $conv->worker_id,
- 'text' => $workerResponseText,
- ]);
-
- // Process the worker response to update status to Hired!
- $worker = Worker::find($conv->worker_id);
- if ($worker) {
- self::processWorkerResponse($conv, $worker, $workerResponseText);
- }
- }
});
return response()->json([
diff --git a/app/Http/Controllers/Api/EmployerProfileController.php b/app/Http/Controllers/Api/EmployerProfileController.php
index 5e58630..b3f31da 100644
--- a/app/Http/Controllers/Api/EmployerProfileController.php
+++ b/app/Http/Controllers/Api/EmployerProfileController.php
@@ -219,7 +219,7 @@ public function getDashboard(Request $request)
];
// Recent Announcements / Events
- $dbAnnouncements = \App\Models\Announcement::latest()->limit(5)->get();
+ $dbAnnouncements = \App\Models\Announcement::where('status', 'approved')->latest()->limit(5)->get();
$recentAnnouncements = $dbAnnouncements->map(function ($ann) {
$body = $ann->body;
$eventDate = null;
diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php
index fbaa6a0..b6e1ac9 100644
--- a/app/Http/Controllers/Api/EmployerWorkerController.php
+++ b/app/Http/Controllers/Api/EmployerWorkerController.php
@@ -52,8 +52,9 @@ private function formatWorker(Worker $w)
];
$photo = $photos[$w->id % 4];
- $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
- $reviewsCount = ($w->id * 4) % 20 + 2;
+ $dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
+ $reviewsCount = $dbReviews->count();
+ $rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0.0;
return [
'id' => $w->id,
@@ -829,24 +830,9 @@ public function getWorkerDetail(Request $request, $id)
if ($reviewsCount > 0) {
$rating = round($dbReviews->avg('rating'), 1);
} else {
- $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
- $reviewsCount = ($w->id * 4) % 20 + 2;
- $reviews = [
- [
- 'id' => 1,
- 'employer_name' => 'Fatima Al Mansoori',
- 'rating' => 5,
- 'date' => 'May 10, 2026',
- 'comment' => 'Extremely reliable and respectful. Professional work and great communication.',
- ],
- [
- 'id' => 2,
- 'employer_name' => 'Michael Harrison',
- 'rating' => 4,
- 'date' => 'Mar 24, 2026',
- 'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
- ]
- ];
+ $rating = 0.0;
+ $reviewsCount = 0;
+ $reviews = [];
}
// Similar workers matching web view
diff --git a/app/Http/Controllers/Api/SponsorController.php b/app/Http/Controllers/Api/SponsorController.php
index d45671e..e0c80b3 100644
--- a/app/Http/Controllers/Api/SponsorController.php
+++ b/app/Http/Controllers/Api/SponsorController.php
@@ -183,6 +183,7 @@ public function postCharityEvent(Request $request)
'body' => $request->body,
'type' => $request->type ?? 'charity',
'sponsor_id' => $sponsor->id,
+ 'status' => 'pending',
]);
return response()->json([
diff --git a/app/Http/Controllers/Api/WorkerAnnouncementController.php b/app/Http/Controllers/Api/WorkerAnnouncementController.php
index 3711439..ec1d1a7 100644
--- a/app/Http/Controllers/Api/WorkerAnnouncementController.php
+++ b/app/Http/Controllers/Api/WorkerAnnouncementController.php
@@ -20,21 +20,44 @@ public function getAnnouncements(Request $request)
$page = (int)$request->input('page', 1);
$perPage = (int)$request->input('per_page', 15);
- $query = Announcement::with('employer.employerProfile')->where('status', 'approved')->latest();
+ $query = Announcement::with(['employer.employerProfile', 'sponsor'])->where('status', 'approved')->latest();
$total = $query->count();
$offset = ($page - 1) * $perPage;
$announcements = $query->skip($offset)->take($perPage)->get()
->map(function ($announcement) {
+ $postedBy = 'System';
+ $organization = 'Migrant Support';
+
+ if ($announcement->sponsor_id) {
+ $postedBy = $announcement->sponsor->full_name;
+ $organization = $announcement->sponsor->organization_name;
+ } elseif ($announcement->employer_id) {
+ $postedBy = $announcement->employer->name;
+ $organization = $announcement->employer->employerProfile->company_name ?? 'Employer';
+ }
+
+ // Decode charity details if they exist in json
+ $charityDetails = null;
+ $content = $announcement->body;
+ if (strpos($announcement->body, '{"type":"Charity"') === 0) {
+ $decoded = json_decode($announcement->body, true);
+ if ($decoded) {
+ $charityDetails = $decoded;
+ $content = $decoded['content'] ?? $announcement->body;
+ }
+ }
+
return [
'id' => $announcement->id,
'title' => $announcement->title,
- 'body' => $announcement->body,
+ 'body' => $content,
'type' => $announcement->type,
- 'employer_name' => $announcement->employer->name ?? 'System',
- 'company_name' => $announcement->employer->employerProfile->company_name ?? 'Migrant Support',
+ 'employer_name' => $postedBy,
+ 'company_name' => $organization,
'created_at' => $announcement->created_at->toISOString(),
'time_ago' => $announcement->created_at->diffForHumans(),
+ 'charity_details' => $charityDetails,
];
});
diff --git a/app/Http/Controllers/Api/WorkerAuthController.php b/app/Http/Controllers/Api/WorkerAuthController.php
index 847093c..b85be59 100644
--- a/app/Http/Controllers/Api/WorkerAuthController.php
+++ b/app/Http/Controllers/Api/WorkerAuthController.php
@@ -41,6 +41,7 @@ public function skills()
return [
'id' => $skill->id,
'name' => $skill->name,
+ 'image_url' => $skill->image_path ? asset('storage/' . $skill->image_path) : null,
];
});
@@ -304,9 +305,6 @@ public function register(Request $request)
'gender' => 'nullable|string|in:male,female,other',
'live_in_out' => 'nullable|string|in:live_in,live_out',
'preferred_location' => 'nullable|string|max:255',
- 'country' => 'nullable|string|max:100',
- 'city' => 'nullable|string|max:100',
- 'area' => 'nullable|string|max:100',
]);
if ($validator->fails()) {
@@ -386,9 +384,6 @@ public function register(Request $request)
'in_country' => $inCountry,
'visa_status' => $inCountry ? $request->visa_status : null,
'preferred_job_type' => $request->preferred_job_type,
- 'country' => $request->country,
- 'city' => $request->city,
- 'area' => $request->area,
]);
if (!empty($skillsArray)) {
diff --git a/app/Http/Controllers/Api/WorkerProfileController.php b/app/Http/Controllers/Api/WorkerProfileController.php
index 08c38f3..aedba66 100644
--- a/app/Http/Controllers/Api/WorkerProfileController.php
+++ b/app/Http/Controllers/Api/WorkerProfileController.php
@@ -562,20 +562,44 @@ public function getDashboard(Request $request)
}
// 4. Latest charity events / announcements list
- $charityEvents = Announcement::with('employer.employerProfile')
+ $charityEvents = Announcement::with(['employer.employerProfile', 'sponsor'])
+ ->where('status', 'approved')
->latest()
->limit(5)
->get()
->map(function ($event) {
+ $postedBy = 'System';
+ $organization = 'Migrant Support';
+
+ if ($event->sponsor_id) {
+ $postedBy = $event->sponsor->full_name;
+ $organization = $event->sponsor->organization_name;
+ } elseif ($event->employer_id) {
+ $postedBy = $event->employer->name;
+ $organization = $event->employer->employerProfile->company_name ?? 'Employer';
+ }
+
+ // Decode charity details if they exist in json
+ $charityDetails = null;
+ $content = $event->body;
+ if (strpos($event->body, '{"type":"Charity"') === 0) {
+ $decoded = json_decode($event->body, true);
+ if ($decoded) {
+ $charityDetails = $decoded;
+ $content = $decoded['content'] ?? $event->body;
+ }
+ }
+
return [
'id' => $event->id,
'title' => $event->title,
- 'body' => $event->body,
+ 'body' => $content,
'type' => $event->type,
- 'employer_name' => $event->employer->name ?? 'System',
- 'company_name' => $event->employer->employerProfile->company_name ?? 'Migrant Support',
+ 'employer_name' => $postedBy,
+ 'company_name' => $organization,
'created_at' => $event->created_at->toIso8601String(),
'time_ago' => $event->created_at->diffForHumans(),
+ 'charity_details' => $charityDetails,
];
});
diff --git a/app/Http/Controllers/Employer/AnnouncementController.php b/app/Http/Controllers/Employer/AnnouncementController.php
index aa44f31..cdc3fb0 100644
--- a/app/Http/Controllers/Employer/AnnouncementController.php
+++ b/app/Http/Controllers/Employer/AnnouncementController.php
@@ -11,7 +11,13 @@ class AnnouncementController extends Controller
{
public function index(Request $request)
{
- $dbAnnouncements = Announcement::where('status', 'approved')->latest()->get();
+ $sess = session('user');
+ $sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
+
+ $dbAnnouncements = Announcement::where('status', 'approved')
+ ->orWhere('employer_id', $sessId)
+ ->latest()
+ ->get();
$announcements = $dbAnnouncements->map(function ($ann) {
$isCharity = true;
@@ -45,6 +51,7 @@ public function index(Request $request)
'audience' => 'Charity',
'isCharity' => $isCharity,
'charityDetails' => $charityDetails,
+ 'status' => $ann->status ?? 'pending',
'created_at' => $ann->created_at->diffForHumans(),
];
})->toArray();
@@ -84,6 +91,7 @@ public function store(Request $request)
'body' => $body,
'type' => 'Charity',
'employer_id' => $sessId,
+ 'status' => 'pending',
]);
return back()->with('success', 'Charity Event posted successfully.');
diff --git a/app/Http/Controllers/Employer/CandidateController.php b/app/Http/Controllers/Employer/CandidateController.php
index c69c562..f7e5577 100644
--- a/app/Http/Controllers/Employer/CandidateController.php
+++ b/app/Http/Controllers/Employer/CandidateController.php
@@ -70,6 +70,11 @@ public function index(Request $request)
'salary' => (int)$w->salary,
'status' => $status,
'applied_at' => $app->created_at->format('M d, Y'),
+ 'preferred_location' => $w->preferred_location,
+ 'preferred_job_type' => $w->preferred_job_type,
+ 'live_in_out' => $w->live_in_out,
+ 'in_country' => (bool)$w->in_country,
+ 'visa_status' => $w->visa_status,
];
})->filter()->values()->toArray();
@@ -97,6 +102,11 @@ public function index(Request $request)
'salary' => (int)$offer->salary,
'status' => $status,
'applied_at' => $offer->created_at->format('M d, Y'),
+ 'preferred_location' => $w->preferred_location,
+ 'preferred_job_type' => $w->preferred_job_type,
+ 'live_in_out' => $w->live_in_out,
+ 'in_country' => (bool)$w->in_country,
+ 'visa_status' => $w->visa_status,
];
})->filter()->values()->toArray();
@@ -108,6 +118,138 @@ public function index(Request $request)
return $w && $w['status'] === 'Hired';
}));
+ // Apply filters: preferred_location, job_type, live_in_out, nationality, in_country, visa_status
+ if ($request->filled('preferred_location')) {
+ $prefLoc = $request->preferred_location;
+ $locsArray = is_array($prefLoc) ? $prefLoc : array_filter(array_map('trim', explode(',', $prefLoc)));
+ $locsArray = array_map('strtolower', $locsArray);
+
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($locsArray) {
+ if (!isset($c['preferred_location'])) return false;
+ $wLoc = strtolower($c['preferred_location']);
+ foreach ($locsArray as $l) {
+ if (str_contains($wLoc, $l)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type');
+ if ($jobTypeParam) {
+ $jobTypesArray = is_array($jobTypeParam) ? $jobTypeParam : array_filter(array_map('trim', explode(',', $jobTypeParam)));
+ $jobTypesArray = array_map('strtolower', $jobTypesArray);
+
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($jobTypesArray) {
+ if (!isset($c['preferred_job_type'])) return false;
+ $wJobType = strtolower($c['preferred_job_type']);
+ foreach ($jobTypesArray as $jt) {
+ if (str_contains($wJobType, $jt)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ $accParam = $request->input('live_in_out') ?? $request->input('accommodation_type') ?? $request->input('accomadation_type');
+ if ($accParam) {
+ $accsArray = is_array($accParam) ? $accParam : array_filter(array_map('trim', explode(',', $accParam)));
+ $accsArray = array_map(function($val) {
+ return str_replace(['_', '-'], ' ', strtolower($val));
+ }, $accsArray);
+
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($accsArray) {
+ if (!isset($c['live_in_out'])) return false;
+ $wAcc = str_replace(['_', '-'], ' ', strtolower($c['live_in_out']));
+ foreach ($accsArray as $a) {
+ if (str_contains($wAcc, $a)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ if ($request->filled('nationality')) {
+ $natInput = $request->nationality;
+ $natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput)));
+ $natsArray = array_map('strtolower', $natsArray);
+
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($natsArray) {
+ if (!isset($c['nationality'])) return false;
+ $wNat = strtolower($c['nationality']);
+ foreach ($natsArray as $n) {
+ if (str_contains($wNat, $n) || $wNat === $n) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ if ($request->has('in_country')) {
+ $inCountryVal = $request->input('in_country');
+ if ($inCountryVal !== null && $inCountryVal !== '') {
+ $isInCountry = filter_var($inCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+ if ($isInCountry === null) {
+ $strVal = strtolower(trim($inCountryVal));
+ if ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'yes') {
+ $isInCountry = true;
+ } elseif ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'no') {
+ $isInCountry = false;
+ }
+ }
+ if ($isInCountry !== null) {
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isInCountry) {
+ return isset($c['in_country']) && (bool)$c['in_country'] === $isInCountry;
+ }));
+ }
+ }
+ }
+
+ if ($request->has('out_country')) {
+ $outCountryVal = $request->input('out_country');
+ if ($outCountryVal !== null && $outCountryVal !== '') {
+ $isOutCountry = filter_var($outCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+ if ($isOutCountry === null) {
+ $strVal = strtolower(trim($outCountryVal));
+ if ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'yes') {
+ $isOutCountry = true;
+ } elseif ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'no') {
+ $isOutCountry = false;
+ }
+ }
+ if ($isOutCountry !== null) {
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isOutCountry) {
+ return isset($c['in_country']) && (bool)$c['in_country'] !== $isOutCountry;
+ }));
+ }
+ }
+ }
+
+ $visaParam = $request->input('visa_status') ?? $request->input('next_visa_type') ?? $request->input('visa_type');
+ if ($visaParam) {
+ $visasArray = is_array($visaParam) ? $visaParam : array_filter(array_map('trim', explode(',', $visaParam)));
+ $visasArray = array_map('strtolower', $visasArray);
+
+ $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($visasArray) {
+ if (!isset($c['visa_status'])) return false;
+ $isInCountry = isset($c['in_country']) && (bool)$c['in_country'];
+ if (!$isInCountry) {
+ return false;
+ }
+ $wVisa = strtolower($c['visa_status']);
+ foreach ($visasArray as $v) {
+ if (str_contains($wVisa, $v)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
return Inertia::render('Employer/SelectedCandidates', [
'selectedWorkers' => $mergedCandidates,
]);
diff --git a/app/Http/Controllers/Employer/MessageController.php b/app/Http/Controllers/Employer/MessageController.php
index ff691ad..12a0f35 100644
--- a/app/Http/Controllers/Employer/MessageController.php
+++ b/app/Http/Controllers/Employer/MessageController.php
@@ -235,34 +235,6 @@ public function send(Request $request, $id)
// Touch conversation updated_at for sorting
$conv->touch();
- // Check if employer sent predefined message "are you looking job?"
- if ($request->text) {
- $textLower = strtolower($request->text);
- if (
- str_contains($textLower, 'looking job') ||
- str_contains($textLower, 'looking for a job') ||
- str_contains($textLower, 'looking for job') ||
- str_contains($textLower, 'are you looking')
- ) {
- // Automatically simulate worker response 'Yes' (triggers the S6 hired flow)
- $workerResponseText = 'Yes';
-
- // Create the message in database
- Message::create([
- 'conversation_id' => $conv->id,
- 'sender_type' => 'worker',
- 'sender_id' => $conv->worker_id,
- 'text' => $workerResponseText,
- ]);
-
- // Process the worker response to update status to Hired!
- $worker = Worker::find($conv->worker_id);
- if ($worker) {
- self::processWorkerResponse($conv, $worker, $workerResponseText);
- }
- }
- }
-
return back();
}
diff --git a/app/Http/Controllers/Employer/ShortlistController.php b/app/Http/Controllers/Employer/ShortlistController.php
index 62bae6b..79d0ded 100644
--- a/app/Http/Controllers/Employer/ShortlistController.php
+++ b/app/Http/Controllers/Employer/ShortlistController.php
@@ -85,8 +85,9 @@ public function index(Request $request)
];
$photo = $photos[$w->id % 4];
- $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
- $reviewsCount = ($w->id * 4) % 20 + 2;
+ $dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
+ $reviewsCount = $dbReviews->count();
+ $rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0.0;
return [
'id' => $w->id,
diff --git a/app/Http/Controllers/Employer/SupportTicketController.php b/app/Http/Controllers/Employer/SupportTicketController.php
index e19c925..f9af728 100644
--- a/app/Http/Controllers/Employer/SupportTicketController.php
+++ b/app/Http/Controllers/Employer/SupportTicketController.php
@@ -6,6 +6,7 @@
use App\Models\SupportTicket;
use App\Models\SupportTicketReply;
use App\Models\User;
+use App\Models\ReportReason;
use Illuminate\Http\Request;
use Inertia\Inertia;
@@ -41,6 +42,7 @@ public function index(Request $request)
}
$tickets = SupportTicket::where('user_id', $user->id)
+ ->with('reason')
->orderBy('created_at', 'desc')
->get()
->map(function ($ticket) {
@@ -48,6 +50,7 @@ public function index(Request $request)
'id' => $ticket->id,
'ticket_number' => $ticket->ticket_number,
'subject' => $ticket->subject,
+ 'reason' => $ticket->reason ? $ticket->reason->reason : null,
'status' => $ticket->status,
'priority' => $ticket->priority,
'created_at' => $ticket->created_at->format('Y-m-d H:i'),
@@ -67,7 +70,14 @@ public function create(Request $request)
return redirect()->route('employer.login');
}
- return Inertia::render('Employer/Support/Create');
+ $reasons = ReportReason::where('status', 'Active')
+ ->where('type', 'Support')
+ ->orderBy('reason', 'asc')
+ ->get(['id', 'reason', 'type']);
+
+ return Inertia::render('Employer/Support/Create', [
+ 'reasons' => $reasons,
+ ]);
}
public function store(Request $request)
@@ -78,6 +88,7 @@ public function store(Request $request)
}
$request->validate([
+ 'reason_id' => 'nullable|exists:report_reasons,id',
'subject' => 'required|string|max:255',
'description' => 'required|string',
'priority' => 'required|string|in:low,medium,high',
@@ -86,6 +97,7 @@ public function store(Request $request)
$ticket = SupportTicket::create([
'ticket_number' => 'TKT-' . rand(100000, 999999),
'user_id' => $user->id,
+ 'reason_id' => $request->reason_id,
'subject' => $request->subject,
'description' => $request->description,
'priority' => $request->priority,
@@ -103,7 +115,7 @@ public function show(Request $request, $id)
return redirect()->route('employer.login');
}
- $ticket = SupportTicket::where('user_id', $user->id)->findOrFail($id);
+ $ticket = SupportTicket::where('user_id', $user->id)->with('reason')->findOrFail($id);
$replies = SupportTicketReply::where('support_ticket_id', $ticket->id)
->with(['user', 'worker'])
@@ -125,6 +137,7 @@ public function show(Request $request, $id)
'id' => $ticket->id,
'ticket_number' => $ticket->ticket_number,
'subject' => $ticket->subject,
+ 'reason' => $ticket->reason ? $ticket->reason->reason : null,
'description' => $ticket->description,
'status' => $ticket->status,
'priority' => $ticket->priority,
diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php
index fe1d9b9..5d58afe 100644
--- a/app/Http/Controllers/Employer/WorkerController.php
+++ b/app/Http/Controllers/Employer/WorkerController.php
@@ -62,7 +62,7 @@ public function index(Request $request)
// Preferred job types: full-time / part-time / live-in / live-out
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
- $preferredJobType = $jobTypes[$w->id % 4];
+ $preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4];
// Emirates ID verification status (now dynamic passport status)
$emiratesIdStatus = $w->emirates_id_status;
@@ -85,8 +85,9 @@ public function index(Request $request)
];
$photo = $photos[$w->id % 4];
- $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
- $reviewsCount = ($w->id * 4) % 20 + 2;
+ $dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
+ $reviewsCount = $dbReviews->count();
+ $rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0.0;
return [
'id' => $w->id,
@@ -111,9 +112,144 @@ public function index(Request $request)
'bio' => $w->bio,
'rating' => $rating,
'reviews_count' => $reviewsCount,
+ 'preferred_location' => $w->preferred_location,
+ 'in_country' => (bool) $w->in_country,
];
})->filter()->values()->toArray();
+ // Apply request filters: preferred_location, job_type, live_in_out, nationality, in_country, visa_status
+ if ($request->filled('preferred_location')) {
+ $prefLoc = $request->preferred_location;
+ $locsArray = is_array($prefLoc) ? $prefLoc : array_filter(array_map('trim', explode(',', $prefLoc)));
+ $locsArray = array_map('strtolower', $locsArray);
+
+ $workers = array_values(array_filter($workers, function ($c) use ($locsArray) {
+ if (!isset($c['preferred_location'])) return false;
+ $wLoc = strtolower($c['preferred_location']);
+ foreach ($locsArray as $l) {
+ if (str_contains($wLoc, $l)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type');
+ if ($jobTypeParam) {
+ $jobTypesArray = is_array($jobTypeParam) ? $jobTypeParam : array_filter(array_map('trim', explode(',', $jobTypeParam)));
+ $jobTypesArray = array_map('strtolower', $jobTypesArray);
+
+ $workers = array_values(array_filter($workers, function ($c) use ($jobTypesArray) {
+ if (!isset($c['preferred_job_type'])) return false;
+ $wJobType = strtolower($c['preferred_job_type']);
+ foreach ($jobTypesArray as $jt) {
+ if (str_contains($wJobType, $jt)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ $accParam = $request->input('live_in_out') ?? $request->input('accommodation_type') ?? $request->input('accomadation_type');
+ if ($accParam) {
+ $accsArray = is_array($accParam) ? $accParam : array_filter(array_map('trim', explode(',', $accParam)));
+ $accsArray = array_map(function($val) {
+ return str_replace(['_', '-'], ' ', strtolower($val));
+ }, $accsArray);
+
+ $workers = array_values(array_filter($workers, function ($c) use ($accsArray) {
+ if (!isset($c['live_in_out'])) return false;
+ $wAcc = str_replace(['_', '-'], ' ', strtolower($c['live_in_out']));
+ foreach ($accsArray as $a) {
+ if (str_contains($wAcc, $a)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ if ($request->filled('nationality')) {
+ $natInput = $request->nationality;
+ $natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput)));
+ $natsArray = array_map('strtolower', $natsArray);
+
+ $workers = array_values(array_filter($workers, function ($c) use ($natsArray) {
+ if (!isset($c['nationality'])) return false;
+ $wNat = strtolower($c['nationality']);
+ foreach ($natsArray as $n) {
+ if (str_contains($wNat, $n) || $wNat === $n) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+ if ($request->has('in_country')) {
+ $inCountryVal = $request->input('in_country');
+ if ($inCountryVal !== null && $inCountryVal !== '') {
+ $isInCountry = filter_var($inCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+ if ($isInCountry === null) {
+ $strVal = strtolower(trim($inCountryVal));
+ if ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'yes') {
+ $isInCountry = true;
+ } elseif ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'no') {
+ $isInCountry = false;
+ }
+ }
+ if ($isInCountry !== null) {
+ $workers = array_values(array_filter($workers, function ($c) use ($isInCountry) {
+ return isset($c['in_country']) && (bool)$c['in_country'] === $isInCountry;
+ }));
+ }
+ }
+ }
+
+ if ($request->has('out_country')) {
+ $outCountryVal = $request->input('out_country');
+ if ($outCountryVal !== null && $outCountryVal !== '') {
+ $isOutCountry = filter_var($outCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+ if ($isOutCountry === null) {
+ $strVal = strtolower(trim($outCountryVal));
+ if ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'yes') {
+ $isOutCountry = true;
+ } elseif ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'no') {
+ $isOutCountry = false;
+ }
+ }
+ if ($isOutCountry !== null) {
+ $workers = array_values(array_filter($workers, function ($c) use ($isOutCountry) {
+ return isset($c['in_country']) && (bool)$c['in_country'] !== $isOutCountry;
+ }));
+ }
+ }
+ }
+
+ $visaParam = $request->input('visa_status') ?? $request->input('next_visa_type') ?? $request->input('visa_type');
+ if ($visaParam) {
+ $visasArray = is_array($visaParam) ? $visaParam : array_filter(array_map('trim', explode(',', $visaParam)));
+ $visasArray = array_map('strtolower', $visasArray);
+
+ $workers = array_values(array_filter($workers, function ($c) use ($visasArray) {
+ if (!isset($c['visa_status'])) return false;
+ $isInCountry = isset($c['in_country']) && (bool)$c['in_country'];
+ if (!$isInCountry) {
+ return false;
+ }
+ $wVisa = strtolower($c['visa_status']);
+ foreach ($visasArray as $v) {
+ if (str_contains($wVisa, $v)) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }
+
+
$shortlistedIds = Shortlist::where('employer_id', $employerId)->pluck('worker_id')->toArray();
$dbCategories = WorkerCategory::pluck('name')->toArray();
@@ -190,28 +326,7 @@ public function show($id)
})->toArray();
$reviewsCount = count($reviews);
- if ($reviewsCount > 0) {
- $rating = round($dbReviews->avg('rating'), 1);
- } else {
- $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
- $reviewsCount = ($w->id * 4) % 20 + 2;
- $reviews = [
- [
- 'id' => 1,
- 'employer_name' => 'Fatima Al Mansoori',
- 'rating' => 5,
- 'date' => 'May 10, 2026',
- 'comment' => 'Extremely reliable and respectful. Professional work and great communication.',
- ],
- [
- 'id' => 2,
- 'employer_name' => 'Michael Harrison',
- 'rating' => 4,
- 'date' => 'Mar 24, 2026',
- 'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
- ]
- ];
- }
+ $rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0;
$simDb = Worker::with('category')
->where('id', '!=', $w->id)
diff --git a/app/Models/Skill.php b/app/Models/Skill.php
index 2928332..e43c2a2 100644
--- a/app/Models/Skill.php
+++ b/app/Models/Skill.php
@@ -9,7 +9,7 @@ class Skill extends Model
{
use HasFactory;
- protected $fillable = ['name'];
+ protected $fillable = ['name', 'image_path'];
public function workers()
{
diff --git a/app/Models/SupportTicket.php b/app/Models/SupportTicket.php
index 9cf60f0..691a866 100644
--- a/app/Models/SupportTicket.php
+++ b/app/Models/SupportTicket.php
@@ -12,6 +12,7 @@ class SupportTicket extends Model
protected $fillable = [
'ticket_number',
'user_id',
+ 'reason_id',
'worker_id',
'subject',
'description',
@@ -24,6 +25,11 @@ public function user()
return $this->belongsTo(User::class, 'user_id');
}
+ public function reason()
+ {
+ return $this->belongsTo(ReportReason::class, 'reason_id');
+ }
+
public function worker()
{
return $this->belongsTo(Worker::class, 'worker_id');
diff --git a/database/migrations/2026_06_09_123604_add_reason_id_to_support_tickets_table.php b/database/migrations/2026_06_09_123604_add_reason_id_to_support_tickets_table.php
new file mode 100644
index 0000000..436528e
--- /dev/null
+++ b/database/migrations/2026_06_09_123604_add_reason_id_to_support_tickets_table.php
@@ -0,0 +1,24 @@
+unsignedBigInteger('reason_id')->nullable()->after('user_id');
+ $table->foreign('reason_id')->references('id')->on('report_reasons')->nullOnDelete();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('support_tickets', function (Blueprint $table) {
+ $table->dropForeign(['reason_id']);
+ $table->dropColumn('reason_id');
+ });
+ }
+};
diff --git a/database/migrations/2026_06_09_152000_seed_support_reasons_to_report_reasons_table.php b/database/migrations/2026_06_09_152000_seed_support_reasons_to_report_reasons_table.php
new file mode 100644
index 0000000..2812814
--- /dev/null
+++ b/database/migrations/2026_06_09_152000_seed_support_reasons_to_report_reasons_table.php
@@ -0,0 +1,29 @@
+ 'Billing & Payment Issues', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'Subscription Plan Changes', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'Technical Bugs & Site Errors', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'Worker Profile Verification Help', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'Employer Account Settings', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'General Feedback & Suggestions', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ['reason' => 'Other Queries', 'type' => 'Support', 'status' => 'Active', 'created_at' => now(), 'updated_at' => now()],
+ ];
+
+ DB::table('report_reasons')->insert($reasons);
+ }
+
+ public function down(): void
+ {
+ DB::table('report_reasons')->where('type', 'Support')->delete();
+ }
+};
diff --git a/database/migrations/2026_06_09_154000_seed_new_skills_to_skills_table.php b/database/migrations/2026_06_09_154000_seed_new_skills_to_skills_table.php
new file mode 100644
index 0000000..fc71f63
--- /dev/null
+++ b/database/migrations/2026_06_09_154000_seed_new_skills_to_skills_table.php
@@ -0,0 +1,25 @@
+ 'Ironing', 'created_at' => now(), 'updated_at' => now()],
+ ['name' => 'Car Washing', 'created_at' => now(), 'updated_at' => now()],
+ ['name' => 'Pet Sitting', 'created_at' => now(), 'updated_at' => now()],
+ ];
+
+ foreach ($skills as $skill) {
+ DB::table('skills')->updateOrInsert(['name' => $skill['name']], $skill);
+ }
+ }
+
+ public function down(): void
+ {
+ DB::table('skills')->whereIn('name', ['Ironing', 'Car Washing', 'Pet Sitting'])->delete();
+ }
+};
diff --git a/database/migrations/2026_06_09_155000_add_image_path_to_skills_table.php b/database/migrations/2026_06_09_155000_add_image_path_to_skills_table.php
new file mode 100644
index 0000000..0275cc9
--- /dev/null
+++ b/database/migrations/2026_06_09_155000_add_image_path_to_skills_table.php
@@ -0,0 +1,22 @@
+string('image_path')->nullable()->after('name');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('skills', function (Blueprint $table) {
+ $table->dropColumn('image_path');
+ });
+ }
+};
diff --git a/public/swagger.json b/public/swagger.json
index 490284d..ea39e7a 100644
--- a/public/swagger.json
+++ b/public/swagger.json
@@ -438,23 +438,20 @@
},
"live_in_out": {
"type": "string",
- "example": "Live-out",
- "description": "Accommodation preference (e.g. 'Live-in', 'Live-out')."
+ "enum": ["live_in", "live_out"],
+ "example": "live_out",
+ "description": "Accommodation preference (live_in, live_out)."
},
- "country": {
+ "gender": {
"type": "string",
- "example": "UAE",
- "description": "Preferred location country."
+ "enum": ["male", "female", "other"],
+ "example": "male",
+ "description": "Gender of the worker (male, female, other)."
},
- "city": {
+ "preferred_location": {
"type": "string",
- "example": "Dubai",
- "description": "Preferred location city."
- },
- "area": {
- "type": "string",
- "example": "Marina",
- "description": "Preferred location area."
+ "example": "Dubai Marina",
+ "description": "Preferred work location/area."
}
}
}
@@ -998,6 +995,23 @@
8
],
"description": "Array of skill IDs"
+ },
+ "gender": {
+ "type": "string",
+ "enum": ["male", "female", "other"],
+ "example": "male",
+ "description": "Gender of the worker (male, female, other)."
+ },
+ "live_in_out": {
+ "type": "string",
+ "enum": ["live_in", "live_out"],
+ "example": "live_out",
+ "description": "Accommodation preference (live_in, live_out)."
+ },
+ "preferred_location": {
+ "type": "string",
+ "example": "Dubai Marina",
+ "description": "Preferred work location/area."
}
}
}
@@ -2618,6 +2632,60 @@
"type": "string"
}
},
+ {
+ "name": "preferred_location",
+ "in": "query",
+ "required": false,
+ "description": "Filter by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "job_type",
+ "in": "query",
+ "required": false,
+ "description": "Filter by job type (comma-separated list, e.g. full-time,part-time).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "accommodation_type",
+ "in": "query",
+ "required": false,
+ "description": "Filter by accommodation type (comma-separated list, e.g. live_in,live_out).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "in_country",
+ "in": "query",
+ "required": false,
+ "description": "Filter by in country status (true/false or yes/no or in/out).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "out_country",
+ "in": "query",
+ "required": false,
+ "description": "Filter by out country status (true/false or yes/no or out/in).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "visa_status",
+ "in": "query",
+ "required": false,
+ "description": "Filter by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.",
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "availability",
"in": "query",
@@ -2864,6 +2932,60 @@
"type": "string"
}
},
+ {
+ "name": "preferred_location",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "job_type",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by job type (comma-separated list, e.g. full-time,part-time).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "accommodation_type",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by accommodation type (comma-separated list, e.g. live_in,live_out).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "in_country",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by in country status (true/false or yes/no or in/out).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "out_country",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by out country status (true/false or yes/no or out/in).",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "visa_status",
+ "in": "query",
+ "required": false,
+ "description": "Filter candidates by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.",
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "availability",
"in": "query",
@@ -3759,6 +3881,15 @@
"type": "string",
"example": "Live-out"
},
+ "gender": {
+ "type": "string",
+ "enum": ["male", "female", "other"],
+ "example": "male"
+ },
+ "preferred_location": {
+ "type": "string",
+ "example": "Dubai Marina"
+ },
"country": {
"type": "string",
"example": "UAE"
diff --git a/public/uploads/documents/1780988095_passport_passport.pdf b/public/uploads/documents/1780988095_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988183_passport_passport.pdf b/public/uploads/documents/1780988183_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988392_passport_passport.pdf b/public/uploads/documents/1780988392_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988597_passport_passport.pdf b/public/uploads/documents/1780988597_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988676_passport_passport.pdf b/public/uploads/documents/1780988676_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988798_passport_passport.pdf b/public/uploads/documents/1780988798_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988812_passport_passport.pdf b/public/uploads/documents/1780988812_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780988845_passport_passport.pdf b/public/uploads/documents/1780988845_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780989037_passport_passport.pdf b/public/uploads/documents/1780989037_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780989071_passport_passport.pdf b/public/uploads/documents/1780989071_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780989259_passport_passport.pdf b/public/uploads/documents/1780989259_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990037_passport_passport.pdf b/public/uploads/documents/1780990037_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990124_passport_passport.pdf b/public/uploads/documents/1780990124_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990245_passport_passport.pdf b/public/uploads/documents/1780990245_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990329_passport_passport.pdf b/public/uploads/documents/1780990329_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990399_passport_passport.pdf b/public/uploads/documents/1780990399_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990412_passport_mds1.png b/public/uploads/documents/1780990412_passport_mds1.png
new file mode 100644
index 0000000..ae18a13
Binary files /dev/null and b/public/uploads/documents/1780990412_passport_mds1.png differ
diff --git a/public/uploads/documents/1780990465_passport_passport.pdf b/public/uploads/documents/1780990465_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780990749_passport_passport.pdf b/public/uploads/documents/1780990749_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780991005_passport_passport.pdf b/public/uploads/documents/1780991005_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780991060_passport_passport.pdf b/public/uploads/documents/1780991060_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/documents/1780996836_passport_1000100671.jpg b/public/uploads/documents/1780996836_passport_1000100671.jpg
new file mode 100644
index 0000000..66666c9
Binary files /dev/null and b/public/uploads/documents/1780996836_passport_1000100671.jpg differ
diff --git a/public/uploads/documents/1780996836_visa_1000100671.jpg b/public/uploads/documents/1780996836_visa_1000100671.jpg
new file mode 100644
index 0000000..66666c9
Binary files /dev/null and b/public/uploads/documents/1780996836_visa_1000100671.jpg differ
diff --git a/public/uploads/documents/1780999934_passport_1000418145.jpg b/public/uploads/documents/1780999934_passport_1000418145.jpg
new file mode 100644
index 0000000..7d2ecb2
Binary files /dev/null and b/public/uploads/documents/1780999934_passport_1000418145.jpg differ
diff --git a/public/uploads/documents/1780999934_visa_1000418145.jpg b/public/uploads/documents/1780999934_visa_1000418145.jpg
new file mode 100644
index 0000000..7d2ecb2
Binary files /dev/null and b/public/uploads/documents/1780999934_visa_1000418145.jpg differ
diff --git a/public/uploads/documents/1781002405_passport_1000100687.jpg b/public/uploads/documents/1781002405_passport_1000100687.jpg
new file mode 100644
index 0000000..d9a219b
Binary files /dev/null and b/public/uploads/documents/1781002405_passport_1000100687.jpg differ
diff --git a/public/uploads/documents/1781002405_visa_1000100687.jpg b/public/uploads/documents/1781002405_visa_1000100687.jpg
new file mode 100644
index 0000000..d9a219b
Binary files /dev/null and b/public/uploads/documents/1781002405_visa_1000100687.jpg differ
diff --git a/public/uploads/documents/1781082894_passport_passport.pdf b/public/uploads/documents/1781082894_passport_passport.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988093_license_license.pdf b/public/uploads/licenses/1780988093_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988183_license_license.pdf b/public/uploads/licenses/1780988183_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988391_license_license.pdf b/public/uploads/licenses/1780988391_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988597_license_license.pdf b/public/uploads/licenses/1780988597_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988676_license_license.pdf b/public/uploads/licenses/1780988676_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988798_license_license.pdf b/public/uploads/licenses/1780988798_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988812_license_license.pdf b/public/uploads/licenses/1780988812_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780988844_license_license.pdf b/public/uploads/licenses/1780988844_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780989036_license_license.pdf b/public/uploads/licenses/1780989036_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780989071_license_license.pdf b/public/uploads/licenses/1780989071_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780989258_license_license.pdf b/public/uploads/licenses/1780989258_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990035_license_license.pdf b/public/uploads/licenses/1780990035_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990120_license_license.pdf b/public/uploads/licenses/1780990120_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990245_license_license.pdf b/public/uploads/licenses/1780990245_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990328_license_license.pdf b/public/uploads/licenses/1780990328_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990399_license_license.pdf b/public/uploads/licenses/1780990399_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990462_license_license.pdf b/public/uploads/licenses/1780990462_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780990748_license_license.pdf b/public/uploads/licenses/1780990748_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780991005_license_license.pdf b/public/uploads/licenses/1780991005_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780991060_license_license.pdf b/public/uploads/licenses/1780991060_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1780997733_license_fDi5IHoHP14P_1024_500.png b/public/uploads/licenses/1780997733_license_fDi5IHoHP14P_1024_500.png
new file mode 100644
index 0000000..24d963e
Binary files /dev/null and b/public/uploads/licenses/1780997733_license_fDi5IHoHP14P_1024_500.png differ
diff --git a/public/uploads/licenses/1781082884_license_license.pdf b/public/uploads/licenses/1781082884_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/public/uploads/licenses/1781082893_license_license.pdf b/public/uploads/licenses/1781082893_license_license.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/resources/js/Layouts/AdminLayout.jsx b/resources/js/Layouts/AdminLayout.jsx
index c47166d..0326a11 100644
--- a/resources/js/Layouts/AdminLayout.jsx
+++ b/resources/js/Layouts/AdminLayout.jsx
@@ -18,7 +18,8 @@ import {
BarChart3,
History,
List,
- LifeBuoy
+ LifeBuoy,
+ Heart
} from 'lucide-react';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
@@ -39,7 +40,7 @@ export default function AdminLayout({ children, title = 'Dashboard' }) {
{ name: 'Campaigns & Alerts', href: '/admin/notifications', icon: BellRing },
{ name: 'Reports & Analytics', href: '/admin/analytics', icon: BarChart3 },
{ name: 'System Audit Logs', href: '/admin/audit-logs', icon: History },
- { name: 'Announcements', href: '/admin/announcements', icon: Megaphone },
+ { name: 'Charity Events', href: '/admin/announcements', icon: Heart },
{ name: 'Skills', href: '/admin/master-data/skills', icon: Settings },
{ name: 'Reason Master', href: '/admin/master-data/reasons', icon: List },
];
diff --git a/resources/js/Pages/Admin/Announcements/Index.jsx b/resources/js/Pages/Admin/Announcements/Index.jsx
index 99e6c2e..83ff9b0 100644
--- a/resources/js/Pages/Admin/Announcements/Index.jsx
+++ b/resources/js/Pages/Admin/Announcements/Index.jsx
@@ -1,12 +1,43 @@
import React, { useState } from 'react';
import { Head, router } from '@inertiajs/react';
import AdminLayout from '../../../Layouts/AdminLayout';
-import { Megaphone, Plus, Trash2, Send, CheckCircle2, Check, X } from 'lucide-react';
+import {
+ Heart, Plus, Trash2, Send, CheckCircle2, Check, X,
+ Calendar, MapPin, Clock, Gift, Sparkles, Search,
+ ChevronDown, ChevronUp, Building, User, XCircle, Eye, BellRing
+} from 'lucide-react';
+
+const hoursList = [
+ '12:00', '12:30', '01:00', '01:30', '02:00', '02:30',
+ '03:00', '03:30', '04:00', '04:30', '05:00', '05:30',
+ '06:00', '06:30', '07:00', '07:30', '08:00', '08:30',
+ '09:00', '09:30', '10:00', '10:30', '11:00', '11:30'
+];
export default function Announcements({ initialAnnouncements }) {
const [announcements, setAnnouncements] = useState(initialAnnouncements || []);
const [isFormOpen, setIsFormOpen] = useState(false);
- const [newAnnouncement, setNewAnnouncement] = useState({ title: '', content: '', audience: 'Both' });
+ const [searchQuery, setSearchQuery] = useState('');
+ const [statusFilter, setStatusFilter] = useState('All');
+ const [expandedCards, setExpandedCards] = useState({});
+
+ // Time picker states
+ const [startTimeHour, setStartTimeHour] = useState('09:00');
+ const [startTimeAmpm, setStartTimeAmpm] = useState('AM');
+ const [endTimeHour, setEndTimeHour] = useState('04:00');
+ const [endTimeAmpm, setEndTimeAmpm] = useState('PM');
+ const [isStartPickerOpen, setIsStartPickerOpen] = useState(false);
+ const [isEndPickerOpen, setIsEndPickerOpen] = useState(false);
+
+ const [newAnnouncement, setNewAnnouncement] = useState({
+ title: '',
+ content: '',
+ provided_items: '',
+ event_date: '',
+ event_time: '09:00 AM - 04:00 PM',
+ location_details: '',
+ location_pin: ''
+ });
const [errors, setErrors] = useState({});
const [toastMessage, setToastMessage] = useState(null);
@@ -19,7 +50,16 @@ export default function Announcements({ initialAnnouncements }) {
e.preventDefault();
let newErrors = {};
if (!newAnnouncement.title.trim()) newErrors.title = 'Title is required';
- if (!newAnnouncement.content.trim()) newErrors.content = 'Content is required';
+ if (!newAnnouncement.content.trim()) newErrors.content = 'Description is required';
+ if (!newAnnouncement.provided_items.trim()) newErrors.provided_items = 'Provided items is required';
+ if (!newAnnouncement.event_date.trim()) newErrors.event_date = 'Event date is required';
+ if (!newAnnouncement.event_time.trim()) newErrors.event_time = 'Event time is required';
+ if (!newAnnouncement.location_details.trim()) newErrors.location_details = 'Location details is required';
+ if (!newAnnouncement.location_pin.trim()) {
+ newErrors.location_pin = 'Location pin URL is required';
+ } else if (!newAnnouncement.location_pin.startsWith('http://') && !newAnnouncement.location_pin.startsWith('https://')) {
+ newErrors.location_pin = 'Must be a valid URL';
+ }
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
@@ -29,22 +69,45 @@ export default function Announcements({ initialAnnouncements }) {
router.post(route('admin.announcements.store'), {
title: newAnnouncement.title,
content: newAnnouncement.content,
- type: newAnnouncement.audience === 'Both' ? 'info' : (newAnnouncement.audience === 'Employers' ? 'success' : 'warning')
+ provided_items: newAnnouncement.provided_items,
+ event_date: newAnnouncement.event_date,
+ event_time: newAnnouncement.event_time,
+ location_details: newAnnouncement.location_details,
+ location_pin: newAnnouncement.location_pin
}, {
onSuccess: () => {
- setNewAnnouncement({ title: '', content: '', audience: 'Both' });
+ setNewAnnouncement({
+ title: '',
+ content: '',
+ provided_items: '',
+ event_date: '',
+ event_time: '09:00 AM - 04:00 PM',
+ location_details: '',
+ location_pin: ''
+ });
+ setStartTimeHour('09:00');
+ setStartTimeAmpm('AM');
+ setEndTimeHour('04:00');
+ setEndTimeAmpm('PM');
setErrors({});
setIsFormOpen(false);
- showToast('Announcement broadcasted successfully');
+ showToast('Charity Event created successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
+ const updateEventTime = (sh, sa, eh, ea) => {
+ setNewAnnouncement(prev => ({
+ ...prev,
+ event_time: `${sh} ${sa} - ${eh} ${ea}`
+ }));
+ };
+
const handleApprove = (id) => {
router.post(route('admin.announcements.approve', id), {}, {
onSuccess: () => {
- showToast('Announcement approved successfully');
+ showToast('Charity Event approved successfully');
setTimeout(() => window.location.reload(), 500);
}
});
@@ -53,26 +116,53 @@ export default function Announcements({ initialAnnouncements }) {
const handleReject = (id) => {
router.post(route('admin.announcements.reject', id), {}, {
onSuccess: () => {
- showToast('Announcement rejected successfully');
+ showToast('Charity Event rejected successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleDelete = (id) => {
- if (confirm('Are you sure you want to delete this announcement?')) {
+ if (confirm('Are you sure you want to delete this charity event?')) {
router.delete(route('admin.announcements.delete', id), {
onSuccess: () => {
- showToast('Announcement deleted');
+ showToast('Charity Event deleted');
setTimeout(() => window.location.reload(), 500);
}
});
}
};
+ const toggleExpand = (id) => {
+ setExpandedCards(prev => ({
+ ...prev,
+ [id]: !prev[id]
+ }));
+ };
+
+ // Calculate dynamic statistics
+ const totalDrives = announcements.length;
+ const pendingCount = announcements.filter(a => a.status === 'pending').length;
+ const approvedCount = announcements.filter(a => a.status === 'approved').length;
+ const rejectedCount = announcements.filter(a => a.status === 'rejected').length;
+
+ // Filter announcements
+ const filteredAnnouncements = announcements.filter(ann => {
+ const matchesSearch =
+ ann.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ ann.content.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ (ann.posted_by && ann.posted_by.toLowerCase().includes(searchQuery.toLowerCase())) ||
+ (ann.organization && ann.organization.toLowerCase().includes(searchQuery.toLowerCase())) ||
+ (ann.charityDetails?.provided_items && ann.charityDetails.provided_items.toLowerCase().includes(searchQuery.toLowerCase()));
+
+ const matchesStatus = statusFilter === 'All' || ann.status === statusFilter.toLowerCase();
+
+ return matchesSearch && matchesStatus;
+ });
+
return (
-
Approve user-submitted events or broadcast messages to all users.
+Manage, approve, or launch community charity initiatives.