diff --git a/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx new file mode 100644 index 0000000..827aa6b --- /dev/null +++ b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx @@ -0,0 +1,717 @@ +'use client'; + +import { useState, use } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { + ArrowLeft, TrendingUp, Plus, X, Edit, Trash2, Bike, DollarSign, Calendar, + CreditCard, FileText, Users, TrendingDown, ArrowRight, Download, Check, + Printer, BarChart3, Wallet, Clock, Shield, Percent, Activity, AlertTriangle +} from 'lucide-react'; +import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions } from '@/data/mockData'; + +export default function InvestmentDetailPage({ params }: { params: Promise<{ id: string; investmentId: string }> }) { + const resolvedParams = use(params); + const { id: investorId, investmentId } = resolvedParams; + const router = useRouter(); + + const investor = initialInvestors.find(i => i.id === investorId); + const investment = investor?.investments?.find((inv: any) => inv.id === investmentId); + + const [activeTab, setActiveTab] = useState('overview'); + const [showAddBikeModal, setShowAddBikeModal] = useState(false); + const [showDeleteBikeModal, setShowDeleteBikeModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [bikeToDelete, setBikeToDelete] = useState(null); + const [selectedBikeId, setSelectedBikeId] = useState(''); + const [editForm, setEditForm] = useState({}); + + if (!investor || !investment) { + return ( +
+
+

Investment Not Found

+

The investment you're looking for doesn't exist.

+ + Back to Investor + +
+
+ ); + } + + const planConfig: Record = { + silver: { bg: 'bg-slate-100', border: 'border-slate-300', text: 'text-slate-600' }, + gold: { bg: 'bg-amber-100', border: 'border-amber-300', text: 'text-amber-700' }, + platinum: { bg: 'bg-purple-100', border: 'border-purple-300', text: 'text-purple-700' }, + diamond: { bg: 'bg-blue-100', border: 'border-blue-300', text: 'text-blue-700' }, + }; + const style = planConfig[investment.planType] || planConfig.gold; + + const assignedBikes = initialBikes.filter((b: any) => b.investorId === investorId); + const investmentTransactions = initialTransactions.filter((t: any) => t.investorId === investorId && t.type === 'investment_return'); + + return ( +
+
+
+
+ +
+
+

{investment.planName}

+ + {investment.planType} Plan + + + {investment.status} + +
+

+ ID: #{investment.id?.slice(-8)} • Investor: {investor.name} +

+
+
+
+ + +
+
+ +
+
+
+
+ +
+

Investment

+
+

৳{investment.totalInvestment.toLocaleString()}

+
+
+
+
+ +
+

Total Return

+
+

৳{investment.actualEarnings.toLocaleString()}

+
+
+
+
+ +
+

Pending

+
+

৳{Math.round(investment.totalInvestment * 0.24 - investment.actualEarnings).toLocaleString()}

+
+
+
+
+ +
+

Expected ROI

+
+

24%

+
+
+ +
+
+
+
+ + + + +
+ +
+ {activeTab === 'overview' && ( +
+
+
+

+ + Investment Details +

+
+
+ Plan Name + {investment.planName} +
+
+ Plan Type + {investment.planType} +
+
+ Period + {investment.startDate} - {investment.endDate || 'Ongoing'} +
+
+ Payment + {investment.paymentMethod} +
+
+ Transaction ID + #{investment.id?.slice(-8)} +
+
+
+ +
+

+ + Plan Configuration +

+
+
+ EV Base Price + ৳200,000 +
+
+ Min Quantity + 1 bike +
+
+ Duration + 12 months +
+
+ Lock-in + 3 months +
+
+ Exit Penalty + 10% +
+
+
+
+ +
+

+ + FICO Share - Jaiben's Profit per Ride +

+

Profit sharing when bikes are rented to end customers

+
+
+

Single Rent

+

45%

+
+
+

Rent to Own

+

55%

+
+
+

Share EV

+

60%

+
+
+
+
+ )} + + {activeTab === 'transactions' && ( +
+
+

Transaction History

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateDescriptionTypeAmountStatus
2024-01-15Investment FundedCredit+৳85,000Completed
2024-02-15Monthly ReturnReturn+৳1,700Completed
2024-03-15Monthly ReturnReturn+৳1,700Completed
2024-04-15Monthly ReturnReturn+৳1,700Completed
+
+
+ )} + + {activeTab === 'pnl' && ( +
+

Profit & Loss Statement

+
+
+ Total Revenue (Ride Rentals) + ৳1,50,000 +
+
+
+ Jaiben's Share (45%) + -৳67,500 +
+
+ Operational Costs + -৳0 +
+
+
+ Net Profit to Investor + ৳82,500 +
+
+
+ )} + + {activeTab === 'journals' && ( +
+

Accounting Journal Entries

+
+
+
+ Entry #JE-001 • {investment.startDate} + Posted +
+
+
+
+

Debit (Dr)

+

Bank - City Bank

+

1200

+
+

৳{investment.totalInvestment.toLocaleString()}

+
+
+
+ +
+
+
+
+

Credit (Cr)

+

Investor Liabilities

+

2200

+
+

৳{investment.totalInvestment.toLocaleString()}

+
+
+
+
+
+ )} +
+
+ +
+
+

+ + Assigned Bikes ({assignedBikes.length}) +

+ +
+ {assignedBikes.length > 0 ? ( +
+ {assignedBikes.map((bike: any) => { + const bikeEarnings = initialTransactions.filter( + (t: any) => t.investorId === investorId && t.bikeId === bike.id + ).reduce((sum: number, t: any) => sum + t.amount, 0); + const planColors: Record = { + silver: 'from-slate-400 to-slate-500', + gold: 'from-amber-400 to-amber-600', + platinum: 'from-purple-400 to-purple-600', + diamond: 'from-blue-400 to-blue-600', + }; + const planBg: Record = { + silver: 'bg-slate-50 border-slate-200', + gold: 'bg-amber-50 border-amber-200', + platinum: 'bg-purple-50 border-purple-200', + diamond: 'bg-blue-50 border-blue-200', + }; + const planText: Record = { + silver: 'text-slate-600', + gold: 'text-amber-700', + platinum: 'text-purple-700', + diamond: 'text-blue-700', + }; + const planBadge: Record = { + silver: 'bg-slate-100 text-slate-600', + gold: 'bg-amber-100 text-amber-700', + platinum: 'bg-purple-100 text-purple-700', + diamond: 'bg-blue-100 text-blue-700', + }; + const rentalTypes: Record = { + single_rent: { label: 'Single Rent', color: 'text-green-600', bg: 'bg-green-100' }, + rent_to_own: { label: 'Rent to Own', color: 'text-blue-600', bg: 'bg-blue-100' }, + share_ev: { label: 'Share EV', color: 'text-purple-600', bg: 'bg-purple-100' }, + }; + const rentalInfo = rentalTypes[bike.rentalType || 'single_rent'] || rentalTypes.single_rent; + return ( +
+
+
+ +
+
+
+
{bike.model}
+ + {investment.planType} + +
+

{bike.plateNumber} • {bike.brand}

+
+
+
+ + ৳{bikeEarnings.toLocaleString()} +
+

Total Earnings

+
+ + + + +
+ +
+
+

Status

+

+ {bike.status} +

+
+
+

Daily Rent

+

৳{bike.currentRent}

+
+
+

Battery

+

{bike.batteryLevel}%

+
+
+

Location

+

{bike.location}

+
+
+

Rental Type

+ + {rentalInfo.label} + +
+
+
+ ); + })} +
+ ) : ( +
+ +

No bikes assigned to this investment yet

+ +
+ )} +
+
+ +
+
+

+ + Investor Profile +

+
+
+ {investor.name.charAt(0)} +
+
+

{investor.name}

+

{investor.email}

+
+
+
+
+ Phone + {investor.phone} +
+
+ ID + {investor.id} +
+ + View Full Profile + + +
+
+ + + +
+

Quick Actions

+
+ + + + +
+
+
+
+
+ + {showAddBikeModal && ( +
+
+
+

+ Assign Bike to Investment +

+ +
+
+
+ + +
+
+
+ + +
+
+
+ )} + + {showEditModal && ( +
+
+
+

+ Edit Investment +

+ +
+
+
+ + setEditForm({ ...editForm, planName: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor" + /> +
+
+ + +
+ {/*
+ + setEditForm({ ...editForm, totalInvestment: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor" + /> +
*/} +
+ + +
+
+
+ + +
+
+
+ )} + + {showDeleteBikeModal && bikeToDelete && ( +
+
+
+

+ Remove Bike +

+ +
+
+

Are you sure you want to remove this bike from this investment?

+
+
+ Bike + {bikeToDelete.model} +
+
+ Plate + {bikeToDelete.plateNumber} +
+
+ Investment + {investment.planName} +
+
+

+ + This action will unassign the bike from the investor. +

+
+
+ + +
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/app/admin/investors/[id]/page.tsx b/src/app/admin/investors/[id]/page.tsx index 00930df..251c1b8 100644 --- a/src/app/admin/investors/[id]/page.tsx +++ b/src/app/admin/investors/[id]/page.tsx @@ -86,6 +86,8 @@ export default function InvestorDetailPage() { const [showAssignBikeModal, setShowAssignBikeModal] = useState(false); const [selectedBikeId, setSelectedBikeId] = useState(''); const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false); + const [showInvestmentSuccessModal, setShowInvestmentSuccessModal] = useState(false); + const [lastCreatedInvestment, setLastCreatedInvestment] = useState(null); const [showInvoiceModal, setShowInvoiceModal] = useState(false); const [showJournalModal, setShowJournalModal] = useState(false); const [selectedInvoice, setSelectedInvoice] = useState(null); @@ -172,35 +174,22 @@ export default function InvestorDetailPage() { paymentMethod: newInvestment.paymentMethod }; - setInvestorJournals([journalEntry, ...investorJournals]); +setInvestorJournals([journalEntry, ...investorJournals]); - console.log('Creating Investment:', { + setLastCreatedInvestment({ id: invId, investorId: investor.id, ...newInvestment, actualEarnings: 0, - status: 'active' as const, + status: 'active', transactionId: transactionRef, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), + debitAccount, + journalEntry }); - alert(`Investment created successfully! - -Investor: ${investor.name} -Investment ID: ${invId} -Transaction Ref: ${transactionRef} -Amount: ৳${newInvestment.totalInvestment.toLocaleString()} - -Accounting Entry Created (Auto-Journal): -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Date: ${newInvestment.startDate} -Ref: ${transactionRef} -Description: ${investor.name} - ${newInvestment.planName} - -Debit (Dr): ${debitAccount.name} ৳${newInvestment.totalInvestment.toLocaleString()} -Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleString()} -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); setShowCreateInvestmentModal(false); + setShowInvestmentSuccessModal(true); setNewInvestment({ planName: '', planType: 'gold', @@ -908,59 +897,114 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri {activeTab === 'bikes' && (
-

Assigned Bikes

- +
+

Assigned Bikes

+

{assignedBikes.length} bikes across {investor.investments?.length || 0} investment plans

+
- {assignedBikes.map(bike => ( -
-
-
-
- + {assignedBikes.map(bike => { + const investment = investor.investments?.find((inv: any) => inv.id === bike.investmentId); + const planColors: Record = { + silver: 'from-slate-400 to-slate-600', + gold: 'from-amber-400 to-amber-600', + platinum: 'from-purple-400 to-purple-600', + diamond: 'from-blue-400 to-blue-600', + }; + const planBadges: Record = { + silver: 'bg-slate-100 text-slate-600', + gold: 'bg-amber-100 text-amber-700', + platinum: 'bg-purple-100 text-purple-700', + diamond: 'bg-blue-100 text-blue-700', + }; + const planBg: Record = { + silver: 'bg-slate-50 border-slate-200', + gold: 'bg-amber-50 border-amber-200', + platinum: 'bg-purple-50 border-purple-200', + diamond: 'bg-blue-50 border-blue-200', + }; + const rentalTypes: Record = { + single_rent: { label: 'Single Rent', color: 'text-green-600' }, + rent_to_own: { label: 'Rent to Own', color: 'text-blue-600' }, + share_ev: { label: 'Share EV', color: 'text-purple-600' }, + }; + const statusConfig: Record = { + rented: { bg: 'bg-green-100', color: 'text-green-700' }, + available: { bg: 'bg-blue-100', color: 'text-blue-700' }, + maintenance: { bg: 'bg-amber-100', color: 'text-amber-700' }, + retired: { bg: 'bg-slate-100', color: 'text-slate-600' }, + }; + const rentalInfo = rentalTypes[bike.rentalType || 'single_rent']; + const planType = investment?.planType || 'gold'; + const status = statusConfig[bike.status] || statusConfig.available; + + return ( + +
+
+
+
+ +
+
+

{bike.model}

+

{bike.brand}

+
+ + {bike.status} +
-
-

{bike.model}

-

{bike.brand}

+ +
+ + {planType} Plan • {investment?.planName || 'Investment'} +
+ +
+
+

Plate

+

{bike.plateNumber.split('-').pop()}

+
+
+

Location

+

{bike.location}

+
+
+ +
+
+ Rental Type + {rentalInfo.label} +
+
+ Total Earnings + ৳{bike.totalEarnings?.toLocaleString() || 0} +
+
+ Battery + 50 ? 'text-green-600' : + bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600' + }`}>{bike.batteryLevel}% +
- - {bike.status} - -
-
-
- Plate - {bike.plateNumber} -
-
- Location - {bike.location} -
-
- Battery - 50 ? 'text-green-600' : bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600'}`}>{bike.batteryLevel}% -
-
- Purchase Price - ৳{bike.purchasePrice?.toLocaleString() || 0} -
-
- Total Earnings - ৳{bike.totalEarnings?.toLocaleString() || 0} -
-
-
- ))} + + ); + })} {assignedBikes.length === 0 && ( -
- -

No bikes assigned yet

+
+ +

No bikes assigned to this investor

+

Bikes will appear here once assigned to investments

)}
@@ -970,57 +1014,92 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri {activeTab === 'investments' && (
-

Investment Plans

-
-
- {investor.investments?.map((inv) => ( -
-
-
-

{inv.planName}

-

{inv.planType} Plan

+ +
+ {investor.investments?.map((inv) => { + const planConfig: Record = { + silver: { bg: 'bg-slate-100', border: 'border-slate-300', icon: 'text-slate-500' }, + gold: { bg: 'bg-amber-100', border: 'border-amber-300', icon: 'text-amber-500' }, + platinum: { bg: 'bg-purple-100', border: 'border-purple-300', icon: 'text-purple-500' }, + diamond: { bg: 'bg-blue-100', border: 'border-blue-300', icon: 'text-blue-500' }, + }; + const style = planConfig[inv.planType] || planConfig.gold; + + return ( +
+
+
+

{inv.planName}

+

{inv.planType} Plan

+
+ + {inv.status} +
- - {inv.status} - -
-
-
-

Investment

-

৳{inv.totalInvestment.toLocaleString()}

-
-
-

Monthly Return

-

৳{inv.monthlyReturn.toLocaleString()}

-
-
-

Expected ROI

-

{inv.expectedRoi}%

-
-
-

Actual Earned

-

৳{inv.actualEarnings.toLocaleString()}

+
+
+
+

Investment

+

৳{inv.totalInvestment.toLocaleString()}

+
+
+

Total Return

+

৳{inv.actualEarnings.toLocaleString()}

+
+
+ +
+
+ Duration + 12 months +
+
+ Lock-in Period + 3 months +
+
+ Early Exit Penalty + 10% +
+
+ +
+ {inv.startDate} - {inv.endDate || 'Ongoing'} + {inv.paymentMethod} +
+
+

ID: #{inv.id?.slice(-6) || 'N/A'}

+
+ + View + + +
+
-
- {inv.startDate} to {inv.endDate || 'Ongoing'} - {inv.paymentMethod} -
-
- ))} - {(!investor.investments || investor.investments.length === 0) && ( -
- -

No investments yet

- -
- )} + ); + })}
+ + {(!investor.investments || investor.investments.length === 0) && ( +
+ +

No Investments Yet

+

Create your first investment plan for this investor

+ +
+ )}
)} @@ -1455,15 +1534,46 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri {showCreateInvestmentModal && (
-
+
-

Create New Investment

+
+

Create New Investment

+

Set up investment for {investor.name}

+
-
+
+
+ +
+ {[ + { id: 'inv_demo_1', name: '1 Bike Plan', tier: 'Economy', evBasePrice: 200000, minQuantity: 1, duration: 12, maxInvestment: 1000000 }, + { id: 'inv_demo_2', name: '5 Bike Plan', tier: 'Standard', evBasePrice: 180000, minQuantity: 5, duration: 24, maxInvestment: 5000000 }, + ].map(plan => ( + + ))} +
+
+
@@ -1472,77 +1582,90 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri value={newInvestment.planName} onChange={(e) => setNewInvestment({ ...newInvestment, planName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="e.g., Gold EV Fleet 2024" + placeholder="Plan name" />
- +
-
- -
- {availableBikesForAssignment.map(bike => ( - - ))} -
-
-
+
+ + +
+
+ + +
+
+ + +

= Qty × Base Price

+
+
+ +
setNewInvestment({ ...newInvestment, totalInvestment: Number(e.target.value), monthlyReturn: Math.round(Number(e.target.value) * newInvestment.expectedRoi / 100 / 12) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="0" + onChange={(e) => setNewInvestment({ ...newInvestment, totalInvestment: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-yellow-50" />
- - setNewInvestment({ ...newInvestment, monthlyReturn: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="0" - /> + + +
+
+ +
+
+ +
- - setNewInvestment({ ...newInvestment, expectedRoi: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="15" - /> + + +
+
+ + +
+
+ +
+

+ + FICO Share - Jaiben's Profit per Ride +

+

Profit sharing when bikes are rented to end customers

+
+
+ + +
+
+ + +
+
+ + +
@@ -1569,7 +1692,7 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
- +