211 lines
11 KiB
TypeScript
211 lines
11 KiB
TypeScript
|
|
import {
|
||
|
|
Users,
|
||
|
|
Bike,
|
||
|
|
DollarSign,
|
||
|
|
TrendingUp,
|
||
|
|
Activity,
|
||
|
|
ArrowUpRight,
|
||
|
|
ArrowDownRight,
|
||
|
|
Clock,
|
||
|
|
Shield,
|
||
|
|
AlertTriangle,
|
||
|
|
CheckCircle,
|
||
|
|
XCircle
|
||
|
|
} from 'lucide-react';
|
||
|
|
import { kycRequests, bikes, rentals, transactions } from '@/data/mockData';
|
||
|
|
|
||
|
|
const stats = [
|
||
|
|
{ label: 'Total Bikers', value: '156', change: '+12%', trend: 'up', icon: Users, color: 'text-blue-600', bg: 'bg-blue-50' },
|
||
|
|
{ label: 'Active Rentals', value: '89', change: '+8%', trend: 'up', icon: Bike, color: 'text-green-600', bg: 'bg-green-50' },
|
||
|
|
{ label: 'Daily Revenue', value: '৳45.6k', change: '+23%', trend: 'up', icon: DollarSign, color: 'text-purple-600', bg: 'bg-purple-50' },
|
||
|
|
{ label: 'Fleet Utilization', value: '78%', change: '-2%', trend: 'down', icon: TrendingUp, color: 'text-amber-600', bg: 'bg-amber-50' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const pendingKYCs = kycRequests.filter(k => k.status === 'pending');
|
||
|
|
const activeRentals = rentals.filter(r => r.status === 'active');
|
||
|
|
|
||
|
|
export default function AdminDashboard() {
|
||
|
|
return (
|
||
|
|
<div className="p-4 lg:p-6">
|
||
|
|
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-2xl lg:text-3xl font-extrabold text-slate-800">Admin Dashboard</h1>
|
||
|
|
<p className="text-sm text-slate-500 mt-1">Manage fleet, users, and operations</p>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span className="text-sm text-slate-500">Last updated:</span>
|
||
|
|
<span className="text-sm font-medium text-slate-700">Just now</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||
|
|
{stats.map((stat, index) => {
|
||
|
|
const Icon = stat.icon;
|
||
|
|
return (
|
||
|
|
<div key={index} className="bg-white rounded-xl p-5 shadow-sm border border-slate-100 hover:shadow-md transition-shadow">
|
||
|
|
<div className="flex items-start justify-between mb-3">
|
||
|
|
<div className={`w-12 h-12 rounded-xl ${stat.bg} flex items-center justify-center`}>
|
||
|
|
<Icon className={`w-6 h-6 ${stat.color}`} />
|
||
|
|
</div>
|
||
|
|
<span className={`text-xs font-semibold px-2 py-1 rounded-full flex items-center gap-1 ${
|
||
|
|
stat.trend === 'up' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
||
|
|
}`}>
|
||
|
|
{stat.trend === 'up' ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||
|
|
{stat.change}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<p className="text-2xl font-extrabold text-slate-800">{stat.value}</p>
|
||
|
|
<p className="text-sm text-slate-500 mt-1">{stat.label}</p>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid lg:grid-cols-3 gap-6 mb-8">
|
||
|
|
<div className="lg:col-span-2 bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
||
|
|
<div className="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
|
||
|
|
<h2 className="font-bold text-slate-800">Recent Rentals</h2>
|
||
|
|
<button className="text-sm text-blue-600 font-medium hover:underline">View All</button>
|
||
|
|
</div>
|
||
|
|
<div className="overflow-x-auto">
|
||
|
|
<table className="w-full">
|
||
|
|
<thead className="bg-slate-50">
|
||
|
|
<tr>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Rental ID</th>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Bike</th>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">User</th>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Type</th>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
|
||
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Daily Rate</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody className="divide-y divide-slate-50">
|
||
|
|
{activeRentals.map(rental => {
|
||
|
|
const bike = bikes.find(b => b.id === rental.bikeId);
|
||
|
|
return (
|
||
|
|
<tr key={rental.id} className="hover:bg-slate-50 transition-colors">
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className="text-sm font-medium text-slate-700">{rental.id}</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className="text-sm text-slate-600">{bike?.model}</span>
|
||
|
|
<span className="text-xs text-slate-400 block">{bike?.plateNumber}</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className="text-sm text-slate-600">{rental.userId}</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className="text-sm text-slate-600 capitalize">{rental.type}</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${
|
||
|
|
rental.status === 'active' ? 'bg-green-100 text-green-700' :
|
||
|
|
rental.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||
|
|
'bg-slate-100 text-slate-500'
|
||
|
|
}`}>
|
||
|
|
<Activity className="w-3 h-3" />
|
||
|
|
{rental.status}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-3">
|
||
|
|
<span className="text-sm font-semibold text-slate-700">৳{rental.dailyRate}</span>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
||
|
|
<div className="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
|
||
|
|
<h2 className="font-bold text-slate-800">KYC Requests</h2>
|
||
|
|
<span className="text-xs font-semibold px-2 py-1 bg-amber-100 text-amber-700 rounded-full">
|
||
|
|
{pendingKYCs.length} pending
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div className="divide-y divide-slate-50">
|
||
|
|
{pendingKYCs.map(kyc => (
|
||
|
|
<div key={kyc.id} className="p-4 hover:bg-slate-50 transition-colors">
|
||
|
|
<div className="flex items-start justify-between mb-3">
|
||
|
|
<div>
|
||
|
|
<p className="font-medium text-slate-700">{kyc.userName}</p>
|
||
|
|
<p className="text-sm text-slate-400">{kyc.phone}</p>
|
||
|
|
<p className="text-xs text-slate-400 mt-1 flex items-center gap-1">
|
||
|
|
<Clock className="w-3 h-3" /> {kyc.submittedAt}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<span className="p-1.5 bg-amber-100 rounded-lg">
|
||
|
|
<Shield className="w-4 h-4 text-amber-600" />
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<button className="flex-1 py-2 bg-green-600 text-white text-sm font-semibold rounded-lg hover:bg-green-700 flex items-center justify-center gap-1">
|
||
|
|
<CheckCircle className="w-4 h-4" /> Approve
|
||
|
|
</button>
|
||
|
|
<button className="flex-1 py-2 border border-red-200 text-red-600 text-sm font-semibold rounded-lg hover:bg-red-50 flex items-center justify-center gap-1">
|
||
|
|
<XCircle className="w-4 h-4" /> Reject
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
{pendingKYCs.length === 0 && (
|
||
|
|
<div className="p-8 text-center">
|
||
|
|
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-2" />
|
||
|
|
<p className="text-sm text-slate-500">No pending KYC requests</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid lg:grid-cols-2 gap-6">
|
||
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||
|
|
<h2 className="font-bold text-slate-800 mb-4">Quick Actions</h2>
|
||
|
|
<div className="grid grid-cols-2 gap-3">
|
||
|
|
<button className="py-3 px-4 bg-accent text-white rounded-lg font-semibold text-sm hover:bg-accent-dark transition-colors">
|
||
|
|
+ Add New Bike
|
||
|
|
</button>
|
||
|
|
<button className="py-3 px-4 bg-blue-600 text-white rounded-lg font-semibold text-sm hover:bg-blue-700 transition-colors">
|
||
|
|
Manage Users
|
||
|
|
</button>
|
||
|
|
<button className="py-3 px-4 bg-purple-600 text-white rounded-lg font-semibold text-sm hover:bg-purple-700 transition-colors">
|
||
|
|
View Reports
|
||
|
|
</button>
|
||
|
|
<button className="py-3 px-4 border border-slate-200 text-slate-600 rounded-lg font-semibold text-sm hover:bg-slate-50 transition-colors">
|
||
|
|
Settings
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||
|
|
<h2 className="font-bold text-slate-800 mb-4">System Alerts</h2>
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div className="flex items-center gap-3 p-3 bg-red-50 rounded-lg border border-red-100">
|
||
|
|
<AlertTriangle className="w-5 h-5 text-red-500" />
|
||
|
|
<div>
|
||
|
|
<p className="text-sm font-medium text-red-700">3 bikes overdue maintenance</p>
|
||
|
|
<p className="text-xs text-red-500">Action required</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center gap-3 p-3 bg-amber-50 rounded-lg border border-amber-100">
|
||
|
|
<Clock className="w-5 h-5 text-amber-500" />
|
||
|
|
<div>
|
||
|
|
<p className="text-sm font-medium text-amber-700">5 pending KYC verifications</p>
|
||
|
|
<p className="text-xs text-amber-500">Review needed</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center gap-3 p-3 bg-green-50 rounded-lg border border-green-100">
|
||
|
|
<CheckCircle className="w-5 h-5 text-green-500" />
|
||
|
|
<div>
|
||
|
|
<p className="text-sm font-medium text-green-700">System running smoothly</p>
|
||
|
|
<p className="text-xs text-green-500">All services operational</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|