feat: implement dynamic payment tracking with local storage and update transaction UI for battery investments
This commit is contained in:
@@ -344,10 +344,61 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
notes: ''
|
notes: ''
|
||||||
});
|
});
|
||||||
const [investmentPayments, setInvestmentPayments] = useState<any[]>([
|
const [investmentPayments, setInvestmentPayments] = useState<any[]>(() => {
|
||||||
|
if (!investment) return [];
|
||||||
|
const isBatteryPlan = investment.assetType === 'battery' || investment.planName?.toLowerCase().includes('battery');
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const stored = localStorage.getItem(`jaiben_payments_${investmentId}`);
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(stored);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBatteryPlan) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: `pay_${investmentId}`,
|
||||||
|
date: investment.startDate || '2024-02-01',
|
||||||
|
amount: investment.totalInvestment,
|
||||||
|
paymentMethod: investment.paymentMethod || 'bank',
|
||||||
|
transactionRef: investment.transactionId || 'TXN-BAT-001',
|
||||||
|
status: 'completed',
|
||||||
|
notes: 'Full Payment'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (investmentId === 'ip2') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'pay1_ip2',
|
||||||
|
date: '2024-01-20',
|
||||||
|
amount: investment.totalInvestment,
|
||||||
|
paymentMethod: 'mobile',
|
||||||
|
transactionRef: 'TXN-002',
|
||||||
|
status: 'completed',
|
||||||
|
notes: 'Full Payment'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default for ip1 or others
|
||||||
|
return [
|
||||||
{ id: 'pay1', date: '2024-01-15', amount: 50000, paymentMethod: 'bank', transactionRef: 'TXN-001', status: 'completed', notes: 'First installment' },
|
{ id: 'pay1', date: '2024-01-15', amount: 50000, paymentMethod: 'bank', transactionRef: 'TXN-001', status: 'completed', notes: 'First installment' },
|
||||||
{ id: 'pay2', date: '2024-01-20', amount: 35000, paymentMethod: 'mobile', transactionRef: 'TXN-002', status: 'completed', notes: 'Second installment' },
|
{ id: 'pay2', date: '2024-01-20', amount: 35000, paymentMethod: 'mobile', transactionRef: 'TXN-002', status: 'completed', notes: 'Second installment' },
|
||||||
]);
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(`jaiben_payments_${investmentId}`, JSON.stringify(investmentPayments));
|
||||||
|
}
|
||||||
|
}, [investmentPayments, investmentId]);
|
||||||
|
|
||||||
if (!investor || !investment) {
|
if (!investor || !investment) {
|
||||||
return (
|
return (
|
||||||
@@ -622,12 +673,7 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
>
|
>
|
||||||
Transactions
|
Transactions
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('payments')}
|
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'payments' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
|
||||||
>
|
|
||||||
Payments
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('pnl')}
|
onClick={() => setActiveTab('pnl')}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'pnl' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'pnl' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
||||||
@@ -762,33 +808,37 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-4 py-3 text-sm text-slate-500">2024-01-15</td>
|
<td className="px-4 py-3 text-sm text-slate-500">{investment.startDate || '2024-01-15'}</td>
|
||||||
<td className="px-4 py-3 text-sm">Investment Funded</td>
|
<td className="px-4 py-3 text-sm">Investment Funded</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Credit</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Credit</span></td>
|
||||||
<td className="px-4 py-3 text-sm text-right font-medium text-green-600">+৳85,000</td>
|
<td className="px-4 py-3 text-sm text-right font-medium text-green-600">+৳{investment.totalInvestment.toLocaleString()}</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{investment.status === 'active' && (
|
||||||
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-4 py-3 text-sm text-slate-500">2024-02-15</td>
|
<td className="px-4 py-3 text-sm text-slate-500">2024-02-15</td>
|
||||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳{(investment.monthlyReturn || (isBattery ? 4500 : 1700)).toLocaleString()}</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-4 py-3 text-sm text-slate-500">2024-03-15</td>
|
<td className="px-4 py-3 text-sm text-slate-500">2024-03-15</td>
|
||||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳{(investment.monthlyReturn || (isBattery ? 4500 : 1700)).toLocaleString()}</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-4 py-3 text-sm text-slate-500">2024-04-15</td>
|
<td className="px-4 py-3 text-sm text-slate-500">2024-04-15</td>
|
||||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳{(investment.monthlyReturn || (isBattery ? 4500 : 1700)).toLocaleString()}</td>
|
||||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -825,12 +875,14 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="font-semibold text-slate-800">Payment History</h4>
|
<h4 className="font-semibold text-slate-800">Payment History</h4>
|
||||||
|
{!isBattery && (investment.totalInvestment - investmentPayments.reduce((sum, p) => sum + p.amount, 0)) > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowPartialPaymentModal(true)}
|
onClick={() => setShowPartialPaymentModal(true)}
|
||||||
className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg flex items-center gap-1 hover:bg-investor-dark"
|
className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg flex items-center gap-1 hover:bg-investor-dark"
|
||||||
>
|
>
|
||||||
<Plus className="w-3 h-3" /> Record Payment
|
<Plus className="w-3 h-3" /> Record Payment
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-slate-50 rounded-xl p-4">
|
<div className="bg-slate-50 rounded-xl p-4">
|
||||||
@@ -938,24 +990,6 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
<button onClick={() => setShowAssignBatteryModal(true)} className="px-3 py-1.5 text-xs bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg flex items-center gap-1 hover:bg-emerald-100 transition-colors">
|
<button onClick={() => setShowAssignBatteryModal(true)} className="px-3 py-1.5 text-xs bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg flex items-center gap-1 hover:bg-emerald-100 transition-colors">
|
||||||
<Plus className="w-3 h-3" /> Assign Existing
|
<Plus className="w-3 h-3" /> Assign Existing
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => {
|
|
||||||
setRegisterBatteryForm({
|
|
||||||
serialNumber: `SN-${new Date().getFullYear()}-${Math.floor(10000 + Math.random() * 90000)}`,
|
|
||||||
brand: 'BYD',
|
|
||||||
model: 'Li-Ion 60V50Ah',
|
|
||||||
type: 'lithium-ion',
|
|
||||||
capacity: 50,
|
|
||||||
voltage: 60,
|
|
||||||
purchasePrice: 45000,
|
|
||||||
deposit: 5000,
|
|
||||||
rentPrice: 150,
|
|
||||||
investorShare: 100,
|
|
||||||
investedAmount: investment.totalInvestment / 10
|
|
||||||
});
|
|
||||||
setShowRegisterBatteryModal(true);
|
|
||||||
}} className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg flex items-center gap-1 hover:bg-investor-dark transition-colors">
|
|
||||||
<Plus className="w-3 h-3" /> Register & Assign New
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{assignedBatteries.length > 0 ? (
|
{assignedBatteries.length > 0 ? (
|
||||||
@@ -1065,20 +1099,6 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
|
|||||||
<button onClick={() => setShowAddBikeModal(true)} className="px-3 py-1.5 text-xs bg-blue-50 text-blue-700 border border-blue-200 rounded-lg flex items-center gap-1 hover:bg-blue-100 transition-colors">
|
<button onClick={() => setShowAddBikeModal(true)} className="px-3 py-1.5 text-xs bg-blue-50 text-blue-700 border border-blue-200 rounded-lg flex items-center gap-1 hover:bg-blue-100 transition-colors">
|
||||||
<Plus className="w-3 h-3" /> Assign Existing
|
<Plus className="w-3 h-3" /> Assign Existing
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => {
|
|
||||||
setRegisterBikeForm({
|
|
||||||
plateNumber: `DHAKA-METRO-HA-${Math.floor(1000 + Math.random() * 9000)}`,
|
|
||||||
brand: 'Etron',
|
|
||||||
model: 'ET50',
|
|
||||||
currentRent: 150,
|
|
||||||
location: 'Banani',
|
|
||||||
purchasePrice: 200000,
|
|
||||||
rentalType: 'single_rent'
|
|
||||||
});
|
|
||||||
setShowRegisterBikeModal(true);
|
|
||||||
}} className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg flex items-center gap-1 hover:bg-investor-dark transition-colors">
|
|
||||||
<Plus className="w-3 h-3" /> Register & Assign New
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{assignedBikes.length > 0 ? (
|
{assignedBikes.length > 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user