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,56 +592,64 @@ 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 => { {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"> <tr key={request.id} className="hover:bg-slate-50 transition-colors">
<div className="flex flex-col lg:flex-row lg:items-start gap-4"> <td className="p-4 align-top">
<div className="flex items-center gap-4"> <div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center"> <div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center shrink-0">
<User className="w-6 h-6 text-slate-600" /> <User className="w-5 h-5 text-slate-600" />
</div> </div>
<div> <div>
<div className="flex items-center gap-2">
<p className="font-semibold text-slate-800">{request.name}</p> <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]}`}> <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} <TypeIcon className="w-3 h-3" /> {request.type}
</span> </span>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${sourceColors[request.applicationSource]}`}> <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]} <Phone className="w-3 h-3" /> {sourceLabels[request.applicationSource]}
</span> </span>
</div> </div>
<p className="text-sm text-slate-500 flex items-center gap-2">
<Phone className="w-3 h-3" /> {request.phone}
<span className="text-slate-300">|</span>
<MapPin className="w-3 h-3" /> {request.location}
</p>
</div> </div>
</div> </div>
</td>
<div className="flex-1"> <td className="p-4 align-top">
<div className="flex flex-wrap gap-4 text-sm text-slate-600"> <div className="space-y-1">
<p className="flex items-center gap-1"> <p className="flex items-center gap-1"><Phone className="w-3.5 h-3.5 text-slate-400" /> {request.phone}</p>
<Clock className="w-3 h-3" /> {request.submittedAt} <p className="flex items-center gap-1"><MapPin className="w-3.5 h-3.5 text-slate-400" /> {request.location}</p>
</p> </div>
<p className="flex items-center gap-1"> </td>
<FileText className="w-3 h-3" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length} <td className="p-4 align-top">
</p> <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 && ( {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`}> <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]} <Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
</span> </span>
)} )}
{request.smsHistory.length > 0 && ( {request.smsHistory.length > 0 && (
<p className="flex items-center gap-1 text-blue-600"> <p className="flex items-center gap-1 text-blue-600 text-xs">
<MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg <MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg
</p> </p>
)} )}
</div> </div>
</div> </td>
<td className="p-4 align-top">
<div className="flex items-center gap-3">
<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-xs font-medium px-2.5 py-1 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" />}
@@ -650,9 +658,11 @@ 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>
</td>
<td className="p-4 align-top text-right">
<div className="flex justify-end gap-2">
{request.status !== 'approved' && request.status !== 'rejected' && ( {request.status !== 'approved' && request.status !== 'rejected' && (
<div className="flex gap-2"> <>
{request.type === 'biker' && request.status === 'under_review' && ( {request.type === 'biker' && request.status === 'under_review' && (
<button <button
onClick={() => handleAction(request, 'approve')} onClick={() => handleAction(request, 'approve')}
@@ -677,24 +687,133 @@ export default function RequestsPage() {
<Store className="w-3 h-3" /> Approve Shop <Store className="w-3 h-3" /> Approve Shop
</button> </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" <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 <Eye className="w-3 h-3" /> Details
</button> </Link>
</div> </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 => {
const TypeIcon = typeIcons[request.type];
return (
<div key={request.id} className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div className="p-4 border-b border-slate-100">
<div className="flex justify-between items-start mb-3">
<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>
<p className="text-xs text-slate-500 flex items-center gap-1 mt-0.5">
<Phone className="w-3 h-3" /> {request.phone}
</p>
</div>
</div>
</div>
<div className="flex flex-wrap gap-2 mt-2">
<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 === '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>
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${typeColors[request.type]}`}>
<TypeIcon className="w-3 h-3" /> {request.type}
</span>
{request.type === 'biker' && request.verificationStage && (
<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`}>
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
</span>
)} )}
</div> </div>
</div> </div>
<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> </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>