feat: add investment success modal and redesign investor bike cards with plan-based styling
This commit is contained in:
@@ -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<any>(null);
|
||||
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
|
||||
const [showJournalModal, setShowJournalModal] = useState(false);
|
||||
const [selectedInvoice, setSelectedInvoice] = useState<any>(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' && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Assigned Bikes</h3>
|
||||
<button
|
||||
onClick={() => setShowAssignBikeModal(true)}
|
||||
className="py-2 px-3 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-1"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> Assign Bike
|
||||
</button>
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-800">Assigned Bikes</h3>
|
||||
<p className="text-sm text-slate-500">{assignedBikes.length} bikes across {investor.investments?.length || 0} investment plans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{assignedBikes.map(bike => (
|
||||
<div key={bike.id} className="bg-slate-50 rounded-xl p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-xl bg-white flex items-center justify-center shadow-sm">
|
||||
<Bike className="w-6 h-6 text-blue-600" />
|
||||
{assignedBikes.map(bike => {
|
||||
const investment = investor.investments?.find((inv: any) => inv.id === bike.investmentId);
|
||||
const planColors: Record<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, { label: string; color: string }> = {
|
||||
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<string, { bg: string; color: string }> = {
|
||||
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 (
|
||||
<Link
|
||||
key={bike.id}
|
||||
href={`/bikes?bike=${bike.id}`}
|
||||
className={`block bg-white rounded-xl border ${planBg[planType]} overflow-hidden hover:shadow-lg transition-all hover:scale-[1.02] cursor-pointer group`}
|
||||
>
|
||||
<div className={`h-2 bg-gradient-to-r ${planColors[planType]}`} />
|
||||
<div className="p-4">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${planColors[planType]} flex items-center justify-center shadow-sm`}>
|
||||
<Bike className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-semibold text-slate-800 truncate">{bike.model}</h4>
|
||||
<p className="text-sm text-slate-500">{bike.brand}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${status.bg} ${status.color} capitalize`}>
|
||||
{bike.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{bike.model}</p>
|
||||
<p className="text-xs text-slate-400">{bike.brand}</p>
|
||||
|
||||
<div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${planBadges[planType]} capitalize mb-3`}>
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
planType === 'silver' ? 'bg-slate-500' :
|
||||
planType === 'gold' ? 'bg-amber-500' :
|
||||
planType === 'platinum' ? 'bg-purple-500' : 'bg-blue-500'
|
||||
}`} />
|
||||
{planType} Plan • {investment?.planName || 'Investment'}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="bg-white/80 rounded-lg p-2">
|
||||
<p className="text-xs text-slate-400">Plate</p>
|
||||
<p className="text-sm font-medium text-slate-700 truncate">{bike.plateNumber.split('-').pop()}</p>
|
||||
</div>
|
||||
<div className="bg-white/80 rounded-lg p-2">
|
||||
<p className="text-xs text-slate-400">Location</p>
|
||||
<p className="text-sm font-medium text-slate-700 truncate">{bike.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/80 rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Rental Type</span>
|
||||
<span className={`text-xs font-semibold ${rentalInfo.color}`}>{rentalInfo.label}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Total Earnings</span>
|
||||
<span className="text-sm font-bold text-green-600">৳{bike.totalEarnings?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Battery</span>
|
||||
<span className={`text-xs font-medium ${
|
||||
bike.batteryLevel > 50 ? 'text-green-600' :
|
||||
bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600'
|
||||
}`}>{bike.batteryLevel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${bikeStatusColors[bike.status]}`}>
|
||||
{bike.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Plate</span>
|
||||
<span className="font-medium">{bike.plateNumber}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Location</span>
|
||||
<span className="font-medium">{bike.location}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Battery</span>
|
||||
<span className={`font-medium ${bike.batteryLevel > 50 ? 'text-green-600' : bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600'}`}>{bike.batteryLevel}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Purchase Price</span>
|
||||
<span className="font-medium text-purple-600">৳{bike.purchasePrice?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Total Earnings</span>
|
||||
<span className="font-medium text-green-600">৳{bike.totalEarnings?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{assignedBikes.length === 0 && (
|
||||
<div className="col-span-full text-center py-8 text-slate-400">
|
||||
<Bike className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p>No bikes assigned yet</p>
|
||||
<div className="col-span-full text-center py-12 border-2 border-dashed border-slate-200 rounded-xl">
|
||||
<Bike className="w-12 h-12 mx-auto mb-3 text-slate-300" />
|
||||
<p className="text-slate-500">No bikes assigned to this investor</p>
|
||||
<p className="text-sm text-slate-400 mt-1">Bikes will appear here once assigned to investments</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -970,57 +1014,92 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
{activeTab === 'investments' && (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-slate-800">Investment Plans</h3>
|
||||
<button onClick={() => setShowCreateInvestmentModal(true)} className="py-2 px-3 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-1">
|
||||
<Plus className="w-4 h-4" /> Add Investment
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-800">Investment Plans</h3>
|
||||
<p className="text-sm text-slate-500">Manage investment portfolios for this investor</p>
|
||||
</div>
|
||||
<button onClick={() => setShowCreateInvestmentModal(true)} className="py-2 px-4 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{investor.investments?.map((inv) => (
|
||||
<div key={inv.id} className="bg-slate-50 rounded-xl p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{inv.planName}</p>
|
||||
<p className="text-xs text-slate-400">{inv.planType} Plan</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{investor.investments?.map((inv) => {
|
||||
const planConfig: Record<string, { bg: string; border: string; icon: string }> = {
|
||||
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 (
|
||||
<div key={inv.id} className={`bg-white rounded-xl border ${style.border} overflow-hidden`}>
|
||||
<div className={`${style.bg} p-4 flex items-center justify-between`}>
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800">{inv.planName}</h4>
|
||||
<p className="text-sm text-slate-500 capitalize">{inv.planType} Plan</p>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${inv.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-200 text-slate-600'}`}>
|
||||
{inv.status}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${planColors[inv.planType]}`}>
|
||||
{inv.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs mb-3">
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Investment</p>
|
||||
<p className="font-medium text-purple-600">৳{inv.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Monthly Return</p>
|
||||
<p className="font-medium text-green-600">৳{inv.monthlyReturn.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Expected ROI</p>
|
||||
<p className="font-medium text-slate-700">{inv.expectedRoi}%</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Actual Earned</p>
|
||||
<p className="font-medium text-green-600">৳{inv.actualEarnings.toLocaleString()}</p>
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<p className="text-xs text-slate-500">Investment</p>
|
||||
<p className="font-bold text-slate-800">৳{inv.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-purple-50 rounded-lg p-3">
|
||||
<p className="text-xs text-purple-600">Total Return</p>
|
||||
<p className="font-bold text-purple-700">৳{inv.actualEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50 rounded-lg p-3 space-y-1.5">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Duration</span>
|
||||
<span className="font-medium">12 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Lock-in Period</span>
|
||||
<span className="font-medium">3 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Early Exit Penalty</span>
|
||||
<span className="font-medium text-red-500">10%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-slate-400">{inv.startDate} - {inv.endDate || 'Ongoing'}</span>
|
||||
<span className="capitalize text-slate-500">{inv.paymentMethod}</span>
|
||||
</div>
|
||||
<div className="pt-3 border-t border-slate-100">
|
||||
<p className="text-xs text-slate-400 mb-2">ID: #{inv.id?.slice(-6) || 'N/A'}</p>
|
||||
<div className="flex gap-2">
|
||||
<Link href={`/admin/investors/${investor.id}/investments/${inv.id}`} className="flex-1 py-2 text-sm font-medium text-slate-600 hover:bg-slate-50 rounded-lg border border-slate-200 text-center">
|
||||
View
|
||||
</Link>
|
||||
<button className="flex-1 py-2 text-sm font-medium text-investor hover:bg-investor/10 rounded-lg border border-investor/30">Statement</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-400">{inv.startDate} to {inv.endDate || 'Ongoing'}</span>
|
||||
<span className="capitalize">{inv.paymentMethod}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(!investor.investments || investor.investments.length === 0) && (
|
||||
<div className="col-span-full text-center py-8 text-slate-400">
|
||||
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p>No investments yet</p>
|
||||
<button className="mt-2 px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">
|
||||
Create First Investment
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{(!investor.investments || investor.investments.length === 0) && (
|
||||
<div className="text-center py-16 bg-slate-50 rounded-xl border-2 border-dashed border-slate-200">
|
||||
<TrendingUp className="w-12 h-12 mx-auto mb-3 text-slate-300" />
|
||||
<h4 className="font-semibold text-slate-600 mb-2">No Investments Yet</h4>
|
||||
<p className="text-slate-400 mb-4">Create your first investment plan for this investor</p>
|
||||
<button onClick={() => setShowCreateInvestmentModal(true)} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark">
|
||||
<Plus className="w-4 h-4 inline mr-1" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1455,15 +1534,46 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
|
||||
{showCreateInvestmentModal && (
|
||||
<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="bg-white rounded-xl shadow-xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h2 className="text-lg font-bold text-slate-800">Create New Investment</h2>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800">Create New Investment</h2>
|
||||
<p className="text-sm text-slate-500">Set up investment for {investor.name}</p>
|
||||
</div>
|
||||
<button onClick={() => setShowCreateInvestmentModal(false)} className="p-2 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 overflow-y-auto flex-1 space-y-4">
|
||||
<div className="p-5 overflow-y-auto flex-1 space-y-5">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-2 block">Select Plan Template</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[
|
||||
{ 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 => (
|
||||
<button
|
||||
key={plan.id}
|
||||
onClick={() => {
|
||||
setNewInvestment({
|
||||
...newInvestment,
|
||||
planName: plan.name,
|
||||
planType: plan.tier.toLowerCase() as any,
|
||||
totalInvestment: plan.evBasePrice * plan.minQuantity,
|
||||
monthlyReturn: 0
|
||||
});
|
||||
}}
|
||||
className={`p-4 rounded-lg border-2 text-left transition-all ${newInvestment.planName === plan.name ? 'border-investor bg-investor/5' : 'border-slate-200 hover:border-investor/50'}`}
|
||||
>
|
||||
<p className="font-semibold text-slate-800">{plan.name}</p>
|
||||
<p className="text-xs text-slate-500">{plan.evBasePrice.toLocaleString()} × {plan.minQuantity} bikes</p>
|
||||
<p className="text-sm text-slate-600 mt-1">Duration: {plan.duration} months</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Name *</label>
|
||||
@@ -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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Type *</label>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Type</label>
|
||||
<select
|
||||
value={newInvestment.planType}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, planType: e.target.value as any, expectedRoi: e.target.value === 'silver' ? 12 : e.target.value === 'gold' ? 15 : e.target.value === 'platinum' ? 18 : 20 })}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, planType: e.target.value as any })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="silver">Silver (12% ROI)</option>
|
||||
<option value="gold">Gold (15% ROI)</option>
|
||||
<option value="platinum">Platinum (18% ROI)</option>
|
||||
<option value="diamond">Diamond (20% ROI)</option>
|
||||
<option value="silver">Silver</option>
|
||||
<option value="gold">Gold</option>
|
||||
<option value="platinum">Platinum</option>
|
||||
<option value="diamond">Diamond</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Bikes</label>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-32 overflow-y-auto border border-slate-200 rounded-lg p-2">
|
||||
{availableBikesForAssignment.map(bike => (
|
||||
<label key={bike.id} className="flex items-center gap-2 p-2 hover:bg-slate-50 rounded cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={newInvestment.selectedBikeIds.includes(bike.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setNewInvestment({ ...newInvestment, selectedBikeIds: [...newInvestment.selectedBikeIds, bike.id] });
|
||||
} else {
|
||||
setNewInvestment({ ...newInvestment, selectedBikeIds: newInvestment.selectedBikeIds.filter(id => id !== bike.id) });
|
||||
}
|
||||
}}
|
||||
className="rounded text-investor"
|
||||
/>
|
||||
<span className="text-sm">{bike.model} ({bike.plateNumber})</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">EV Base Price (৳)</label>
|
||||
<input type="number" value={200000} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Minimum Quantity (Bikes)</label>
|
||||
<input type="number" value={1} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Min Investment (৳)</label>
|
||||
<input type="number" value={200000} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
<p className="text-xs text-slate-400 mt-1">= Qty × Base Price</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Investment Amount (৳) *</label>
|
||||
<input
|
||||
type="number"
|
||||
value={newInvestment.totalInvestment}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Monthly Return (৳)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={newInvestment.monthlyReturn}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, monthlyReturn: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="0"
|
||||
/>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Max Investment (৳)</label>
|
||||
<input type="number" value={1000000} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Duration (Months)</label>
|
||||
<input type="number" value={12} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Expected ROI (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={newInvestment.expectedRoi}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, expectedRoi: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="15"
|
||||
/>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Lock-in Period (Months)</label>
|
||||
<input type="number" value={3} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Early Exit Penalty (%)</label>
|
||||
<input type="number" value={10} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
FICO Share - Jaiben's Profit per Ride
|
||||
</h4>
|
||||
<p className="text-xs text-amber-600 mb-3">Profit sharing when bikes are rented to end customers</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Single Rent (%)</label>
|
||||
<input type="number" value={45} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Rent to Own (%)</label>
|
||||
<input type="number" value={55} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Share an EV (%)</label>
|
||||
<input type="number" value={60} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1569,7 +1692,7 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method *</label>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method</label>
|
||||
<select
|
||||
value={newInvestment.paymentMethod}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, paymentMethod: e.target.value as any })}
|
||||
@@ -1594,72 +1717,66 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Notes</label>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Description</label>
|
||||
<textarea
|
||||
value={newInvestment.notes}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, notes: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
rows={2}
|
||||
placeholder="Additional notes..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
const getDebitAccountDisplay = (method: string) => {
|
||||
switch (method) {
|
||||
case 'bank': return 'Bank - City Bank (1200)';
|
||||
case 'cash': return 'Cash in Hand (1100)';
|
||||
case 'mobile': return 'bKash Business (1300)';
|
||||
case 'cheque': return 'Cheque Receivable (1410)';
|
||||
default: return 'Bank - City Bank (1200)';
|
||||
}
|
||||
};
|
||||
const debitDisplay = getDebitAccountDisplay(newInvestment.paymentMethod);
|
||||
|
||||
return (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-green-600" />
|
||||
<h4 className="font-semibold text-green-800">Auto-Journal Entry</h4>
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
|
||||
<h4 className="font-semibold text-green-800 mb-3 flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Auto-Journal Entry
|
||||
</h4>
|
||||
<div className="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div className="flex items-center justify-between text-sm mb-2">
|
||||
<span className="text-slate-500">Date:</span>
|
||||
<span className="font-medium">{newInvestment.startDate || 'Not set'}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm mb-3">
|
||||
<span className="text-slate-500">Reference:</span>
|
||||
<span className="font-medium">{newInvestment.transactionReference || `INV/${new Date().getFullYear()}/auto`}</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg border border-green-200">
|
||||
<div>
|
||||
<p className="text-xs text-green-600 font-medium uppercase">Debit (Dr)</p>
|
||||
<p className="font-medium text-slate-800">
|
||||
{newInvestment.paymentMethod === 'bank' ? 'Bank - City Bank' : newInvestment.paymentMethod === 'cash' ? 'Cash in Hand' : 'bKash Business'} ({newInvestment.paymentMethod === 'bank' ? '1200' : newInvestment.paymentMethod === 'cash' ? '1100' : '1300'})
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="text-sm text-green-700 space-y-2">
|
||||
<p className="text-xs text-green-600 uppercase font-medium mb-2">Double EntryAccounting</p>
|
||||
<div className="flex items-center justify-between bg-white rounded p-2 border border-green-100">
|
||||
<div>
|
||||
<p className="font-medium">Debit (Dr)</p>
|
||||
<p className="text-xs text-green-600">{debitDisplay}</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-8 h-8 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600">▼</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-1">
|
||||
<div className="w-6 h-6 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between bg-white rounded p-2 border border-green-100">
|
||||
<div>
|
||||
<p className="font-medium">Credit (Cr)</p>
|
||||
<p className="text-xs text-green-600">Investor Liabilities (2200)</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="mt-3 pt-2 border-t border-green-200">
|
||||
<p className="text-xs text-green-600">Transaction Ref: Auto Generate</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<div>
|
||||
<p className="text-xs text-blue-600 font-medium uppercase">Credit (Cr)</p>
|
||||
<p className="font-medium text-slate-800">Investor Liabilities (2200)</p>
|
||||
</div>
|
||||
<p className="font-bold text-blue-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowCreateInvestmentModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button onClick={() => setShowCreateInvestmentModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateInvestment}
|
||||
disabled={!newInvestment.planName || !newInvestment.totalInvestment}
|
||||
className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark disabled:opacity-50 flex items-center gap-2"
|
||||
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" /> Create Investment
|
||||
<Plus className="w-4 h-4" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2015,6 +2132,90 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showInvestmentSuccessModal && lastCreatedInvestment && (
|
||||
<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-lg">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between bg-green-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<Check className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-green-800">Investment Created!</h2>
|
||||
<p className="text-sm text-green-600">Auto-journal entry generated</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => setShowInvestmentSuccessModal(false)} className="p-2 hover:bg-green-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-green-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-4">
|
||||
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Investment ID</span>
|
||||
<span className="text-sm font-medium">#{lastCreatedInvestment.id.slice(-8)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Transaction Ref</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.transactionId}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Amount</span>
|
||||
<span className="text-sm font-bold text-green-600">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Plan</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.planName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Date</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.startDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
|
||||
<h4 className="text-sm font-semibold text-green-800 mb-3 flex items-center gap-2">
|
||||
<FileText className="w-4 h-4" />
|
||||
Auto-Journal Entry
|
||||
</h4>
|
||||
<div className="bg-white rounded-lg p-3 border border-green-100">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-2 bg-green-50 rounded border border-green-200">
|
||||
<div>
|
||||
<p className="text-xs text-green-600 font-medium uppercase">Debit (Dr)</p>
|
||||
<p className="text-sm font-medium text-slate-800">{lastCreatedInvestment.debitAccount?.name}</p>
|
||||
<p className="text-xs text-slate-400">{lastCreatedInvestment.debitAccount?.code}</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-6 h-6 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-2 bg-blue-50 rounded border border-blue-200">
|
||||
<div>
|
||||
<p className="text-xs text-blue-600 font-medium uppercase">Credit (Cr)</p>
|
||||
<p className="text-sm font-medium text-slate-800">Investor Liabilities</p>
|
||||
<p className="text-xs text-slate-400">2200</p>
|
||||
</div>
|
||||
<p className="font-bold text-blue-700">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowInvestmentSuccessModal(false)} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 flex items-center gap-2">
|
||||
<Check className="w-4 h-4" /> Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user