diff --git a/src/app/investor/page.tsx b/src/app/investor/page.tsx index bcae764..d6ca11d 100644 --- a/src/app/investor/page.tsx +++ b/src/app/investor/page.tsx @@ -11,7 +11,7 @@ export default function InvestorDashboardPage() { const availableBalance = investor.totalEarnings - investor.totalWithdrawn - investor.withdrawalPending; return ( -
+

Welcome back, {investor.name.split(' ')[0]} 👋

@@ -44,7 +44,7 @@ export default function InvestorDashboardPage() {

à§³{(investor.totalInvested / 1000).toFixed(0)}k

- +
@@ -93,8 +93,8 @@ export default function InvestorDashboardPage() { {investorBikes.length > 0 ? (
{investorBikes.slice(0, 3).map(bike => ( - @@ -110,10 +110,9 @@ export default function InvestorDashboardPage() {

Daily Rent

- + }`}> {bike.status}
@@ -121,13 +120,13 @@ export default function InvestorDashboardPage() { ))}
) : ( -
-
- -
-

No bikes assigned yet

-

Once you make an investment, assigned bikes will appear here.

-
+
+
+ +
+

No bikes assigned yet

+

Once you make an investment, assigned bikes will appear here.

+
)}
@@ -144,7 +143,7 @@ export default function InvestorDashboardPage() { Withdraw Funds
- +
@@ -154,7 +153,7 @@ export default function InvestorDashboardPage() {

Manage documents

- +
diff --git a/src/app/investor/rental-history/page.tsx b/src/app/investor/rental-history/page.tsx new file mode 100644 index 0000000..90e4e11 --- /dev/null +++ b/src/app/investor/rental-history/page.tsx @@ -0,0 +1,375 @@ +'use client'; + +import { useState } from 'react'; +import { + History, Bike, DollarSign, Clock, User, Download, Search, + ChevronLeft, ChevronRight, CheckCircle, XCircle, AlertCircle, Calendar +} from 'lucide-react'; +import { investors, bikes, rentalPayments } from '@/data/mockData'; + +export default function RentalHistoryPage() { + const investor = investors.find(i => i.id === 'inv1') || investors[0]; + const investorBikes = bikes.filter(b => b.investorId === investor.id); + const investorPayments = rentalPayments.filter(p => p.investorId === investor.id); + + const [bikeFilter, setBikeFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [sortBy, setSortBy] = useState<'date' | 'amount'>('date'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const [page, setPage] = useState(1); + const [searchQuery, setSearchQuery] = useState(''); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const pageSize = 10; + + const filteredPayments = investorPayments.filter(p => { + if (bikeFilter !== 'all' && p.bikeId !== bikeFilter) return false; + if (statusFilter !== 'all' && p.status !== statusFilter) return false; + if (searchQuery && !p.bikerName.toLowerCase().includes(searchQuery.toLowerCase()) && + !p.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()) && + !p.plateNumber.toLowerCase().includes(searchQuery.toLowerCase())) return false; + if (dateFrom && new Date(p.date) < new Date(dateFrom)) return false; + if (dateTo && new Date(p.date) > new Date(dateTo)) return false; + return true; + }); + + const sortedPayments = [...filteredPayments].sort((a, b) => { + if (sortBy === 'date') { + return sortOrder === 'asc' + ? new Date(a.date).getTime() - new Date(b.date).getTime() + : new Date(b.date).getTime() - new Date(a.date).getTime(); + } else { + return sortOrder === 'asc' ? a.amount - b.amount : b.amount - a.amount; + } + }); + + const totalPages = Math.ceil(sortedPayments.length / pageSize); + const paginatedPayments = sortedPayments.slice((page - 1) * pageSize, page * pageSize); + + const totalCollected = investorPayments.filter(p => p.status === 'paid').reduce((sum, p) => sum + p.amount, 0); + const totalPending = investorPayments.filter(p => p.status === 'pending').reduce((sum, p) => sum + p.amount, 0); + const totalFailed = investorPayments.filter(p => p.status === 'failed').reduce((sum, p) => sum + p.amount, 0); + const activeRentals = new Set(investorPayments.filter(p => p.status === 'paid').map(p => p.bikeId)).size; + + const statusConfig: Record = { + paid: { label: 'Paid', bg: 'bg-green-100', color: 'text-green-700', icon: CheckCircle }, + 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 planConfig: Record = { + single: { label: 'Single Rent', bg: 'bg-green-100', color: 'text-green-700' }, + 'rent-to-own': { label: 'Rent to Own', bg: 'bg-blue-100', color: 'text-blue-700' }, + share_ev: { label: 'Share EV', bg: 'bg-purple-100', color: 'text-purple-700' }, + }; + + return ( +
+ {/* Header */} +
+
+
+

+ Rental History +

+

Track daily rental payments from your bikes

+
+ +
+
+ + {/* Stats Cards */} +
+
+
+
+ +
+
+

Total Collected

+

à§³{totalCollected.toLocaleString()}

+
+
+
+
+
+
+ +
+
+

Active Rentals

+

{activeRentals}

+
+
+
+
+
+
+ +
+
+

Pending

+

à§³{totalPending.toLocaleString()}

+
+
+
+
+
+
+ +
+
+

Failed

+

à§³{totalFailed.toLocaleString()}

+
+
+
+
+ + {/* Main Table Card */} +
+ {/* Filters */} +
+
+
+
+ + setSearchQuery(e.target.value)} + className="pl-9 pr-4 py-2 border border-slate-200 rounded-lg text-sm w-48 sm:w-64" + /> +
+ + +
+
+
+ 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 */} +
+ {paginatedPayments.length > 0 ? paginatedPayments.map((payment) => { + const status = statusConfig[payment.status] || statusConfig.pending; + const plan = planConfig[payment.planType] || planConfig.single; + const StatusIcon = status.icon; + return ( +
+
+
+
+ +

{payment.bikeModel}

+
+

{payment.plateNumber}

+
+ +

{payment.bikerName}

+
+
+
+

à§³{payment.amount.toLocaleString()}

+ + {status.label} + +
+
+
+ {payment.date} + {plan.label} +
+
+ ); + }) : ( +
+ +

No rental payments found

+
+ )} +
+ + {/* Table View - Desktop */} +
+ + + + + + + + + + + + + + + {paginatedPayments.length > 0 ? paginatedPayments.map((payment) => { + const status = statusConfig[payment.status] || statusConfig.pending; + const plan = planConfig[payment.planType] || planConfig.single; + const StatusIcon = status.icon; + return ( + + + + + + + + + + + ); + }) : ( + + + + )} + +
{ + if (sortBy === 'date') setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + else { setSortBy('date'); setSortOrder('desc'); } + }} + 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' ? '↑' : '↓'}} +
+
BikeBikerPlanDuration { + if (sortBy === 'amount') setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + else { setSortBy('amount'); setSortOrder('desc'); } + }} + 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' ? '↑' : '↓'}} +
+
MethodStatus
+
+

{payment.date}

+

{payment.transactionId || payment.id}

+
+
+
+ +
+

{payment.bikeModel}

+

{payment.plateNumber}

+
+
+
+
+ + {payment.bikerName} +
+
+ + {plan.label} + + + {payment.duration} + +

à§³{payment.amount.toLocaleString()}

+
+ {payment.paymentMethod} + + + {status.label} + +
+ +

No rental payments found

+
+
+ + {/* Pagination */} + {sortedPayments.length > pageSize && ( +
+

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

+
+ + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNum = totalPages <= 5 ? i + 1 : page <= 3 ? i + 1 : page >= totalPages - 2 ? totalPages - 4 + i : page - 2 + i; + return ( + + ); + })} + +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/investor/withdraw/page.tsx b/src/app/investor/withdraw/page.tsx index b8bd81c..c76c97c 100644 --- a/src/app/investor/withdraw/page.tsx +++ b/src/app/investor/withdraw/page.tsx @@ -107,7 +107,7 @@ export default function InvestorWithdrawPage() { }; return ( -
+
{/* Header */}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 76db878..bb4306e 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -67,6 +67,7 @@ const bikerNavItems = [ const investorNavItems = [ { label: 'Dashboard', href: '/investor', icon: BarChart3 }, { label: 'My Investments', href: '/investor/plans', icon: Target }, + { label: 'Rental History', href: '/investor/rental-history', icon: FileText }, { label: 'Withdraw', href: '/investor/withdraw', icon: CreditCard }, { label: 'My Profile', href: '/investor/profile', icon: User }, ]; @@ -106,7 +107,9 @@ export default function Sidebar() { ] : isInvestor ? [ { label: 'Home', href: '/investor', icon: BarChart3 }, { label: 'Investments', href: '/investor/plans', icon: Target }, + { label: 'History', href: '/investor/rental-history', icon: FileText }, { label: 'Withdraw', href: '/investor/withdraw', icon: CreditCard }, + { label: 'Profile', href: '/investor/profile', icon: User }, ] : isShop ? [ { label: 'Home', href: '/shop', icon: Store }, { label: 'Deliveries', href: '/shop/deliveries', icon: Truck }, @@ -194,8 +197,8 @@ export default function Sidebar() {
- {/* Bottom Navigation for Mobile */} -