From 7d0698509a8e1927d6974cf92b16e7b8159abd22 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Wed, 10 Jun 2026 16:14:59 +0530 Subject: [PATCH] worker dashboard api --- .../Admin/AdminExtraController.php | 10 +- .../Api/EmployerAnnouncementController.php | 4 + .../Api/WorkerProfileController.php | 9 ++ .../Employer/AnnouncementController.php | 1 + .../Controllers/Employer/WorkerController.php | 2 + app/Models/Announcement.php | 1 + ...140_add_remarks_to_announcements_table.php | 28 ++++++ .../1781087120_passport_passport.pdf | 0 .../1781087242_passport_passport.pdf | 0 .../1781087905_passport_passport.pdf | 0 .../1781087935_passport_passport.pdf | 0 .../1781087948_passport_passport.pdf | 0 .../1781088129_passport_national-id.jpg | Bin 0 -> 12796 bytes .../licenses/1781087119_license_license.pdf | 0 .../licenses/1781087242_license_license.pdf | 0 .../licenses/1781087905_license_license.pdf | 0 .../licenses/1781087935_license_license.pdf | 0 .../licenses/1781087948_license_license.pdf | 0 resources/js/Layouts/EmployerLayout.jsx | 26 ++++- .../js/Pages/Admin/Announcements/Index.jsx | 81 +++++++++++++-- resources/js/Pages/Employer/Announcements.jsx | 12 +-- tests/Feature/AnnouncementApiTest.php | 35 +++++++ tests/Feature/WorkerJourneyApiTest.php | 92 ++++++++++++++++++ 23 files changed, 285 insertions(+), 16 deletions(-) create mode 100644 database/migrations/2026_06_10_141140_add_remarks_to_announcements_table.php create mode 100644 public/uploads/documents/1781087120_passport_passport.pdf create mode 100644 public/uploads/documents/1781087242_passport_passport.pdf create mode 100644 public/uploads/documents/1781087905_passport_passport.pdf create mode 100644 public/uploads/documents/1781087935_passport_passport.pdf create mode 100644 public/uploads/documents/1781087948_passport_passport.pdf create mode 100644 public/uploads/documents/1781088129_passport_national-id.jpg create mode 100644 public/uploads/licenses/1781087119_license_license.pdf create mode 100644 public/uploads/licenses/1781087242_license_license.pdf create mode 100644 public/uploads/licenses/1781087905_license_license.pdf create mode 100644 public/uploads/licenses/1781087935_license_license.pdf create mode 100644 public/uploads/licenses/1781087948_license_license.pdf diff --git a/app/Http/Controllers/Admin/AdminExtraController.php b/app/Http/Controllers/Admin/AdminExtraController.php index 7e5ef60..81590ae 100644 --- a/app/Http/Controllers/Admin/AdminExtraController.php +++ b/app/Http/Controllers/Admin/AdminExtraController.php @@ -486,6 +486,7 @@ public function announcements() 'posted_by' => $postedBy, 'organization' => $organization, 'status' => $ann->status ?? 'pending', + 'remarks' => $ann->remarks, 'created_at' => $ann->created_at ? $ann->created_at->format('M d, Y h:i A') : 'Just now', 'charityDetails' => $charityDetails, ]; @@ -547,8 +548,15 @@ public function approveAnnouncement(Request $request, $id) */ public function rejectAnnouncement(Request $request, $id) { + $request->validate([ + 'remarks' => 'required|string|max:1000', + ]); + $ann = \App\Models\Announcement::findOrFail($id); - $ann->update(['status' => 'rejected']); + $ann->update([ + 'status' => 'rejected', + 'remarks' => $request->remarks, + ]); return back()->with('success', 'Charity Event rejected successfully.'); } diff --git a/app/Http/Controllers/Api/EmployerAnnouncementController.php b/app/Http/Controllers/Api/EmployerAnnouncementController.php index 3b70148..daa98a7 100644 --- a/app/Http/Controllers/Api/EmployerAnnouncementController.php +++ b/app/Http/Controllers/Api/EmployerAnnouncementController.php @@ -36,6 +36,8 @@ public function getAnnouncements(Request $request) 'title' => $announcement->title, 'body' => $announcement->body, 'type' => $announcement->type, + 'status' => $announcement->status ?? 'pending', + 'remarks' => $announcement->remarks, 'created_at' => $announcement->created_at->toISOString(), 'time_ago' => $announcement->created_at->diffForHumans(), ]; @@ -123,6 +125,8 @@ public function createAnnouncement(Request $request) 'title' => $announcement->title, 'body' => $announcement->body, 'type' => $announcement->type, + 'status' => $announcement->status ?? 'pending', + 'remarks' => $announcement->remarks, 'created_at' => $announcement->created_at->toISOString(), 'time_ago' => $announcement->created_at->diffForHumans(), ] diff --git a/app/Http/Controllers/Api/WorkerProfileController.php b/app/Http/Controllers/Api/WorkerProfileController.php index aedba66..2020713 100644 --- a/app/Http/Controllers/Api/WorkerProfileController.php +++ b/app/Http/Controllers/Api/WorkerProfileController.php @@ -7,6 +7,7 @@ use App\Models\Worker; use App\Models\WorkerDocument; use App\Models\ProfileView; +use App\Models\Conversation; use App\Models\EmployerProfile; use App\Models\Announcement; use Illuminate\Http\Request; @@ -538,6 +539,12 @@ public function getDashboard(Request $request) // 2. Count of sponsors who viewed the profile $viewsCount = ProfileView::where('worker_id', $worker->id)->count(); + // Unique count of employers who viewed this profile + $profileViewed = ProfileView::where('worker_id', $worker->id)->distinct('employer_id')->count('employer_id'); + + // Unique count of employers who contacted this worker + $employerContacted = Conversation::where('worker_id', $worker->id)->distinct('employer_id')->count('employer_id'); + // 3. Currently working sponsor/employer $currentSponsor = null; $acceptedOffer = JobOffer::where('worker_id', $worker->id) @@ -608,6 +615,8 @@ public function getDashboard(Request $request) 'data' => [ 'active_status' => $activeStatus, 'count_sponsors_viewed' => $viewsCount, + 'employer_contacted' => $employerContacted, + 'profile_viewed' => $profileViewed, 'currently_working_sponsor' => $currentSponsor, 'latest_charity_events_list' => $charityEvents ] diff --git a/app/Http/Controllers/Employer/AnnouncementController.php b/app/Http/Controllers/Employer/AnnouncementController.php index cdc3fb0..34f0ae4 100644 --- a/app/Http/Controllers/Employer/AnnouncementController.php +++ b/app/Http/Controllers/Employer/AnnouncementController.php @@ -52,6 +52,7 @@ public function index(Request $request) 'isCharity' => $isCharity, 'charityDetails' => $charityDetails, 'status' => $ann->status ?? 'pending', + 'remarks' => $ann->remarks, 'created_at' => $ann->created_at->diffForHumans(), ]; })->toArray(); diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php index 5d58afe..9053c4d 100644 --- a/app/Http/Controllers/Employer/WorkerController.php +++ b/app/Http/Controllers/Employer/WorkerController.php @@ -379,6 +379,8 @@ public function show($id) 'reviews_count' => $reviewsCount, 'reviews' => $reviews, 'similar_workers' => $similarWorkers, + 'status' => $w->status, + 'availability_status' => $w->status, ]; return Inertia::render('Employer/Workers/Show', [ diff --git a/app/Models/Announcement.php b/app/Models/Announcement.php index 8a6a417..00dd476 100644 --- a/app/Models/Announcement.php +++ b/app/Models/Announcement.php @@ -16,6 +16,7 @@ class Announcement extends Model 'employer_id', 'sponsor_id', 'status', + 'remarks', ]; /** diff --git a/database/migrations/2026_06_10_141140_add_remarks_to_announcements_table.php b/database/migrations/2026_06_10_141140_add_remarks_to_announcements_table.php new file mode 100644 index 0000000..cabee56 --- /dev/null +++ b/database/migrations/2026_06_10_141140_add_remarks_to_announcements_table.php @@ -0,0 +1,28 @@ +text('remarks')->nullable()->after('status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('announcements', function (Blueprint $table) { + $table->dropColumn('remarks'); + }); + } +}; diff --git a/public/uploads/documents/1781087120_passport_passport.pdf b/public/uploads/documents/1781087120_passport_passport.pdf new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/documents/1781087242_passport_passport.pdf b/public/uploads/documents/1781087242_passport_passport.pdf new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/documents/1781087905_passport_passport.pdf b/public/uploads/documents/1781087905_passport_passport.pdf new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/documents/1781087935_passport_passport.pdf b/public/uploads/documents/1781087935_passport_passport.pdf new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/documents/1781087948_passport_passport.pdf b/public/uploads/documents/1781087948_passport_passport.pdf new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/documents/1781088129_passport_national-id.jpg b/public/uploads/documents/1781088129_passport_national-id.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d266d0749a9afcaceebe4ca00469a597e18156af GIT binary patch literal 12796 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 {navItems.map((item) => { const Icon = item.icon; - const isActive = url.startsWith(item.href); + let isActive = url.startsWith(item.href); + + if (url.startsWith('/employer/workers/')) { + const isHired = props.worker?.status === 'Hired' || props.worker?.availability_status === 'Hired'; + if (isHired) { + if (item.href === '/employer/candidates') { + isActive = true; + } else if (item.href === '/employer/workers') { + isActive = false; + } + } + } return ( {mobileTabs.map((tab) => { const Icon = tab.icon; - const isActive = url.startsWith(tab.href); + let isActive = url.startsWith(tab.href); + + if (url.startsWith('/employer/workers/')) { + const isHired = props.worker?.status === 'Hired' || props.worker?.availability_status === 'Hired'; + if (isHired) { + if (tab.href === '/employer/candidates') { + isActive = true; + } else if (tab.href === '/employer/workers') { + isActive = false; + } + } + } return ( { setToastMessage(message); @@ -114,8 +116,20 @@ export default function Announcements({ initialAnnouncements }) { }; const handleReject = (id) => { - router.post(route('admin.announcements.reject', id), {}, { + setRejectingAnnouncementId(id); + setRejectionRemarks(''); + }; + + const handleRejectSubmit = (e) => { + e.preventDefault(); + if (!rejectionRemarks.trim()) return; + + router.post(route('admin.announcements.reject', rejectingAnnouncementId), { + remarks: rejectionRemarks + }, { onSuccess: () => { + setRejectingAnnouncementId(null); + setRejectionRemarks(''); showToast('Charity Event rejected successfully'); setTimeout(() => window.location.reload(), 500); } @@ -528,6 +542,59 @@ export default function Announcements({ initialAnnouncements }) { )} + {/* Rejection remarks modal */} + {rejectingAnnouncementId && ( +
+
+
+

+ + Reject Charity Event +

+ +
+ +
+
+ + +
+ +
+ + +
+
+
+
+ )} + {/* Compact Grid of Events */} {filteredAnnouncements.length === 0 ? (
@@ -567,12 +634,7 @@ export default function Announcements({ initialAnnouncements }) { }`}> {ann.status} - {ann.status === 'approved' && ( - - - Push & Reminder Scheduled - - )} +
@@ -632,6 +694,11 @@ export default function Announcements({ initialAnnouncements }) { {isExpanded ? : } )} + {ann.status === 'rejected' && ann.remarks && ( +
+ Remarks: {ann.remarks} +
+ )}
{/* Details metadata row (compact icons) */} diff --git a/resources/js/Pages/Employer/Announcements.jsx b/resources/js/Pages/Employer/Announcements.jsx index 2fe7883..fb8e880 100644 --- a/resources/js/Pages/Employer/Announcements.jsx +++ b/resources/js/Pages/Employer/Announcements.jsx @@ -484,12 +484,7 @@ export default function Announcements({ initialAnnouncements }) { }`}> {ann.status === 'approved' ? t('status_approved', 'APPROVED') : (ann.status === 'rejected' ? t('status_rejected', 'REJECTED') : t('status_pending', 'PENDING'))} - {ann.status === 'approved' && ( - - - {t('push_reminder_scheduled', 'Push & Reminder Scheduled')} - - )} + @@ -510,6 +505,11 @@ export default function Announcements({ initialAnnouncements }) { {isExpanded ? : } )} + {ann.status === 'rejected' && ann.remarks && ( +
+ {t('rejection_remarks_label', 'Remarks')}: {ann.remarks} +
+ )} {/* Details metadata row (compact icons) */} diff --git a/tests/Feature/AnnouncementApiTest.php b/tests/Feature/AnnouncementApiTest.php index dcca34b..1258c73 100644 --- a/tests/Feature/AnnouncementApiTest.php +++ b/tests/Feature/AnnouncementApiTest.php @@ -265,4 +265,39 @@ public function test_worker_cannot_see_pending_announcements_until_approved() $this->assertCount(1, $response->json('data.announcements')); $this->assertEquals('Pending Ann', $response->json('data.announcements.0.title')); } + + /** + * Test admin can reject announcement with remarks. + */ + public function test_admin_can_reject_announcement_with_remarks() + { + $admin = User::create([ + 'name' => 'Admin User', + 'email' => 'admin@example.com', + 'password' => bcrypt('password'), + 'role' => 'admin', + ]); + + $announcement = Announcement::create([ + 'title' => 'Pending Ann', + 'body' => 'Event description.', + 'type' => 'Charity', + 'employer_id' => $this->employer->id, + 'status' => 'pending', + ]); + + $response = $this->actingAs($admin) + ->withSession(['user' => $admin]) + ->post('/admin/announcements/' . $announcement->id . '/reject', [ + 'remarks' => 'Inappropriate content or location details.', + ]); + + $response->assertStatus(302); // redirects back + + $this->assertDatabaseHas('announcements', [ + 'id' => $announcement->id, + 'status' => 'rejected', + 'remarks' => 'Inappropriate content or location details.', + ]); + } } diff --git a/tests/Feature/WorkerJourneyApiTest.php b/tests/Feature/WorkerJourneyApiTest.php index 5b940e5..df77631 100644 --- a/tests/Feature/WorkerJourneyApiTest.php +++ b/tests/Feature/WorkerJourneyApiTest.php @@ -587,4 +587,96 @@ public function test_nationalities_list_api() 'name' => 'Indian' ]); } + + /** + * Test worker dashboard stats including employer_contacted and profile_viewed unique counts. + */ + public function test_worker_dashboard_stats() + { + // 1. Create a worker + $worker = Worker::create([ + 'name' => 'Rahul Sharma', + 'email' => 'rahul@example.com', + 'phone' => '+971501234567', + 'language' => 'HI', + 'password' => bcrypt('password'), + 'nationality' => 'India', + 'age' => 25, + 'salary' => 1500, + 'availability' => 'Immediate', + 'experience' => 'Not Specified', + 'religion' => 'Not Specified', + 'bio' => 'Test', + 'category_id' => $this->category->id, + 'verified' => true, + 'status' => 'active', + 'api_token' => 'worker-test-token-dashboard', + ]); + + // Create passport document to prevent "Passport Pending" 404 + WorkerDocument::create([ + 'worker_id' => $worker->id, + 'type' => 'passport', + 'number' => '123456', + 'file_path' => 'passports/test.pdf', + ]); + + // 2. Create two employers + $employer1 = \App\Models\User::create([ + 'name' => 'Employer One', + 'email' => 'emp1@example.com', + 'password' => bcrypt('password'), + 'role' => 'employer', + 'api_token' => 'emp1-token', + ]); + + $employer2 = \App\Models\User::create([ + 'name' => 'Employer Two', + 'email' => 'emp2@example.com', + 'password' => bcrypt('password'), + 'role' => 'employer', + 'api_token' => 'emp2-token', + ]); + + // 3. Employer 1 views worker\'s profile twice (should count as 1) + $response = $this->withHeaders([ + 'Authorization' => 'Bearer emp1-token', + ])->getJson('/api/employers/workers/' . $worker->id); + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer emp1-token', + ])->getJson('/api/employers/workers/' . $worker->id); + $response->assertStatus(200); + + // 4. Employer 2 views worker\'s profile once + $response = $this->withHeaders([ + 'Authorization' => 'Bearer emp2-token', + ])->getJson('/api/employers/workers/' . $worker->id); + $response->assertStatus(200); + + // Assert profile_views database has 2 unique views + $this->assertEquals(2, \App\Models\ProfileView::where('worker_id', $worker->id)->count()); + + // 5. Create a conversation (contact) from Employer 1 + \App\Models\Conversation::create([ + 'employer_id' => $employer1->id, + 'worker_id' => $worker->id, + ]); + + // 6. Hit the worker dashboard API + $dashboardResponse = $this->withHeaders([ + 'Authorization' => 'Bearer worker-test-token-dashboard', + ])->getJson('/api/workers/dashboard'); + + $dashboardResponse->assertStatus(200) + ->assertJson([ + 'success' => true, + 'data' => [ + 'active_status' => 'active', + 'profile_viewed' => 2, + 'employer_contacted' => 1, + ] + ]); + } }