511 lines
31 KiB
JavaScript

import React, { useState } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import AdminLayout from '@/Layouts/AdminLayout';
import {
Users,
ShieldCheck,
CreditCard,
BadgeDollarSign,
CheckCircle,
ArrowRight,
ArrowUpRight,
MessageSquare,
TrendingUp,
Percent,
PieChart,
Sparkles
} from 'lucide-react';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
export default function Dashboard({ stats, recent_verifications, recent_subscriptions }) {
const [activeTrendTab, setActiveTrendTab] = useState('All');
const [selectedSlice, setSelectedSlice] = useState(null);
// Mock trend monthly data for custom SVG Line chart
const trendData = [
{ month: 'Dec', basic: 110, premium: 60, vip: 15 },
{ month: 'Jan', basic: 130, premium: 70, vip: 18 },
{ month: 'Feb', basic: 145, premium: 82, vip: 22 },
{ month: 'Mar', basic: 160, premium: 88, vip: 24 },
{ month: 'Apr', basic: 172, premium: 92, vip: 28 },
{ month: 'May', basic: 184, premium: 98, vip: 30 }
];
// Compute lines points for SVG Spline
const getCoordinatesForLine = (key, height, width, maxVal) => {
const points = [];
const paddingLeft = 35;
const paddingRight = 15;
const paddingTop = 20;
const paddingBottom = 25;
const graphHeight = height - paddingTop - paddingBottom;
const graphWidth = width - paddingLeft - paddingRight;
trendData.forEach((d, idx) => {
const val = d[key];
const x = paddingLeft + (idx / (trendData.length - 1)) * graphWidth;
const y = paddingTop + graphHeight - (val / maxVal) * graphHeight;
points.push(`${x},${y}`);
});
return points.join(' ');
};
return (
<AdminLayout title="Dashboard Overview">
<Head title="Admin Dashboard" />
{/* Welcome banner */}
<div className="bg-slate-900 rounded-3xl p-6 text-white mb-8 relative overflow-hidden font-sans shadow-lg shadow-slate-900/10">
<div className="absolute top-0 right-0 w-64 h-64 bg-teal-500/10 rounded-full blur-3xl -translate-y-12 translate-x-12" />
<div className="absolute bottom-0 left-1/3 w-48 h-48 bg-teal-700/15 rounded-full blur-2xl" />
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="space-y-1">
<div className="flex items-center space-x-2 bg-teal-500/20 text-teal-300 px-3 py-1 rounded-full text-xs font-bold w-fit border border-teal-500/20">
<Sparkles className="w-3.5 h-3.5" />
<span>System Status: Online</span>
</div>
<h2 className="text-2xl font-black tracking-tight mt-2">Welcome back, Administrator</h2>
<p className="text-sm text-slate-400 font-medium">UAE domestic workers platform is operating optimally. 12 automated verifications completed today.</p>
</div>
<div className="flex items-center gap-2">
<Link
href="/admin/analytics"
className="bg-teal-600 hover:bg-teal-500 text-white font-bold text-xs uppercase tracking-wider px-5 py-3 rounded-xl transition-all shadow-md shadow-teal-600/20 flex items-center gap-1.5"
>
<TrendingUp className="w-4 h-4" />
<span>Full Analytics Hub</span>
</Link>
</div>
</div>
</div>
{/* Stat Cards Row */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 font-sans">
{/* Total Workers & active/inactive */}
<div className="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col justify-between hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-slate-400 uppercase tracking-wider">Workers Registered</span>
<div className="w-10 h-10 bg-teal-50 rounded-xl flex items-center justify-center shadow-inner">
<Users className="w-5 h-5 text-[#0F6E56]" />
</div>
</div>
<div className="mt-4">
<h3 className="text-3xl font-black text-slate-900 tracking-tight">
{stats?.total_workers?.toLocaleString() || 1420}
</h3>
<div className="flex items-center space-x-2 mt-2 text-xs font-bold">
<span className="text-emerald-600">{stats?.active_workers || 980} Active</span>
<span className="text-slate-300"></span>
<span className="text-slate-400">{stats?.inactive_workers || 440} Inactive</span>
</div>
</div>
</div>
{/* Verification Stats */}
<div className="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col justify-between hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-slate-400 uppercase tracking-wider">Vetting & OCR Rate</span>
<div className="w-10 h-10 bg-emerald-50 rounded-lg flex items-center justify-center shadow-inner">
<ShieldCheck className="w-5 h-5 text-emerald-600" />
</div>
</div>
<div className="mt-4">
<h3 className="text-3xl font-black text-slate-900 tracking-tight">
{stats?.verification_rate || 88.5}%
</h3>
<p className="text-xs text-slate-500 font-medium mt-2 flex items-center">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500 mr-1 inline" />
<span>1,256 Profiles Auto-OCR Verified</span>
</p>
</div>
</div>
{/* Chat-To-Hire and Hiring Conversion */}
<div className="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col justify-between hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-slate-400 uppercase tracking-wider">Hiring Conversion</span>
<div className="w-10 h-10 bg-blue-50 rounded-lg flex items-center justify-center shadow-inner">
<Percent className="w-5 h-5 text-blue-600" />
</div>
</div>
<div className="mt-4">
<h3 className="text-3xl font-black text-slate-900 tracking-tight">
{stats?.hiring_conversion_rate || 14.8}%
</h3>
<div className="flex items-center space-x-2 mt-2 text-xs font-bold text-blue-600">
<MessageSquare className="w-3.5 h-3.5" />
<span>{stats?.chat_to_hire_rate || 12.6}% Chat-to-Hire</span>
</div>
</div>
</div>
{/* Subscription Revenue */}
<div className="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col justify-between hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-slate-400 uppercase tracking-wider">Monthly Subscription Revenue</span>
<div className="w-10 h-10 bg-emerald-50 rounded-lg flex items-center justify-center shadow-inner">
<BadgeDollarSign className="w-5 h-5 text-emerald-600" />
</div>
</div>
<div className="mt-4">
<h3 className="text-3xl font-black text-slate-900 tracking-tight">
AED {stats?.revenue_this_month_aed?.toLocaleString() || '48,500'}
</h3>
<p className="text-xs text-emerald-600 font-bold mt-2 flex items-center">
<ArrowUpRight className="w-3.5 h-3.5 mr-0.5" />
<span>+{stats?.new_employers_this_week || 28} Sponsors Added This Week</span>
</p>
</div>
</div>
</div>
{/* Required Analytics and Graphical reports */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8 font-sans">
{/* 1. Subscription Trends Spline Chart */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm">
<div className="flex items-center justify-between mb-6">
<div>
<h3 className="text-base font-black text-slate-900 tracking-tight">Subscription Sign-ups Trend</h3>
<p className="text-xs text-slate-500 font-medium uppercase tracking-widest mt-1">Growth curves across basic, premium & VIP plans</p>
</div>
{/* Selector Tabs */}
<div className="flex bg-slate-100 p-1 rounded-xl">
{['All', 'Premium', 'VIP'].map((tab) => (
<button
key={tab}
onClick={() => setActiveTrendTab(tab)}
className={`px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-wider transition-all ${
activeTrendTab === tab ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-900'
}`}
>
{tab}
</button>
))}
</div>
</div>
{/* Custom SVG Line Chart */}
<div className="relative">
<svg className="w-full h-56" viewBox="0 0 450 200">
{/* Grid Lines */}
<line x1="35" y1="20" x2="435" y2="20" stroke="#f1f5f9" strokeWidth="1" />
<line x1="35" y1="75" x2="435" y2="75" stroke="#f1f5f9" strokeWidth="1" />
<line x1="35" y1="130" x2="435" y2="130" stroke="#f1f5f9" strokeWidth="1" />
<line x1="35" y1="180" x2="435" y2="180" stroke="#cbd5e1" strokeWidth="1" />
{/* Line 1: Basic */}
{(activeTrendTab === 'All') && (
<polyline
fill="none"
stroke="#94a3b8"
strokeWidth="3.5"
strokeLinecap="round"
strokeLinejoin="round"
points={getCoordinatesForLine('basic', 200, 450, 200)}
className="transition-all duration-500 opacity-60"
/>
)}
{/* Line 2: Premium */}
{(activeTrendTab === 'All' || activeTrendTab === 'Premium') && (
<polyline
fill="none"
stroke="#3b82f6"
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
points={getCoordinatesForLine('premium', 200, 450, 200)}
className="transition-all duration-500"
/>
)}
{/* Line 3: VIP */}
{(activeTrendTab === 'All' || activeTrendTab === 'VIP') && (
<polyline
fill="none"
stroke="#10b981"
strokeWidth="4.5"
strokeLinecap="round"
strokeLinejoin="round"
points={getCoordinatesForLine('vip', 200, 450, 200)}
className="transition-all duration-500"
/>
)}
{/* X-Axis Labels */}
{trendData.map((d, i) => {
const x = 35 + (i / 5) * 385;
return (
<text key={i} x={x} y="195" textAnchor="middle" className="text-[10px] font-bold fill-slate-400 font-mono">
{d.month}
</text>
);
})}
{/* Y-Axis Labels */}
<text x="25" y="25" textAnchor="end" className="text-[10px] font-black fill-slate-300">200</text>
<text x="25" y="80" textAnchor="end" className="text-[10px] font-black fill-slate-300">100</text>
<text x="25" y="135" textAnchor="end" className="text-[10px] font-black fill-slate-300">50</text>
<text x="25" y="185" textAnchor="end" className="text-[10px] font-black fill-slate-300">0</text>
</svg>
{/* Custom Legend */}
<div className="flex items-center justify-center space-x-6 mt-4">
<div className="flex items-center space-x-2 text-xs font-bold text-slate-600">
<div className="w-3 h-1 bg-slate-400 rounded" />
<span>Basic Search ({trendData[5].basic})</span>
</div>
<div className="flex items-center space-x-2 text-xs font-bold text-slate-600">
<div className="w-3 h-1 bg-blue-500 rounded" />
<span>Premium Pass ({trendData[5].premium})</span>
</div>
<div className="flex items-center space-x-2 text-xs font-bold text-slate-600">
<div className="w-3 h-1 bg-emerald-500 rounded" />
<span>VIP Concierge ({trendData[5].vip})</span>
</div>
</div>
</div>
</div>
{/* 2. Worker Availability Report Donut Chart */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm flex flex-col justify-between">
<div>
<h3 className="text-base font-black text-slate-900 tracking-tight flex items-center gap-1.5">
<PieChart className="w-4 h-4 text-[#0F6E56]" />
<span>Worker Availability Status Reports</span>
</h3>
<p className="text-xs text-slate-500 font-medium uppercase tracking-widest mt-1">Real-time availability of candidates pool</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 items-center my-4">
{/* Custom Donut Chart */}
<div className="flex justify-center relative">
<svg className="w-40 h-40" viewBox="0 0 100 100">
{/* Segment 1: Available Now (640 / 1420 = 45%) -> Stroke-dasharray: 45, 55 */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#10b981"
strokeWidth="12"
strokeDasharray="45 55"
strokeDashoffset="25"
onMouseEnter={() => setSelectedSlice('Available')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
{/* Segment 2: Contracted (480 / 1420 = 34%) -> Stroke-dasharray: 34, 66 */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#94a3b8"
strokeWidth="12"
strokeDasharray="34 66"
strokeDashoffset="-20"
onMouseEnter={() => setSelectedSlice('Contracted')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
{/* Segment 3: In Interview (300 / 1420 = 21%) -> Stroke-dasharray: 21, 79 */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#3b82f6"
strokeWidth="12"
strokeDasharray="21 79"
strokeDashoffset="-54"
onMouseEnter={() => setSelectedSlice('Interviewing')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
{/* Inner Text */}
<circle cx="50" cy="50" r="28" fill="white" />
<text x="50" y="47" textAnchor="middle" className="text-[10px] font-black fill-slate-800">
{selectedSlice || 'TOTAL'}
</text>
<text x="50" y="60" textAnchor="middle" className="text-[8px] font-black fill-slate-400 tracking-wider">
{selectedSlice === 'Available' ? '640 Workers' : selectedSlice === 'Contracted' ? '480 Workers' : selectedSlice === 'Interviewing' ? '300 Workers' : '1,420 Total'}
</text>
</svg>
</div>
{/* Interactive legends */}
<div className="space-y-3">
<div className="p-3 bg-emerald-50/50 border border-emerald-100 rounded-xl flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-2.5 h-2.5 bg-emerald-500 rounded-full" />
<span className="text-xs font-bold text-slate-700">Available Now</span>
</div>
<span className="text-xs font-black text-slate-900">640 (45%)</span>
</div>
<div className="p-3 bg-blue-50/50 border border-blue-100 rounded-xl flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-2.5 h-2.5 bg-blue-500 rounded-full" />
<span className="text-xs font-bold text-slate-700">In Interview</span>
</div>
<span className="text-xs font-black text-slate-900">300 (21%)</span>
</div>
<div className="p-3 bg-slate-50 border border-slate-100 rounded-xl flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-2.5 h-2.5 bg-slate-400 rounded-full" />
<span className="text-xs font-bold text-slate-700">Engaged</span>
</div>
<span className="text-xs font-black text-slate-900">480 (34%)</span>
</div>
</div>
</div>
</div>
</div>
{/* 3. Hiring Funnel & Chat-To-Hire Conversion */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm mb-8 font-sans">
<div className="mb-6">
<h3 className="text-base font-black text-slate-900 tracking-tight">Hiring Conversion & Chat-to-Hire funnel</h3>
<p className="text-xs text-slate-500 font-medium uppercase tracking-widest mt-1">Platform-wide worker placement workflow stats</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{[
{ stage: 'Profiles Browsed', val: '12,500', pct: '100%', bg: 'bg-slate-100 border-slate-200 text-slate-600' },
{ stage: 'Chats Initiated', val: '4,820', pct: '38.5%', bg: 'bg-teal-50 border-teal-100 text-teal-700' },
{ stage: 'Interviews Held', val: '1,860', pct: '14.8%', bg: 'bg-blue-50 border-blue-100 text-blue-700' },
{ stage: 'Offers Sent', val: '920', pct: '7.3%', bg: 'bg-purple-50 border-purple-100 text-purple-700' },
{ stage: 'Workers Hired', val: '610', pct: '4.8%', bg: 'bg-emerald-50 border-emerald-100 text-emerald-700' }
].map((step, i) => (
<div key={i} className={`p-4 border rounded-2xl ${step.bg} flex flex-col justify-between relative`}>
{i < 4 && (
<div className="hidden md:block absolute -right-3 top-1/2 -translate-y-1/2 z-10 w-6 h-6 bg-white border border-slate-100 rounded-full flex items-center justify-center shadow-sm">
<ArrowRight className="w-3.5 h-3.5 text-slate-400" />
</div>
)}
<div>
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400 block mb-1">Step 0{i+1}</span>
<h4 className="text-xs font-black tracking-tight">{step.stage}</h4>
</div>
<div className="mt-4">
<div className="text-xl font-black tracking-tight">{step.val}</div>
<div className="text-[10px] font-bold mt-0.5">Ratio: {step.pct}</div>
</div>
</div>
))}
</div>
</div>
{/* Recent Verifications Section */}
<div className="bg-white rounded-xl border border-gray-200 shadow-sm mb-8 overflow-hidden font-sans">
<div className="p-5 border-b border-gray-100 flex items-center justify-between bg-white">
<div>
<h2 className="text-base font-semibold text-gray-800 tracking-tight">Recent Verifications</h2>
<p className="text-xs text-gray-500 mt-0.5">Profiles automatically verified via document proof</p>
</div>
<Link
href="/admin/workers/verifications"
className="text-xs font-semibold text-[#0F6E56] hover:text-[#085041] flex items-center transition-colors"
>
View OCR Vetting queue <ArrowRight className="w-3.5 h-3.5 ml-1" />
</Link>
</div>
{recent_verifications && recent_verifications.length > 0 ? (
<Table>
<TableHeader>
<TableRow className="bg-slate-50/50 hover:bg-slate-50/50">
<TableHead className="font-semibold text-slate-600 text-xs h-10">Name</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10">Type</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10">Document</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10">Processed</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10 text-right pr-6">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{recent_verifications.map((item) => (
<TableRow key={item.id} className="hover:bg-slate-50/80 transition-colors">
<TableCell className="font-medium text-gray-900 text-sm">{item.name}</TableCell>
<TableCell>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
item.type === 'Worker' ? 'bg-teal-50 text-[#0F6E56]' : 'bg-indigo-50 text-indigo-700'
}`}>
{item.type}
</span>
</TableCell>
<TableCell className="text-sm text-gray-600">{item.document_type}</TableCell>
<TableCell className="text-xs text-gray-500">{item.processed_at}</TableCell>
<TableCell className="text-right pr-6">
<span className="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold bg-emerald-100 text-emerald-800">
<CheckCircle className="w-3 h-3 mr-1" />
{item.status || 'Verified'}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<div className="p-12 text-center flex flex-col items-center justify-center">
<CheckCircle className="w-12 h-12 text-emerald-500 mb-3" />
<h3 className="text-base font-semibold text-gray-800 tracking-tight">No recent activity</h3>
<p className="text-xs text-gray-500 mt-1">Automatic verification system is monitoring new registrations.</p>
</div>
)}
</div>
{/* Recent Subscriptions Section */}
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden font-sans">
<div className="p-5 border-b border-gray-100 flex items-center justify-between bg-white">
<div>
<h2 className="text-base font-semibold text-gray-800 tracking-tight">Recent Subscriptions</h2>
<p className="text-xs text-gray-500 mt-0.5">Latest sponsor subscription activations and renewals</p>
</div>
<Link
href="/admin/payments"
className="text-xs font-semibold text-[#0F6E56] hover:text-[#085041] flex items-center transition-colors"
>
View all payments <ArrowRight className="w-3.5 h-3.5 ml-1" />
</Link>
</div>
{recent_subscriptions && recent_subscriptions.length > 0 ? (
<Table>
<TableHeader>
<TableRow className="bg-slate-50/50 hover:bg-slate-50/50">
<TableHead className="font-semibold text-slate-600 text-xs h-10">Sponsor</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10">Plan</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10">Amount</TableHead>
<TableHead className="font-semibold text-slate-600 text-xs h-10 text-right pr-6">Date</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{recent_subscriptions.map((item) => (
<TableRow key={item.id} className="hover:bg-slate-50/80 transition-colors">
<TableCell className="font-medium text-gray-900 text-sm">{item.employer_name}</TableCell>
<TableCell>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700">
{item.plan_name}
</span>
</TableCell>
<TableCell className="text-sm font-medium text-gray-900">
AED {item.amount_aed}
</TableCell>
<TableCell className="text-xs text-gray-500 text-right pr-6">{item.subscribed_at}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<div className="p-12 text-center text-gray-500 text-xs font-medium">
No recent subscriptions found.
</div>
)}
</div>
</AdminLayout>
);
}