feat: implement responsive desktop table view for KYC requests in admin dashboard

This commit is contained in:
sazzadulalambd
2026-05-06 19:56:19 +06:00
parent 5265d69668
commit c2ba941c8c

View File

@@ -592,57 +592,149 @@ export default function RequestsPage() {
</div> </div>
</div> </div>
<div className="divide-y divide-slate-50"> {/* Desktop Table View */}
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-left text-sm text-slate-600">
<thead className="bg-slate-50 text-slate-500 font-medium border-b border-slate-100">
<tr>
<th className="p-4">Applicant</th>
<th className="p-4">Contact & Location</th>
<th className="p-4">Application Details</th>
<th className="p-4">Status</th>
<th className="p-4 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{filteredRequests.map(request => {
const TypeIcon = typeIcons[request.type];
return (
<tr key={request.id} className="hover:bg-slate-50 transition-colors">
<td className="p-4 align-top">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center shrink-0">
<User className="w-5 h-5 text-slate-600" />
</div>
<div>
<p className="font-semibold text-slate-800">{request.name}</p>
<div className="flex items-center gap-2 mt-1">
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full ${typeColors[request.type]}`}>
<TypeIcon className="w-3 h-3" /> {request.type}
</span>
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full ${sourceColors[request.applicationSource]}`}>
<Phone className="w-3 h-3" /> {sourceLabels[request.applicationSource]}
</span>
</div>
</div>
</div>
</td>
<td className="p-4 align-top">
<div className="space-y-1">
<p className="flex items-center gap-1"><Phone className="w-3.5 h-3.5 text-slate-400" /> {request.phone}</p>
<p className="flex items-center gap-1"><MapPin className="w-3.5 h-3.5 text-slate-400" /> {request.location}</p>
</div>
</td>
<td className="p-4 align-top">
<div className="space-y-1">
<p className="flex items-center gap-1"><Clock className="w-3.5 h-3.5 text-slate-400" /> {request.submittedAt}</p>
<p className="flex items-center gap-1"><FileText className="w-3.5 h-3.5 text-slate-400" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}</p>
{request.type === 'biker' && request.verificationStage && (
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
</span>
)}
{request.smsHistory.length > 0 && (
<p className="flex items-center gap-1 text-blue-600 text-xs">
<MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg
</p>
)}
</div>
</td>
<td className="p-4 align-top">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[request.status]}`}>
{request.status === 'pending' && <Clock className="w-3 h-3" />}
{request.status === 'documents_needed' && <AlertCircle className="w-3 h-3" />}
{request.status === 'under_review' && <Eye className="w-3 h-3" />}
{request.status === 'approved' && <Check className="w-3 h-3" />}
{request.status === 'rejected' && <X className="w-3 h-3" />}
{statusLabels[request.status]}
</span>
</td>
<td className="p-4 align-top text-right">
<div className="flex justify-end gap-2">
{request.status !== 'approved' && request.status !== 'rejected' && (
<>
{request.type === 'biker' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
>
<Check className="w-3 h-3" /> Make Biker
</button>
)}
{request.type === 'investor' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="py-1.5 px-3 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center gap-1"
>
<DollarSign className="w-3 h-3" /> Make Investor
</button>
)}
{request.type === 'swapstation' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
>
<Store className="w-3 h-3" /> Approve Shop
</button>
)}
</>
)}
<Link
href={`/admin/kyc/${request.id}`}
className="py-1.5 px-3 border border-slate-200 text-slate-600 text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center gap-1 inline-flex"
>
<Eye className="w-3 h-3" /> Details
</Link>
</div>
</td>
</tr>
);
})}
{filteredRequests.length === 0 && (
<tr>
<td colSpan={5} className="p-12 text-center">
<Users className="w-12 h-12 text-slate-300 mx-auto mb-4" />
<p className="text-slate-500">No requests found</p>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Mobile/Tablet Card View */}
<div className="lg:hidden p-4 space-y-4 bg-slate-50/50">
{filteredRequests.map(request => { {filteredRequests.map(request => {
const TypeIcon = typeIcons[request.type]; const TypeIcon = typeIcons[request.type];
return ( return (
<Link key={request.id} href={`/admin/kyc/${request.id}`} className="block p-5 hover:bg-slate-50 transition-colors"> <div key={request.id} className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div className="flex flex-col lg:flex-row lg:items-start gap-4"> <div className="p-4 border-b border-slate-100">
<div className="flex items-center gap-4"> <div className="flex justify-between items-start mb-3">
<div className="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center"> <div className="flex items-center gap-3">
<User className="w-6 h-6 text-slate-600" /> <div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center shrink-0">
</div> <User className="w-5 h-5 text-slate-600" />
<div>
<div className="flex items-center gap-2">
<p className="font-semibold text-slate-800">{request.name}</p>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${typeColors[request.type]}`}>
<TypeIcon className="w-3 h-3" /> {request.type}
</span>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${sourceColors[request.applicationSource]}`}>
<Phone className="w-3 h-3" /> {sourceLabels[request.applicationSource]}
</span>
</div> </div>
<p className="text-sm text-slate-500 flex items-center gap-2"> <div>
<Phone className="w-3 h-3" /> {request.phone} <p className="font-semibold text-slate-800">{request.name}</p>
<span className="text-slate-300">|</span> <p className="text-xs text-slate-500 flex items-center gap-1 mt-0.5">
<MapPin className="w-3 h-3" /> {request.location} <Phone className="w-3 h-3" /> {request.phone}
</p>
</div>
</div>
<div className="flex-1">
<div className="flex flex-wrap gap-4 text-sm text-slate-600">
<p className="flex items-center gap-1">
<Clock className="w-3 h-3" /> {request.submittedAt}
</p>
<p className="flex items-center gap-1">
<FileText className="w-3 h-3" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}
</p>
{request.type === 'biker' && request.verificationStage && (
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
</span>
)}
{request.smsHistory.length > 0 && (
<p className="flex items-center gap-1 text-blue-600">
<MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg
</p> </p>
)} </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex flex-wrap gap-2 mt-2">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[request.status]}`}> <span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${statusColors[request.status]}`}>
{request.status === 'pending' && <Clock className="w-3 h-3" />} {request.status === 'pending' && <Clock className="w-3 h-3" />}
{request.status === 'documents_needed' && <AlertCircle className="w-3 h-3" />} {request.status === 'documents_needed' && <AlertCircle className="w-3 h-3" />}
{request.status === 'under_review' && <Eye className="w-3 h-3" />} {request.status === 'under_review' && <Eye className="w-3 h-3" />}
@@ -650,51 +742,78 @@ export default function RequestsPage() {
{request.status === 'rejected' && <X className="w-3 h-3" />} {request.status === 'rejected' && <X className="w-3 h-3" />}
{statusLabels[request.status]} {statusLabels[request.status]}
</span> </span>
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${typeColors[request.type]}`}>
{request.status !== 'approved' && request.status !== 'rejected' && ( <TypeIcon className="w-3 h-3" /> {request.type}
<div className="flex gap-2"> </span>
{request.type === 'biker' && request.status === 'under_review' && ( {request.type === 'biker' && request.verificationStage && (
<button <span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
onClick={() => handleAction(request, 'approve')} <Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1" </span>
>
<Check className="w-3 h-3" /> Make Biker
</button>
)}
{request.type === 'investor' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="py-1.5 px-3 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center gap-1"
>
<DollarSign className="w-3 h-3" /> Make Investor
</button>
)}
{request.type === 'swapstation' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
>
<Store className="w-3 h-3" /> Approve Shop
</button>
)}
<button
onClick={(e) => { e.preventDefault(); router.push(`/admin/kyc/${request.id}`); }}
className="py-1.5 px-3 border border-slate-200 text-slate-600 text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center gap-1"
>
<Eye className="w-3 h-3" /> Details
</button>
</div>
)} )}
</div> </div>
</div> </div>
</Link>
<div className="p-4 space-y-2 text-sm text-slate-600">
<div className="flex justify-between items-center">
<span className="flex items-center gap-1 text-xs"><MapPin className="w-3.5 h-3.5 text-slate-400" /> {request.location}</span>
<span className="flex items-center gap-1 text-xs"><Clock className="w-3.5 h-3.5 text-slate-400" /> {request.submittedAt}</span>
</div>
<div className="flex justify-between items-center">
<span className="flex items-center gap-1 text-xs">
<FileText className="w-3.5 h-3.5 text-slate-400" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}
</span>
{request.smsHistory.length > 0 && (
<span className="flex items-center gap-1 text-blue-600 text-xs">
<MessageSquare className="w-3.5 h-3.5" /> {request.smsHistory.length} msgs
</span>
)}
</div>
</div>
<div className="p-3 border-t border-slate-100 bg-slate-50 flex gap-2">
{request.status !== 'approved' && request.status !== 'rejected' && (
<>
{request.type === 'biker' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="flex-1 py-2 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center justify-center gap-1"
>
<Check className="w-3 h-3" /> Approve
</button>
)}
{request.type === 'investor' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="flex-1 py-2 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center justify-center gap-1"
>
<DollarSign className="w-3 h-3" /> Approve
</button>
)}
{request.type === 'swapstation' && request.status === 'under_review' && (
<button
onClick={() => handleAction(request, 'approve')}
className="flex-1 py-2 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center justify-center gap-1"
>
<Store className="w-3 h-3" /> Approve
</button>
)}
</>
)}
<Link
href={`/admin/kyc/${request.id}`}
className="flex-1 py-2 border border-slate-200 text-slate-700 bg-white text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center justify-center gap-1 text-center"
>
<Eye className="w-3 h-3" /> Details
</Link>
</div>
</div>
); );
})} })}
{filteredRequests.length === 0 && ( {filteredRequests.length === 0 && (
<div className="p-12 text-center"> <div className="p-8 text-center bg-white rounded-xl border border-slate-200">
<Users className="w-12 h-12 text-slate-300 mx-auto mb-4" /> <Users className="w-12 h-12 text-slate-300 mx-auto mb-3" />
<p className="text-slate-500">No requests found</p> <p className="text-slate-500 text-sm">No requests found</p>
</div> </div>
)} )}
</div> </div>