diff --git a/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx index f0d715f..94e2b2b 100644 --- a/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx +++ b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx @@ -1,14 +1,15 @@ 'use client'; -import { useState, use } from 'react'; +import { useState, use, useEffect } 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, - Receipt, CreditCardIcon + Receipt, CreditCardIcon, Battery, Zap } from 'lucide-react'; +import toast from 'react-hot-toast'; import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions } from '@/data/mockData'; export default function InvestmentDetailPage({ params }: { params: Promise<{ id: string; investmentId: string }> }) { @@ -16,17 +17,320 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id: const { id: investorId, investmentId } = resolvedParams; const router = useRouter(); - const investor = initialInvestors.find(i => i.id === investorId); + const [investors, setInvestors] = useState(() => { + 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 [batteries, setBatteries] = useState(() => { + 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 investor = investors.find(i => i.id === investorId); const investment = investor?.investments?.find((inv: any) => inv.id === investmentId); + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem('jaiben_investors', JSON.stringify(investors)); + } + }, [investors]); + + // 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: any) => 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: any) => 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]); + + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem('jaiben_batteries', JSON.stringify(batteries)); + } + }, [batteries]); + + const [bikes, setBikes] = useState(() => { + 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 [unassignedBatteries, setUnassignedBatteries] = useState(() => { + 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_unassigned_batteries', JSON.stringify(unassignedBatteries)); + } + }, [unassignedBatteries]); + const [activeTab, setActiveTab] = useState('overview'); const [showAddBikeModal, setShowAddBikeModal] = useState(false); + const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false); + const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false); + const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false); + + const [selectedBikeId, setSelectedBikeId] = useState(''); + const [registerBikeForm, setRegisterBikeForm] = useState({ + plateNumber: '', + brand: 'Etron', + model: 'ET50', + currentRent: 150, + location: 'Banani', + purchasePrice: 200000, + rentalType: 'single_rent' + }); + + const [selectedBatteryId, setSelectedBatteryId] = useState(''); + const [assignBatteryForm, setAssignBatteryForm] = useState({ + investorShare: 100, + investedAmount: 45000, + deposit: 5000, + rentPrice: 150 + }); + + const [registerBatteryForm, setRegisterBatteryForm] = useState({ + serialNumber: '', + brand: 'BYD', + model: 'Li-Ion 60V50Ah', + type: 'lithium-ion', + capacity: 50, + voltage: 60, + purchasePrice: 45000, + deposit: 5000, + rentPrice: 150, + investorShare: 100, + investedAmount: 45000 + }); + const [showDeleteBikeModal, setShowDeleteBikeModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showStatementHistory, setShowStatementHistory] = useState(false); const [showPartialPaymentModal, setShowPartialPaymentModal] = useState(false); const [bikeToDelete, setBikeToDelete] = useState(null); - const [selectedBikeId, setSelectedBikeId] = useState(''); const [editForm, setEditForm] = useState({}); const [statementHistory, setStatementHistory] = useState([ { id: 'stmt1', date: '2024-03-25', type: 'Monthly Statement', period: 'March 2024', downloadedBy: 'Admin', downloadedAt: '2024-03-25 10:30' }, @@ -59,6 +363,8 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id: ); } + const isBattery = investment.assetType === 'battery' || investment.planName?.toLowerCase().includes('battery'); + 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' }, @@ -67,9 +373,159 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id: }; const style = planConfig[investment.planType] || planConfig.gold; - const assignedBikes = initialBikes.filter((b: any) => b.investorId === investorId); + const assignedBikes = bikes.filter((b: any) => b.investorId === investorId && b.investmentId === investmentId); + const assignedBatteries = batteries.filter((b: any) => b.investorId === investorId && b.investmentId === investmentId); const investmentTransactions = initialTransactions.filter((t: any) => t.investorId === investorId && t.type === 'investment_return'); + // Handlers + const handleAssignBike = () => { + if (!selectedBikeId) { + toast.error('Please select a bike'); + return; + } + const bike = bikes.find(b => b.id === selectedBikeId); + if (!bike) return; + + setBikes(prev => prev.map(b => { + if (b.id === selectedBikeId) { + return { + ...b, + investorId, + investorName: investor.name, + investmentId, + status: 'rented', + totalEarnings: b.totalEarnings || 0 + }; + } + return b; + })); + toast.success(`Bike ${bike.model} (${bike.plateNumber}) assigned to this investment!`); + setShowAddBikeModal(false); + setSelectedBikeId(''); + }; + + const handleRegisterAndAssignBike = () => { + if (!registerBikeForm.plateNumber) { + toast.error('Please enter a plate number'); + return; + } + 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, + investorName: investor.name, + investmentId, + status: 'rented', + batteryLevel: 100, + totalEarnings: 0 + }; + setBikes(prev => [...prev, newBike]); + toast.success(`New bike registered and assigned to this investment!`); + setShowRegisterBikeModal(false); + }; + + 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 unassigned successfully!'); + }; + + const handleAssignBattery = () => { + if (!selectedBatteryId) { + toast.error('Please select a battery'); + return; + } + const bat = unassignedBatteries.find(b => b.id === selectedBatteryId); + if (!bat) return; + + const assigned = { + ...bat, + investorId, + investorName: investor.name, + investorSharePercentage: Number(assignBatteryForm.investorShare), + investedAmount: Number(assignBatteryForm.investedAmount), + investmentId, + deposit: Number(assignBatteryForm.deposit), + rentPrice: Number(assignBatteryForm.rentPrice), + status: 'in-use' + }; + + setBatteries(prev => [...prev, assigned]); + setUnassignedBatteries(prev => prev.filter(b => b.id !== selectedBatteryId)); + toast.success(`Battery SN: ${bat.serialNumber} assigned to this investment!`); + setShowAssignBatteryModal(false); + setSelectedBatteryId(''); + }; + + const handleRegisterAndAssignBattery = () => { + if (!registerBatteryForm.serialNumber) { + toast.error('Please enter serial number'); + return; + } + const newBat = { + id: `BAT-${Date.now()}`, + serialNumber: registerBatteryForm.serialNumber, + brand: registerBatteryForm.brand, + model: registerBatteryForm.model, + type: registerBatteryForm.type, + capacity: Number(registerBatteryForm.capacity), + voltage: Number(registerBatteryForm.voltage), + purchaseDate: new Date().toISOString().split('T')[0], + purchasePrice: Number(registerBatteryForm.purchasePrice), + deposit: Number(registerBatteryForm.deposit), + rentPrice: Number(registerBatteryForm.rentPrice), + investorId, + investorName: investor.name, + investorSharePercentage: Number(registerBatteryForm.investorShare), + investedAmount: Number(registerBatteryForm.investedAmount), + investmentId, + status: 'in-use', + currentSoc: 100, + health: 100, + cycleCount: 0 + }; + + setBatteries(prev => [...prev, newBat]); + toast.success('New battery registered and assigned!'); + setShowRegisterBatteryModal(false); + }; + + const handleUnassignBattery = (batteryId: string) => { + const bat = batteries.find(b => b.id === batteryId); + if (!bat) return; + + setBatteries(prev => prev.filter(b => b.id !== batteryId)); + setUnassignedBatteries(prev => [ + ...prev, + { + ...bat, + investorId: null, + investorName: null, + investorSharePercentage: null, + investedAmount: null, + investmentId: null, + status: 'available' + } + ]); + toast.success('Battery unassigned successfully!'); + }; + return (
@@ -226,50 +682,64 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
- EV Base Price - ৳200,000 + {isBattery ? 'Battery Base Price' : 'EV Base Price'} + ৳{isBattery ? (investment.batteryBasePrice || 15000).toLocaleString() : (investment.evBasePrice || 200000).toLocaleString()}
Min Quantity - 1 bike + {investment.minimumQuantity || (isBattery ? 10 : 1)} {isBattery ? 'packs' : 'bike'}
Duration - 12 months + {investment.duration || 12} months
Lock-in - 3 months + {investment.lockInPeriod || 3} months
Exit Penalty - 10% + {investment.earlyExitPenalty || 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%

+ {isBattery ? ( +
+

+ + Partner Yield Sharing Percentage +

+

Profit sharing ratio when batteries are utilized

+
+

Profit Share

+

{investment.profitSharePercent || 40}%

-
+ ) : ( +
+

+ + 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%

+
+
+
+ )}
)} @@ -457,136 +927,298 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
-
-
-

- - 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 ( -
-
-
- + {isBattery ? ( +
+
+

+ + Assigned Batteries ({assignedBatteries.length}) +

+
+ + +
+
+ {assignedBatteries.length > 0 ? ( +
+ {assignedBatteries.map((bat: any) => { + return ( +
+
+
+ +
+
+
+
{bat.model}
+ + {bat.type} + +
+

{bat.serialNumber} • {bat.brand}

+
+
+
+ + ৳{((bat.rentPrice || 150) * 30).toLocaleString()} +
+

Est. Monthly Yield

+
+
-
-
-
{bike.model}
- - {investment.planType} + +
+
+

Status

+

+ {bat.status} +

+
+
+

Co-ownership Share

+

{bat.investorSharePercentage}%

+
+
+

SoC / Health

+

{bat.currentSoc}% / {bat.health}%

+
+
+

Cycle Count

+

{bat.cycleCount} cycles

+
+
+
+ ); + })} +
+ ) : ( +
+ +

No battery packs assigned to this investment yet

+
+ + +
+
+ )} +
+ ) : ( +
+
+

+ + 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}
-

{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

- -
- )} -
+ ); + })} +
+ ) : ( +
+ +

No bikes assigned to this investment yet

+
+ + +
+
+ )} +
+ )}
@@ -663,7 +1295,7 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id: className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor" > - {initialBikes.filter((b: any) => !b.investorId).map((bike: any) => ( + {bikes.filter((b: any) => !b.investorId).map((bike: any) => ( ))} @@ -673,7 +1305,11 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id: -
@@ -681,6 +1317,305 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
)} + {showRegisterBikeModal && ( +
+
+
+

Register & Assign New Bike

+ +
+ +
+
+ + 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" + /> +
+ +
+
+ + setRegisterBikeForm({ ...registerBikeForm, brand: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBikeForm({ ...registerBikeForm, model: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setRegisterBikeForm({ ...registerBikeForm, purchasePrice: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBikeForm({ ...registerBikeForm, currentRent: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+ + setRegisterBikeForm({ ...registerBikeForm, location: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+ +
+ + +
+
+ +
+ + +
+
+
+ )} + + {showAssignBatteryModal && ( +
+
+
+

Assign Battery to Investment

+ +
+ +
+
+ + +
+ +
+
+ + setAssignBatteryForm({ ...assignBatteryForm, investorShare: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setAssignBatteryForm({ ...assignBatteryForm, investedAmount: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setAssignBatteryForm({ ...assignBatteryForm, deposit: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setAssignBatteryForm({ ...assignBatteryForm, rentPrice: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+
+ +
+ + +
+
+
+ )} + + {showRegisterBatteryModal && ( +
+
+
+

Register & Assign New Battery

+ +
+ +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, serialNumber: e.target.value })} + placeholder="SN-2024-XXXXX" + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+ +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, brand: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, model: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, capacity: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, voltage: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, purchasePrice: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, deposit: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, rentPrice: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBatteryForm({ ...registerBatteryForm, investorShare: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+ + setRegisterBatteryForm({ ...registerBatteryForm, investedAmount: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+ + +
+
+
+ )} + {showEditModal && (
diff --git a/src/app/admin/investors/[id]/page.tsx b/src/app/admin/investors/[id]/page.tsx index e1ba549..0250536 100644 --- a/src/app/admin/investors/[id]/page.tsx +++ b/src/app/admin/investors/[id]/page.tsx @@ -75,9 +75,49 @@ export default function InvestorDetailPage() { const router = useRouter(); const investorId = params.id as string; - const [investors, setInvestors] = useState(initialInvestors); + const [investors, setInvestors] = useState(() => { + 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(() => { + 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(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(null); @@ -122,57 +172,232 @@ export default function InvestorDetailPage() { const [newDoc, setNewDoc] = useState({ type: 'nid', number: '', url: '' }); const [editingMobileIndex, setEditingMobileIndex] = useState(null); - const [batteries, setBatteries] = useState([ - { - 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(() => { + 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([ - { 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(() => { + 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' && (
-
+
-

Assigned Bikes

-

{assignedBikes.length} bikes across {investor.investments?.length || 0} EV Investment Plans

+

+ + Assigned Bikes +

+

+ {assignedBikes.length} bikes across {investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType).length || 0} EV Investment Plans +

+
+
+ +
@@ -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 ( -
@@ -1277,7 +1606,7 @@ export default function InvestorDetailPage() {
{planType} Plan • {investment?.planName || 'Investment'}
@@ -1309,8 +1638,24 @@ export default function InvestorDetailPage() { }`}>{bike.batteryLevel}%
+ +
+ + View Details + + +
- +
); })} {assignedBikes.length === 0 && ( @@ -3071,6 +3416,116 @@ export default function InvestorDetailPage() {
)} + {showRegisterBikeModal && ( +
+
+
+

Register & Assign New Bike

+ +
+ +
+
+ + 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" + /> +
+ +
+
+ + setRegisterBikeForm({ ...registerBikeForm, brand: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBikeForm({ ...registerBikeForm, model: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+
+ + setRegisterBikeForm({ ...registerBikeForm, purchasePrice: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + setRegisterBikeForm({ ...registerBikeForm, currentRent: Number(e.target.value) })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ +
+ + setRegisterBikeForm({ ...registerBikeForm, location: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+ )} + {showCreateInvestmentModal && (
diff --git a/src/data/mockData.ts b/src/data/mockData.ts index a7e7a70..99af70f 100644 --- a/src/data/mockData.ts +++ b/src/data/mockData.ts @@ -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' } ] }, {