review, dashboard api
This commit is contained in:
parent
b60b14fb9d
commit
eb75dca482
@ -7,12 +7,69 @@
|
|||||||
use App\Models\Message;
|
use App\Models\Message;
|
||||||
use App\Models\Worker;
|
use App\Models\Worker;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\JobOffer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
class EmployerMessageController extends Controller
|
class EmployerMessageController extends Controller
|
||||||
{
|
{
|
||||||
|
public static function processWorkerResponse($conv, $worker, $text)
|
||||||
|
{
|
||||||
|
$replyText = strtolower(trim($text));
|
||||||
|
|
||||||
|
if ($replyText === 'yes' || $replyText === 'no') {
|
||||||
|
// Find the last message sent by the employer in this conversation (excluding the current worker message)
|
||||||
|
$lastEmployerMessage = Message::where('conversation_id', $conv->id)
|
||||||
|
->where('sender_type', 'employer')
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($lastEmployerMessage) {
|
||||||
|
$questionText = strtolower($lastEmployerMessage->text);
|
||||||
|
$isLookingJobQuestion = str_contains($questionText, 'looking job') ||
|
||||||
|
str_contains($questionText, 'looking for a job') ||
|
||||||
|
str_contains($questionText, 'looking for job') ||
|
||||||
|
str_contains($questionText, 'are you looking');
|
||||||
|
|
||||||
|
if ($isLookingJobQuestion) {
|
||||||
|
if ($replyText === 'yes') {
|
||||||
|
// S6 Outcome: Update status as Hired
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'Hired',
|
||||||
|
'availability' => 'Hired'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Accept existing direct offer or application
|
||||||
|
$offer = JobOffer::where('employer_id', $conv->employer_id)
|
||||||
|
->where('worker_id', $worker->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($offer) {
|
||||||
|
$offer->update(['status' => 'accepted']);
|
||||||
|
} else {
|
||||||
|
JobOffer::create([
|
||||||
|
'employer_id' => $conv->employer_id,
|
||||||
|
'worker_id' => $worker->id,
|
||||||
|
'work_date' => now()->format('Y-m-d'),
|
||||||
|
'location' => 'Dubai',
|
||||||
|
'salary' => $worker->salary ?: 2000,
|
||||||
|
'notes' => 'Hired via chat agreement.',
|
||||||
|
'status' => 'accepted',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else if ($replyText === 'no') {
|
||||||
|
// S5 Outcome: Update status as Hidden (Auto-Hidden from search)
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'hidden',
|
||||||
|
'availability' => 'Not Available'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all conversations for the authorized employer.
|
* Get all conversations for the authorized employer.
|
||||||
*
|
*
|
||||||
@ -186,6 +243,32 @@ public function sendMessage(Request $request, $id)
|
|||||||
|
|
||||||
// Touch conversation updated_at for sorting
|
// Touch conversation updated_at for sorting
|
||||||
$conv->touch();
|
$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([
|
return response()->json([
|
||||||
|
|||||||
144
app/Http/Controllers/Api/EmployerReviewController.php
Normal file
144
app/Http/Controllers/Api/EmployerReviewController.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Worker;
|
||||||
|
use App\Models\Review;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EmployerReviewController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add a new review for a worker.
|
||||||
|
* POST /api/employers/reviews
|
||||||
|
*/
|
||||||
|
public function addReview(Request $request)
|
||||||
|
{
|
||||||
|
/** @var User $employer */
|
||||||
|
$employer = $request->attributes->get('employer');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'worker_id' => 'required|exists:workers,id',
|
||||||
|
'rating' => 'required|integer|min:1|max:5',
|
||||||
|
'comment' => 'nullable|string|max:1000',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Validation error.',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if employer has already reviewed this worker
|
||||||
|
$existingReview = Review::where('employer_id', $employer->id)
|
||||||
|
->where('worker_id', $request->worker_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingReview) {
|
||||||
|
// If they already have a review, we can update it or direct them to the edit endpoint.
|
||||||
|
// To be robust, let's update it directly and inform them it was updated (acting as an edit).
|
||||||
|
$existingReview->update([
|
||||||
|
'rating' => $request->rating,
|
||||||
|
'comment' => $request->comment,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Review updated successfully (existing review updated).',
|
||||||
|
'data' => [
|
||||||
|
'review' => $existingReview
|
||||||
|
]
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new review
|
||||||
|
$review = Review::create([
|
||||||
|
'employer_id' => $employer->id,
|
||||||
|
'worker_id' => $request->worker_id,
|
||||||
|
'rating' => $request->rating,
|
||||||
|
'comment' => $request->comment,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Review added successfully.',
|
||||||
|
'data' => [
|
||||||
|
'review' => $review
|
||||||
|
]
|
||||||
|
], 201);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
logger()->error('Mobile API Add Review Failure: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'An error occurred while adding the review.',
|
||||||
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit/update an existing review.
|
||||||
|
* PUT /api/employers/reviews/{id}
|
||||||
|
*/
|
||||||
|
public function editReview(Request $request, $id)
|
||||||
|
{
|
||||||
|
/** @var User $employer */
|
||||||
|
$employer = $request->attributes->get('employer');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'rating' => 'required|integer|min:1|max:5',
|
||||||
|
'comment' => 'nullable|string|max:1000',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Validation error.',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$review = Review::where('id', $id)
|
||||||
|
->where('employer_id', $employer->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$review) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Review not found or unauthorized.'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$review->update([
|
||||||
|
'rating' => $request->rating,
|
||||||
|
'comment' => $request->comment,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Review edited successfully.',
|
||||||
|
'data' => [
|
||||||
|
'review' => $review
|
||||||
|
]
|
||||||
|
], 200);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
logger()->error('Mobile API Edit Review Failure: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'An error occurred while updating the review.',
|
||||||
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,8 @@
|
|||||||
use App\Models\JobOffer;
|
use App\Models\JobOffer;
|
||||||
use App\Models\JobApplication;
|
use App\Models\JobApplication;
|
||||||
use App\Models\JobPost;
|
use App\Models\JobPost;
|
||||||
|
use App\Models\Review;
|
||||||
|
use App\Models\ProfileView;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
@ -376,6 +378,14 @@ public function getWorkerDetail(Request $request, $id)
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record employer profile view (Requirement 3)
|
||||||
|
if ($employer) {
|
||||||
|
ProfileView::updateOrCreate(
|
||||||
|
['employer_id' => $employer->id, 'worker_id' => $w->id],
|
||||||
|
['updated_at' => now()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Map languages
|
// Map languages
|
||||||
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
||||||
|
|
||||||
@ -409,10 +419,24 @@ public function getWorkerDetail(Request $request, $id)
|
|||||||
];
|
];
|
||||||
$photo = $photos[$w->id % 4];
|
$photo = $photos[$w->id % 4];
|
||||||
|
|
||||||
|
// Fetch dynamic database reviews (Requirement 1)
|
||||||
|
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
||||||
|
$reviews = $dbReviews->map(function ($rev) {
|
||||||
|
return [
|
||||||
|
'id' => $rev->id,
|
||||||
|
'employer_name' => $rev->employer->name ?? 'Employer',
|
||||||
|
'rating' => $rev->rating,
|
||||||
|
'date' => $rev->created_at->format('M d, Y'),
|
||||||
|
'comment' => $rev->comment,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$reviewsCount = count($reviews);
|
||||||
|
if ($reviewsCount > 0) {
|
||||||
|
$rating = round($dbReviews->avg('rating'), 1);
|
||||||
|
} else {
|
||||||
$rating = 4.0 + (($w->id * 3) % 10) / 10.0;
|
$rating = 4.0 + (($w->id * 3) % 10) / 10.0;
|
||||||
$reviewsCount = ($w->id * 4) % 20 + 2;
|
$reviewsCount = ($w->id * 4) % 20 + 2;
|
||||||
|
|
||||||
// Hardcoded premium reviews matching web detail view
|
|
||||||
$reviews = [
|
$reviews = [
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
@ -429,6 +453,7 @@ public function getWorkerDetail(Request $request, $id)
|
|||||||
'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
|
'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Similar workers matching web view
|
// Similar workers matching web view
|
||||||
$simDb = Worker::with('category')
|
$simDb = Worker::with('category')
|
||||||
|
|||||||
@ -6,12 +6,69 @@
|
|||||||
use App\Models\Conversation;
|
use App\Models\Conversation;
|
||||||
use App\Models\Message;
|
use App\Models\Message;
|
||||||
use App\Models\Worker;
|
use App\Models\Worker;
|
||||||
|
use App\Models\JobOffer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
class WorkerMessageController extends Controller
|
class WorkerMessageController extends Controller
|
||||||
{
|
{
|
||||||
|
public static function processWorkerResponse($conv, $worker, $text)
|
||||||
|
{
|
||||||
|
$replyText = strtolower(trim($text));
|
||||||
|
|
||||||
|
if ($replyText === 'yes' || $replyText === 'no') {
|
||||||
|
// Find the last message sent by the employer in this conversation (excluding the current worker message)
|
||||||
|
$lastEmployerMessage = Message::where('conversation_id', $conv->id)
|
||||||
|
->where('sender_type', 'employer')
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($lastEmployerMessage) {
|
||||||
|
$questionText = strtolower($lastEmployerMessage->text);
|
||||||
|
$isLookingJobQuestion = str_contains($questionText, 'looking job') ||
|
||||||
|
str_contains($questionText, 'looking for a job') ||
|
||||||
|
str_contains($questionText, 'looking for job') ||
|
||||||
|
str_contains($questionText, 'are you looking');
|
||||||
|
|
||||||
|
if ($isLookingJobQuestion) {
|
||||||
|
if ($replyText === 'yes') {
|
||||||
|
// S6 Outcome: Update status as Hired
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'Hired',
|
||||||
|
'availability' => 'Hired'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Accept existing direct offer or application
|
||||||
|
$offer = JobOffer::where('employer_id', $conv->employer_id)
|
||||||
|
->where('worker_id', $worker->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($offer) {
|
||||||
|
$offer->update(['status' => 'accepted']);
|
||||||
|
} else {
|
||||||
|
JobOffer::create([
|
||||||
|
'employer_id' => $conv->employer_id,
|
||||||
|
'worker_id' => $worker->id,
|
||||||
|
'work_date' => now()->format('Y-m-d'),
|
||||||
|
'location' => 'Dubai',
|
||||||
|
'salary' => $worker->salary ?: 2000,
|
||||||
|
'notes' => 'Hired via chat agreement.',
|
||||||
|
'status' => 'accepted',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else if ($replyText === 'no') {
|
||||||
|
// S5 Outcome: Update status as Hidden (Auto-Hidden from search)
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'hidden',
|
||||||
|
'availability' => 'Not Available'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all conversations for the authorized worker.
|
* Get all conversations for the authorized worker.
|
||||||
*
|
*
|
||||||
@ -181,6 +238,9 @@ public function sendMessage(Request $request, $id)
|
|||||||
|
|
||||||
// Touch conversation updated_at for sorting
|
// Touch conversation updated_at for sorting
|
||||||
$conv->touch();
|
$conv->touch();
|
||||||
|
|
||||||
|
// Process the worker response to update status to Hired or Hidden
|
||||||
|
self::processWorkerResponse($conv, $worker, $request->text);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
use App\Models\JobOffer;
|
use App\Models\JobOffer;
|
||||||
use App\Models\Worker;
|
use App\Models\Worker;
|
||||||
use App\Models\WorkerDocument;
|
use App\Models\WorkerDocument;
|
||||||
|
use App\Models\ProfileView;
|
||||||
|
use App\Models\EmployerProfile;
|
||||||
|
use App\Models\Announcement;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@ -431,4 +434,140 @@ public function respondToOffer(Request $request, $id)
|
|||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get profile view statistics and employer listing for the worker dashboard.
|
||||||
|
* GET /api/workers/dashboard/views
|
||||||
|
*/
|
||||||
|
public function getProfileViews(Request $request)
|
||||||
|
{
|
||||||
|
/** @var Worker $worker */
|
||||||
|
$worker = $request->attributes->get('worker');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get views count
|
||||||
|
$viewsCount = ProfileView::where('worker_id', $worker->id)->count();
|
||||||
|
|
||||||
|
// Get list of employers/sponsors who viewed, with their details
|
||||||
|
$views = ProfileView::where('worker_id', $worker->id)
|
||||||
|
->with('employer')
|
||||||
|
->latest('updated_at')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$list = $views->map(function ($view) {
|
||||||
|
$emp = $view->employer;
|
||||||
|
|
||||||
|
$empProfile = null;
|
||||||
|
if ($emp) {
|
||||||
|
$empProfile = EmployerProfile::where('user_id', $emp->id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $view->id,
|
||||||
|
'employer_id' => $view->employer_id,
|
||||||
|
'employer_name' => $emp->name ?? 'Employer/Sponsor',
|
||||||
|
'company_name' => $empProfile->company_name ?? 'Private Sponsor',
|
||||||
|
'nationality' => $empProfile->nationality ?? 'UAE',
|
||||||
|
'city' => $empProfile->city ?? 'Dubai',
|
||||||
|
'viewed_at' => $view->updated_at->toIso8601String(),
|
||||||
|
'viewed_at_formatted' => $view->updated_at->diffForHumans(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'views_count' => $viewsCount,
|
||||||
|
'views_list' => $list
|
||||||
|
]
|
||||||
|
], 200);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
logger()->error('Worker Dashboard Views API Failure: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'An error occurred while fetching profile views statistics.',
|
||||||
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get consolidated worker dashboard details (Requirement: dashboard api).
|
||||||
|
* GET /api/workers/dashboard
|
||||||
|
*/
|
||||||
|
public function getDashboard(Request $request)
|
||||||
|
{
|
||||||
|
/** @var Worker $worker */
|
||||||
|
$worker = $request->attributes->get('worker');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Active status
|
||||||
|
$activeStatus = $worker->status;
|
||||||
|
|
||||||
|
// 2. Count of sponsors who viewed the profile
|
||||||
|
$viewsCount = ProfileView::where('worker_id', $worker->id)->count();
|
||||||
|
|
||||||
|
// 3. Currently working sponsor/employer
|
||||||
|
$currentSponsor = null;
|
||||||
|
$acceptedOffer = JobOffer::where('worker_id', $worker->id)
|
||||||
|
->where('status', 'accepted')
|
||||||
|
->with('employer.employerProfile')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($acceptedOffer && $acceptedOffer->employer) {
|
||||||
|
$emp = $acceptedOffer->employer;
|
||||||
|
$empProfile = $emp->employerProfile;
|
||||||
|
$currentSponsor = [
|
||||||
|
'id' => $emp->id,
|
||||||
|
'name' => $emp->name,
|
||||||
|
'company_name' => $empProfile->company_name ?? 'Private Sponsor',
|
||||||
|
'nationality' => $empProfile->nationality ?? 'UAE',
|
||||||
|
'city' => $empProfile->city ?? 'Dubai',
|
||||||
|
'email' => $emp->email,
|
||||||
|
'phone' => $emp->phone,
|
||||||
|
'hired_at' => $acceptedOffer->updated_at->toIso8601String(),
|
||||||
|
'hired_at_formatted' => $acceptedOffer->updated_at->format('M d, Y'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Latest charity events / announcements list
|
||||||
|
$charityEvents = Announcement::with('employer.employerProfile')
|
||||||
|
->latest()
|
||||||
|
->limit(5)
|
||||||
|
->get()
|
||||||
|
->map(function ($event) {
|
||||||
|
return [
|
||||||
|
'id' => $event->id,
|
||||||
|
'title' => $event->title,
|
||||||
|
'body' => $event->body,
|
||||||
|
'type' => $event->type,
|
||||||
|
'employer_name' => $event->employer->name ?? 'System',
|
||||||
|
'company_name' => $event->employer->employerProfile->company_name ?? 'Migrant Support',
|
||||||
|
'created_at' => $event->created_at->toIso8601String(),
|
||||||
|
'time_ago' => $event->created_at->diffForHumans(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'active_status' => $activeStatus,
|
||||||
|
'count_sponsors_viewed' => $viewsCount,
|
||||||
|
'currently_working_sponsor' => $currentSponsor,
|
||||||
|
'latest_charity_events_list' => $charityEvents
|
||||||
|
]
|
||||||
|
], 200);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
logger()->error('Worker Dashboard API Failure: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'An error occurred while loading the worker dashboard.',
|
||||||
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
use App\Models\Conversation;
|
use App\Models\Conversation;
|
||||||
use App\Models\Message;
|
use App\Models\Message;
|
||||||
use App\Models\Worker;
|
use App\Models\Worker;
|
||||||
|
use App\Models\JobOffer;
|
||||||
|
|
||||||
class MessageController extends Controller
|
class MessageController extends Controller
|
||||||
{
|
{
|
||||||
@ -36,6 +37,62 @@ private function resolveCurrentUser()
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function processWorkerResponse($conv, $worker, $text)
|
||||||
|
{
|
||||||
|
$replyText = strtolower(trim($text));
|
||||||
|
|
||||||
|
if ($replyText === 'yes' || $replyText === 'no') {
|
||||||
|
// Find the last message sent by the employer in this conversation (excluding the current worker message)
|
||||||
|
$lastEmployerMessage = Message::where('conversation_id', $conv->id)
|
||||||
|
->where('sender_type', 'employer')
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($lastEmployerMessage) {
|
||||||
|
$questionText = strtolower($lastEmployerMessage->text);
|
||||||
|
$isLookingJobQuestion = str_contains($questionText, 'looking job') ||
|
||||||
|
str_contains($questionText, 'looking for a job') ||
|
||||||
|
str_contains($questionText, 'looking for job') ||
|
||||||
|
str_contains($questionText, 'are you looking');
|
||||||
|
|
||||||
|
if ($isLookingJobQuestion) {
|
||||||
|
if ($replyText === 'yes') {
|
||||||
|
// S6 Outcome: Update status as Hired
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'Hired',
|
||||||
|
'availability' => 'Hired'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Accept existing direct offer or application
|
||||||
|
$offer = JobOffer::where('employer_id', $conv->employer_id)
|
||||||
|
->where('worker_id', $worker->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($offer) {
|
||||||
|
$offer->update(['status' => 'accepted']);
|
||||||
|
} else {
|
||||||
|
JobOffer::create([
|
||||||
|
'employer_id' => $conv->employer_id,
|
||||||
|
'worker_id' => $worker->id,
|
||||||
|
'work_date' => now()->format('Y-m-d'),
|
||||||
|
'location' => 'Dubai',
|
||||||
|
'salary' => $worker->salary ?: 2000,
|
||||||
|
'notes' => 'Hired via chat agreement.',
|
||||||
|
'status' => 'accepted',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else if ($replyText === 'no') {
|
||||||
|
// S5 Outcome: Update status as Hidden (Auto-Hidden from search)
|
||||||
|
$worker->update([
|
||||||
|
'status' => 'hidden',
|
||||||
|
'availability' => 'Not Available'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$user = $this->resolveCurrentUser();
|
$user = $this->resolveCurrentUser();
|
||||||
@ -150,6 +207,32 @@ public function send(Request $request, $id)
|
|||||||
// Touch conversation updated_at for sorting
|
// Touch conversation updated_at for sorting
|
||||||
$conv->touch();
|
$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 back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@
|
|||||||
use App\Models\WorkerCategory;
|
use App\Models\WorkerCategory;
|
||||||
use App\Models\Shortlist;
|
use App\Models\Shortlist;
|
||||||
use App\Models\JobOffer;
|
use App\Models\JobOffer;
|
||||||
|
use App\Models\Review;
|
||||||
|
use App\Models\ProfileView;
|
||||||
|
|
||||||
class WorkerController extends Controller
|
class WorkerController extends Controller
|
||||||
{
|
{
|
||||||
@ -135,6 +137,15 @@ public function show($id)
|
|||||||
{
|
{
|
||||||
$w = Worker::with(['category', 'skills', 'documents'])->findOrFail($id);
|
$w = Worker::with(['category', 'skills', 'documents'])->findOrFail($id);
|
||||||
|
|
||||||
|
// Record employer profile view (Requirement 3)
|
||||||
|
$user = $this->resolveCurrentUser();
|
||||||
|
if ($user) {
|
||||||
|
ProfileView::updateOrCreate(
|
||||||
|
['employer_id' => $user->id, 'worker_id' => $w->id],
|
||||||
|
['updated_at' => now()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : ( ($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic'] );
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : ( ($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic'] );
|
||||||
|
|
||||||
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
|
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
|
||||||
@ -158,9 +169,24 @@ public function show($id)
|
|||||||
];
|
];
|
||||||
$photo = $photos[$w->id % 4];
|
$photo = $photos[$w->id % 4];
|
||||||
|
|
||||||
|
// Fetch dynamic database reviews (Requirement 1)
|
||||||
|
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
||||||
|
$reviews = $dbReviews->map(function ($rev) {
|
||||||
|
return [
|
||||||
|
'id' => $rev->id,
|
||||||
|
'employer_name' => $rev->employer->name ?? 'Employer',
|
||||||
|
'rating' => $rev->rating,
|
||||||
|
'date' => $rev->created_at->format('M d, Y'),
|
||||||
|
'comment' => $rev->comment,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$reviewsCount = count($reviews);
|
||||||
|
if ($reviewsCount > 0) {
|
||||||
|
$rating = round($dbReviews->avg('rating'), 1);
|
||||||
|
} else {
|
||||||
$rating = 4.0 + (($w->id * 3) % 10) / 10.0;
|
$rating = 4.0 + (($w->id * 3) % 10) / 10.0;
|
||||||
$reviewsCount = ($w->id * 4) % 20 + 2;
|
$reviewsCount = ($w->id * 4) % 20 + 2;
|
||||||
|
|
||||||
$reviews = [
|
$reviews = [
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
@ -177,6 +203,7 @@ public function show($id)
|
|||||||
'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
|
'comment' => 'Very punctual, did exactly what was expected. Highly recommend.',
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$simDb = Worker::with('category')
|
$simDb = Worker::with('category')
|
||||||
->where('id', '!=', $w->id)
|
->where('id', '!=', $w->id)
|
||||||
|
|||||||
26
app/Models/ProfileView.php
Normal file
26
app/Models/ProfileView.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ProfileView extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'employer_id',
|
||||||
|
'worker_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function employer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'employer_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function worker()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Worker::class, 'worker_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Models/Review.php
Normal file
28
app/Models/Review.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Review extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'employer_id',
|
||||||
|
'worker_id',
|
||||||
|
'rating',
|
||||||
|
'comment',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function employer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'employer_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function worker()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Worker::class, 'worker_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,4 +60,14 @@ public function offers()
|
|||||||
{
|
{
|
||||||
return $this->hasMany(JobOffer::class, 'worker_id');
|
return $this->hasMany(JobOffer::class, 'worker_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reviews()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Review::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function profileViews()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProfileView::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('reviews', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('employer_id')->constrained('users')->onDelete('cascade');
|
||||||
|
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
|
||||||
|
$table->integer('rating');
|
||||||
|
$table->text('comment')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Ensure an employer can only review a worker once
|
||||||
|
$table->unique(['employer_id', 'worker_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('reviews');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('profile_views', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('employer_id')->constrained('users')->onDelete('cascade');
|
||||||
|
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Unique combination to track unique employer-worker views and update updated_at on duplicate
|
||||||
|
$table->unique(['employer_id', 'worker_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('profile_views');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2062,6 +2062,318 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/workers/dashboard/views": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Worker/Dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get Worker Profile Views Statistics",
|
||||||
|
"description": "Allows authenticated workers to see the total count and detailed list of employers/sponsors who viewed their full profile.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Profile views statistics retrieved successfully.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"views_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 5
|
||||||
|
},
|
||||||
|
"views_list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"employer_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
},
|
||||||
|
"employer_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Ahmad"
|
||||||
|
},
|
||||||
|
"company_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Ahmad Tech Ltd"
|
||||||
|
},
|
||||||
|
"nationality": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "UAE"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Dubai"
|
||||||
|
},
|
||||||
|
"viewed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2026-06-01T15:10:24.000000Z"
|
||||||
|
},
|
||||||
|
"viewed_at_formatted": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "2 minutes ago"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/workers/dashboard": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Worker/Dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get Worker Consolidated Dashboard Data",
|
||||||
|
"description": "Allows authenticated workers to retrieve their dashboard metrics: active status, profile views count, currently working sponsor/employer details, and latest charity events/announcements.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Worker dashboard details retrieved successfully.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active_status": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "active"
|
||||||
|
},
|
||||||
|
"count_sponsors_viewed": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 5
|
||||||
|
},
|
||||||
|
"currently_working_sponsor": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Ahmad"
|
||||||
|
},
|
||||||
|
"company_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Ahmad Tech Ltd"
|
||||||
|
},
|
||||||
|
"nationality": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "UAE"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Dubai"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "ahmad@example.com"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "+971509990001"
|
||||||
|
},
|
||||||
|
"hired_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2026-06-01T15:10:24.000000Z"
|
||||||
|
},
|
||||||
|
"hired_at_formatted": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Jun 01, 2026"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latest_charity_events_list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Free Dental Checkup Camp"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Emirates Charity is providing free screening."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "charity"
|
||||||
|
},
|
||||||
|
"employer_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Emirates Charity"
|
||||||
|
},
|
||||||
|
"company_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Emirates Charity Foundation"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2026-06-01T10:00:00.000000Z"
|
||||||
|
},
|
||||||
|
"time_ago": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "5 hours ago"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/employers/reviews": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Employer/Reviews"
|
||||||
|
],
|
||||||
|
"summary": "Add or Update Worker Review",
|
||||||
|
"description": "Allows employers to submit a review score and description comment for a worker. If the employer has already left a review, this will update it.",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"worker_id",
|
||||||
|
"rating"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"worker_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 136,
|
||||||
|
"description": "Worker ID being reviewed."
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 5,
|
||||||
|
"example": 5,
|
||||||
|
"description": "Review score from 1 to 5."
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Highly professional, punctual and did a great job!",
|
||||||
|
"description": "Optional detailed comment."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Review added successfully."
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"description": "Review updated successfully."
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/employers/reviews/{id}": {
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Employer/Reviews"
|
||||||
|
],
|
||||||
|
"summary": "Edit Worker Review",
|
||||||
|
"description": "Allows employers to edit their previously created review score and comment by review ID.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "Review ID reference.",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"rating"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"rating": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 5,
|
||||||
|
"example": 4,
|
||||||
|
"description": "Updated rating score from 1 to 5."
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Updated feedback comment.",
|
||||||
|
"description": "Updated review comment description."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Review edited successfully."
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Review not found or unauthorized."
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
@ -77,6 +77,8 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
setMessages(prev => [...prev, newMsg]);
|
setMessages(prev => [...prev, newMsg]);
|
||||||
setInput('');
|
setInput('');
|
||||||
|
|
||||||
|
const isLookingJobQuery = text.toLowerCase().includes('looking') && text.toLowerCase().includes('job');
|
||||||
|
|
||||||
router.post(`/employer/messages/${conversation.id}/send`, {
|
router.post(`/employer/messages/${conversation.id}/send`, {
|
||||||
text: text
|
text: text
|
||||||
}, {
|
}, {
|
||||||
@ -93,19 +95,24 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
setIsTyping(true);
|
setIsTyping(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsTyping(false);
|
setIsTyping(false);
|
||||||
const autoReply = {
|
|
||||||
id: Date.now() + 1,
|
|
||||||
sender: 'worker',
|
|
||||||
text: t('auto_reply_text', "Thank you for your response! I have reviewed your offer proposal and am looking forward to our video interview."),
|
|
||||||
time: t('just_now', 'Just Now')
|
|
||||||
};
|
|
||||||
setMessages(prev => [...prev, autoReply]);
|
|
||||||
|
|
||||||
// Pop up a clear message received notification for the sponsor
|
// Reload conversation to pull actual database messages (including automated worker 'Yes')
|
||||||
|
router.reload({
|
||||||
|
preserveScroll: true,
|
||||||
|
onSuccess: () => {
|
||||||
|
if (isLookingJobQuery) {
|
||||||
|
toast.success(t('candidate_hired_success', "Candidate response: YES! Automatically marked as HIRED!"), {
|
||||||
|
description: t('candidate_hired_desc', "{name} has been successfully added to your Hired candidate pipeline.").replace('{name}', conversation.worker_name),
|
||||||
|
duration: 6000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
toast.info(t('new_message_from', 'New message from {name}!').replace('{name}', conversation.worker_name), {
|
toast.info(t('new_message_from', 'New message from {name}!').replace('{name}', conversation.worker_name), {
|
||||||
description: autoReply.text,
|
description: t('auto_reply_text', "Thank you for your response! I have reviewed your offer proposal and am looking forward to our video interview."),
|
||||||
duration: 5000
|
duration: 5000
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
@ -136,6 +143,7 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chips = [
|
const chips = [
|
||||||
|
t('chip_looking_job', "Are you looking for a job?"),
|
||||||
t('chip_interview', "Are you available for a video interview today?"),
|
t('chip_interview', "Are you available for a video interview today?"),
|
||||||
t('chip_salary', "What is your final expected salary?"),
|
t('chip_salary', "What is your final expected salary?"),
|
||||||
t('chip_visa', "Can you transfer your visa immediately?")
|
t('chip_visa', "Can you transfer your visa immediately?")
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
use App\Http\Controllers\Api\EmployerMessageController;
|
use App\Http\Controllers\Api\EmployerMessageController;
|
||||||
use App\Http\Controllers\Api\EmployerAnnouncementController;
|
use App\Http\Controllers\Api\EmployerAnnouncementController;
|
||||||
use App\Http\Controllers\Api\EmployerProfileController;
|
use App\Http\Controllers\Api\EmployerProfileController;
|
||||||
|
use App\Http\Controllers\Api\EmployerReviewController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -51,6 +52,8 @@
|
|||||||
Route::post('/workers/go-live', [WorkerProfileController::class, 'goLive']);
|
Route::post('/workers/go-live', [WorkerProfileController::class, 'goLive']);
|
||||||
Route::post('/workers/profile/toggle-availability', [WorkerProfileController::class, 'toggleAvailability']);
|
Route::post('/workers/profile/toggle-availability', [WorkerProfileController::class, 'toggleAvailability']);
|
||||||
Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']);
|
Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']);
|
||||||
|
Route::get('/workers/dashboard/views', [WorkerProfileController::class, 'getProfileViews']);
|
||||||
|
Route::get('/workers/dashboard', [WorkerProfileController::class, 'getDashboard']);
|
||||||
|
|
||||||
// Job Offers Management
|
// Job Offers Management
|
||||||
Route::get('/workers/offers', [WorkerProfileController::class, 'getOffers']);
|
Route::get('/workers/offers', [WorkerProfileController::class, 'getOffers']);
|
||||||
@ -91,4 +94,8 @@
|
|||||||
|
|
||||||
// Payment History Management
|
// Payment History Management
|
||||||
Route::get('/employers/payments', [\App\Http\Controllers\Api\EmployerPaymentController::class, 'getPayments']);
|
Route::get('/employers/payments', [\App\Http\Controllers\Api\EmployerPaymentController::class, 'getPayments']);
|
||||||
|
|
||||||
|
// Review Management
|
||||||
|
Route::post('/employers/reviews', [EmployerReviewController::class, 'addReview']);
|
||||||
|
Route::put('/employers/reviews/{id}', [EmployerReviewController::class, 'editReview']);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user