2026-06-05 15:02:41 +05:30

451 lines
28 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);
// Dynamic trend monthly data for custom SVG Line chart
const trendData = stats?.trend_data || [
{ month: 'Dec', basic: 0, premium: 0, vip: 0 },
{ month: 'Jan', basic: 0, premium: 0, vip: 0 },
{ month: 'Feb', basic: 0, premium: 0, vip: 0 },
{ month: 'Mar', basic: 0, premium: 0, vip: 0 },
{ month: 'Apr', basic: 0, premium: 0, vip: 0 },
{ month: 'May', basic: 0, premium: 0, vip: 0 }
];
const maxVal = Math.max(
10,
...trendData.map(d => Math.max(d.basic || 0, d.premium || 0, d.vip || 0))
);
// Compute lines points for SVG Spline
const getCoordinatesForLine = (key, height, width, max) => {
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] || 0;
const x = paddingLeft + (idx / (trendData.length - 1)) * graphWidth;
const y = paddingTop + graphHeight - (val / max) * graphHeight;
points.push(`${x},${y}`);
});
return points.join(' ');
};
return (
<AdminLayout title="Dashboard">
<Head title="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. {stats?.verifications_today || 0} 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() || 0}
</h3>
<div className="flex items-center space-x-2 mt-2 text-xs font-bold">
<span className="text-emerald-600">{stats?.active_workers || 0} Active</span>
<span className="text-slate-300"></span>
<span className="text-slate-400">{stats?.inactive_workers || 0} 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">Verification & 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 || 0}%
</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>{stats?.verified_workers_count?.toLocaleString() || 0} 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 || 0}%
</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 || 0}% 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() || 0}
</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 || 0} Employers 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, maxVal)}
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, maxVal)}
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, maxVal)}
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">{maxVal}</text>
<text x="25" y="80" textAnchor="end" className="text-[10px] font-black fill-slate-300">{Math.round(maxVal / 2)}</text>
<text x="25" y="135" textAnchor="end" className="text-[10px] font-black fill-slate-300">{Math.round(maxVal / 4)}</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[trendData.length - 1]?.basic || 0})</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[trendData.length - 1]?.premium || 0})</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[trendData.length - 1]?.vip || 0})</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>
{(() => {
const activeVal = stats?.worker_availability?.['Active'] || 0;
const hiddenVal = stats?.worker_availability?.['Hidden'] || 0;
const hiredVal = stats?.worker_availability?.['Hired'] || 0;
const totalVal = activeVal + hiddenVal + hiredVal;
const activePct = totalVal > 0 ? Math.round((activeVal / totalVal) * 100) : 0;
const hiddenPct = totalVal > 0 ? Math.round((hiddenVal / totalVal) * 100) : 0;
const hiredPct = totalVal > 0 ? Math.round((hiredVal / totalVal) * 100) : 0;
return (
<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">
{totalVal > 0 ? (
<g className="transform -rotate-90 origin-center">
{/* Segment 1: Active */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#10b981"
strokeWidth="12"
pathLength="100"
strokeDasharray={`${activePct} ${100 - activePct}`}
strokeDashoffset="0"
onMouseEnter={() => setSelectedSlice('Active')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
{/* Segment 2: Hidden */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#94a3b8"
strokeWidth="12"
pathLength="100"
strokeDasharray={`${hiddenPct} ${100 - hiddenPct}`}
strokeDashoffset={`-${activePct}`}
onMouseEnter={() => setSelectedSlice('Hidden')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
{/* Segment 3: Hired */}
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#3b82f6"
strokeWidth="12"
pathLength="100"
strokeDasharray={`${hiredPct} ${100 - hiredPct}`}
strokeDashoffset={`-${activePct + hiddenPct}`}
onMouseEnter={() => setSelectedSlice('Hired')}
onMouseLeave={() => setSelectedSlice(null)}
className="cursor-pointer transition-all duration-300 hover:stroke-[14]"
/>
</g>
) : (
/* Empty State Grey Circle */
<circle
cx="50" cy="50" r="40"
fill="transparent"
stroke="#e2e8f0"
strokeWidth="12"
/>
)}
{/* 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 === 'Active' ? `${activeVal} Workers` : selectedSlice === 'Hidden' ? `${hiddenVal} Workers` : selectedSlice === 'Hired' ? `${hiredVal} Workers` : `${totalVal} 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">Active</span>
</div>
<span className="text-xs font-black text-slate-900">{activeVal} ({activePct}%)</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">Hidden</span>
</div>
<span className="text-xs font-black text-slate-900">{hiddenVal} ({hiddenPct}%)</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">Hired</span>
</div>
<span className="text-xs font-black text-slate-900">{hiredVal} ({hiredPct}%)</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-4 gap-4">
{(() => {
const funnelData = stats?.funnel || {
profiles_browsed: 0,
chats_initiated: 0,
candidates_shortlisted: 0,
workers_hired: 0
};
const browsed = funnelData.profiles_browsed || 0;
const chats = funnelData.chats_initiated || 0;
const shortlisted = funnelData.candidates_shortlisted || 0;
const hired = funnelData.workers_hired || 0;
const funnelSteps = [
{ stage: 'Profiles Browsed', val: browsed.toLocaleString(), pct: '100%', bg: 'bg-slate-100 border-slate-200 text-slate-600' },
{ stage: 'Chats Initiated', val: chats.toLocaleString(), pct: browsed > 0 ? `${((chats / browsed) * 100).toFixed(1)}%` : '0%', bg: 'bg-teal-50 border-teal-100 text-teal-700' },
{ stage: 'Candidates Shortlisted', val: shortlisted.toLocaleString(), pct: browsed > 0 ? `${((shortlisted / browsed) * 100).toFixed(1)}%` : '0%', bg: 'bg-blue-50 border-blue-100 text-blue-700' },
{ stage: 'Workers Hired', val: hired.toLocaleString(), pct: browsed > 0 ? `${((hired / browsed) * 100).toFixed(1)}%` : '0%', bg: 'bg-emerald-50 border-emerald-100 text-emerald-700' }
];
return funnelSteps.map((step, i) => (
<div key={i} className={`p-4 border rounded-2xl ${step.bg} flex flex-col justify-between relative`}>
{i < 3 && (
<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>
</AdminLayout>
);
}