847 lines
33 KiB
PHP
847 lines
33 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 = [];
|
|
|
|
$moderationQueue = [];
|
|
|
|
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 = DB::table('audit_logs')
|
|
->where('category', 'campaign')
|
|
->orderBy('created_at', 'desc')
|
|
->get()
|
|
->map(function ($log) {
|
|
$data = json_decode($log->action, true) ?: [
|
|
'channel' => 'push',
|
|
'recipient_type' => 'All Active Workers',
|
|
'title' => 'Broadcast Alert',
|
|
'message' => $log->action
|
|
];
|
|
return [
|
|
'id' => $log->id,
|
|
'channel' => $data['channel'] ?? 'push',
|
|
'recipient_type' => $data['recipient_type'] ?? 'All Active Workers',
|
|
'title' => $data['title'] ?? 'Broadcast Alert',
|
|
'message' => $data['message'] ?? '',
|
|
'sent_at' => date('Y-m-d H:i', strtotime($log->created_at)),
|
|
];
|
|
});
|
|
|
|
return Inertia::render('Admin/Notifications/Index', [
|
|
'campaigns' => $campaigns,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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'
|
|
]);
|
|
|
|
$campaignData = [
|
|
'channel' => $request->channel,
|
|
'recipient_type' => $request->recipients,
|
|
'title' => $request->title,
|
|
'message' => $request->message,
|
|
];
|
|
|
|
DB::table('audit_logs')->insert([
|
|
'category' => 'campaign',
|
|
'user' => auth()->user() ? auth()->user()->email : 'Admin',
|
|
'action' => json_encode($campaignData),
|
|
'ip_address' => $request->ip(),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// Send FCM push notifications for broadcast
|
|
if ($request->channel === 'push' || $request->channel === 'both') {
|
|
$recipientsLower = strtolower($request->recipients);
|
|
if (str_contains($recipientsLower, 'worker') || $recipientsLower === 'all') {
|
|
$workers = \App\Models\Worker::whereNotNull('fcm_token')->get();
|
|
foreach ($workers as $worker) {
|
|
\App\Services\FCMService::sendPushNotification(
|
|
$worker->fcm_token,
|
|
$request->title,
|
|
$request->message,
|
|
['type' => 'broadcast']
|
|
);
|
|
}
|
|
}
|
|
if (str_contains($recipientsLower, 'employer') || $recipientsLower === 'all') {
|
|
$employers = \App\Models\User::where('role', 'employer')->whereNotNull('fcm_token')->get();
|
|
foreach ($employers as $employer) {
|
|
\App\Services\FCMService::sendPushNotification(
|
|
$employer->fcm_token,
|
|
$request->title,
|
|
$request->message,
|
|
['type' => 'broadcast']
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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'
|
|
]);
|
|
|
|
$payment = DB::table('payments')->where('id', $id)->first();
|
|
if ($payment) {
|
|
DB::table('payments')->where('id', $id)->update([
|
|
'status' => 'refunded',
|
|
'description' => $payment->description . " (Refunded: " . $request->refund_reason . ")",
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
DB::table('audit_logs')->insert([
|
|
'category' => 'admin_action',
|
|
'user' => auth()->user() ? auth()->user()->email : 'Admin',
|
|
'action' => "Refunded payment #{$id} of amount AED {$request->amount}. Reason: {$request->refund_reason}",
|
|
'ip_address' => $request->ip() ?: '127.0.0.1',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
}
|
|
|
|
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(Request $request)
|
|
{
|
|
$reportType = $request->input('report_type', 'workers');
|
|
$startDate = $request->input('start_date');
|
|
$endDate = $request->input('end_date');
|
|
$status = $request->input('status', 'all');
|
|
$search = $request->input('search');
|
|
$nationality = $request->input('nationality', 'all');
|
|
$export = $request->input('export');
|
|
$perPage = (int)$request->input('per_page', 10);
|
|
if ($perPage < 1) $perPage = 10;
|
|
|
|
$headers = [];
|
|
$query = null;
|
|
$orderColumn = 'created_at';
|
|
$mapFn = null;
|
|
|
|
if ($reportType === 'workers') {
|
|
$query = DB::table('workers');
|
|
if ($startDate) {
|
|
$query->whereDate('created_at', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->whereDate('created_at', '<=', $endDate);
|
|
}
|
|
if ($status && $status !== 'all') {
|
|
$query->where('status', $status);
|
|
}
|
|
if ($nationality && $nationality !== 'all') {
|
|
$query->where('nationality', $nationality);
|
|
}
|
|
if ($search) {
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('email', 'like', "%{$search}%")
|
|
->orWhere('nationality', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$orderColumn = 'created_at';
|
|
$headers = ['Worker ID', 'Name', 'Email', 'Phone', 'Nationality', 'Status', 'Salary (AED)', 'Verified', 'Joined Date'];
|
|
$mapFn = function($w) {
|
|
return [
|
|
'id' => 'WRK-' . str_pad($w->id, 4, '0', STR_PAD_LEFT),
|
|
'name' => $w->name,
|
|
'email' => $w->email,
|
|
'phone' => $w->phone ?: 'N/A',
|
|
'nationality' => $w->nationality ?: 'N/A',
|
|
'status' => $w->status,
|
|
'salary' => (int)$w->salary,
|
|
'verified' => $w->verified ? 'Yes' : 'No',
|
|
'joined_at' => date('Y-m-d', strtotime($w->created_at)),
|
|
];
|
|
};
|
|
|
|
} elseif ($reportType === 'employers') {
|
|
$query = DB::table('users')
|
|
->leftJoin('employer_profiles', 'users.id', '=', 'employer_profiles.user_id')
|
|
->leftJoin('sponsors', 'users.email', '=', 'sponsors.email')
|
|
->where('users.role', 'employer')
|
|
->select(
|
|
'users.id',
|
|
'users.name',
|
|
'users.email',
|
|
'sponsors.status as sponsor_status',
|
|
'sponsors.subscription_status',
|
|
'users.created_at'
|
|
);
|
|
|
|
if ($startDate) {
|
|
$query->whereDate('users.created_at', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->whereDate('users.created_at', '<=', $endDate);
|
|
}
|
|
if ($status && $status !== 'all') {
|
|
$query->where('sponsors.subscription_status', $status);
|
|
}
|
|
if ($search) {
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('users.name', 'like', "%{$search}%")
|
|
->orWhere('users.email', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$orderColumn = 'users.created_at';
|
|
$headers = ['Employer ID', 'Name', 'Email', 'Status', 'Subscription Status', 'Total Spent', 'Joined Date'];
|
|
$mapFn = function($emp) {
|
|
$totalPaid = DB::table('payments')->where('user_id', $emp->id)->where('status', 'success')->sum('amount');
|
|
return [
|
|
'id' => 'EMP-' . str_pad($emp->id, 4, '0', STR_PAD_LEFT),
|
|
'name' => $emp->name,
|
|
'email' => $emp->email,
|
|
'status' => ucfirst($emp->sponsor_status ?? 'Active'),
|
|
'subscription' => ucfirst($emp->subscription_status ?? 'Inactive'),
|
|
'total_spent' => number_format($totalPaid, 2) . ' AED',
|
|
'joined_at' => date('Y-m-d', strtotime($emp->created_at)),
|
|
];
|
|
};
|
|
|
|
} elseif ($reportType === 'payments') {
|
|
$query = DB::table('payments')
|
|
->leftJoin('users', 'payments.user_id', '=', 'users.id')
|
|
->select('payments.*', 'users.name as user_name', 'users.email as user_email');
|
|
|
|
if ($startDate) {
|
|
$query->whereDate('payments.created_at', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->whereDate('payments.created_at', '<=', $endDate);
|
|
}
|
|
if ($status && $status !== 'all') {
|
|
$query->where('payments.status', $status);
|
|
}
|
|
if ($search) {
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('users.name', 'like', "%{$search}%")
|
|
->orWhere('payments.description', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$orderColumn = 'payments.created_at';
|
|
$headers = ['Payment ID', 'Employer Name', 'Employer Email', 'Amount', 'Description', 'Status', 'Date'];
|
|
$mapFn = function($pay) {
|
|
return [
|
|
'id' => 'PAY-' . str_pad($pay->id, 4, '0', STR_PAD_LEFT),
|
|
'employer_name' => $pay->user_name ?: 'System Guest / Sponsor',
|
|
'employer_email' => $pay->user_email ?: 'no-email@marketplace.com',
|
|
'amount' => number_format((float)$pay->amount, 2) . ' AED',
|
|
'description' => $pay->description ?: 'Subscription Plan',
|
|
'status' => ucfirst($pay->status),
|
|
'date' => date('Y-m-d H:i', strtotime($pay->created_at)),
|
|
];
|
|
};
|
|
|
|
} elseif ($reportType === 'tickets') {
|
|
$query = DB::table('support_tickets')
|
|
->leftJoin('users', 'support_tickets.user_id', '=', 'users.id')
|
|
->leftJoin('report_reasons', 'support_tickets.reason_id', '=', 'report_reasons.id')
|
|
->select('support_tickets.*', 'users.name as user_name', 'users.email as user_email', 'report_reasons.reason as reason_name');
|
|
|
|
if ($startDate) {
|
|
$query->whereDate('support_tickets.created_at', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->whereDate('support_tickets.created_at', '<=', $endDate);
|
|
}
|
|
if ($status && $status !== 'all') {
|
|
$query->where('support_tickets.status', $status);
|
|
}
|
|
if ($search) {
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('users.name', 'like', "%{$search}%")
|
|
->orWhere('support_tickets.subject', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$orderColumn = 'support_tickets.created_at';
|
|
$headers = ['Ticket ID', 'User Name', 'User Email', 'Subject', 'Reason', 'Status', 'Voice Note Attached', 'Created Date'];
|
|
$mapFn = function($tick) {
|
|
return [
|
|
'id' => 'TCK-' . str_pad($tick->id, 4, '0', STR_PAD_LEFT),
|
|
'user_name' => $tick->user_name ?: 'System User',
|
|
'user_email' => $tick->user_email ?: 'N/A',
|
|
'subject' => $tick->subject,
|
|
'reason' => $tick->reason_name ?: 'General Support',
|
|
'status' => ucfirst($tick->status),
|
|
'has_voice_note' => $tick->voice_note_path ? 'Yes' : 'No',
|
|
'created_at' => date('Y-m-d H:i', strtotime($tick->created_at)),
|
|
];
|
|
};
|
|
|
|
} elseif ($reportType === 'safety') {
|
|
$query = DB::table('moderation_reports');
|
|
|
|
if ($startDate) {
|
|
$query->whereDate('reported_at', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->whereDate('reported_at', '<=', $endDate);
|
|
}
|
|
if ($status && $status !== 'all') {
|
|
$query->where('status', $status);
|
|
}
|
|
if ($search) {
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('reported_user_name', 'like', "%{$search}%")
|
|
->orWhere('reported_by_name', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$orderColumn = 'reported_at';
|
|
$headers = ['Report ID', 'Reported User', 'Reported By', 'Reason', 'Status', 'Reported At'];
|
|
$mapFn = function($rep) {
|
|
return [
|
|
'id' => $rep->id,
|
|
'reported_user' => $rep->reported_user_name . ' (' . $rep->reported_user_role . ')',
|
|
'reported_by' => $rep->reported_by_name . ' (' . $rep->reported_by_role . ')',
|
|
'reason' => $rep->reason,
|
|
'status' => ucfirst($rep->status),
|
|
'reported_at' => date('Y-m-d H:i', strtotime($rep->reported_at)),
|
|
];
|
|
};
|
|
}
|
|
|
|
// Add sorting
|
|
$query->orderBy($orderColumn, 'desc');
|
|
|
|
if ($export === 'csv') {
|
|
$data = $query->get()->map($mapFn);
|
|
$filename = "report_" . $reportType . "_" . date('Ymd_His') . ".csv";
|
|
$callback = function() use ($data, $headers) {
|
|
$file = fopen('php://output', 'w');
|
|
|
|
// UTF-8 BOM
|
|
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
|
|
|
|
fputcsv($file, $headers);
|
|
foreach ($data as $row) {
|
|
fputcsv($file, array_values((array)$row));
|
|
}
|
|
fclose($file);
|
|
};
|
|
|
|
return response()->stream($callback, 200, [
|
|
"Content-type" => "text/csv; charset=UTF-8",
|
|
"Content-Disposition" => "attachment; filename={$filename}",
|
|
"Pragma" => "no-cache",
|
|
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
|
|
"Expires" => "0"
|
|
]);
|
|
}
|
|
|
|
// Normal paginated view
|
|
$paginated = $query->paginate($perPage)->withQueryString();
|
|
$paginated->getCollection()->transform($mapFn);
|
|
|
|
// Fetch distinct nationalities for filter
|
|
$nationalities = DB::table('workers')
|
|
->whereNotNull('nationality')
|
|
->where('nationality', '!=', '')
|
|
->distinct()
|
|
->pluck('nationality')
|
|
->toArray();
|
|
|
|
return Inertia::render('Admin/Analytics/Index', [
|
|
'reportType' => $reportType,
|
|
'startDate' => $startDate ?: '',
|
|
'endDate' => $endDate ?: '',
|
|
'status' => $status,
|
|
'search' => $search ?: '',
|
|
'nationality' => $nationality,
|
|
'headers' => $headers,
|
|
'reportData' => $paginated,
|
|
'nationalities' => $nationalities,
|
|
'perPage' => $perPage,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Charity Events & Drives management
|
|
*/
|
|
public function announcements()
|
|
{
|
|
$announcements = \App\Models\Announcement::with(['employer.employerProfile', 'sponsor'])->latest()->get()->map(function ($ann) {
|
|
$postedBy = 'System';
|
|
$organization = 'Migrant Support';
|
|
|
|
if ($ann->sponsor_id) {
|
|
$postedBy = $ann->sponsor->full_name;
|
|
$organization = $ann->sponsor->organization_name;
|
|
} elseif ($ann->employer_id) {
|
|
$postedBy = $ann->employer->name;
|
|
$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' => $content,
|
|
'type' => $ann->type ?? 'Charity',
|
|
'posted_by' => $postedBy,
|
|
'organization' => $organization,
|
|
'posted_by_email' => $ann->sponsor ? $ann->sponsor->email : ($ann->employer ? $ann->employer->email : null),
|
|
'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,
|
|
];
|
|
});
|
|
|
|
return Inertia::render('Admin/Events/Index', [
|
|
'initialAnnouncements' => $announcements,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Store admin charity event
|
|
*/
|
|
public function storeAnnouncement(Request $request)
|
|
{
|
|
$request->validate([
|
|
'title' => 'required|string|max:255',
|
|
'content' => 'required|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,
|
|
]);
|
|
|
|
$ann = \App\Models\Announcement::create([
|
|
'title' => $request->title,
|
|
'body' => $body,
|
|
'type' => 'Charity',
|
|
'status' => 'approved',
|
|
]);
|
|
|
|
$this->notifyUsersOfEvent($ann);
|
|
|
|
return back()->with('success', 'Charity Event created successfully.');
|
|
}
|
|
|
|
/**
|
|
* Approve charity event
|
|
*/
|
|
public function approveAnnouncement(Request $request, $id)
|
|
{
|
|
$ann = \App\Models\Announcement::findOrFail($id);
|
|
$ann->update(['status' => 'approved']);
|
|
|
|
$this->notifyUsersOfEvent($ann);
|
|
|
|
return back()->with('success', 'Charity Event approved successfully.');
|
|
}
|
|
|
|
/**
|
|
* Reject charity event
|
|
*/
|
|
public function rejectAnnouncement(Request $request, $id)
|
|
{
|
|
$request->validate([
|
|
'remarks' => 'required|string|max:1000',
|
|
]);
|
|
|
|
$ann = \App\Models\Announcement::findOrFail($id);
|
|
$ann->update([
|
|
'status' => 'rejected',
|
|
'remarks' => $request->remarks,
|
|
]);
|
|
|
|
return back()->with('success', 'Charity Event rejected successfully.');
|
|
}
|
|
|
|
/**
|
|
* Delete charity event
|
|
*/
|
|
public function deleteAnnouncement(Request $request, $id)
|
|
{
|
|
$ann = \App\Models\Announcement::findOrFail($id);
|
|
$ann->delete();
|
|
|
|
return back()->with('success', 'Charity Event deleted successfully.');
|
|
}
|
|
|
|
/**
|
|
* Broadcast FCM Push Notification for new Event/Announcement.
|
|
*/
|
|
protected function notifyUsersOfEvent($ann)
|
|
{
|
|
$title = "New Event: " . $ann->title;
|
|
$body = $ann->body;
|
|
|
|
if (str_starts_with($ann->body, '{"type":"Charity"')) {
|
|
$decoded = json_decode($ann->body, true);
|
|
if ($decoded && isset($decoded['content'])) {
|
|
$body = $decoded['content'];
|
|
}
|
|
}
|
|
|
|
// Notify all workers
|
|
$workers = \App\Models\Worker::whereNotNull('fcm_token')->get();
|
|
foreach ($workers as $worker) {
|
|
\App\Services\FCMService::sendPushNotification(
|
|
$worker->fcm_token,
|
|
$title,
|
|
$body,
|
|
[
|
|
'type' => 'announcement',
|
|
'announcement_id' => $ann->id,
|
|
]
|
|
);
|
|
}
|
|
|
|
// Notify all employers
|
|
$employers = \App\Models\User::where('role', 'employer')->whereNotNull('fcm_token')->get();
|
|
foreach ($employers as $employer) {
|
|
\App\Services\FCMService::sendPushNotification(
|
|
$employer->fcm_token,
|
|
$title,
|
|
$body,
|
|
[
|
|
'type' => 'announcement',
|
|
'announcement_id' => $ann->id,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|