From a3fc9b9b75680d5e6630b8dd31a6e57ed6892a20 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Mon, 15 Jun 2026 15:43:40 +0530 Subject: [PATCH] admin report module --- .../Admin/AdminExtraController.php | 276 ++++++-- .../Api/EmployerMessageController.php | 4 +- .../Controllers/Employer/JobController.php | 2 +- .../Employer/MessageController.php | 2 +- resources/js/Pages/Admin/Analytics/Index.jsx | 618 +++++++++++------- routes/web.php | 6 +- 6 files changed, 607 insertions(+), 301 deletions(-) diff --git a/app/Http/Controllers/Admin/AdminExtraController.php b/app/Http/Controllers/Admin/AdminExtraController.php index 611a946..f241ace 100644 --- a/app/Http/Controllers/Admin/AdminExtraController.php +++ b/app/Http/Controllers/Admin/AdminExtraController.php @@ -360,66 +360,242 @@ public function refundPayment(Request $request, $id) /** * Interactive Reports Hub */ - public function analytics() + /** + * Interactive Reports Hub + */ + public function analytics(Request $request) { - // 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(), - ]; + $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'); + $export = $request->input('export'); - // 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(), - ]; + $headers = []; + $data = collect(); - // 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(), - ]; + 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 ($search) { + $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('nationality', 'like', "%{$search}%"); + }); + } - $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) { + $headers = ['Worker ID', 'Name', 'Email', 'Phone', 'Nationality', 'Status', 'Salary (AED)', 'Verified', 'Joined Date']; + $data = $query->orderBy('created_at', 'desc')->get()->map(function($w) { 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)), + '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)), ]; }); - $totalRevenue = DB::table('payments')->where('status', 'success')->sum('amount'); + } 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}%"); + }); + } + + $headers = ['Employer ID', 'Name', 'Email', 'Status', 'Subscription Status', 'Total Spent', 'Joined Date']; + $data = $query->orderBy('users.created_at', 'desc')->get()->map(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}%"); + }); + } + + $headers = ['Payment ID', 'Employer Name', 'Employer Email', 'Amount', 'Description', 'Status', 'Date']; + $data = $query->orderBy('payments.created_at', 'desc')->get()->map(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}%"); + }); + } + + $headers = ['Ticket ID', 'User Name', 'User Email', 'Subject', 'Reason', 'Status', 'Voice Note Attached', 'Created Date']; + $data = $query->orderBy('support_tickets.created_at', 'desc')->get()->map(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}%"); + }); + } + + $headers = ['Report ID', 'Reported User', 'Reported By', 'Reason', 'Status', 'Reported At']; + $data = $query->orderBy('reported_at', 'desc')->get()->map(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)), + ]; + }); + } + + if ($export === 'csv') { + $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" + ]); + } + + // Fetch distinct nationalities for filter + $nationalities = DB::table('workers') + ->whereNotNull('nationality') + ->where('nationality', '!=', '') + ->distinct() + ->pluck('nationality') + ->toArray(); return Inertia::render('Admin/Analytics/Index', [ - 'worker_stats' => $workerStats, - 'sponsor_stats' => $sponsorStats, - 'dispute_stats' => $disputeStats, - 'safety_stats' => $safetyStats, - 'payments' => $payments, - 'total_revenue' => (float)$totalRevenue, + 'reportType' => $reportType, + 'startDate' => $startDate ?: '', + 'endDate' => $endDate ?: '', + 'status' => $status, + 'search' => $search ?: '', + 'headers' => $headers, + 'reportData' => $data->toArray(), + 'nationalities' => $nationalities, ]); } diff --git a/app/Http/Controllers/Api/EmployerMessageController.php b/app/Http/Controllers/Api/EmployerMessageController.php index 5f66ffe..e2ab989 100644 --- a/app/Http/Controllers/Api/EmployerMessageController.php +++ b/app/Http/Controllers/Api/EmployerMessageController.php @@ -101,7 +101,7 @@ public function getConversations(Request $request) 'worker_name' => $conv->worker->name ?? 'Candidate', 'nationality' => $conv->worker->nationality ?? 'Unknown', 'worker_status' => strtolower($conv->worker->status ?? 'active'), - 'salary' => ($conv->worker->salary ?? $conv->worker->expected_salary ?? 2000) . ' AED', + 'salary' => ($conv->worker->salary ?? 2000) . ' AED', 'last_message' => $lastMsg->text ?? 'No messages yet.', 'unread_count' => $unreadCount, 'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now', @@ -176,7 +176,7 @@ public function getMessages(Request $request, $id) 'worker_name' => $conv->worker->name ?? 'Candidate', 'nationality' => $conv->worker->nationality ?? 'Unknown', 'worker_status' => strtolower($conv->worker->status ?? 'active'), - 'salary' => ($conv->worker->salary ?? $conv->worker->expected_salary ?? 2000) . ' AED', + 'salary' => ($conv->worker->salary ?? 2000) . ' AED', ]; return response()->json([ diff --git a/app/Http/Controllers/Employer/JobController.php b/app/Http/Controllers/Employer/JobController.php index 822495c..b028fab 100644 --- a/app/Http/Controllers/Employer/JobController.php +++ b/app/Http/Controllers/Employer/JobController.php @@ -123,7 +123,7 @@ public function applicants($id) 'id' => $worker->id, 'name' => $worker->name, 'nationality' => $worker->nationality, - 'salary' => (int) $worker->expected_salary, + 'salary' => (int) $worker->salary, 'experience' => ($worker->experience_years ?? 3) . ' Years', 'status' => ucfirst($app->status), // Applied, Shortlisted, Hired, Rejected 'match_score' => $worker->match_score ?? 90, diff --git a/app/Http/Controllers/Employer/MessageController.php b/app/Http/Controllers/Employer/MessageController.php index b5418e1..5c02382 100644 --- a/app/Http/Controllers/Employer/MessageController.php +++ b/app/Http/Controllers/Employer/MessageController.php @@ -167,7 +167,7 @@ public function show($id) 'worker_name' => $activeConv->worker->name ?? 'Candidate', 'worker_status' => strtolower($activeConv->worker->status ?? 'active'), 'online' => true, - 'salary' => ($activeConv->worker->expected_salary ?? 2000) . ' AED', + 'salary' => ($activeConv->worker->salary ?? 2000) . ' AED', 'nationality' => $activeConv->worker->nationality ?? 'Unknown', ]; diff --git a/resources/js/Pages/Admin/Analytics/Index.jsx b/resources/js/Pages/Admin/Analytics/Index.jsx index 0396822..8eb6f05 100644 --- a/resources/js/Pages/Admin/Analytics/Index.jsx +++ b/resources/js/Pages/Admin/Analytics/Index.jsx @@ -1,281 +1,412 @@ -import React, { useState } from 'react'; -import { Head } from '@inertiajs/react'; +import React, { useState, useEffect } from 'react'; +import { Head, router } from '@inertiajs/react'; import AdminLayout from '@/Layouts/AdminLayout'; import { - FileText, Users, + Building2, ShieldAlert, - CreditCard, + DollarSign, + LifeBuoy, Search, RefreshCw, Download, - Building2, - DollarSign + Calendar, + Filter, + FileSpreadsheet, + Globe, + X, + FileText } from 'lucide-react'; -export default function AnalyticsHub({ - worker_stats = { total: 0, verified: 0, active: 0, inactive: 0 }, - sponsor_stats = { total: 0, verified: 0, active_subscribers: 0, unpaid: 0 }, - dispute_stats = { total: 0, open: 0, under_review: 0, resolved: 0 }, - safety_stats = { total: 0, pending: 0, resolved: 0 }, - payments = [], - total_revenue = 0 +export default function ReportsHub({ + reportType = 'workers', + startDate = '', + endDate = '', + status = 'all', + search = '', + headers = [], + reportData = [], + nationalities = [] }) { - const [searchTerm, setSearchTerm] = useState(''); - const [statusFilter, setStatusFilter] = useState('all'); + // Local filter state + const [localStartDate, setLocalStartDate] = useState(startDate); + const [localEndDate, setLocalEndDate] = useState(endDate); + const [localStatus, setLocalStatus] = useState(status); + const [localSearch, setLocalSearch] = useState(search); + const [localNationality, setLocalNationality] = useState('all'); - // Filter payments dynamically - const filteredPayments = payments.filter(payment => { - const matchesSearch = - payment.id.toString().toLowerCase().includes(searchTerm.toLowerCase()) || - payment.user_name.toLowerCase().includes(searchTerm.toLowerCase()) || - payment.user_email.toLowerCase().includes(searchTerm.toLowerCase()) || - payment.description.toLowerCase().includes(searchTerm.toLowerCase()); - - const matchesStatus = statusFilter === 'all' || payment.status.toLowerCase() === statusFilter.toLowerCase(); + // Sync with props when props change (e.g. after tab switch) + useEffect(() => { + setLocalStartDate(startDate); + setLocalEndDate(endDate); + setLocalStatus(status); + setLocalSearch(search); + }, [reportType, startDate, endDate, status, search]); - return matchesSearch && matchesStatus; - }); + const handleTabChange = (type) => { + router.get('/admin/analytics', { + report_type: type, + start_date: localStartDate, + end_date: localEndDate, + status: 'all', + search: '' + }, { + preserveState: false + }); + }; + + const applyFilters = () => { + router.get('/admin/analytics', { + report_type: reportType, + start_date: localStartDate, + end_date: localEndDate, + status: localStatus, + search: localSearch, + nationality: reportType === 'workers' ? localNationality : undefined + }, { + preserveState: true, + replace: true + }); + }; + + const resetFilters = () => { + setLocalStartDate(''); + setLocalEndDate(''); + setLocalStatus('all'); + setLocalSearch(''); + setLocalNationality('all'); + + router.get('/admin/analytics', { + report_type: reportType, + start_date: '', + end_date: '', + status: 'all', + search: '' + }); + }; const handleExportCSV = () => { - const headers = ['Payment ID,Customer,Email,Details,Amount,Status,Date\n']; - const rows = filteredPayments.map(p => - `"${p.id}","${p.user_name}","${p.user_email}","${p.description}",${p.amount},"${p.status}","${p.created_at}"` - ); - const blob = new Blob([headers.concat(rows.join('\n'))], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.setAttribute('download', `financial_report_${new Date().toISOString().split('T')[0]}.csv`); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + const params = new URLSearchParams({ + report_type: reportType, + start_date: localStartDate, + end_date: localEndDate, + status: localStatus, + search: localSearch, + export: 'csv' + }); + if (reportType === 'workers' && localNationality !== 'all') { + params.append('nationality', localNationality); + } + window.location.href = `/admin/analytics?${params.toString()}`; + }; + + // Context-aware status list for dropdown + const getStatusOptions = () => { + switch (reportType) { + case 'workers': + return [ + { value: 'all', label: 'All Statuses' }, + { value: 'Available', label: 'Available' }, + { value: 'hired', label: 'Hired' }, + { value: 'inactive', label: 'Inactive' }, + { value: 'suspended', label: 'Suspended' }, + { value: 'banned', label: 'Banned' } + ]; + case 'employers': + return [ + { value: 'all', label: 'All Subscriptions' }, + { value: 'active', label: 'Active Pass' }, + { value: 'inactive', label: 'Inactive Pass' } + ]; + case 'payments': + return [ + { value: 'all', label: 'All Payments' }, + { value: 'success', label: 'Success' }, + { value: 'failed', label: 'Failed' }, + { value: 'refunded', label: 'Refunded' } + ]; + case 'tickets': + return [ + { value: 'all', label: 'All Statuses' }, + { value: 'Open', label: 'Open' }, + { value: 'In-Progress', label: 'In-Progress' }, + { value: 'Resolved', label: 'Resolved' }, + { value: 'Closed', label: 'Closed' } + ]; + case 'safety': + return [ + { value: 'all', label: 'All Statuses' }, + { value: 'Pending', label: 'Pending' }, + { value: 'Resolved', label: 'Resolved' } + ]; + default: + return [{ value: 'all', label: 'All Statuses' }]; + } + }; + + const tabs = [ + { id: 'workers', label: 'Workers List', icon: Users, desc: 'Registration & status report' }, + { id: 'employers', label: 'Employers List', icon: Building2, desc: 'Subscription & spending report' }, + { id: 'payments', label: 'Payments Ledger', icon: DollarSign, desc: 'Revenue & transactions report' }, + { id: 'tickets', label: 'Support Inquiries', icon: LifeBuoy, desc: 'Inquiries & voice notes report' }, + { id: 'safety', label: 'Safety Moderation', icon: ShieldAlert, desc: 'Moderation & flagged user reports' }, + ]; + + const renderCell = (val, cellIdx, type) => { + const lowerVal = String(val).toLowerCase(); + + // Status Badges + if (['active', 'available', 'success', 'yes', 'approved', 'resolved'].includes(lowerVal)) { + return ( + + + {val} + + ); + } + if (['pending', 'in-progress', 'under review', 'no'].includes(lowerVal)) { + return ( + + + {val} + + ); + } + if (['failed', 'inactive', 'suspended', 'banned', 'closed', 'rejected'].includes(lowerVal)) { + return ( + + + {val} + + ); + } + + // ID Formats + if (typeof val === 'string' && (val.startsWith('WRK-') || val.startsWith('EMP-') || val.startsWith('PAY-') || val.startsWith('TCK-') || val.startsWith('REP-'))) { + return ( + + {val} + + ); + } + + // Email style + if (typeof val === 'string' && val.includes('@')) { + return {val}; + } + + return {val}; }; return ( - - + + -
+
- {/* Simplified Header */} -
-
-

Platform Reports

-

Summary of workers, employers, disputes, and payments.

+ {/* Header Title & Description */} +
+
+
- -
- - +
+ + Management Portal + +

Platform Reports Portal

+

+ Gain direct access to database operational reports, track registrations, monitor logs, analyze payment histories, and export filtered tabular datasets. +

- {/* Simplified Summary Cards */} -
- - {/* Workers Card */} -
-
-
- -
- - Workers - -
-
- Total Workers - {worker_stats.total} -
-
-
- Verified - {worker_stats.verified} -
-
- Active - {worker_stats.active} -
-
-
- - {/* Sponsors Card */} -
-
-
- -
- - Employers - -
-
- Total Employers - {sponsor_stats.total} -
-
-
- Active Plan - {sponsor_stats.active_subscribers} -
-
- Unpaid - {sponsor_stats.unpaid} -
-
-
- - {/* Disputes Card */} -
-
-
- -
- - Disputes - -
-
- Total Disputes - {dispute_stats.open + dispute_stats.under_review} -
-
-
- Resolved - {dispute_stats.resolved} -
-
- Pending Flags - {safety_stats.pending} -
-
-
- - {/* Revenue Card */} -
-
-
- -
- - Revenue - -
-
- Total Revenue - - AED {total_revenue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - -
-
-
- Total Paid - {payments.length} -
-
- Average Amount - - AED {payments.length > 0 ? (total_revenue / payments.length).toFixed(0) : '0'} - -
-
-
- -
- - {/* Simplified Payment Ledger */} -
-
-
-

- - Payment List -

-

Search and check payments.

-
- - {/* Search & Status Filters */} -
-
- - setSearchTerm(e.target.value)} - /> -
- - +
+
+ +
+ + {tab.label} + +
+ + {tab.desc} + + + ); + })} +
+ + {/* Filter & Table Block */} +
+ + {/* Filter Bar */} +
+
+ + Filters & Query Parameters +
+ +
+ {/* Start Date */} +
+ +
+ + setLocalStartDate(e.target.value)} + /> +
+
+ + {/* End Date */} +
+ +
+ + setLocalEndDate(e.target.value)} + /> +
+
+ + {/* Status filter */} +
+ + +
+ + {/* Workers Tab specific filter: Nationality */} + {reportType === 'workers' ? ( +
+ +
+ + +
+
+ ) : ( +
+ )} + + {/* Search Box */} +
+ +
+ + setLocalSearch(e.target.value)} + /> +
+
+
+ + {/* Action buttons */} +
+ + Matches found: {reportData.length} records + + +
+ + + + + +
- {/* Payments Table */} + {/* Report Table View */}
- +
- - - - - - - + + {headers.map((hdr, idx) => ( + + ))} - - {filteredPayments.map(p => ( - - - - - - - + + {reportData.map((row, idx) => ( + + {Object.values(row).map((val, cellIdx) => ( + + ))} ))} - {filteredPayments.length === 0 && ( + {reportData.length === 0 && ( - )} @@ -283,7 +414,6 @@ export default function AnalyticsHub({
Payment IDCustomerDetailsAmountStatusDate
{hdr}
TXN-{p.id} -
- {p.user_name} - {p.user_email} -
-
{p.description} - {p.amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {p.currency} - - - {p.status} - - {p.created_at}
+ {renderCell(val, cellIdx, reportType)} +
- No payments found. + +
+ + No Records Found + + There are no database records matching your current filter configuration. + +
-
); diff --git a/routes/web.php b/routes/web.php index d656359..eaeeab6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -252,13 +252,13 @@ Route::get('/workers', [\App\Http\Controllers\Employer\WorkerController::class, 'index'])->name('employer.workers'); Route::get('/workers/{id}', [\App\Http\Controllers\Employer\WorkerController::class, 'show'])->name('employer.workers.show'); Route::get('/workers/{id}/hire', function ($id) { - $worker = \App\Models\Worker::with('category')->findOrFail($id); + $worker = \App\Models\Worker::findOrFail($id); $workerData = [ 'id' => $worker->id, 'name' => $worker->name, 'nationality' => $worker->nationality, - 'category' => $worker->category->name ?? 'General Worker', - 'salary' => (int) $worker->expected_salary, + 'category' => $worker->preferred_job_type ?? 'Domestic Worker', + 'salary' => (int) $worker->salary, ]; return Inertia::render('Employer/Hiring/Confirm', ['worker' => $workerData]); })->name('employer.hiring.confirm'); -- 2.43.0