695 lines
34 KiB
TypeScript
695 lines
34 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { useParams, useRouter } from 'next/navigation';
|
||
import {
|
||
Shield, Check, Clock, Bike, User, Phone,
|
||
MapPin, FileText, Image, DollarSign, Wrench, Battery,
|
||
CheckCircle, XCircle, ArrowLeft, Save, Printer, Send,
|
||
MessageSquare, Edit, UserCheck, Wallet, Store, Globe, Calendar, Briefcase, Plus, Upload
|
||
} from 'lucide-react';
|
||
|
||
type ApplicationSource = 'app' | 'web' | 'walkin' | 'referral';
|
||
type KYCType = 'biker' | 'investor' | 'shop' | 'merchant' | 'general';
|
||
type RiderPlan = 'daily_rent' | 'weekly_rent' | 'monthly_rent' | 'rent_to_own' | 'share_ev';
|
||
type VerificationStage = 'application' | 'document_collection' | 'risk_check' | 'plan_selection' | 'payment' | 'agreement' | 'allocated' | 'active';
|
||
|
||
interface Document {
|
||
id: string;
|
||
name: string;
|
||
status: 'pending' | 'uploaded' | 'approved' | 'rejected';
|
||
imageUrl?: string;
|
||
rejectedReason?: string;
|
||
uploadedAt?: string;
|
||
}
|
||
|
||
interface Request {
|
||
id: string;
|
||
applicationSource: ApplicationSource;
|
||
sourceDetails?: string;
|
||
name: string;
|
||
phone: string;
|
||
email: string;
|
||
type: KYCType;
|
||
status: 'pending' | 'documents_needed' | 'under_review' | 'risk_check' | 'approved' | 'rejected';
|
||
verificationStage: VerificationStage;
|
||
submittedAt: string;
|
||
location: string;
|
||
address: string;
|
||
requiredDocuments: Document[];
|
||
riderPlan?: RiderPlan;
|
||
nomineeDetails?: { name: string; phone: string; relationship: string; nid: string };
|
||
employmentInfo?: { company: string; dailyEarning: number; whyEV: string; experience: string };
|
||
riskCheck?: { nidVerified: boolean; nomineeNidVerified: boolean; deliveryPlatformStatus: string; paymentReliability: string; notes: string };
|
||
agreement?: { dailyRentObligation: number; latePenalty: number; signedAt?: string };
|
||
evAllocation?: { evId: string; bikeModel: string; batteryId: string; hubLocation: string; gpsActivated: boolean };
|
||
securityDeposit?: number;
|
||
advancePayment?: number;
|
||
paymentMethod?: 'bank' | 'wallet' | 'cash';
|
||
bikeRequested?: string;
|
||
scheduleDate?: string;
|
||
notes: string[];
|
||
messageHistory: { date: string; message: string; from: 'admin' | 'user' }[];
|
||
}
|
||
|
||
const mockRequests: Request[] = [
|
||
{
|
||
id: 'REQ001',
|
||
applicationSource: 'app',
|
||
name: 'Rahim Ahmed',
|
||
phone: '01712345678',
|
||
email: 'rahim@email.com',
|
||
type: 'biker',
|
||
status: 'pending',
|
||
verificationStage: 'application',
|
||
submittedAt: '2024-03-20',
|
||
location: 'Gulshan, Dhaka',
|
||
address: 'House 12, Road 5, Gulshan 1',
|
||
requiredDocuments: [
|
||
{ id: 'd1', name: 'NID Front', status: 'uploaded', uploadedAt: '2024-03-20' },
|
||
{ id: 'd2', name: 'NID Back', status: 'uploaded', uploadedAt: '2024-03-20' },
|
||
{ id: 'd3', name: 'Driving License', status: 'pending' },
|
||
{ id: 'd4', name: 'Profile Photo', status: 'uploaded', uploadedAt: '2024-03-20' },
|
||
],
|
||
riderPlan: 'daily_rent',
|
||
employmentInfo: { company: 'Foodpanda', dailyEarning: 2500, whyEV: 'Low maintenance, good for delivery', experience: '3 years bike riding' },
|
||
nomineeDetails: { name: 'Fatema', phone: '01712345699', relationship: 'Wife', nid: '1234567890123' },
|
||
securityDeposit: 5000,
|
||
advancePayment: 500,
|
||
bikeRequested: 'AIMA Lightning',
|
||
notes: ['Downloaded app and applied through mobile'],
|
||
messageHistory: [],
|
||
},
|
||
{
|
||
id: 'REQ002',
|
||
applicationSource: 'walkin',
|
||
sourceDetails: 'Gulshan Hub',
|
||
name: 'Karim Hasan',
|
||
phone: '01712345679',
|
||
email: 'karim@email.com',
|
||
type: 'investor',
|
||
status: 'documents_needed',
|
||
verificationStage: 'document_collection',
|
||
submittedAt: '2024-03-19',
|
||
location: 'Banani, Dhaka',
|
||
address: 'Flat 3B, House 22, Banani',
|
||
requiredDocuments: [
|
||
{ id: 'd5', name: 'NID', status: 'uploaded', uploadedAt: '2024-03-19' },
|
||
{ id: 'd6', name: 'TIN Certificate', status: 'pending' },
|
||
{ id: 'd7', name: 'Bank Statement', status: 'pending' },
|
||
],
|
||
notes: ['Walked in at Gulshan office - referred by current biker'],
|
||
messageHistory: [],
|
||
},
|
||
];
|
||
|
||
const statusColors: Record<string, string> = {
|
||
pending: 'bg-amber-100 text-amber-700',
|
||
documents_needed: 'bg-orange-100 text-orange-700',
|
||
under_review: 'bg-blue-100 text-blue-700',
|
||
risk_check: 'bg-purple-100 text-purple-700',
|
||
approved: 'bg-green-100 text-green-700',
|
||
rejected: 'bg-red-100 text-red-700',
|
||
};
|
||
|
||
const stageLabels: Record<string, string> = {
|
||
application: 'Application',
|
||
document_collection: 'Documents',
|
||
risk_check: 'Risk Check',
|
||
plan_selection: 'Plan Selection',
|
||
payment: 'Payment',
|
||
agreement: 'Agreement',
|
||
allocated: 'EV Allocated',
|
||
active: 'Active',
|
||
};
|
||
|
||
const sourceLabels: Record<string, string> = {
|
||
app: 'Mobile App',
|
||
web: 'Website',
|
||
walkin: 'Walk-in',
|
||
referral: 'Referral',
|
||
};
|
||
|
||
const planLabels: Record<string, string> = {
|
||
daily_rent: 'Daily Rent',
|
||
weekly_rent: 'Weekly Rent',
|
||
monthly_rent: 'Monthly Rent',
|
||
rent_to_own: 'Rent-to-Own',
|
||
share_ev: 'Share EV',
|
||
};
|
||
|
||
const typeIcons: Record<string, any> = {
|
||
biker: Bike,
|
||
investor: DollarSign,
|
||
shop: Store,
|
||
merchant: User,
|
||
};
|
||
|
||
export default function KYCDetailPage() {
|
||
const params = useParams();
|
||
const router = useRouter();
|
||
const id = params.id as string;
|
||
|
||
const [request, setRequest] = useState<Request | null>(null);
|
||
const [editMode, setEditMode] = useState(false);
|
||
const [editForm, setEditForm] = useState<Partial<Request>>({});
|
||
const [showMessageModal, setShowMessageModal] = useState(false);
|
||
const [showAddNoteModal, setShowAddNoteModal] = useState(false);
|
||
const [newNoteText, setNewNoteText] = useState('');
|
||
const [newMessageText, setNewMessageText] = useState('');
|
||
const [showAddDocModal, setShowAddDocModal] = useState(false);
|
||
const [newDocName, setNewDocName] = useState('');
|
||
const [showUploadDocModal, setShowUploadDocModal] = useState(false);
|
||
const [uploadDocId, setUploadDocId] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
const found = mockRequests.find(r => r.id === id);
|
||
if (found) {
|
||
setRequest(found);
|
||
setEditForm(found);
|
||
}
|
||
}, [id]);
|
||
|
||
if (!request) {
|
||
return (
|
||
<div className="p-6 flex items-center justify-center min-h-[50vh]">
|
||
<div className="text-center">
|
||
<Shield className="w-16 h-16 text-slate-300 mx-auto mb-4" />
|
||
<p className="text-slate-500">Request not found</p>
|
||
<button
|
||
onClick={() => router.push('/admin/kyc')}
|
||
className="mt-4 px-4 py-2 bg-accent text-white rounded-lg text-sm"
|
||
>
|
||
Back to KYC
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const handleSaveEdit = () => {
|
||
setRequest(prev => prev ? { ...prev, ...editForm } : null);
|
||
setEditMode(false);
|
||
};
|
||
|
||
const handleAddNote = () => {
|
||
if (!request || !newNoteText.trim()) return;
|
||
setRequest(prev => prev ? { ...prev, notes: [...prev.notes, newNoteText] } : null);
|
||
setNewNoteText('');
|
||
setShowAddNoteModal(false);
|
||
};
|
||
|
||
const handleSendMessage = () => {
|
||
if (!request || !newMessageText.trim()) return;
|
||
setRequest(prev => prev ? {
|
||
...prev,
|
||
messageHistory: [...prev.messageHistory, { date: new Date().toISOString().split('T')[0], message: newMessageText, from: 'admin' as const }]
|
||
} : null);
|
||
setNewMessageText('');
|
||
setShowMessageModal(false);
|
||
};
|
||
|
||
const handleAddDocument = () => {
|
||
if (!request || !newDocName.trim()) return;
|
||
setRequest(prev => prev ? {
|
||
...prev,
|
||
requiredDocuments: [...prev.requiredDocuments, { id: `doc-${Date.now()}`, name: newDocName, status: 'pending' as const }]
|
||
} : null);
|
||
setNewDocName('');
|
||
setShowAddDocModal(false);
|
||
};
|
||
|
||
const handleApproveDocument = (docId: string) => {
|
||
if (!request) return;
|
||
setRequest(prev => prev ? {
|
||
...prev,
|
||
requiredDocuments: prev.requiredDocuments.map(doc =>
|
||
doc.id === docId ? { ...doc, status: 'approved' as const } : doc
|
||
)
|
||
} : null);
|
||
};
|
||
|
||
const handleRejectDocument = (docId: string) => {
|
||
const reason = prompt('Enter rejection reason:');
|
||
if (reason) {
|
||
setRequest(prev => prev ? {
|
||
...prev,
|
||
requiredDocuments: prev.requiredDocuments.map(doc =>
|
||
doc.id === docId ? { ...doc, status: 'rejected' as const, rejectedReason: reason } : doc
|
||
)
|
||
} : null);
|
||
}
|
||
};
|
||
|
||
const handleUploadDocument = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = e.target.files?.[0];
|
||
if (!file || !uploadDocId || !request) return;
|
||
const imageUrl = URL.createObjectURL(file);
|
||
setRequest(prev => prev ? {
|
||
...prev,
|
||
requiredDocuments: prev.requiredDocuments.map(doc =>
|
||
doc.id === uploadDocId ? { ...doc, status: 'uploaded' as const, imageUrl, uploadedAt: new Date().toISOString() } : doc
|
||
)
|
||
} : null);
|
||
setShowUploadDocModal(false);
|
||
};
|
||
|
||
const openUploadModal = (docId: string) => {
|
||
setUploadDocId(docId);
|
||
setShowUploadDocModal(true);
|
||
};
|
||
|
||
const TypeIcon = typeIcons[request.type];
|
||
|
||
return (
|
||
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
||
<button
|
||
onClick={() => router.push('/admin/kyc')}
|
||
className="flex items-center gap-2 text-slate-600 hover:text-slate-800 mb-4"
|
||
>
|
||
<ArrowLeft className="w-4 h-4" /> Back to KYC
|
||
</button>
|
||
|
||
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
||
<div className="p-6 border-b border-slate-100">
|
||
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
|
||
<div>
|
||
<div className="flex items-center gap-3">
|
||
<h1 className="text-2xl font-extrabold text-slate-800">{request.id}</h1>
|
||
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[request.status]}`}>
|
||
{request.status.replace('_', ' ')}
|
||
</span>
|
||
{request.type === 'biker' && request.verificationStage && (
|
||
<span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full bg-cyan-100 text-cyan-700">
|
||
{stageLabels[request.verificationStage]}
|
||
</span>
|
||
)}
|
||
</div>
|
||
<p className="text-slate-500 mt-1">{request.name} • {request.submittedAt}</p>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
{editMode ? (
|
||
<>
|
||
<button onClick={handleSaveEdit} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2">
|
||
<Save className="w-4 h-4" /> Save
|
||
</button>
|
||
<button onClick={() => { setEditForm(request); setEditMode(false); }} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
|
||
Cancel
|
||
</button>
|
||
</>
|
||
) : (
|
||
<>
|
||
{request.type === 'biker' && request.status !== 'approved' && (
|
||
<button
|
||
onClick={() => {
|
||
if (confirm('Approve this request and create biker profile?')) {
|
||
setRequest(prev => prev ? { ...prev, status: 'approved', verificationStage: 'active' } : null);
|
||
alert('Biker created successfully!');
|
||
}
|
||
}}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 flex items-center gap-2"
|
||
>
|
||
<Bike className="w-4 h-4" /> Make Biker
|
||
</button>
|
||
)}
|
||
{request.type === 'investor' && request.status !== 'approved' && (
|
||
<button
|
||
onClick={() => {
|
||
if (confirm('Approve this request and create investor profile?')) {
|
||
setRequest(prev => prev ? { ...prev, status: 'approved' } : null);
|
||
alert('Investor created successfully!');
|
||
}
|
||
}}
|
||
className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 flex items-center gap-2"
|
||
>
|
||
<DollarSign className="w-4 h-4" /> Make Investor
|
||
</button>
|
||
)}
|
||
{request.type === 'shop' && request.status !== 'approved' && (
|
||
<button
|
||
onClick={() => {
|
||
if (confirm('Approve this request and create shop profile?')) {
|
||
setRequest(prev => prev ? { ...prev, status: 'approved' } : null);
|
||
alert('Shop created successfully!');
|
||
}
|
||
}}
|
||
className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"
|
||
>
|
||
<Store className="w-4 h-4" /> Make Shop
|
||
</button>
|
||
)}
|
||
{request.type === 'merchant' && request.status !== 'approved' && (
|
||
<button
|
||
onClick={() => {
|
||
if (confirm('Approve this request and create merchant profile?')) {
|
||
setRequest(prev => prev ? { ...prev, status: 'approved' } : null);
|
||
alert('Merchant created successfully!');
|
||
}
|
||
}}
|
||
className="px-4 py-2 bg-orange-600 text-white rounded-lg text-sm hover:bg-orange-700 flex items-center gap-2"
|
||
>
|
||
<User className="w-4 h-4" /> Make Merchant
|
||
</button>
|
||
)}
|
||
<button onClick={() => setEditMode(true)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2">
|
||
<Edit className="w-4 h-4" /> Edit
|
||
</button>
|
||
<button onClick={() => setShowAddNoteModal(true)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2">
|
||
<MessageSquare className="w-4 h-4" /> Note
|
||
</button>
|
||
<button onClick={() => setShowMessageModal(true)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2">
|
||
<Send className="w-4 h-4" /> Message
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-6 grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<div className="space-y-4">
|
||
<div className="bg-blue-50 p-4 rounded-xl border border-blue-100">
|
||
<h3 className="font-semibold text-blue-800 mb-3 flex items-center gap-2">
|
||
<User className="w-5 h-5" /> Personal Info
|
||
</h3>
|
||
{editMode ? (
|
||
<div className="space-y-3">
|
||
<input
|
||
type="text"
|
||
value={editForm.name || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||
placeholder="Full Name"
|
||
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={editForm.phone || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })}
|
||
placeholder="Phone"
|
||
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
|
||
/>
|
||
<input
|
||
type="email"
|
||
value={editForm.email || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, email: e.target.value })}
|
||
placeholder="Email"
|
||
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={editForm.location || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, location: e.target.value })}
|
||
placeholder="Location"
|
||
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between"><span className="text-sm text-blue-600">Name</span><span className="text-sm font-medium text-blue-800">{request.name}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-blue-600">Phone</span><span className="text-sm font-medium text-blue-800">{request.phone}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-blue-600">Email</span><span className="text-sm font-medium text-blue-800">{request.email || '-'}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-blue-600">Location</span><span className="text-sm font-medium text-blue-800">{request.location}</span></div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="bg-green-50 p-4 rounded-xl border border-green-100">
|
||
<h3 className="font-semibold text-green-800 mb-3 flex items-center gap-2">
|
||
<Globe className="w-5 h-5" /> Source
|
||
</h3>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between"><span className="text-sm text-green-600">Source</span><span className="text-sm font-medium text-green-800">{sourceLabels[request.applicationSource]}</span></div>
|
||
{request.sourceDetails && <div className="flex justify-between"><span className="text-sm text-green-600">Details</span><span className="text-sm font-medium text-green-800">{request.sourceDetails}</span></div>}
|
||
<div className="flex justify-between"><span className="text-sm text-green-600">Type</span><span className="text-sm font-medium text-green-800 capitalize flex items-center gap-1"><TypeIcon className="w-4 h-4" /> {request.type}</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
{request.type === 'biker' && (
|
||
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100">
|
||
<h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2">
|
||
<Calendar className="w-5 h-5" /> Plan Selection
|
||
</h3>
|
||
{editMode ? (
|
||
<div className="space-y-3">
|
||
<select
|
||
value={editForm.riderPlan || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, riderPlan: e.target.value as RiderPlan })}
|
||
className="w-full px-3 py-2 border border-purple-200 rounded-lg text-sm"
|
||
>
|
||
<option value="">Select Plan</option>
|
||
<option value="daily_rent">Daily Rent</option>
|
||
<option value="weekly_rent">Weekly Rent</option>
|
||
<option value="monthly_rent">Monthly Rent</option>
|
||
<option value="rent_to_own">Rent-to-Own</option>
|
||
<option value="share_ev">Share EV</option>
|
||
</select>
|
||
<input
|
||
type="text"
|
||
value={editForm.bikeRequested || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, bikeRequested: e.target.value })}
|
||
placeholder="Bike Model"
|
||
className="w-full px-3 py-2 border border-purple-200 rounded-lg text-sm"
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between"><span className="text-sm text-purple-600">Plan</span><span className="text-sm font-medium text-purple-800">{request.riderPlan ? planLabels[request.riderPlan] : '-'}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-purple-600">Bike</span><span className="text-sm font-medium text-purple-800">{request.bikeRequested || '-'}</span></div>
|
||
{request.scheduleDate && <div className="flex justify-between"><span className="text-sm text-purple-600">Schedule</span><span className="text-sm font-medium text-purple-800">{request.scheduleDate}</span></div>}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{request.type === 'biker' && request.nomineeDetails && (
|
||
<div className="bg-pink-50 p-4 rounded-xl border border-pink-100">
|
||
<h3 className="font-semibold text-pink-800 mb-3 flex items-center gap-2">
|
||
<User className="w-5 h-5" /> Nominee Details
|
||
</h3>
|
||
{editMode ? (
|
||
<div className="space-y-3">
|
||
<input
|
||
type="text"
|
||
value={editForm.nomineeDetails?.name || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, nomineeDetails: { ...editForm.nomineeDetails!, name: e.target.value } })}
|
||
placeholder="Nominee Name"
|
||
className="w-full px-3 py-2 border border-pink-200 rounded-lg text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={editForm.nomineeDetails?.phone || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, nomineeDetails: { ...editForm.nomineeDetails!, phone: e.target.value } })}
|
||
placeholder="Phone"
|
||
className="w-full px-3 py-2 border border-pink-200 rounded-lg text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={editForm.nomineeDetails?.relationship || ''}
|
||
onChange={(e) => setEditForm({ ...editForm, nomineeDetails: { ...editForm.nomineeDetails!, relationship: e.target.value } })}
|
||
placeholder="Relationship"
|
||
className="w-full px-3 py-2 border border-pink-200 rounded-lg text-sm"
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between"><span className="text-sm text-pink-600">Name</span><span className="text-sm font-medium text-pink-800">{request.nomineeDetails.name}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-pink-600">Phone</span><span className="text-sm font-medium text-pink-800">{request.nomineeDetails.phone}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-pink-600">Relationship</span><span className="text-sm font-medium text-pink-800">{request.nomineeDetails.relationship}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-pink-600">NID</span><span className="text-sm font-medium text-pink-800">{request.nomineeDetails.nid}</span></div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div className="bg-amber-50 p-4 rounded-xl border border-amber-100">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<h3 className="font-semibold text-amber-800 flex items-center gap-2">
|
||
<FileText className="w-5 h-5" /> Documents ({request.requiredDocuments.length})
|
||
</h3>
|
||
<button
|
||
onClick={() => setShowAddDocModal(true)}
|
||
className="text-xs px-2 py-1 bg-white rounded border border-amber-200 text-amber-700 hover:bg-amber-100 flex items-center gap-1"
|
||
>
|
||
<Plus className="w-3 h-3" /> Add
|
||
</button>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{request.requiredDocuments.map((doc) => (
|
||
<div key={doc.id} className="flex items-center justify-between p-2 bg-white rounded-lg">
|
||
<div className="flex items-center gap-2">
|
||
{doc.status === 'pending' && <Clock className="w-4 h-4 text-amber-400" />}
|
||
{doc.status === 'uploaded' && <FileText className="w-4 h-4 text-blue-400" />}
|
||
{doc.status === 'approved' && <CheckCircle className="w-4 h-4 text-green-500" />}
|
||
{doc.status === 'rejected' && <XCircle className="w-4 h-4 text-red-500" />}
|
||
<span className="text-sm text-slate-700">{doc.name}</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
{doc.status === 'pending' && (
|
||
<button onClick={() => openUploadModal(doc.id)} className="p-1 bg-amber-100 text-amber-600 rounded hover:bg-amber-200" title="Upload"><Upload className="w-4 h-4" /></button>
|
||
)}
|
||
{(doc.status === 'uploaded' || doc.status === 'approved') && doc.imageUrl && (
|
||
<button onClick={() => window.open(doc.imageUrl, '_blank')} className="p-1 bg-blue-100 text-blue-600 rounded hover:bg-blue-200" title="View"><Image className="w-4 h-4" /></button>
|
||
)}
|
||
{doc.status === 'uploaded' && (
|
||
<>
|
||
<button onClick={() => handleApproveDocument(doc.id)} className="p-1 bg-green-100 text-green-600 rounded hover:bg-green-200" title="Approve"><CheckCircle className="w-4 h-4" /></button>
|
||
<button onClick={() => handleRejectDocument(doc.id)} className="p-1 bg-red-100 text-red-600 rounded hover:bg-red-200" title="Reject"><XCircle className="w-4 h-4" /></button>
|
||
</>
|
||
)}
|
||
{doc.status === 'approved' && <span className="text-xs px-2 py-1 bg-green-100 text-green-700 rounded-full">Approved</span>}
|
||
{doc.status === 'rejected' && <span className="text-xs px-2 py-1 bg-red-100 text-red-700 rounded-full">Rejected</span>}
|
||
{doc.status === 'pending' && <span className="text-xs px-2 py-1 bg-amber-100 text-amber-700 rounded-full">Pending</span>}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{request.type === 'biker' && request.employmentInfo && (
|
||
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
||
<h3 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
|
||
<Briefcase className="w-5 h-5" /> Employment
|
||
</h3>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between"><span className="text-sm text-slate-600">Company</span><span className="text-sm font-medium text-slate-800">{request.employmentInfo.company}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-slate-600">Daily Earning</span><span className="text-sm font-medium text-slate-800">৳{request.employmentInfo.dailyEarning}</span></div>
|
||
<div className="flex justify-between"><span className="text-sm text-slate-600">Experience</span><span className="text-sm font-medium text-slate-800">{request.employmentInfo.experience}</span></div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
||
<h3 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
|
||
<MessageSquare className="w-5 h-5" /> Notes ({request.notes.length})
|
||
</h3>
|
||
{request.notes.length > 0 ? (
|
||
<div className="space-y-2">
|
||
{request.notes.map((note, idx) => (
|
||
<div key={idx} className="text-sm text-slate-600 p-2 bg-white rounded-lg">• {note}</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-slate-400">No notes yet</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="bg-indigo-50 p-4 rounded-xl border border-indigo-100">
|
||
<h3 className="font-semibold text-indigo-800 mb-3 flex items-center gap-2">
|
||
<Send className="w-5 h-5" /> Messages ({request.messageHistory.length})
|
||
</h3>
|
||
{request.messageHistory.length > 0 ? (
|
||
<div className="space-y-2 max-h-40 overflow-y-auto">
|
||
{request.messageHistory.map((msg, idx) => (
|
||
<div key={idx} className={`text-sm p-2 rounded-lg ${msg.from === 'admin' ? 'bg-blue-100 text-blue-800' : 'bg-white text-slate-600'}`}>
|
||
<span className="font-medium">{msg.from === 'admin' ? 'Admin' : 'User'}:</span> {msg.message}
|
||
<span className="text-xs text-slate-400 ml-2">{msg.date}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-indigo-400">No messages yet</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{showMessageModal && (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||
<h3 className="font-semibold text-slate-800">Send Message</h3>
|
||
<button onClick={() => setShowMessageModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
||
</div>
|
||
<div className="p-4">
|
||
<textarea
|
||
value={newMessageText}
|
||
onChange={(e) => setNewMessageText(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||
rows={4}
|
||
placeholder="Type message to send to user..."
|
||
/>
|
||
</div>
|
||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||
<button onClick={() => setShowMessageModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||
<button onClick={handleSendMessage} disabled={!newMessageText.trim()} className="px-4 py-2 bg-accent text-white rounded-lg disabled:opacity-50">Send</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showAddNoteModal && (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||
<h3 className="font-semibold text-slate-800">Add Note</h3>
|
||
<button onClick={() => setShowAddNoteModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
||
</div>
|
||
<div className="p-4">
|
||
<textarea
|
||
value={newNoteText}
|
||
onChange={(e) => setNewNoteText(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||
rows={4}
|
||
placeholder="Enter note..."
|
||
/>
|
||
</div>
|
||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||
<button onClick={() => setShowAddNoteModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||
<button onClick={handleAddNote} disabled={!newNoteText.trim()} className="px-4 py-2 bg-accent text-white rounded-lg disabled:opacity-50">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showAddDocModal && (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||
<h3 className="font-semibold text-slate-800">Request New Document</h3>
|
||
<button onClick={() => setShowAddDocModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
||
</div>
|
||
<div className="p-4">
|
||
<input
|
||
type="text"
|
||
value={newDocName}
|
||
onChange={(e) => setNewDocName(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||
placeholder="Document name..."
|
||
/>
|
||
</div>
|
||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||
<button onClick={() => setShowAddDocModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||
<button onClick={handleAddDocument} disabled={!newDocName.trim()} className="px-4 py-2 bg-accent text-white rounded-lg disabled:opacity-50">Add Document</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showUploadDocModal && (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||
<h3 className="font-semibold text-slate-800">Upload Document</h3>
|
||
<button onClick={() => setShowUploadDocModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
||
</div>
|
||
<div className="p-4">
|
||
<input
|
||
type="file"
|
||
accept="image/*,.pdf"
|
||
onChange={handleUploadDocument}
|
||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm file:mr-4 file:px-4 file:py-2 file:bg-accent file:text-white file:rounded-lg file:border-0 cursor-pointer"
|
||
/>
|
||
<p className="text-xs text-slate-500 mt-2">Supported: JPG, PNG, PDF</p>
|
||
</div>
|
||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||
<button onClick={() => setShowUploadDocModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
} |