From cab01a25ac6cc0e1874da20092bf1cb1aca1d5de Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Fri, 15 May 2026 03:10:37 +0600 Subject: [PATCH] feat: add partial payment support for investments and update mock data fields --- src/app/admin/investors/[id]/page.tsx | 70 +++- src/app/investor/plans/new/page.tsx | 395 ------------------- src/app/investor/plans/page.tsx | 529 ++++++++++++++++++++------ src/data/mockData.ts | 5 +- 4 files changed, 480 insertions(+), 519 deletions(-) delete mode 100644 src/app/investor/plans/new/page.tsx diff --git a/src/app/admin/investors/[id]/page.tsx b/src/app/admin/investors/[id]/page.tsx index 273aa97..fb84a44 100644 --- a/src/app/admin/investors/[id]/page.tsx +++ b/src/app/admin/investors/[id]/page.tsx @@ -133,6 +133,8 @@ export default function InvestorDetailPage() { planType: 'gold' as 'silver' | 'gold' | 'platinum' | 'diamond', selectedBikeIds: [] as string[], totalInvestment: 0, + paidAmount: 0, + paymentOption: 'full' as 'full' | 'partial', monthlyReturn: 0, expectedRoi: 15, startDate: new Date().toISOString().split('T')[0], @@ -205,7 +207,7 @@ export default function InvestorDetailPage() { sourceType: 'investor_funding', createdAt: new Date().toISOString(), type: 'investment', - amount: newInvestment.totalInvestment, + amount: newInvestment.paidAmount, paymentMethod: newInvestment.paymentMethod }; @@ -230,6 +232,8 @@ export default function InvestorDetailPage() { planType: 'gold', selectedBikeIds: [], totalInvestment: 0, + paidAmount: 0, + paymentOption: 'full', monthlyReturn: 0, expectedRoi: 15, startDate: new Date().toISOString().split('T')[0], @@ -2229,6 +2233,8 @@ export default function InvestorDetailPage() { planName: plan.name, planType: plan.tier.toLowerCase() as any, totalInvestment: plan.evBasePrice * plan.minQuantity, + paidAmount: plan.evBasePrice * plan.minQuantity, + paymentOption: 'full', monthlyReturn: 0 }); }} @@ -2290,7 +2296,14 @@ export default function InvestorDetailPage() { setNewInvestment({ ...newInvestment, totalInvestment: Number(e.target.value) })} + onChange={(e) => { + const val = Number(e.target.value); + setNewInvestment({ + ...newInvestment, + totalInvestment: val, + paidAmount: newInvestment.paymentOption === 'full' ? val : Math.max(val * 0.5, newInvestment.paidAmount) + }); + }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-yellow-50" /> @@ -2416,7 +2429,7 @@ export default function InvestorDetailPage() { {newInvestment.paymentMethod === 'bank' ? 'Bank - City Bank' : newInvestment.paymentMethod === 'cash' ? 'Cash in Hand' : 'bKash Business'} ({newInvestment.paymentMethod === 'bank' ? '1200' : newInvestment.paymentMethod === 'cash' ? '1100' : '1300'})

-

৳{newInvestment.totalInvestment.toLocaleString()}

+

৳{newInvestment.paidAmount.toLocaleString()}

@@ -2428,11 +2441,60 @@ export default function InvestorDetailPage() {

Credit (Cr)

Investor Liabilities (2200)

-

৳{newInvestment.totalInvestment.toLocaleString()}

+

৳{newInvestment.paidAmount.toLocaleString()}

+ +
+ +
+ + + +
+ + {newInvestment.paymentOption === 'partial' && ( +
+ +
+ + setNewInvestment({ ...newInvestment, paidAmount: Number(e.target.value) })} + className="w-full pl-8 pr-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-investor outline-none text-sm" + /> +
+

+ Remaining ৳{(newInvestment.totalInvestment - newInvestment.paidAmount).toLocaleString()} will be recorded as pending. +

+
+ )} +
diff --git a/src/app/investor/plans/new/page.tsx b/src/app/investor/plans/new/page.tsx deleted file mode 100644 index 533dc58..0000000 --- a/src/app/investor/plans/new/page.tsx +++ /dev/null @@ -1,395 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { - ArrowLeft, Zap, Shield, TrendingUp, Check, Info, - Calendar, DollarSign, CreditCard, FileText, ChevronRight, - AlertCircle, ArrowDown, Wallet, Clock, Activity -} from 'lucide-react'; -import toast from 'react-hot-toast'; - -const PLAN_TEMPLATES = [ - { - id: '1bike', - name: '1 Bike Plan', - description: 'Investment plan for 1 bike - perfect for small investors', - evBasePrice: 200000, - minQty: 1, - minInvestment: 200000, - maxInvestment: 1000000, - duration: 12, - lockIn: 3, - exitPenalty: 10, - ficoShare: { single: 45, own: 55, ev: 60 }, - icon: Zap, - color: 'bg-blue-600', - lightColor: 'bg-blue-50 text-blue-700' - }, - { - id: '5bike', - name: '5 Bike Plan', - description: 'Perfect for established investors looking for better returns', - evBasePrice: 180000, - minQty: 5, - minInvestment: 900000, - maxInvestment: 5000000, - duration: 24, - lockIn: 6, - exitPenalty: 15, - ficoShare: { single: 40, own: 50, ev: 55 }, - icon: TrendingUp, - color: 'bg-purple-600', - lightColor: 'bg-purple-50 text-purple-700' - } -]; - -export default function NewInvestmentPage() { - const router = useRouter(); - const [step, setStep] = useState<'select' | 'form'>('select'); - const [selectedTemplate, setSelectedTemplate] = useState(null); - - const [formData, setFormData] = useState({ - planName: '', - planType: 'Gold', - investmentAmount: 0, - initialPayment: 0, - startDate: new Date().toISOString().split('T')[0], - endDate: '', - paymentMethod: 'Bank Transfer', - transactionRef: '', - description: '' - }); - - const handleSelectTemplate = (template: typeof PLAN_TEMPLATES[0]) => { - setSelectedTemplate(template); - setFormData({ - ...formData, - planName: template.name, - investmentAmount: template.minInvestment, - initialPayment: Math.floor(template.minInvestment * 0.5) // Default 50% initial - }); - setStep('form'); - }; - - const handleCreateInvestment = (e: React.FormEvent) => { - e.preventDefault(); - if (formData.investmentAmount < (selectedTemplate?.minInvestment || 0)) { - toast.error(`Minimum investment for this plan is ৳${selectedTemplate?.minInvestment.toLocaleString()}`); - return; - } - toast.success('Investment request created successfully! Admin will review and assign bikes.'); - router.push('/investor/plans'); - }; - - return ( -
-
- -
-

- - {step === 'select' ? 'Select Investment Plan' : 'Configure New Investment'} -

-

- {step === 'select' ? 'Choose a template to start' : `Set up investment under ${selectedTemplate?.name}`} -

-
-
- - {step === 'select' && ( -
- {PLAN_TEMPLATES.map((plan) => { - const Icon = plan.icon; - return ( -
handleSelectTemplate(plan)} - > -
- -
-

{plan.name}

-

{plan.description}

- -
-
- Min Investment - ৳{plan.minInvestment.toLocaleString()} -
-
- EV Base Price - ৳{plan.evBasePrice.toLocaleString()} -
-
- Duration - {plan.duration} Months -
-
- - -
- ); - })} -
- )} - - {step === 'form' && selectedTemplate && ( -
-
-
- {/* Core Configuration */} -
-

- Plan Configuration -

- -
-
- - setFormData({...formData, planName: e.target.value})} - className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-investor/20 focus:border-investor outline-none transition-all font-medium" - placeholder="e.g. My First Bike" - required - /> -
-
- - -
-
- -
-
-

EV Base Price

-

৳{selectedTemplate.evBasePrice.toLocaleString()}

-
-
-

Min Qty

-

{selectedTemplate.minQty} Bike(s)

-
-
-

Min Invest

-

৳{selectedTemplate.minInvestment.toLocaleString()}

-
-
-

Max Invest

-

৳{selectedTemplate.maxInvestment.toLocaleString()}

-
-
- -
-
- - setFormData({...formData, investmentAmount: Number(e.target.value)})} - className="w-full px-4 py-3 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-investor/20 focus:border-investor outline-none transition-all font-extrabold" - required - /> -
-
- - setFormData({...formData, initialPayment: Number(e.target.value)})} - className="w-full px-4 py-3 border border-investor rounded-xl text-sm focus:ring-2 focus:ring-investor/20 outline-none transition-all font-extrabold text-investor" - placeholder="Amount to pay now" - required - /> -

Pay part of your investment now to confirm

-
-
-
- - {/* Profit Sharing Policy */} -
-

- FICO Share - Jaiben's Profit per Ride -

-

Profit sharing when bikes are rented to end customers

- -
-
-

Single Rent

-

{selectedTemplate.ficoShare.single}%

-
-
-

Rent to Own

-

{selectedTemplate.ficoShare.own}%

-
-
-

Share an EV

-

{selectedTemplate.ficoShare.ev}%

-
-
-
- - {/* Auto-Journal Entry Visualization */} -
-
-

- Auto-Journal Entry (Draft) -

- -
-
- Account Details - Amount (৳) -
- -
-
-

Debit (Dr)

-

Bank - City Bank

-

CODE: 1200

-
-

৳{formData.investmentAmount.toLocaleString()}

-
- -
-
- -
-
- -
-
-

Credit (Cr)

-

Investor Liabilities

-

CODE: 2200

-
-

৳{formData.investmentAmount.toLocaleString()}

-
- -
-

Reference: INV/{new Date().getFullYear()}/AUTO-{Math.random().toString(36).substring(7).toUpperCase()}

-

Status: Draft - On Creation

-
-
-
-
- -
- {/* Time & Period */} -
-

- Schedule -

-
-
- - setFormData({...formData, startDate: e.target.value})} - className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-investor/20 outline-none font-bold" - required - /> -
-
- - setFormData({...formData, endDate: e.target.value})} - className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-investor/20 outline-none font-bold" - /> -
-
- -

- Duration is fixed at {selectedTemplate.duration} months with a {selectedTemplate.lockIn} month lock-in period as per template. -

-
-
-
- - {/* Payment Details */} -
-

- Payment -

-
-
- - -
-
- - setFormData({...formData, transactionRef: e.target.value})} - className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-investor/20 outline-none font-bold" - placeholder="Auto-generated if empty" - /> -
-
-
- Pay Now - ৳{formData.initialPayment.toLocaleString()} -
-
- Balance Due - ৳{(formData.investmentAmount - formData.initialPayment).toLocaleString()} -
-
-
-
- - {/* Actions */} -
- - -
-
-
- - )} -
- ); -} diff --git a/src/app/investor/plans/page.tsx b/src/app/investor/plans/page.tsx index 2244493..f495079 100644 --- a/src/app/investor/plans/page.tsx +++ b/src/app/investor/plans/page.tsx @@ -2,140 +2,433 @@ import { useState } from 'react'; import Link from 'next/link'; -import { Target, ArrowRight, Zap, TrendingUp, CreditCard, Plus, FileText, ChevronRight, Wallet, Clock, Percent } from 'lucide-react'; +import { Target, Plus, Zap, ChevronRight, ArrowRight, Edit, Trash2, Eye, TrendingUp, X, CreditCard } from 'lucide-react'; import { investors } from '@/data/mockData'; import toast from 'react-hot-toast'; export default function MyInvestmentsPage() { - const investor = investors[0]; // mock logged-in investor + const investor = investors[0]; + const [showCreateModal, setShowCreateModal] = useState(false); + const [selectedTemplate, setSelectedTemplate] = useState(null); + const [newInvestment, setNewInvestment] = useState({ + planName: '', planType: 'gold', totalInvestment: 0, initialPayment: 0, paymentType: 'full', + startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: '' + }); + + const PLAN_TEMPLATES = [ + { id: '1bike', name: '1 Bike Plan', tier: 'Standard', evBasePrice: 200000, minQuantity: 1, duration: 12, maxInvestment: 1000000 }, + { id: '5bike', name: '5 Bike Plan', tier: 'Premium', evBasePrice: 180000, minQuantity: 5, duration: 24, maxInvestment: 5000000 }, + { id: '10bike', name: '10 Bike Plan', tier: 'Enterprise', evBasePrice: 170000, minQuantity: 10, duration: 36, maxInvestment: 10000000 }, + ]; + + 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 handleCreate = () => { + if (!newInvestment.planName || !newInvestment.totalInvestment) { + toast.error('Please fill all required fields'); + return; + } + if (newInvestment.paymentType === 'partial' && newInvestment.initialPayment < newInvestment.totalInvestment * 0.5) { + toast.error('Initial payment must be at least 50% of investment amount'); + return; + } + toast.success(newInvestment.paymentType === 'partial' + ? `Investment created! Initial: ৳${newInvestment.initialPayment.toLocaleString()}, Balance: ৳${(newInvestment.totalInvestment - newInvestment.initialPayment).toLocaleString()}` + : 'Investment created successfully!' + ); + setShowCreateModal(false); + setSelectedTemplate(null); + setNewInvestment({ planName: '', planType: 'gold', totalInvestment: 0, initialPayment: 0, paymentType: 'full', startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: '' }); + }; return (
-
-
-

- My Investments -

-

Manage your active portfolios and track your earnings.

-
- - - New Investment - -
- - {/* Portfolio Summary */} -
-
-

- Portfolio Overview -

-
-
-
-
-
- -
-

Total Invested

-
-

৳{investor.totalInvested.toLocaleString()}

-
-
-
-
- -
-

Total Returns

-
-

৳{investor.totalEarnings.toLocaleString()}

-
-
-
-
- -
-

Active Bikes

-
-

{investor.activeBikes} Units

+ {/* Header */} +
+
+
+

+ My Investments +

+

Manage your active portfolios and track your earnings.

+
- {/* My Active Investments List */} -
-
-

- Active Investment Plans -

- - {investor.investments?.length || 0} Total - + {/* Investment Plans Cards */} +
+
+
+

Investment Plans

+

Manage investment portfolios for this investor

+
-
- - - - - - - - - - - - {investor.investments && investor.investments.length > 0 ? investor.investments.map((inv) => ( - - - - - - - - )) : ( - - - - )} - -
Investment PlanCapital InvestedActual ReturnsStatusAction
-

{inv.planName}

-
- {inv.planType} - Started: {inv.startDate} + +
+ {investor.investments?.map((inv) => { + const style = planConfig[inv.planType] || planConfig.gold; + return ( +
+
+
+

{inv.planName}

+

{inv.planType} Plan

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

Investment

+

৳{inv.totalInvestment.toLocaleString()}

-
-

৳{inv.totalInvestment.toLocaleString()}

-
-

৳{inv.actualEarnings.toLocaleString()}

-

+{inv.expectedRoi}% ROI

-
- -
- {inv.status} - -
- - View Details - -
-
- -

No active investments found.

- - Start your first investment +
+

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
-
+
+
+
+ ); + })}
+ + {(!investor.investments || investor.investments.length === 0) && ( +
+ +

No Investments Yet

+

Create your first investment plan

+ +
+ )}
+ + {/* Create Investment Modal */} + {showCreateModal && ( +
+
+
+
+

+ Create New Investment +

+

Set up investment for {investor.name}

+
+ +
+ +
+ {!selectedTemplate ? ( + <> +
+ +
+ {PLAN_TEMPLATES.map(plan => ( + + ))} +
+
+ + ) : ( + <> +
+ + | + {selectedTemplate.name} +
+ +
+
+ + setNewInvestment({ ...newInvestment, planName: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Plan name" + /> +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +

= Qty × Base Price

+
+
+ +
+
+ + 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" + /> +
+
+ + +
+
+ +
+

+ Payment Options +

+
+ + +
+ {newInvestment.paymentType === 'partial' && ( +
+ + { + const val = Number(e.target.value); + if (val >= newInvestment.totalInvestment * 0.5 && val <= newInvestment.totalInvestment) { + setNewInvestment({ ...newInvestment, initialPayment: val }); + } + }} + className="w-full px-3 py-2 border border-investor rounded-lg text-sm bg-white" + /> +

Balance: ৳{(newInvestment.totalInvestment - newInvestment.initialPayment).toLocaleString()}

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

+ FICO Share - Profit per Ride +

+

Profit sharing when bikes are rented to end customers

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + setNewInvestment({ ...newInvestment, startDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + setNewInvestment({ ...newInvestment, endDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ +
+
+ + +
+
+ + setNewInvestment({ ...newInvestment, transactionReference: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Auto-generated if empty" /> +
+
+ +
+ +