feat: implement persistent local storage state for batteries, bikes, and investors with dynamic patching logic

This commit is contained in:
sazzadulalambd
2026-05-19 18:11:35 +06:00
parent 2a891df398
commit cd6d6e4386
3 changed files with 1615 additions and 222 deletions

View File

@@ -75,9 +75,49 @@ export default function InvestorDetailPage() {
const router = useRouter();
const investorId = params.id as string;
const [investors, setInvestors] = useState<Investor[]>(initialInvestors);
const [investors, setInvestors] = useState<Investor[]>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('jaiben_investors');
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
console.error(e);
}
}
}
return initialInvestors;
});
const investor = investors.find(i => i.id === investorId);
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('jaiben_investors', JSON.stringify(investors));
}
}, [investors]);
const [bikes, setBikes] = useState<any[]>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('jaiben_bikes');
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
console.error(e);
}
}
}
return initialBikes;
});
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('jaiben_bikes', JSON.stringify(bikes));
}
}, [bikes]);
const assignedBikes = bikes.filter(b => b.investorId === investorId);
const [settings, setSettings] = useState<any>(null);
useEffect(() => {
@@ -93,13 +133,23 @@ export default function InvestorDetailPage() {
}
}, []);
const assignedBikes = initialBikes.filter(b => b.investorId === investorId);
// 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 [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false);
const [registerBikeForm, setRegisterBikeForm] = useState({
plateNumber: '',
brand: 'Etron',
model: 'ET50',
currentRent: 150,
location: 'Banani',
purchasePrice: 200000,
rentalType: 'single_rent',
investmentId: ''
});
const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false);
const [showInvestmentSuccessModal, setShowInvestmentSuccessModal] = useState(false);
const [lastCreatedInvestment, setLastCreatedInvestment] = useState<any>(null);
@@ -122,57 +172,232 @@ export default function InvestorDetailPage() {
const [newDoc, setNewDoc] = useState({ type: 'nid', number: '', url: '' });
const [editingMobileIndex, setEditingMobileIndex] = useState<number | null>(null);
const [batteries, setBatteries] = useState<any[]>([
{
id: 'BAT-001',
serialNumber: 'SN-2024-00001',
brand: 'EVE Energy',
model: 'Li-Ion 60V50Ah',
type: 'lithium-ion',
capacity: 50,
voltage: 60,
purchaseDate: '2024-01-15',
purchasePrice: 45000,
deposit: 5000,
rentPrice: 150,
investorId: investorId,
investorName: investor?.name || 'Md. Hasan Mahmud',
investorSharePercentage: 60,
investedAmount: 45000,
investmentId: 'ip1',
status: 'in-use',
currentSoc: 78,
health: 95,
cycleCount: 156
},
{
id: 'BAT-002',
serialNumber: 'SN-2024-00002',
brand: 'CATL',
model: 'LiFePO4 48V40Ah',
type: 'lifepo4',
capacity: 40,
voltage: 48,
purchaseDate: '2024-02-10',
purchasePrice: 38000,
deposit: 4000,
rentPrice: 120,
investorId: investorId,
investorName: investor?.name || 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 38000,
investmentId: 'ip2',
status: 'available',
currentSoc: 92,
health: 98,
cycleCount: 45
const [batteries, setBatteries] = useState<any[]>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('jaiben_batteries');
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
console.error(e);
}
}
}
]);
return [
{
id: 'BAT-001',
serialNumber: 'SN-2024-00001',
brand: 'EVE Energy',
model: 'Li-Ion 60V50Ah',
type: 'lithium-ion',
capacity: 50,
voltage: 60,
purchaseDate: '2024-01-15',
purchasePrice: 45000,
deposit: 5000,
rentPrice: 150,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 45000,
investmentId: 'ip3',
status: 'in-use',
currentSoc: 78,
health: 95,
cycleCount: 156
},
{
id: 'BAT-002',
serialNumber: 'SN-2024-00002',
brand: 'CATL',
model: 'LiFePO4 48V40Ah',
type: 'lifepo4',
capacity: 40,
voltage: 48,
purchaseDate: '2024-02-10',
purchasePrice: 38000,
deposit: 4000,
rentPrice: 120,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 38000,
investmentId: 'ip3',
status: 'available',
currentSoc: 92,
health: 98,
cycleCount: 45
},
{
id: 'BAT-005',
serialNumber: 'SN-2024-00005',
brand: 'BYD',
model: 'Li-Ion 60V50Ah',
type: 'lithium-ion',
capacity: 50,
voltage: 60,
purchaseDate: '2024-02-15',
purchasePrice: 45000,
deposit: 5000,
rentPrice: 150,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 45000,
investmentId: 'ip3',
status: 'in-use',
currentSoc: 82,
health: 97,
cycleCount: 18
}
];
});
const [unassignedBatteries, setUnassignedBatteries] = useState<any[]>([
{ id: 'BAT-003', serialNumber: 'SN-2024-00003', brand: 'BYD', model: 'Li-Ion 72V60Ah', type: 'lithium-ion', capacity: 60, voltage: 72, purchasePrice: 52000, deposit: 6000, rentPrice: 180, status: 'available', currentSoc: 85, health: 97, cycleCount: 12 },
{ id: 'BAT-004', serialNumber: 'SN-2024-00004', brand: 'Panasonic', model: 'Li-Ion 60V40Ah', type: 'lithium-ion', capacity: 40, voltage: 60, purchasePrice: 41000, deposit: 4500, rentPrice: 130, status: 'available', currentSoc: 90, health: 99, cycleCount: 8 }
]);
const [unassignedBatteries, setUnassignedBatteries] = useState<any[]>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('jaiben_unassigned_batteries');
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
console.error(e);
}
}
}
return [
{ id: 'BAT-003', serialNumber: 'SN-2024-00003', brand: 'BYD', model: 'Li-Ion 72V60Ah', type: 'lithium-ion', capacity: 60, voltage: 72, purchasePrice: 52000, deposit: 6000, rentPrice: 180, status: 'available', currentSoc: 85, health: 97, cycleCount: 12 },
{ id: 'BAT-004', serialNumber: 'SN-2024-00004', brand: 'Panasonic', model: 'Li-Ion 60V40Ah', type: 'lithium-ion', capacity: 40, voltage: 60, purchasePrice: 41000, deposit: 4500, rentPrice: 130, status: 'available', currentSoc: 90, health: 99, cycleCount: 8 }
];
});
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('jaiben_batteries', JSON.stringify(batteries));
}
}, [batteries]);
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('jaiben_unassigned_batteries', JSON.stringify(unassignedBatteries));
}
}, [unassignedBatteries]);
// Patch local storage state to make sure inv1 has the Standard Battery Plan (ip3) and correct assigned batteries
useEffect(() => {
if (!investor) return;
const hasIp3 = investor.investments?.some(inv => inv.id === 'ip3');
if (!hasIp3) {
const updated = investors.map(i => {
if (i.id === investorId) {
const investments = i.investments || [];
return {
...i,
totalInvested: 300000,
investments: [
...investments.filter(inv => inv.id !== 'ip3'),
{
id: 'ip3',
investorId: investorId,
planName: 'Standard Battery Plan',
planType: 'silver' as const,
assetType: 'battery' as const,
batteryIds: ['BAT-001', 'BAT-002', 'BAT-005'],
totalInvestment: 150000,
monthlyReturn: 4500,
expectedRoi: 16,
actualEarnings: 9000,
startDate: '2024-02-01',
endDate: '2025-02-01',
status: 'active' as const,
paymentMethod: 'bank' as const,
transactionId: 'invt3',
createdAt: '2024-02-01'
}
]
};
}
return i;
});
setInvestors(updated);
}
}, [investors, investorId, investor]);
useEffect(() => {
const hasBat5 = batteries.some(b => b.id === 'BAT-005');
const hasIp3Assignment = batteries.some(b => b.id === 'BAT-001' && b.investmentId === 'ip3');
if (!hasBat5 || !hasIp3Assignment) {
const updated = [
{
id: 'BAT-001',
serialNumber: 'SN-2024-00001',
brand: 'EVE Energy',
model: 'Li-Ion 60V50Ah',
type: 'lithium-ion',
capacity: 50,
voltage: 60,
purchaseDate: '2024-01-15',
purchasePrice: 45000,
deposit: 5000,
rentPrice: 150,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 45000,
investmentId: 'ip3',
status: 'in-use',
currentSoc: 78,
health: 95,
cycleCount: 156
},
{
id: 'BAT-002',
serialNumber: 'SN-2024-00002',
brand: 'CATL',
model: 'LiFePO4 48V40Ah',
type: 'lifepo4',
capacity: 40,
voltage: 48,
purchaseDate: '2024-02-10',
purchasePrice: 38000,
deposit: 4000,
rentPrice: 120,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 38000,
investmentId: 'ip3',
status: 'available',
currentSoc: 92,
health: 98,
cycleCount: 45
},
{
id: 'BAT-005',
serialNumber: 'SN-2024-00005',
brand: 'BYD',
model: 'Li-Ion 60V50Ah',
type: 'lithium-ion',
capacity: 50,
voltage: 60,
purchaseDate: '2024-02-15',
purchasePrice: 45000,
deposit: 5000,
rentPrice: 150,
investorId: investorId,
investorName: 'Md. Hasan Mahmud',
investorSharePercentage: 100,
investedAmount: 45000,
investmentId: 'ip3',
status: 'in-use',
currentSoc: 82,
health: 97,
cycleCount: 18
},
...batteries.filter(b => b.id !== 'BAT-001' && b.id !== 'BAT-002' && b.id !== 'BAT-005')
];
setBatteries(updated);
}
}, [batteries, investorId]);
const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false);
const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
@@ -269,14 +494,84 @@ export default function InvestorDetailPage() {
);
}
const availableBikesForAssignment = initialBikes.filter(b => !b.investorId && b.status === 'available');
const availableBikesForAssignment = bikes.filter(b => !b.investorId && b.status === 'available');
const handleAssignBike = () => {
alert('Bike assignment functionality - would update bike investorId here');
if (!selectedBikeId) {
toast.error('Please select a bike');
return;
}
const bikeToAssign = bikes.find(b => b.id === selectedBikeId);
if (!bikeToAssign) return;
// Find first active EV Investment Plan of the investor to attach to
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
setBikes(prev => prev.map(b => {
if (b.id === selectedBikeId) {
return {
...b,
investorId: investorId,
investorName: investor.name,
investmentId: evInv?.id || 'ip1',
status: 'rented',
totalEarnings: b.totalEarnings || 0
};
}
return b;
}));
toast.success(`Bike ${bikeToAssign.model} (${bikeToAssign.plateNumber}) successfully assigned!`);
setShowAssignBikeModal(false);
setSelectedBikeId('');
};
const handleRegisterAndAssignBike = () => {
if (!registerBikeForm.plateNumber) {
toast.error('Please enter a plate number');
return;
}
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
const newBike = {
id: `BIKE-${Date.now()}`,
plateNumber: registerBikeForm.plateNumber,
brand: registerBikeForm.brand,
model: registerBikeForm.model,
currentRent: Number(registerBikeForm.currentRent),
location: registerBikeForm.location,
purchasePrice: Number(registerBikeForm.purchasePrice),
rentalType: registerBikeForm.rentalType,
investorId: investorId,
investorName: investor.name,
investmentId: registerBikeForm.investmentId || evInv?.id || 'ip1',
status: 'rented',
batteryLevel: 100,
totalEarnings: 0
};
setBikes(prev => [...prev, newBike]);
setShowRegisterBikeModal(false);
toast.success(`New bike ${newBike.model} registered and assigned!`);
};
const handleUnassignBike = (bikeId: string) => {
setBikes(prev => prev.map(b => {
if (b.id === bikeId) {
return {
...b,
investorId: null,
investorName: null,
investmentId: null,
status: 'available'
};
}
return b;
}));
toast.success('Bike successfully unassigned from investor!');
};
const handleCreateInvestment = () => {
const invId = `INV-${Date.now()}`;
const year = new Date().getFullYear();
@@ -1211,10 +1506,45 @@ export default function InvestorDetailPage() {
{activeTab === 'bikes' && (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<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} EV Investment Plans</p>
<h3 className="font-bold text-lg text-slate-800 flex items-center gap-2">
<Bike className="w-5 h-5 text-investor" />
Assigned Bikes
</h3>
<p className="text-sm text-slate-500">
{assignedBikes.length} bikes across {investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType).length || 0} EV Investment Plans
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<button
onClick={() => {
setSelectedBikeId('');
setShowAssignBikeModal(true);
}}
className="px-4 py-2 bg-blue-50 text-blue-700 border border-blue-200 rounded-lg text-sm font-semibold hover:bg-blue-100 flex items-center gap-2 transition-colors"
>
<Plus className="w-4 h-4" /> Assign Existing
</button>
<button
onClick={() => {
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
setRegisterBikeForm({
plateNumber: `DHAKA-METRO-HA-${Math.floor(1000 + Math.random() * 9000)}`,
brand: 'Etron',
model: 'ET50',
currentRent: 150,
location: 'Banani',
purchasePrice: 200000,
rentalType: 'single_rent',
investmentId: evInv?.id || ''
});
setShowRegisterBikeModal(true);
}}
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-semibold hover:bg-investor-dark flex items-center gap-2 shadow-sm transition-colors"
>
<Plus className="w-4 h-4" /> Register & Assign New
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -1249,15 +1579,14 @@ export default function InvestorDetailPage() {
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 rentalInfo = rentalTypes[bike.rentalType || 'single_rent'] || rentalTypes.single_rent;
const planType = investment?.planType || 'gold';
const status = statusConfig[bike.status] || statusConfig.available;
return (
<Link
<div
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`}
className={`block bg-white rounded-xl border ${planBg[planType]} overflow-hidden hover:shadow-lg transition-all group relative`}
>
<div className={`h-2 bg-gradient-to-r ${planColors[planType]}`} />
<div className="p-4">
@@ -1277,7 +1606,7 @@ export default function InvestorDetailPage() {
<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 === 'platinum' ? 'bg-purple-500' : 'bg-blue-500'
}`} />
{planType} Plan {investment?.planName || 'Investment'}
</div>
@@ -1309,8 +1638,24 @@ export default function InvestorDetailPage() {
}`}>{bike.batteryLevel}%</span>
</div>
</div>
<div className="mt-3 pt-3 border-t border-slate-100 flex justify-end gap-2">
<Link href={`/bikes?bike=${bike.id}`} className="text-xs text-slate-500 hover:text-slate-800 font-semibold transition-colors px-2 py-1 bg-slate-100 rounded">
View Details
</Link>
<button
onClick={() => {
if (confirm(`Are you sure you want to unassign bike ${bike.model} (${bike.plateNumber})?`)) {
handleUnassignBike(bike.id);
}
}}
className="text-xs text-red-600 hover:text-red-800 font-semibold transition-colors px-2 py-1 bg-red-50 rounded"
>
Unassign
</button>
</div>
</div>
</Link>
</div>
);
})}
{assignedBikes.length === 0 && (
@@ -3071,6 +3416,116 @@ export default function InvestorDetailPage() {
</div>
)}
{showRegisterBikeModal && (
<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-md">
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
<h2 className="text-lg font-bold text-slate-800">Register & Assign New Bike</h2>
<button onClick={() => setShowRegisterBikeModal(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 space-y-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Plate Number</label>
<input
type="text"
value={registerBikeForm.plateNumber}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, plateNumber: e.target.value })}
placeholder="DHAKA-METRO-HA-1234"
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Brand</label>
<input
type="text"
value={registerBikeForm.brand}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, brand: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Model</label>
<input
type="text"
value={registerBikeForm.model}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, model: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Purchase Price (৳)</label>
<input
type="number"
value={registerBikeForm.purchasePrice}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, purchasePrice: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Daily Rent (৳)</label>
<input
type="number"
value={registerBikeForm.currentRent}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, currentRent: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Location</label>
<input
type="text"
value={registerBikeForm.location}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, location: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Rental Type</label>
<select
value={registerBikeForm.rentalType}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, rentalType: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="single_rent">Single Rent</option>
<option value="rent_to_own">Rent to Own</option>
<option value="share_ev">Share EV</option>
</select>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Attach to Investment Plan</label>
<select
value={registerBikeForm.investmentId}
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, investmentId: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">Select plan</option>
{investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType).map((inv: any) => (
<option key={inv.id} value={inv.id}>{inv.planName} ({inv.id})</option>
))}
</select>
</div>
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
<button onClick={() => setShowRegisterBikeModal(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={handleRegisterAndAssignBike} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">Register & Assign</button>
</div>
</div>
</div>
)}
{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-3xl max-h-[90vh] overflow-hidden flex flex-col">

View File

@@ -96,7 +96,9 @@ export interface InvestmentPlan {
investorId: string;
planName: string;
planType: 'silver' | 'gold' | 'platinum' | 'diamond';
bikeIds: string[];
bikeIds?: string[];
batteryIds?: string[];
assetType?: 'bike' | 'battery';
totalInvestment: number;
monthlyReturn: number;
expectedRoi: number;
@@ -363,7 +365,7 @@ export const investors: Investor[] = [
emergencyContactName: 'Fatema Begum',
emergencyContactRelation: 'Wife',
emergencyContactPhone: '01712345679',
totalInvested: 150000,
totalInvested: 300000,
totalEarnings: 114250,
activeBikes: 2,
withdrawalPending: 3000,
@@ -387,7 +389,8 @@ export const investors: Investor[] = [
referralEarnings: 2500,
investments: [
{ id: 'ip1', investorId: 'inv1', planName: 'Gold EV Fleet 2024', planType: 'gold', bikeIds: ['b1'], totalInvestment: 85000, monthlyReturn: 2500, expectedRoi: 18, actualEarnings: 10000, startDate: '2024-01-15', endDate: '2025-01-14', status: 'active', paymentMethod: 'bank', transactionId: 'invt1', createdAt: '2024-01-15' },
{ id: 'ip2', investorId: 'inv1', planName: 'Gold City Commuter', planType: 'gold', bikeIds: ['b2'], totalInvestment: 65000, monthlyReturn: 2125, expectedRoi: 18, actualEarnings: 4250, startDate: '2024-01-20', endDate: '2025-01-19', status: 'active', paymentMethod: 'mobile', transactionId: 'invt2', createdAt: '2024-01-20' }
{ id: 'ip2', investorId: 'inv1', planName: 'Gold City Commuter', planType: 'gold', bikeIds: ['b2'], totalInvestment: 65000, monthlyReturn: 2125, expectedRoi: 18, actualEarnings: 4250, startDate: '2024-01-20', endDate: '2025-01-19', status: 'active', paymentMethod: 'mobile', transactionId: 'invt2', createdAt: '2024-01-20' },
{ id: 'ip3', investorId: 'inv1', planName: 'Standard Battery Plan', planType: 'silver', assetType: 'battery', batteryIds: ['BAT-001', 'BAT-002'], totalInvestment: 150000, monthlyReturn: 4500, expectedRoi: 16, actualEarnings: 9000, startDate: '2024-02-01', endDate: '2025-02-01', status: 'active', paymentMethod: 'bank', transactionId: 'invt3', createdAt: '2024-02-01' }
]
},
{