refactor: add pagination, filtering, and responsive design improvements to withdrawal request page
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { CreditCard, Wallet, ArrowUpRight, History, CheckCircle, Clock, Building2, Smartphone, AlertCircle, Settings, X, Bike } from 'lucide-react';
|
||||
import { CreditCard, Wallet, History, CheckCircle, Clock, Building2, Smartphone, AlertCircle, Settings, X, Bike, ChevronDown, Search, Filter, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { investors, transactions, bikes } from '@/data/mockData';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function InvestorWithdrawPage() {
|
||||
const investorBikes = bikes.filter(b => b.investorId === investor.id);
|
||||
const availableBalance = investor.totalEarnings - investor.totalWithdrawn - investor.pendingEarnings;
|
||||
|
||||
const withdrawHistory = transactions.filter(t => t.investorId === investor.id && t.type === 'withdrawal').sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
const allWithdrawals = transactions.filter(t => t.investorId === investor.id && t.type === 'withdrawal').sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false);
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
@@ -24,6 +24,10 @@ export default function InvestorWithdrawPage() {
|
||||
const [autoWithdrawMin, setAutoWithdrawMin] = useState('1000');
|
||||
const [autoWithdrawAccount, setAutoWithdrawAccount] = useState('');
|
||||
|
||||
const [withdrawPage, setWithdrawPage] = useState(1);
|
||||
const [withdrawPageSize, setWithdrawPageSize] = useState(5);
|
||||
const [withdrawFilter, setWithdrawFilter] = useState('all');
|
||||
|
||||
const planColors: Record<string, string> = {
|
||||
silver: 'bg-slate-100 text-slate-700',
|
||||
gold: 'bg-amber-100 text-amber-700',
|
||||
@@ -46,6 +50,13 @@ export default function InvestorWithdrawPage() {
|
||||
return sum + (bike?.totalEarnings || 0);
|
||||
}, 0);
|
||||
|
||||
const filteredWithdrawals = allWithdrawals.filter(w => withdrawFilter === 'all' || w.status === withdrawFilter);
|
||||
const totalWithdrawPages = Math.ceil(filteredWithdrawals.length / withdrawPageSize);
|
||||
const paginatedWithdrawals = filteredWithdrawals.slice((withdrawPage - 1) * withdrawPageSize, withdrawPage * withdrawPageSize);
|
||||
|
||||
const totalCompleted = allWithdrawals.filter(w => w.status === 'completed').reduce((sum, w) => sum + w.amount, 0);
|
||||
const totalPending = allWithdrawals.filter(w => w.status === 'pending').reduce((sum, w) => sum + w.amount, 0);
|
||||
|
||||
const toggleSelectAll = (checked: boolean) => {
|
||||
setSelectAll(checked);
|
||||
if (checked) {
|
||||
@@ -96,89 +107,147 @@ export default function InvestorWithdrawPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
||||
<div className="p-4 lg:p-6 max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-xl lg:text-2xl font-bold text-slate-800">Withdraw Funds</h1>
|
||||
<p className="text-sm text-slate-500">Request withdrawals to your bank or mobile banking accounts</p>
|
||||
<p className="text-sm text-slate-500 mt-1">Request withdrawals to your bank or mobile banking accounts</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAutoWithdrawModal(true)}
|
||||
className="px-4 py-2.5 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-50 flex items-center gap-2 shadow-sm"
|
||||
className="px-4 py-2.5 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-50 flex items-center gap-2 shadow-sm bg-white w-fit"
|
||||
>
|
||||
<Settings className="w-4 h-4" /> Configure Auto-Withdraw
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
className="px-6 py-3 bg-investor text-white rounded-xl font-bold text-sm shadow-md hover:bg-investor-dark transition-all flex items-center gap-2 whitespace-nowrap"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" /> Withdrawal Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Balance Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div className="md:col-span-2 bg-investor rounded-xl p-5 text-white shadow-lg relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/3 blur-2xl"></div>
|
||||
{/* Balance Cards - Mobile Responsive Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 mb-4">
|
||||
<div className="sm:col-span-2 bg-investor to-investor-dark rounded-xl p-5 text-white shadow-lg relative overflow-hidden order-first lg:order-first">
|
||||
<div className="absolute top-0 right-0 w-24 h-24 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/3 blur-2xl"></div>
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-2 text-investor-light mb-2">
|
||||
<Wallet className="w-5 h-5" />
|
||||
<span className="font-medium text-sm">Available Balance</span>
|
||||
<Wallet className="w-4 h-4" />
|
||||
<span className="font-medium text-xs">Available Balance</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold mb-1">৳{availableBalance.toLocaleString()}</h2>
|
||||
<p className="text-3xl font-bold mb-1">৳{availableBalance.toLocaleString()}</p>
|
||||
<p className="text-xs text-white/80">Ready to withdraw</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3">
|
||||
<div className="bg-white rounded-xl p-4 border border-slate-200 shadow-sm">
|
||||
<p className="text-xs text-amber-600 font-medium mb-1">Pending Request</p>
|
||||
<p className="text-xl font-bold text-amber-600">৳{investor.pendingEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-4 border border-slate-200 shadow-sm">
|
||||
<p className="text-xs text-slate-600 font-medium mb-1">Total Withdrawn</p>
|
||||
<p className="text-xl font-bold text-slate-700">৳{investor.totalWithdrawn.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alert + Action Button */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 mb-6">
|
||||
<div className="flex-1 bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3 order-last sm:order-first">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-amber-800 mb-1">Pending Requests</h4>
|
||||
<p className="text-xs text-amber-700">You currently have <b>৳{investor.pendingEarnings.toLocaleString()}</b> in pending withdrawals. Processing takes 1-3 business days.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-5 border border-slate-200 shadow-sm">
|
||||
<p className="text-sm text-slate-600 font-medium mb-1">Total Withdrawn</p>
|
||||
<p className="text-2xl font-bold text-slate-700">৳{investor.totalWithdrawn.toLocaleString()}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
className="px-5 py-3 bg-investor text-white rounded-xl font-semibold text-sm shadow-md hover:bg-investor-dark transition-all flex items-center justify-center gap-2 whitespace-nowrap sm:w-auto"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" /> Withdrawal Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Alert + Action Button */}
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
|
||||
</div>
|
||||
|
||||
{/* Recent Withdrawals */}
|
||||
{/* Recent Withdrawals Card */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
|
||||
<div className="p-4 border-b border-slate-100 bg-slate-50">
|
||||
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||
<History className="w-5 h-5 text-slate-400" /> Recent Withdrawals
|
||||
</h3>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||
<History className="w-5 h-5 text-slate-400" /> Recent Withdrawals
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={withdrawFilter}
|
||||
onChange={(e) => { setWithdrawFilter(e.target.value); setWithdrawPage(1); }}
|
||||
className="px-3 py-1.5 border border-slate-200 rounded-lg text-xs bg-white"
|
||||
>
|
||||
<option value="all">All</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="pending">Pending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
|
||||
|
||||
|
||||
{/* Card View - Mobile/Tablet */}
|
||||
<div className="lg:hidden divide-y divide-slate-100">
|
||||
{paginatedWithdrawals.length > 0 ? paginatedWithdrawals.map((t) => (
|
||||
<div key={t.id} className="p-4 sm:p-5 hover:bg-slate-50 transition-colors">
|
||||
<div className="flex items-start justify-between gap-3 mb-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-slate-800 mb-1">{t.description || 'Withdrawal'}</p>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<span>{t.createdAt}</span>
|
||||
{t.referenceNumber && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span className="hidden sm:inline">{t.referenceNumber}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<p className="text-base font-bold text-slate-800">৳{t.amount.toLocaleString()}</p>
|
||||
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-semibold mt-1 ${t.status === 'completed' ? 'bg-green-100 text-green-700' :
|
||||
t.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{t.status === 'completed' && <CheckCircle className="w-3 h-3" />}
|
||||
{t.status === 'pending' && <Clock className="w-3 h-3" />}
|
||||
{t.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)) : (
|
||||
<div className="px-4 py-12 text-center text-sm text-slate-500">
|
||||
<History className="w-10 h-10 mx-auto mb-2 text-slate-300" />
|
||||
No withdrawals found.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Table View - Desktop */}
|
||||
|
||||
|
||||
<div className="hidden lg:block overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-slate-50 border-b border-slate-100">
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Date</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Ref / Desc</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Method</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Amount</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
|
||||
<tr className="bg-white border-b border-slate-100">
|
||||
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Date</th>
|
||||
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider hidden sm:table-cell">Ref</th>
|
||||
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Description</th>
|
||||
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Amount</th>
|
||||
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{withdrawHistory.length > 0 ? withdrawHistory.map((t) => (
|
||||
{paginatedWithdrawals.length > 0 ? paginatedWithdrawals.map((t) => (
|
||||
<tr key={t.id} className="hover:bg-slate-50 transition-colors">
|
||||
<td className="px-5 py-4 text-sm text-slate-600 whitespace-nowrap">{t.createdAt}</td>
|
||||
<td className="px-5 py-4">
|
||||
<p className="text-sm font-medium text-slate-800">{t.referenceNumber || 'Withdrawal'}</p>
|
||||
<p className="text-xs text-slate-500 truncate max-w-[200px]">{t.description}</p>
|
||||
<td className="px-4 py-3.5 text-xs text-slate-600 whitespace-nowrap">{t.createdAt}</td>
|
||||
<td className="px-4 py-3.5 text-xs text-slate-500 hidden sm:table-cell">{t.referenceNumber || '-'}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<p className="text-sm font-medium text-slate-800">{t.description || 'Withdrawal'}</p>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-sm text-slate-600 capitalize">{t.paymentMethod}</td>
|
||||
<td className="px-5 py-4 text-sm font-bold text-slate-800">৳{t.amount.toLocaleString()}</td>
|
||||
<td className="px-5 py-4">
|
||||
<span className={`inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-semibold ${t.status === 'completed' ? 'bg-green-100 text-green-700' :
|
||||
<td className="px-4 py-3.5 text-sm font-bold text-slate-800">৳{t.amount.toLocaleString()}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-semibold ${t.status === 'completed' ? 'bg-green-100 text-green-700' :
|
||||
t.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
@@ -190,98 +259,132 @@ export default function InvestorWithdrawPage() {
|
||||
</tr>
|
||||
)) : (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-5 py-12 text-center text-sm text-slate-500">
|
||||
<td colSpan={5} className="px-4 py-12 text-center text-sm text-slate-500">
|
||||
<History className="w-10 h-10 mx-auto mb-2 text-slate-300" />
|
||||
No withdrawal history found.
|
||||
No withdrawals found.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalWithdrawPages > 1 && (
|
||||
<div className="p-4 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<p className="text-xs text-slate-500">
|
||||
Showing {((withdrawPage - 1) * withdrawPageSize) + 1} to {Math.min(withdrawPage * withdrawPageSize, filteredWithdrawals.length)} of {filteredWithdrawals.length}
|
||||
</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setWithdrawPage(p => Math.max(1, p - 1))}
|
||||
disabled={withdrawPage === 1}
|
||||
className="p-1.5 border border-slate-200 rounded-lg disabled:opacity-50 hover:bg-slate-50"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4 text-slate-500" />
|
||||
</button>
|
||||
{Array.from({ length: Math.min(5, totalWithdrawPages) }, (_, i) => {
|
||||
const page = totalWithdrawPages <= 5 ? i + 1 : withdrawPage <= 3 ? i + 1 : withdrawPage >= totalWithdrawPages - 2 ? totalWithdrawPages - 4 + i : withdrawPage - 2 + i;
|
||||
return (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => setWithdrawPage(page)}
|
||||
className={`w-8 h-8 rounded-lg text-xs font-medium ${withdrawPage === page ? 'bg-investor text-white' : 'border border-slate-200 hover:bg-slate-50'}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
onClick={() => setWithdrawPage(p => Math.min(totalWithdrawPages, p + 1))}
|
||||
disabled={withdrawPage === totalWithdrawPages}
|
||||
className="p-1.5 border border-slate-200 rounded-lg disabled:opacity-50 hover:bg-slate-50"
|
||||
>
|
||||
<ChevronRight className="w-4 h-4 text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Withdrawal Request Modal */}
|
||||
{showWithdrawModal && (
|
||||
<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-2xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between bg-gradient-to-r from-slate-50 to-white">
|
||||
<h3 className="text-lg font-bold text-slate-800">Create Withdrawal Request</h3>
|
||||
<button onClick={() => setShowWithdrawModal(false)} className="p-1.5 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 overflow-y-auto flex-1 space-y-5">
|
||||
{/* Balance Cards */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<p className="text-xs text-green-600 font-medium">Available Balance</p>
|
||||
<p className="text-lg font-bold text-green-700">৳{availableBalance.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-amber-50 rounded-lg p-4 border border-amber-200">
|
||||
<p className="text-xs text-amber-600 font-medium">Pending Request</p>
|
||||
<p className="text-lg font-bold text-amber-700">৳{investor.pendingEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
||||
<p className="text-xs text-slate-600 font-medium">Total Withdrawn</p>
|
||||
<p className="text-lg font-bold text-slate-700">৳{investor.totalWithdrawn.toLocaleString()}</p>
|
||||
</div>
|
||||
{
|
||||
showWithdrawModal && (
|
||||
<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-2xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="p-4 sm:p-5 border-b border-slate-100 flex items-center justify-between bg-gradient-to-r from-slate-50 to-white">
|
||||
<h3 className="text-base sm:text-lg font-bold text-slate-800">Create Withdrawal Request</h3>
|
||||
<button onClick={() => setShowWithdrawModal(false)} className="p-1.5 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Select Investment Plans & Bikes */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-3">Select Investment Plans & Bikes</h4>
|
||||
<div className="space-y-4">
|
||||
{/* Select All */}
|
||||
<div className="flex items-center gap-3 p-4 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="selectAll"
|
||||
checked={selectAll}
|
||||
onChange={(e) => toggleSelectAll(e.target.checked)}
|
||||
className="w-5 h-5 text-investor rounded border-slate-300"
|
||||
/>
|
||||
<label htmlFor="selectAll" className="flex-1 cursor-pointer">
|
||||
<span className="font-semibold text-slate-800">Select All</span>
|
||||
<p className="text-xs text-slate-500">Include all investments and bikes</p>
|
||||
</label>
|
||||
<div className="p-4 sm:p-5 overflow-y-auto flex-1 space-y-4 sm:space-y-5">
|
||||
{/* Balance Cards */}
|
||||
<div className="grid grid-cols-3 gap-2 sm:gap-3">
|
||||
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
|
||||
<p className="text-xs text-green-600 font-medium">Available</p>
|
||||
<p className="text-sm font-bold text-green-700">৳{availableBalance.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-amber-50 rounded-lg p-3 border border-amber-200">
|
||||
<p className="text-xs text-amber-600 font-medium">Pending</p>
|
||||
<p className="text-sm font-bold text-amber-700">৳{investor.pendingEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3 border border-slate-200">
|
||||
<p className="text-xs text-slate-600 font-medium">Withdrawn</p>
|
||||
<p className="text-sm font-bold text-slate-700">৳{investor.totalWithdrawn.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Investment Plans with Bikes */}
|
||||
<div className="border border-slate-200 rounded-lg overflow-hidden">
|
||||
<div className="bg-slate-100 px-4 py-3 border-b border-slate-200">
|
||||
<p className="text-sm font-semibold text-slate-700">Investment Plans</p>
|
||||
{/* Select Investment Plans & Bikes */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-2">Select Investment Plans & Bikes</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="selectAll"
|
||||
checked={selectAll}
|
||||
onChange={(e) => toggleSelectAll(e.target.checked)}
|
||||
className="w-5 h-5 text-investor rounded border-slate-300"
|
||||
/>
|
||||
<label htmlFor="selectAll" className="flex-1 cursor-pointer">
|
||||
<span className="font-semibold text-slate-800">Select All</span>
|
||||
<p className="text-xs text-slate-500">Include all investments and bikes</p>
|
||||
</label>
|
||||
</div>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{investor.investments?.map((inv: any) => {
|
||||
const invBikes = assignedBikes.filter((b: any) => b.investmentId === inv.id);
|
||||
const invEarnings = invBikes.reduce((sum: number, b: any) => sum + (b.totalEarnings || 0), 0);
|
||||
return (
|
||||
<div key={inv.id} className="p-4">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`plan-${inv.id}`}
|
||||
checked={selectAll || selectedPlans.includes(inv.id)}
|
||||
disabled={selectAll}
|
||||
onChange={() => togglePlan(inv.id, invBikes)}
|
||||
className="w-5 h-5 text-investor rounded border-slate-300"
|
||||
/>
|
||||
<label htmlFor={`plan-${inv.id}`} className="flex-1 flex items-center justify-between cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold text-slate-800">{inv.planName}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs capitalize font-medium ${planColors[inv.planType]}`}>{inv.planType}</span>
|
||||
</div>
|
||||
<span className="text-base font-bold text-green-600">৳{invEarnings.toLocaleString()}</span>
|
||||
</label>
|
||||
</div>
|
||||
{!selectAll && invBikes.length > 0 && (
|
||||
<div className="ml-8 pl-4 border-l-2 border-slate-200 space-y-2">
|
||||
{invBikes.map((bike: any) => {
|
||||
const statusStyle = bikeStatusColors[bike.status] || bikeStatusColors.available;
|
||||
return (
|
||||
<div key={bike.id} className="flex items-center gap-3 p-2 bg-white rounded-lg border border-slate-100">
|
||||
|
||||
<div className="border border-slate-200 rounded-lg overflow-hidden">
|
||||
<div className="bg-slate-100 px-3 py-2 border-b border-slate-200">
|
||||
<p className="text-sm font-semibold text-slate-700">Investment Plans</p>
|
||||
</div>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{investor.investments?.map((inv: any) => {
|
||||
const invBikes = assignedBikes.filter((b: any) => b.investmentId === inv.id);
|
||||
const invEarnings = invBikes.reduce((sum: number, b: any) => sum + (b.totalEarnings || 0), 0);
|
||||
return (
|
||||
<div key={inv.id} className="p-3 sm:p-4">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`plan-${inv.id}`}
|
||||
checked={selectAll || selectedPlans.includes(inv.id)}
|
||||
disabled={selectAll}
|
||||
onChange={() => togglePlan(inv.id, invBikes)}
|
||||
className="w-4 h-4 text-investor rounded border-slate-300"
|
||||
/>
|
||||
<label htmlFor={`plan-${inv.id}`} className="flex-1 flex items-center justify-between cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-slate-800 text-sm">{inv.planName}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs capitalize font-medium ${planColors[inv.planType]}`}>{inv.planType}</span>
|
||||
</div>
|
||||
<span className="text-sm font-bold text-green-600">৳{invEarnings.toLocaleString()}</span>
|
||||
</label>
|
||||
</div>
|
||||
{!selectAll && invBikes.length > 0 && (
|
||||
<div className="ml-6 sm:ml-8 pl-3 sm:pl-4 border-l-2 border-slate-200 space-y-2">
|
||||
{invBikes.map((bike: any) => (
|
||||
<div key={bike.id} className="flex items-center gap-2 p-2 bg-white rounded-lg border border-slate-100">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`bike-${bike.id}`}
|
||||
@@ -298,184 +401,184 @@ export default function InvestorWithdrawPage() {
|
||||
</label>
|
||||
<span className="text-xs text-green-600 font-semibold">৳{bike.totalEarnings?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Withdrawal Amount */}
|
||||
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-semibold text-slate-800">Withdrawal Amount</h4>
|
||||
<span className={`text-xs px-3 py-1 rounded-full font-medium ${selectAll ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'}`}>
|
||||
{selectAll ? 'All Selected' : `${selectedBikes.length} bikes`}
|
||||
</span>
|
||||
{/* Withdrawal Amount */}
|
||||
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-semibold text-slate-800 text-sm">Withdrawal Amount</h4>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full font-medium ${selectAll ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'}`}>
|
||||
{selectAll ? 'All Selected' : `${selectedBikes.length} bikes`}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-green-700 mb-1">৳{calculatedAmount.toLocaleString()}</p>
|
||||
<p className="text-xs text-slate-500">Based on {selectAll ? 'all' : selectedBikes.length} selected bike(s) earnings</p>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-green-700 mb-2">৳{calculatedAmount.toLocaleString()}</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Based on {selectAll ? 'all' : selectedBikes.length} selected bike(s) earnings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Method */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-3">Payment Method</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{(investor as any).bankAccounts?.map((account: any) => (
|
||||
{/* Payment Method */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-2">Payment Method</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{(investor as any).bankAccounts?.map((account: any) => (
|
||||
<div
|
||||
key={account.id}
|
||||
onClick={() => { setPaymentMethod('bank'); setSelectedAccount(account.id); }}
|
||||
className={`p-3 sm:p-4 rounded-lg border cursor-pointer transition-all ${selectedAccount === account.id ? 'border-investor bg-investor/5 ring-2 ring-investor/20' : 'border-slate-200 hover:border-slate-300'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg flex items-center justify-center bg-slate-100">
|
||||
<Building2 className="w-4 h-4 sm:w-5 sm:h-5 text-slate-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-slate-800">{account.bankName}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{account.accountNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{(investor as any).mobileBanking && (
|
||||
<div
|
||||
key={account.id}
|
||||
onClick={() => { setPaymentMethod('bank'); setSelectedAccount(account.id); }}
|
||||
className={`p-4 rounded-lg border cursor-pointer transition-all ${selectedAccount === account.id ? 'border-investor bg-investor/5 ring-2 ring-investor/20' : 'border-slate-200 hover:border-slate-300 hover:shadow-sm'}`}
|
||||
onClick={() => { setPaymentMethod('mobile'); setSelectedAccount('mobile'); }}
|
||||
className={`mt-2 p-3 sm:p-4 rounded-lg border cursor-pointer transition-all ${selectedAccount === 'mobile' ? 'border-investor bg-investor/5 ring-2 ring-investor/20' : 'border-slate-200 hover:border-slate-300'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg flex items-center justify-center bg-slate-100">
|
||||
<Building2 className="w-5 h-5 text-slate-500" />
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-pink-100 flex items-center justify-center">
|
||||
<Smartphone className="w-4 h-4 sm:w-5 sm:h-5 text-pink-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-slate-800">{account.bankName}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{account.accountNumber}</p>
|
||||
<p className="text-sm font-semibold text-slate-800">{(investor as any).mobileBanking}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{(investor as any).mobileBankingNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{(investor as any).mobileBanking && (
|
||||
<div
|
||||
onClick={() => { setPaymentMethod('mobile'); setSelectedAccount('mobile'); }}
|
||||
className={`mt-3 p-4 rounded-lg border cursor-pointer transition-all ${selectedAccount === 'mobile' ? 'border-investor bg-investor/5 ring-2 ring-investor/20' : 'border-slate-200 hover:border-slate-300 hover:shadow-sm'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-pink-100 flex items-center justify-center">
|
||||
<Smartphone className="w-5 h-5 text-pink-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-slate-800">{(investor as any).mobileBanking}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{(investor as any).mobileBankingNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-200 flex justify-end gap-3 bg-slate-50">
|
||||
<button onClick={() => setShowWithdrawModal(false)} className="px-5 py-2.5 border border-slate-300 text-slate-600 rounded-lg text-sm font-medium hover:bg-white hover:shadow-sm transition-all">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmitWithdraw}
|
||||
disabled={!selectedAccount || (selectedBikes.length === 0 && !selectAll)}
|
||||
className="px-6 py-2.5 bg-investor text-white rounded-lg text-sm font-semibold hover:bg-investor-dark disabled:opacity-50 disabled:cursor-not-allowed shadow-sm transition-all flex items-center gap-2"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" /> Submit Request
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Auto-Withdraw Modal */}
|
||||
{showAutoWithdrawModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60] p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800">Auto-Withdraw Settings</h3>
|
||||
<button onClick={() => setShowAutoWithdrawModal(false)} className="p-1.5 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 space-y-5">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Enable Auto-Withdraw</label>
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
||||
<button
|
||||
onClick={() => setAutoWithdrawEnabled(!autoWithdrawEnabled)}
|
||||
className={`w-12 h-6 rounded-full transition-colors ${autoWithdrawEnabled ? 'bg-green-500' : 'bg-slate-200'}`}
|
||||
>
|
||||
<span className={`block w-5 h-5 bg-white rounded-full shadow transition-transform ${autoWithdrawEnabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
<span className="text-sm text-slate-600">Automatically withdraw earnings</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Withdrawal Frequency</label>
|
||||
<select
|
||||
value={autoWithdrawFreq}
|
||||
onChange={(e) => setAutoWithdrawFreq(e.target.value)}
|
||||
className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm bg-white"
|
||||
>
|
||||
<option value="as_per_request">As Requested</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Minimum Amount</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-3 py-2.5 bg-slate-100 rounded-lg text-slate-500 font-bold">৳</span>
|
||||
<input
|
||||
type="number"
|
||||
value={autoWithdrawMin}
|
||||
onChange={(e) => setAutoWithdrawMin(e.target.value)}
|
||||
className="flex-1 px-3 py-2.5 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="1000"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-1.5">Minimum balance required for auto-withdrawal</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Destination Account</label>
|
||||
<div className="space-y-2">
|
||||
{(investor as any).bankAccounts?.map((account: any) => (
|
||||
<div key={account.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
||||
<input
|
||||
type="radio"
|
||||
name="destAccount"
|
||||
checked={autoWithdrawAccount === account.id}
|
||||
onChange={() => setAutoWithdrawAccount(account.id)}
|
||||
className="w-4 h-4 text-investor"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-slate-700">{account.bankName}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{account.accountNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(investor as any).mobileBanking && (
|
||||
<div className="flex items-center gap-3 p-3 bg-purple-50 rounded-lg border border-purple-100">
|
||||
<input
|
||||
type="radio"
|
||||
name="destAccount"
|
||||
checked={autoWithdrawAccount === 'mobile'}
|
||||
onChange={() => setAutoWithdrawAccount('mobile')}
|
||||
className="w-4 h-4 text-investor"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-slate-700">{(investor as any).mobileBanking}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{(investor as any).mobileBankingNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
|
||||
<button onClick={() => setShowAutoWithdrawModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-white">Cancel</button>
|
||||
<button onClick={handleSaveAutoWithdraw} className="px-5 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark shadow-sm">Save Settings</button>
|
||||
|
||||
<div className="p-4 sm:p-5 border-t border-slate-200 flex justify-end gap-2 sm:gap-3 bg-slate-50">
|
||||
<button onClick={() => setShowWithdrawModal(false)} className="px-4 py-2 sm:py-2.5 border border-slate-300 text-slate-600 rounded-lg text-sm font-medium hover:bg-white hover:shadow-sm transition-all">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmitWithdraw}
|
||||
disabled={!selectedAccount || (selectedBikes.length === 0 && !selectAll)}
|
||||
className="px-5 py-2 sm:py-2.5 bg-investor text-white rounded-lg text-sm font-semibold hover:bg-investor-dark disabled:opacity-50 disabled:cursor-not-allowed shadow-sm transition-all flex items-center gap-2"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" /> Submit Request
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Auto-Withdraw Modal */}
|
||||
{
|
||||
showAutoWithdrawModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60] p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800">Auto-Withdraw Settings</h3>
|
||||
<button onClick={() => setShowAutoWithdrawModal(false)} className="p-1.5 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Enable Auto-Withdraw</label>
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
||||
<button
|
||||
onClick={() => setAutoWithdrawEnabled(!autoWithdrawEnabled)}
|
||||
className={`w-12 h-6 rounded-full transition-colors ${autoWithdrawEnabled ? 'bg-green-500' : 'bg-slate-200'}`}
|
||||
>
|
||||
<span className={`block w-5 h-5 bg-white rounded-full shadow transition-transform ${autoWithdrawEnabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
<span className="text-sm text-slate-600">Automatically withdraw earnings</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Withdrawal Frequency</label>
|
||||
<select
|
||||
value={autoWithdrawFreq}
|
||||
onChange={(e) => setAutoWithdrawFreq(e.target.value)}
|
||||
className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm bg-white"
|
||||
>
|
||||
<option value="as_per_request">As Requested</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Minimum Amount</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-3 py-2.5 bg-slate-100 rounded-lg text-slate-500 font-bold">৳</span>
|
||||
<input
|
||||
type="number"
|
||||
value={autoWithdrawMin}
|
||||
onChange={(e) => setAutoWithdrawMin(e.target.value)}
|
||||
className="flex-1 px-3 py-2.5 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="1000"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-1.5">Minimum balance required for auto-withdrawal</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-700 mb-2 block">Destination Account</label>
|
||||
<div className="space-y-2">
|
||||
{(investor as any).bankAccounts?.map((account: any) => (
|
||||
<div key={account.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
||||
<input
|
||||
type="radio"
|
||||
name="destAccount"
|
||||
checked={autoWithdrawAccount === account.id}
|
||||
onChange={() => setAutoWithdrawAccount(account.id)}
|
||||
className="w-4 h-4 text-investor"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-slate-700">{account.bankName}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{account.accountNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(investor as any).mobileBanking && (
|
||||
<div className="flex items-center gap-3 p-3 bg-purple-50 rounded-lg border border-purple-100">
|
||||
<input
|
||||
type="radio"
|
||||
name="destAccount"
|
||||
checked={autoWithdrawAccount === 'mobile'}
|
||||
onChange={() => setAutoWithdrawAccount('mobile')}
|
||||
className="w-4 h-4 text-investor"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-slate-700">{(investor as any).mobileBanking}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{(investor as any).mobileBankingNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
|
||||
<button onClick={() => setShowAutoWithdrawModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-white">Cancel</button>
|
||||
<button onClick={handleSaveAutoWithdraw} className="px-5 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark shadow-sm">Save Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user