467 lines
19 KiB
PHP
467 lines
19 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
use Inertia\Inertia;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class AdminExtraController extends Controller
|
|
{
|
|
/**
|
|
* Safety & Moderation Dashboard
|
|
*/
|
|
public function safety()
|
|
{
|
|
$reports = DB::table('moderation_reports')->get()->map(function ($report) {
|
|
return [
|
|
'id' => $report->id,
|
|
'type' => $report->type,
|
|
'reported_user_name' => $report->reported_user_name,
|
|
'reported_user_role' => $report->reported_user_role,
|
|
'reported_user_avatar' => $report->reported_user_avatar,
|
|
'reported_by_name' => $report->reported_by_name,
|
|
'reported_by_role' => $report->reported_by_role,
|
|
'reported_by_avatar' => $report->reported_by_avatar,
|
|
'reason' => $report->reason,
|
|
'priority' => $report->priority,
|
|
'status' => $report->status,
|
|
'description' => $report->description,
|
|
'reported_at' => date('M d, Y h:i A', strtotime($report->reported_at)),
|
|
];
|
|
});
|
|
|
|
$rules = [
|
|
['id' => 1, 'name' => 'IP-Range Duplication Alert', 'trigger' => 'More than 3 worker accounts from the same IP', 'status' => 'Active'],
|
|
['id' => 2, 'name' => 'OCR Match Confidence Threshold', 'trigger' => 'Passport verification confidence below 75%', 'status' => 'Active'],
|
|
['id' => 3, 'name' => 'Keyword Abuse Filter', 'trigger' => 'Vulgar/abusive words in employer messages', 'status' => 'Active'],
|
|
['id' => 4, 'name' => 'Availability Change Spammer', 'trigger' => 'Toggling availability status > 10 times in 24 hrs', 'status' => 'Inactive'],
|
|
];
|
|
|
|
$moderationQueue = [
|
|
[
|
|
'id' => 'MOD-01',
|
|
'user' => 'Leila Bekri',
|
|
'type' => 'Worker Bio Update',
|
|
'content' => 'I am a highly skilled executive housekeeper and private chef with 8 years of luxury hospitality experience in Dubai. Contact me at +971-55-901-2384 for immediate placement.',
|
|
'flag' => 'Contains contact details (Phone number violation)',
|
|
'status' => 'Pending Approval'
|
|
],
|
|
[
|
|
'id' => 'MOD-02',
|
|
'user' => 'Fatima Zahra',
|
|
'type' => 'Worker Profile Photo',
|
|
'content' => 'https://images.unsplash.com/photo-1544717305-2782549b5136?q=80&w=600',
|
|
'flag' => 'Professional headshot check',
|
|
'status' => 'Approved'
|
|
]
|
|
];
|
|
|
|
return Inertia::render('Admin/Safety/Index', [
|
|
'reports' => $reports,
|
|
'rules' => $rules,
|
|
'moderation_queue' => $moderationQueue
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve a safety report
|
|
*/
|
|
public function resolveSafetyReport(Request $request, $id)
|
|
{
|
|
$request->validate([
|
|
'action' => 'required|string',
|
|
'admin_notes' => 'nullable|string'
|
|
]);
|
|
|
|
DB::table('moderation_reports')
|
|
->where('id', $id)
|
|
->update([
|
|
'status' => 'Resolved',
|
|
'admin_notes' => $request->admin_notes ?: 'Resolved by administrator',
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
// Dynamically log event
|
|
DB::table('audit_logs')->insert([
|
|
'category' => 'admin_action',
|
|
'user' => 'SuperAdmin (support@marketplace.com)',
|
|
'action' => 'Resolved Safety Report ' . $id . ' with action: ' . strtoupper($request->action) . '. Notes: ' . ($request->admin_notes ?: 'none'),
|
|
'ip_address' => $request->ip() ?: '127.0.0.1',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
return back()->with('success', "Safety Report {$id} has been resolved successfully with action: " . strtoupper($request->action));
|
|
}
|
|
|
|
/**
|
|
* Disputes Management Module
|
|
*/
|
|
public function disputes()
|
|
{
|
|
$tickets = DB::table('disputes')->get()->map(function ($dispute) {
|
|
// Decode or initialize logs dynamically
|
|
$logs = [];
|
|
if ($dispute->chat_logs) {
|
|
$logs = json_decode($dispute->chat_logs, true) ?: [];
|
|
} else {
|
|
$logs = [
|
|
['sender' => $dispute->party_two_name, 'time' => '10:05 AM', 'message' => "Regarding the dispute on {$dispute->type}: we need to resolve this issue about {$dispute->reason} as soon as possible."],
|
|
['sender' => $dispute->party_one_name, 'time' => '10:12 AM', 'message' => 'I have already explained my side. Please check the contract agreement details.'],
|
|
['sender' => $dispute->party_two_name, 'time' => '10:14 AM', 'message' => 'That is not what we agreed upon. Let the admin mediator review the records.']
|
|
];
|
|
// Save it back so it becomes persistent in DB immediately!
|
|
DB::table('disputes')->where('id', $dispute->id)->update([
|
|
'chat_logs' => json_encode($logs)
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'id' => $dispute->id,
|
|
'party_one_name' => $dispute->party_one_name,
|
|
'party_one_role' => $dispute->party_one_role,
|
|
'party_one_avatar' => $dispute->party_one_avatar,
|
|
'party_two_name' => $dispute->party_two_name,
|
|
'party_two_role' => $dispute->party_two_role,
|
|
'party_two_avatar' => $dispute->party_two_avatar,
|
|
'type' => $dispute->type,
|
|
'reason' => $dispute->reason,
|
|
'priority' => $dispute->priority,
|
|
'status' => $dispute->status,
|
|
'assigned_to' => $dispute->assigned_to,
|
|
'created_at' => date('M d, Y h:i A', strtotime($dispute->created_at)),
|
|
'chat_logs' => $logs,
|
|
'evidence' => [
|
|
['type' => 'Contract', 'name' => 'hiring_agreement_signed.pdf', 'url' => '#'],
|
|
['type' => 'ID Proof', 'name' => 'emirates_id_verification.pdf', 'url' => '#']
|
|
],
|
|
'admin_notes' => $dispute->admin_notes ?: 'Awaiting confirmation of contract terms under UAE law.'
|
|
];
|
|
});
|
|
|
|
return Inertia::render('Admin/Disputes/Index', [
|
|
'tickets' => $tickets
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve a Dispute Ticket
|
|
*/
|
|
public function resolveDispute(Request $request, $id)
|
|
{
|
|
$request->validate([
|
|
'resolution' => 'required|string',
|
|
'action_taken' => 'required|string',
|
|
]);
|
|
|
|
$dispute = DB::table('disputes')->where('id', $id)->first();
|
|
if (!$dispute) {
|
|
return back()->withErrors(['error' => 'Dispute not found']);
|
|
}
|
|
|
|
// Decode existing logs or set default
|
|
$logs = [];
|
|
if ($dispute->chat_logs) {
|
|
$logs = json_decode($dispute->chat_logs, true) ?: [];
|
|
} else {
|
|
$logs = [
|
|
['sender' => $dispute->party_two_name, 'time' => '10:05 AM', 'message' => "Regarding the dispute on {$dispute->type}: we need to resolve this issue about {$dispute->reason} as soon as possible."],
|
|
['sender' => $dispute->party_one_name, 'time' => '10:12 AM', 'message' => 'I have already explained my side. Please check the contract agreement details.'],
|
|
['sender' => $dispute->party_two_name, 'time' => '10:14 AM', 'message' => 'That is not what we agreed upon. Let the admin mediator review the records.']
|
|
];
|
|
}
|
|
|
|
// Add resolution details
|
|
$logs[] = [
|
|
'sender' => 'Mediator (Admin)',
|
|
'time' => date('h:i A'),
|
|
'message' => '⚠️ RESOLVED & CLOSED CASE: ' . $request->resolution
|
|
];
|
|
|
|
DB::table('disputes')
|
|
->where('id', $id)
|
|
->update([
|
|
'status' => 'Resolved',
|
|
'admin_notes' => $dispute->admin_notes ? ($dispute->admin_notes . "\n• Resolution: " . $request->resolution) : ("• Resolution: " . $request->resolution),
|
|
'chat_logs' => json_encode($logs),
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
// Dynamically log event
|
|
DB::table('audit_logs')->insert([
|
|
'category' => 'admin_action',
|
|
'user' => 'SuperAdmin (support@marketplace.com)',
|
|
'action' => 'Resolved Dispute Ticket ' . $id . ' with resolution: ' . $request->resolution . ' (' . strtoupper($request->action_taken) . ')',
|
|
'ip_address' => $request->ip() ?: '127.0.0.1',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
return back()->with('success', "Dispute ticket {$id} has been resolved successfully. Resolution: " . strtoupper($request->action_taken));
|
|
}
|
|
|
|
/**
|
|
* Add admin notes to a dispute
|
|
*/
|
|
public function addDisputeNote(Request $request, $id)
|
|
{
|
|
$request->validate([
|
|
'note' => 'required|string'
|
|
]);
|
|
|
|
// Retrieve existing dispute
|
|
$dispute = DB::table('disputes')->where('id', $id)->first();
|
|
if (!$dispute) {
|
|
return back()->withErrors(['error' => 'Dispute not found']);
|
|
}
|
|
|
|
// Decode existing logs or set default
|
|
$logs = [];
|
|
if ($dispute->chat_logs) {
|
|
$logs = json_decode($dispute->chat_logs, true) ?: [];
|
|
} else {
|
|
$logs = [
|
|
['sender' => $dispute->party_two_name, 'time' => '10:05 AM', 'message' => "Regarding the dispute on {$dispute->type}: we need to resolve this issue about {$dispute->reason} as soon as possible."],
|
|
['sender' => $dispute->party_one_name, 'time' => '10:12 AM', 'message' => 'I have already explained my side. Please check the contract agreement details.'],
|
|
['sender' => $dispute->party_two_name, 'time' => '10:14 AM', 'message' => 'That is not what we agreed upon. Let the admin mediator review the records.']
|
|
];
|
|
}
|
|
|
|
// Append new reply from mediator
|
|
$logs[] = [
|
|
'sender' => 'Mediator (Admin)',
|
|
'time' => date('h:i A'),
|
|
'message' => $request->note
|
|
];
|
|
|
|
// Update database
|
|
DB::table('disputes')
|
|
->where('id', $id)
|
|
->update([
|
|
'admin_notes' => $dispute->admin_notes ? ($dispute->admin_notes . "\n• " . $request->note) : ("• " . $request->note),
|
|
'chat_logs' => json_encode($logs),
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
// Dynamically log event
|
|
DB::table('audit_logs')->insert([
|
|
'category' => 'admin_action',
|
|
'user' => 'SuperAdmin (support@marketplace.com)',
|
|
'action' => 'Mediator posted a reply note to Dispute ticket ' . $id . ': "' . substr($request->note, 0, 100) . '..."',
|
|
'ip_address' => $request->ip() ?: '127.0.0.1',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
return back()->with('success', "Mediator reply and logs successfully updated on Dispute ticket {$id}.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Notifications & Campaign Management
|
|
*/
|
|
public function notifications()
|
|
{
|
|
$campaigns = [
|
|
[
|
|
'id' => 1,
|
|
'channel' => 'Push Notification',
|
|
'recipient_type' => 'All Active Workers',
|
|
'title' => 'Availability Update Reminder',
|
|
'message' => 'Hi, please update your availability status in the app to remain visible to hiring employers this week!',
|
|
'sent_at' => '2026-05-22 10:00 AM',
|
|
'delivery_rate' => '94.2%',
|
|
'clicks' => '1,120 clicks'
|
|
],
|
|
[
|
|
'id' => 2,
|
|
'channel' => 'WhatsApp Notification',
|
|
'recipient_type' => 'Unverified Workers (Morocco & Philippines)',
|
|
'title' => 'Emirates ID Upload Alert',
|
|
'message' => 'Hello! Upload your Emirates ID to unlock direct hiring and premium salaries on the UAE Domestic Worker Marketplace. Click here: [Link]',
|
|
'sent_at' => '2026-05-20 02:30 PM',
|
|
'delivery_rate' => '98.5%',
|
|
'clicks' => '654 clicks'
|
|
],
|
|
[
|
|
'id' => 3,
|
|
'channel' => 'Push Notification',
|
|
'recipient_type' => 'Premium Employers',
|
|
'title' => 'Weekend Candidates Alert',
|
|
'message' => '12 new highly-rated verified housekeeping candidates joined today. Browse and schedule interviews now!',
|
|
'sent_at' => '2026-05-19 08:00 AM',
|
|
'delivery_rate' => '91.8%',
|
|
'clicks' => '420 clicks'
|
|
]
|
|
];
|
|
|
|
$whatsappTemplates = [
|
|
[
|
|
'name' => 'availability_checkin_trigger',
|
|
'category' => 'Utility',
|
|
'language' => 'English & Arabic',
|
|
'text' => 'Hi {{1}}, we noticed you haven\'t updated your job availability since last week. Are you still seeking work? Reply 1 for Yes, 2 for No.',
|
|
'status' => 'Approved'
|
|
],
|
|
[
|
|
'name' => 'employer_verification_passed',
|
|
'category' => 'Account Status',
|
|
'language' => 'English',
|
|
'text' => 'Dear {{1}}, congratulations! Your employer profile for {{2}} has been verified. You can now access full candidate dossiers. Browse workers here: {{3}}',
|
|
'status' => 'Approved'
|
|
],
|
|
[
|
|
'name' => 'dispute_escalation_alert',
|
|
'category' => 'Security',
|
|
'language' => 'English',
|
|
'text' => 'Important: A dispute ticket ({{1}}) has been initiated regarding contract {{2}}. An admin mediator will reach out shortly.',
|
|
'status' => 'Pending Approval'
|
|
]
|
|
];
|
|
|
|
return Inertia::render('Admin/Notifications/Index', [
|
|
'campaigns' => $campaigns,
|
|
'whatsapp_templates' => $whatsappTemplates
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Broadcast notification campaign
|
|
*/
|
|
public function broadcastNotification(Request $request)
|
|
{
|
|
$request->validate([
|
|
'channel' => 'required|in:push,whatsapp,both',
|
|
'recipients' => 'required|string',
|
|
'title' => 'required|string|max:100',
|
|
'message' => 'required|string'
|
|
]);
|
|
|
|
return back()->with('success', "Notification campaign broadcasted successfully to all {$request->recipients} users!");
|
|
}
|
|
|
|
/**
|
|
* Payments, Revenue Reports & Refund Control
|
|
*/
|
|
public function refundPayment(Request $request, $id)
|
|
{
|
|
$request->validate([
|
|
'refund_reason' => 'required|string',
|
|
'amount' => 'required|numeric|min:1'
|
|
]);
|
|
|
|
return back()->with('success', "Refund of AED {$request->amount} initiated successfully for transaction {$id}. Refund reason: {$request->refund_reason}");
|
|
}
|
|
|
|
/**
|
|
* Interactive Reports Hub
|
|
*/
|
|
public function analytics()
|
|
{
|
|
// 1. Worker Stats
|
|
$workerStats = [
|
|
'total' => DB::table('workers')->count(),
|
|
'verified' => DB::table('workers')->where('verified', 1)->count(),
|
|
'active' => DB::table('workers')->where('status', 'active')->count(),
|
|
'inactive' => DB::table('workers')->where('status', 'inactive')->count(),
|
|
];
|
|
|
|
// 2. Sponsor Stats
|
|
$sponsorStats = [
|
|
'total' => DB::table('sponsors')->count(),
|
|
'verified' => DB::table('sponsors')->where('is_verified', 1)->count(),
|
|
'active_subscribers' => DB::table('sponsors')->where('subscription_status', 'active')->count(),
|
|
'unpaid' => DB::table('sponsors')->where('payment_status', 'unpaid')->count(),
|
|
];
|
|
|
|
// 3. Dispute & Safety Stats
|
|
$disputeStats = [
|
|
'total' => DB::table('disputes')->count(),
|
|
'open' => DB::table('disputes')->where('status', 'Open')->count(),
|
|
'under_review' => DB::table('disputes')->where('status', 'Under Review')->count(),
|
|
'resolved' => DB::table('disputes')->where('status', 'Resolved')->count(),
|
|
];
|
|
|
|
$safetyStats = [
|
|
'total' => DB::table('moderation_reports')->count(),
|
|
'pending' => DB::table('moderation_reports')->where('status', 'Pending')->count(),
|
|
'resolved' => DB::table('moderation_reports')->where('status', 'Resolved')->count(),
|
|
];
|
|
|
|
// 4. Financial Payments & Ledger
|
|
$payments = DB::table('payments')
|
|
->leftJoin('users', 'payments.user_id', '=', 'users.id')
|
|
->select('payments.*', 'users.name as user_name', 'users.email as user_email')
|
|
->orderBy('payments.created_at', 'desc')
|
|
->get()
|
|
->map(function($payment) {
|
|
return [
|
|
'id' => $payment->id,
|
|
'user_name' => $payment->user_name ?: 'System Guest / Sponsor',
|
|
'user_email' => $payment->user_email ?: 'no-email@marketplace.com',
|
|
'amount' => (float)$payment->amount,
|
|
'currency' => $payment->currency,
|
|
'description' => $payment->description,
|
|
'status' => $payment->status,
|
|
'created_at' => date('M d, Y h:i A', strtotime($payment->created_at)),
|
|
];
|
|
});
|
|
|
|
$totalRevenue = DB::table('payments')->where('status', 'success')->sum('amount');
|
|
|
|
return Inertia::render('Admin/Analytics/Index', [
|
|
'worker_stats' => $workerStats,
|
|
'sponsor_stats' => $sponsorStats,
|
|
'dispute_stats' => $disputeStats,
|
|
'safety_stats' => $safetyStats,
|
|
'payments' => $payments,
|
|
'total_revenue' => (float)$totalRevenue,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Global Database Searchable Audit Logs
|
|
*/
|
|
public function auditLogs(Request $request)
|
|
{
|
|
$searchTerm = $request->input('search', '');
|
|
$category = $request->input('category', 'all');
|
|
|
|
$query = DB::table('audit_logs');
|
|
|
|
if ($category !== 'all') {
|
|
$query->where('category', $category);
|
|
}
|
|
|
|
if (!empty($searchTerm)) {
|
|
$query->where(function($q) use ($searchTerm) {
|
|
$q->where('user', 'like', '%' . $searchTerm . '%')
|
|
->orWhere('action', 'like', '%' . $searchTerm . '%')
|
|
->orWhere('id', 'like', '%' . $searchTerm . '%');
|
|
});
|
|
}
|
|
|
|
$logs = $query->orderBy('created_at', 'desc')
|
|
->orderBy('id', 'desc')
|
|
->get()
|
|
->map(function ($log) {
|
|
return [
|
|
'id' => 'LOG-' . str_pad($log->id, 5, '0', STR_PAD_LEFT),
|
|
'category' => $log->category,
|
|
'user' => $log->user,
|
|
'action' => $log->action,
|
|
'ip_address' => $log->ip_address ?: 'Unknown',
|
|
'timestamp' => date('Y-m-d H:i', strtotime($log->created_at)),
|
|
];
|
|
});
|
|
|
|
return Inertia::render('Admin/AuditLogs/Index', [
|
|
'logs' => $logs,
|
|
'search' => $searchTerm,
|
|
'category' => $category,
|
|
]);
|
|
}
|
|
}
|