diff --git a/src/app/investor/investments/[id]/page.tsx b/src/app/investor/investments/[id]/page.tsx index 7575ad3..53aea75 100644 --- a/src/app/investor/investments/[id]/page.tsx +++ b/src/app/investor/investments/[id]/page.tsx @@ -323,13 +323,17 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi

{assignedBatteries.length} battery pack{assignedBatteries.length !== 1 ? 's' : ''} assigned to this investment

{assignedBatteries.map((battery) => ( -
-
+ +
-
{battery.model}
+
{battery.model}
{battery.status}

{battery.serialNumber} • {battery.brand}

@@ -339,25 +343,32 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi {battery.location}
-
-

Est. Monthly Return

-

৳{(investment.monthlyReturn / assignedBatteries.length).toLocaleString()}

-

Total: ৳{battery.earnings.toLocaleString()}

+
+
+

Est. Monthly Return

+

৳{(investment.monthlyReturn / assignedBatteries.length).toLocaleString()}

+

Total: ৳{battery.earnings.toLocaleString()}

+
+
-
+ ))}
) : (

{assignedBikes.length} bike{assignedBikes.length !== 1 ? 's' : ''} assigned to this investment

{assignedBikes.map((bike) => ( -
-
- {bike.model} + +
+ {bike.model}
-
{bike.model}
+
{bike.model}
{bike.status}

{bike.plateNumber} • {bike.brand}

@@ -366,12 +377,15 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi {bike.location}
-
-

Est. Monthly Return

-

৳{bike.currentRent}

-

Total: ৳{bike.totalEarnings.toLocaleString()}

+
+
+

Est. Monthly Return

+

৳{bike.currentRent}

+

Total: ৳{bike.totalEarnings.toLocaleString()}

+
+
-
+ ))}
) diff --git a/src/app/investor/investments/[id]/rental-history/[assetId]/page.tsx b/src/app/investor/investments/[id]/rental-history/[assetId]/page.tsx new file mode 100644 index 0000000..32ff0d4 --- /dev/null +++ b/src/app/investor/investments/[id]/rental-history/[assetId]/page.tsx @@ -0,0 +1,551 @@ +'use client'; + +import { useState, use } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { + ArrowLeft, Battery, Bike, Calendar, Clock, Download, MapPin, Search, + TrendingUp, User, XCircle, CheckCircle2, ChevronLeft, ChevronRight, + Shield, RefreshCw, DollarSign, Activity, AlertCircle, CalendarRange +} from 'lucide-react'; +import { investors } from '@/data/mockData'; +import toast from 'react-hot-toast'; +import InvestorNotification from '@/components/InvestorNotification'; + +interface RentalTransaction { + id: string; + date: string; + riderName: string; + duration: string; + amount: number; + status: 'paid' | 'pending' | 'failed'; + payoutMethod: string; + swapCount?: number; +} + +export default function AssetRentalHistoryPage({ params }: { params: Promise<{ id: string; assetId: string }> }) { + const resolvedParams = use(params); + const { id: investmentId, assetId } = resolvedParams; + const router = useRouter(); + + const investor = investors[0]; + const investment = investor.investments?.find((inv: any) => inv.id === investmentId); + + // Asset verification + const isBattery = assetId.startsWith('BAT-') || assetId.startsWith('bat-') || assetId.toLowerCase().includes('battery'); + + // Specific Asset Details + const bikeDetails = { + id: 'b1', + model: 'Etron ET50', + brand: 'Etron', + plateNumber: 'Dhaka Metro Cha-1234', + image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=800&h=600&fit=crop', + status: 'rented', + currentRent: 350, + totalEarnings: 114250, + batteryLevel: 78, + range: '60 km', + location: 'Gulshan 1 Hub', + purchasePrice: 85000, + utilization: '94%', + avgDailyHours: '8.4 hrs', + }; + + const batteryDetails: Record = { + 'BAT-001': { + id: 'BAT-001', + serialNumber: 'SN-2024-00001', + brand: 'EVE Energy', + model: 'Li-Ion 60V50Ah', + status: 'In-Use', + cycleCount: 156, + soc: '78% / 95%', + earnings: 4500, + location: 'Dhaka Central Hub', + purchasePrice: 45000, + utilization: '97%', + dailyRent: 150, + }, + 'BAT-002': { + id: 'BAT-002', + serialNumber: 'SN-2024-00002', + brand: 'EVE Energy', + model: 'Li-Ion 60V50Ah', + status: 'In-Use', + cycleCount: 142, + soc: '82% / 96%', + earnings: 4500, + location: 'Dhaka Central Hub', + purchasePrice: 45000, + utilization: '95%', + dailyRent: 150, + } + }; + + const currentBattery = batteryDetails[assetId] || batteryDetails['BAT-001']; + const assetDisplayName = isBattery ? currentBattery.model : bikeDetails.model; + const assetSubName = isBattery ? currentBattery.serialNumber : bikeDetails.plateNumber; + + // Generate highly realistic rent transaction history + const [transactions] = useState(() => { + const list: RentalTransaction[] = []; + const riders = ['Sajib Islam', 'Nayeem Chowdhury', 'Rakib Hasan', 'Kamal Hossain', 'Arifur Rahman']; + const methods = ['bKash', 'Nagad', 'Rocket', 'Bank Transfer']; + + const days = isBattery ? 30 : 20; + const baseDate = new Date(); + + for (let i = 0; i < days; i++) { + const date = new Date(); + date.setDate(baseDate.getDate() - i); + const dateString = date.toISOString().split('T')[0]; + + const riderIndex = (i + 3) % riders.length; + const methodIndex = (i + 1) % methods.length; + + // status distribution + let status: 'paid' | 'pending' | 'failed' = 'paid'; + if (i === 1) status = 'pending'; + else if (i === 5) status = 'failed'; + + const amount = isBattery ? currentBattery.dailyRent : bikeDetails.currentRent; + + list.push({ + id: `TX-${isBattery ? 'BAT' : 'BK'}-${10000 + i}`, + date: dateString, + riderName: riders[riderIndex], + duration: '1 Day', + amount: amount, + status: status, + payoutMethod: methods[methodIndex], + swapCount: isBattery ? 2 + (i % 3) : undefined + }); + } + return list; + }); + + // Filter & Sorting State + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [sortBy, setSortBy] = useState<'date' | 'amount' | 'rider'>('date'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const [page, setPage] = useState(1); + const pageSize = 10; + + // Handler functions + const handleSort = (field: 'date' | 'amount' | 'rider') => { + if (sortBy === field) { + setSortOrder(prev => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setSortBy(field); + setSortOrder('desc'); + } + setPage(1); + }; + + // Filter Logic + const filteredTransactions = transactions.filter(tx => { + if (statusFilter !== 'all' && tx.status !== statusFilter) return false; + if (searchQuery && !tx.riderName.toLowerCase().includes(searchQuery.toLowerCase()) && !tx.id.toLowerCase().includes(searchQuery.toLowerCase())) return false; + if (dateFrom && new Date(tx.date) < new Date(dateFrom)) return false; + if (dateTo && new Date(tx.date) > new Date(dateTo)) return false; + return true; + }); + + // Sort Logic + const sortedTransactions = [...filteredTransactions].sort((a, b) => { + let comparison = 0; + if (sortBy === 'date') { + comparison = new Date(a.date).getTime() - new Date(b.date).getTime(); + } else if (sortBy === 'amount') { + comparison = a.amount - b.amount; + } else if (sortBy === 'rider') { + comparison = a.riderName.localeCompare(b.riderName); + } + return sortOrder === 'desc' ? -comparison : comparison; + }); + + // Pagination + const totalPages = Math.ceil(sortedTransactions.length / pageSize); + const paginatedTransactions = sortedTransactions.slice((page - 1) * pageSize, page * pageSize); + + const statusConfig: Record = { + paid: { label: 'Paid', bg: 'bg-green-100', color: 'text-green-700', icon: CheckCircle2 }, + pending: { label: 'Pending', bg: 'bg-amber-100', color: 'text-amber-700', icon: Clock }, + failed: { label: 'Failed', bg: 'bg-red-100', color: 'text-red-700', icon: XCircle }, + }; + + const totalCollected = filteredTransactions + .filter(t => t.status === 'paid') + .reduce((sum, t) => sum + t.amount, 0); + + const pendingAmount = filteredTransactions + .filter(t => t.status === 'pending') + .reduce((sum, t) => sum + t.amount, 0); + + return ( +
+ + +
+ + {/* Header - EXACT copy of other page's spacing & structure */} +
+
+
+ +
+

+ {isBattery ? : } + {assetDisplayName} +

+

+ ID: #{assetId.toUpperCase()} • {isBattery ? `Serial Number: ${currentBattery.serialNumber}` : `Plate: ${bikeDetails.plateNumber}`} +

+
+
+ + +
+
+ + {/* Info Grid & Stats Cards - MATCHES OTHER PAGES PRECISELY */} +
+
+
+
+ +
+
+

Total Collected

+

৳{totalCollected.toLocaleString()}

+
+
+
+ +
+
+
+ +
+
+

Utilization Rate

+

{isBattery ? currentBattery.utilization : bikeDetails.utilization}

+
+
+
+ +
+
+
+ +
+
+

Pending

+

৳{pendingAmount.toLocaleString()}

+
+
+
+ +
+
+
+ +
+
+

Hub Location

+

+ {isBattery ? currentBattery.location.split(' ')[0] : bikeDetails.location.split(' ')[0]} +

+
+
+
+
+ + {/* Main Table Card - MATCHES OTHER PAGES PRECISELY */} +
+ + {/* Filters - MATCHES OTHER PAGES PRECISELY */} +
+
+ +
+
+ + { setSearchQuery(e.target.value); setPage(1); }} + className="pl-9 pr-4 py-2 border border-slate-200 rounded-lg text-sm w-48 sm:w-64 focus:outline-none" + /> +
+ + +
+ +
+
+ Date Range +
+
+ { setDateFrom(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> + to + { setDateTo(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> +
+ {(dateFrom || dateTo) && ( + + )} + + +
+ +
+
+ + {/* Card View - Mobile/Tablet */} +
+ {paginatedTransactions.length > 0 ? ( + paginatedTransactions.map((tx) => { + const status = statusConfig[tx.status] || statusConfig.pending; + const StatusIcon = status.icon; + return ( +
+
+
+
+ +

{tx.riderName}

+
+

Ref: {tx.id}

+ {isBattery && ( +

Swaps: {tx.swapCount}

+ )} +
+ +
+

৳{tx.amount.toLocaleString()}

+ + {status.label} + +
+
+ +
+ {tx.date} + {tx.payoutMethod} +
+
+ ); + }) + ) : ( +
+ +

No rental payments found

+
+ )} +
+ + {/* Table View - Desktop */} +
+ + + + + + + {isBattery && ( + + )} + + + + + + + + + {paginatedTransactions.length > 0 ? ( + paginatedTransactions.map((tx) => { + const status = statusConfig[tx.status] || statusConfig.pending; + const StatusIcon = status.icon; + return ( + + + + + {isBattery && ( + + )} + + + + + + ); + }) + ) : ( + + + + )} + +
handleSort('date')} + className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase cursor-pointer hover:bg-slate-100" + > +
+ Date {sortBy === 'date' && {sortOrder === 'asc' ? '↑' : '↓'}} +
+
+ Transaction ID + handleSort('rider')} + className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase cursor-pointer hover:bg-slate-100" + > +
+ Rider (Biker) {sortBy === 'rider' && {sortOrder === 'asc' ? '↑' : '↓'}} +
+
+ Swaps + + Duration + handleSort('amount')} + className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase cursor-pointer hover:bg-slate-100" + > +
+ Amount {sortBy === 'amount' && {sortOrder === 'asc' ? '↑' : '↓'}} +
+
+ Method + + Status +
+
+

{tx.date}

+
+
+ {tx.id} + +
+ + {tx.riderName} +
+
+ {tx.swapCount} Swaps + + {tx.duration} + +

৳{tx.amount.toLocaleString()}

+
+ {tx.payoutMethod} + + + + {status.label} + +
+ +

No rental payments found

+
+
+ + {/* Pagination - MATCHES OTHER PAGES PRECISELY */} + {sortedTransactions.length > pageSize && ( +
+

+ Showing {((page - 1) * pageSize) + 1} to {Math.min(page * pageSize, sortedTransactions.length)} of {sortedTransactions.length} +

+ +
+ + + {Array.from({ length: totalPages }, (_, i) => i + 1).map(pageNum => ( + + ))} + + +
+
+ )} + +
+
+
+ ); +}