From ae94ce0427c1c5f9a17f0f067b9a01daed4256ee Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Wed, 22 Apr 2026 01:18:03 +0600 Subject: [PATCH] Add CRUD forms: Bank, Mobile Banking, Tax, Documents + Transactions withdraw list --- src/app/admin/investors/[id]/page.tsx | 514 +++++++++++++++++++++----- 1 file changed, 415 insertions(+), 99 deletions(-) diff --git a/src/app/admin/investors/[id]/page.tsx b/src/app/admin/investors/[id]/page.tsx index c8010c4..a2e1655 100644 --- a/src/app/admin/investors/[id]/page.tsx +++ b/src/app/admin/investors/[id]/page.tsx @@ -47,13 +47,23 @@ export default function InvestorDetailPage() { const investor = investors.find(i => i.id === investorId); const assignedBikes = initialBikes.filter(b => b.investorId === investorId); - const investorTransactions = initialTransactions.filter(t => t.userId === investor?.userId); + // Investor transactions are filtered below const [activeTab, setActiveTab] = useState('overview'); const [showEditModal, setShowEditModal] = useState(false); const [showAssignBikeModal, setShowAssignBikeModal] = useState(false); const [selectedBikeId, setSelectedBikeId] = useState(''); const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false); + const [showBankModal, setShowBankModal] = useState(false); + const [showMobileBankingModal, setShowMobileBankingModal] = useState(false); + const [showTaxModal, setShowTaxModal] = useState(false); + const [showDocModal, setShowDocModal] = useState(false); + const [editingBank, setEditingBank] = useState({ bankName: '', bankAccountName: '', bankAccountNumber: '', bankBranch: '', bankRouting: '' }); + const [editingMobileBanking, setEditingMobileBanking] = useState({ provider: '', number: '', isPrimary: false }); + const [editingTax, setEditingTax] = useState({ tinNumber: '', passportNumber: '' }); + const [newDoc, setNewDoc] = useState({ type: 'nid', number: '', url: '' }); + const [editingMobileIndex, setEditingMobileIndex] = useState(null); + const investorTransactions = initialTransactions.filter(t => t.investorId === investorId); const [newInvestment, setNewInvestment] = useState({ planName: '', planType: 'gold' as 'silver' | 'gold' | 'platinum' | 'diamond', @@ -494,43 +504,80 @@ export default function InvestorDetailPage() {
-

Bank Details

+
+

Bank Details

+ +
- {investor.bankName && ( -
- -
-

Bank

-

{investor.bankName}

-

{investor.bankBranch}

+ {investor.bankName ? ( + <> +
+ +
+

Bank

+

{investor.bankName}

+

{investor.bankBranch}

+
-
- )} - {investor.bankAccountNumber && ( -
- -
-

Account

-

{investor.bankAccountName}

-

{investor.bankAccountNumber}

+
+ +
+

Account

+

{investor.bankAccountName}

+

{investor.bankAccountNumber}

+ {investor.bankRouting &&

Routing: {investor.bankRouting}

} +
+ + ) : ( +
+ +

No bank details added

)}
-

Mobile Banking

+
+

Mobile Banking

+ +
- {investor.mobileBanking && ( -
- -
-

{investor.mobileBanking} (Primary)

-

{investor.mobileBankingNumber}

+ {investor.mobileBanking ? ( +
+ +
+

Primary

+

{investor.mobileBanking}

+

{investor.mobileBankingNumber}

+
- )} + ) : null} {investor.additionalMobileBanking?.map((mb, idx) => (
@@ -538,31 +585,69 @@ export default function InvestorDetailPage() {

{mb.provider}

{mb.number}

- {mb.verified && ( + {mb.verified ? ( Verified + ) : ( + Pending )} + +
))} -
- -

Tax Information

-
- {investor.tinNumber && ( -
- -
-

TIN

-

{investor.tinNumber}

-
+ {(!investor.mobileBanking && (!investor.additionalMobileBanking || investor.additionalMobileBanking.length === 0)) && ( +
+ +

No mobile banking added

)} - {investor.passportNumber && ( -
- -
-

Passport

-

{investor.passportNumber}

-
+
+ +
+

Tax Information

+ +
+
+ {investor.tinNumber || investor.passportNumber ? ( + <> + {investor.tinNumber && ( +
+ +
+

TIN

+

{investor.tinNumber}

+
+
+ )} + {investor.passportNumber && ( +
+ +
+

Passport

+

{investor.passportNumber}

+
+
+ )} + + ) : ( +
+ +

No tax info added

)}
@@ -596,92 +681,168 @@ export default function InvestorDetailPage() { {activeTab === 'transactions' && (
-

Transactions

- +

All Transactions

+
+ + +
-
- {investorTransactions.map(tx => ( -
-
-
- + +
+

Pending Withdrawals

+
+ {investorTransactions.filter(t => t.type === 'withdrawal' && t.status === 'pending').map(tx => ( +
+
+
+ +
+
+

{tx.description}

+

Ref: {tx.referenceNumber || tx.id} • {tx.createdAt}

+
-
-

{tx.description}

-

{tx.createdAt}

+
+

-৳{tx.amount.toLocaleString()}

+ + Pending +
-
-

- {tx.type === 'earning' ? '+' : tx.type === 'withdrawal' ? '-' : '+'}৳{tx.amount.toLocaleString()} -

- - {tx.status} - + ))} + {investorTransactions.filter(t => t.type === 'withdrawal' && t.status === 'pending').length === 0 && ( +
+

No pending withdrawals

-
- ))} - {investorTransactions.length === 0 && ( -
- -

No transactions yet

-
- )} + )} +
+
+ +
+

Transaction History

+
+ {investorTransactions.map(tx => ( +
+
+
+ +
+
+

{tx.description}

+

{tx.createdAt} {tx.referenceNumber && `• Ref: ${tx.referenceNumber}`}

+
+
+
+

+ {tx.type === 'earning' || tx.type === 'bike_earning' || tx.type === 'investment_return' || tx.type === 'referral_bonus' ? '+' : tx.type === 'withdrawal' || tx.type === 'penalty' || tx.type === 'adjustment' ? '-' : '+'}৳{tx.amount.toLocaleString()} +

+ + {tx.status} + +
+
+ ))} + {investorTransactions.length === 0 && ( +
+ +

No transactions yet

+
+ )} +
)} {activeTab === 'documents' && (
-

Uploaded Documents

-
+
+

KYC Documents

+ +
+ +
{investor.kycDocuments?.map((doc, idx) => ( -
+
- +
+ +

{doc.type.replace('_', ' ')}

-

{doc.number || 'No number'}

+

{doc.number || 'No document number'} • Uploaded: {doc.uploadedAt || 'N/A'}

{doc.verified ? ( - + Verified ) : ( - - Pending + + Pending Review )} + +
))} {(!investor.kycDocuments || investor.kycDocuments.length === 0) && ( -
+
-

No documents uploaded

+

No documents uploaded yet

)}
-
- -

Drag and drop files here, or click to browse

- + +
+

KYC Status: {investor.kycStatus.toUpperCase()}

+

+ {investor.kycStatus === 'verified' + ? 'All documents have been verified. Investor is fully verified.' + : investor.kycStatus === 'pending' + ? 'Documents are under review. Verification typically takes 24-48 hours.' + : 'Please upload required documents for verification.'} +

)} @@ -896,6 +1057,161 @@ export default function InvestorDetailPage() {
)} + + {showBankModal && ( +
+
+
+

Bank Details

+ +
+
+
+ + setEditingBank({ ...editingBank, bankName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Standard Chartered Bank" /> +
+
+ + setEditingBank({ ...editingBank, bankBranch: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Gulshan Branch" /> +
+
+ + setEditingBank({ ...editingBank, bankAccountName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Account holder name" /> +
+
+ + setEditingBank({ ...editingBank, bankAccountNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Account number" /> +
+
+ + setEditingBank({ ...editingBank, bankRouting: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Routing number" /> +
+
+
+ {investor.bankName && ( + + )} +
+ + +
+
+
+
+ )} + + {showMobileBankingModal && ( +
+
+
+

{editingMobileIndex !== null ? 'Edit' : 'Add'} Mobile Banking

+ +
+
+
+ + +
+
+ + setEditingMobileBanking({ ...editingMobileBanking, number: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="01XXXXXXXXX" /> +
+
+ setEditingMobileBanking({ ...editingMobileBanking, isPrimary: e.target.checked })} className="rounded text-investor" /> + Set as primary account +
+
+
+ + +
+
+
+ )} + + {showTaxModal && ( +
+
+
+

Tax Information

+ +
+
+
+ + setEditingTax({ ...editingTax, tinNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="TIN Number" /> +
+
+ + setEditingTax({ ...editingTax, passportNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Passport Number" /> +
+
+
+ {(investor.tinNumber || investor.passportNumber) && ( + + )} +
+ + +
+
+
+
+ )} + + {showDocModal && ( +
+
+
+

Upload Document

+ +
+
+
+ + +
+
+ + setNewDoc({ ...newDoc, number: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Document number (optional)" /> +
+
+ +
+ +

Click to upload or drag and drop

+

PNG, JPG, PDF up to 10MB

+
+
+
+
+ + +
+
+
+ )}
); } \ No newline at end of file