diff --git a/src/app/admin/rentals/[id]/page.tsx b/src/app/admin/rentals/[id]/page.tsx index c25cc96..c9a6e2b 100644 --- a/src/app/admin/rentals/[id]/page.tsx +++ b/src/app/admin/rentals/[id]/page.tsx @@ -1,20 +1,29 @@ 'use client'; import { useState, useEffect } from 'react'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams } from 'next/navigation'; +import Link from 'next/link'; import { ArrowLeft, Bike, User, Calendar, DollarSign, Wallet, Shield, CheckCircle, XCircle, Clock, Edit, Save, Plus, Trash2, Image, Upload, Lock, Unlock, AlertTriangle, MessageSquare, MapPin, - Phone, MessageCircle + Phone, MessageCircle, Play, Check, X, FileText, Download } from 'lucide-react'; +import { + canRentalAccept, canRentalReject, canRentalCancel, canRentalEdit, + canRentalImageApprove, canRentalLock, canRentalUnlock +} from '../../../../lib/auth'; -type RentalStatus = 'active' | 'pending' | 'completed' | 'disputed' | 'cancelled' | 'locked'; +type RentalStatus = 'pending' | 'accepted' | 'active' | 'completed' | 'cancelled' | 'locked' | 'disputed'; type RentalType = 'single' | 'shared' | 'rent-to-own'; +type PaymentMethod = 'cash' | 'bank' | 'wallet'; +type PaymentStatus = 'paid' | 'partial' | 'overdue' | 'unpaid'; +type PenaltyLevel = 'none' | 'day1' | 'day2' | 'day3'; interface BikeImage { id: string; - type: 'front' | 'back' | 'left' | 'right'; - url?: string; + url: string; + type: 'front' | 'back' | 'left' | 'right' | 'battery'; + approved?: boolean; } interface Note { @@ -27,171 +36,273 @@ interface Rental { id: string; bikeId: string; userId: string; + userName: string; + userPhone: string; + bikeModel: string; + bikePlate: string; + bikeBattery: number; type: RentalType; status: RentalStatus; startDate: string; endDate?: string; + contractMonths?: number; + subscriptionType: 'daily' | 'weekly' | 'monthly'; deposit: number; + depositPaymentMethod: PaymentMethod; + depositPaid: boolean; dailyRate: number; + weeklyRate: number; + monthlyRate: number; totalPaid: number; - dueRental?: number; + dueRental: number; + paymentStatus: PaymentStatus; + penaltyLevel: PenaltyLevel; + penaltyAmount: number; lockedAt?: string; lockedReason?: string; - hubId?: string; - hubName?: string; + hubId: string; + hubName: string; + initialImages?: BikeImage[]; + imagesApproved: boolean; + bikerNote?: string; + rejectNote?: string; + createdAt: string; + acceptedAt?: string; + activatedAt?: string; + lockHistory?: LockEvent[]; +} + +interface LockEvent { + id: string; + action: 'locked' | 'unlocked'; + reason?: string; + performedBy: string; + performedAt: string; } const mockRentals: Rental[] = [ { id: 'RNT-001', bikeId: 'BIKE-001', - userId: 'USR-001', + userId: 'USR-003', + userName: 'Jamal Uddin', + userPhone: '+8801912345678', + bikeModel: 'AIMA Lightning', + bikePlate: 'Dhaka Metro Cha-9012', + bikeBattery: 87, type: 'single', status: 'active', startDate: '2024-01-15', - deposit: 5000, - dailyRate: 300, - totalPaid: 81900, + contractMonths: 12, + subscriptionType: 'monthly', + deposit: 3000, + depositPaymentMethod: 'cash', + depositPaid: true, + dailyRate: 150, + weeklyRate: 900, + monthlyRate: 3500, + totalPaid: 38500, dueRental: 0, + paymentStatus: 'paid', + penaltyLevel: 'none', + penaltyAmount: 0, hubId: 'HUB-001', - hubName: 'Gulshan Hub' + hubName: 'Gulshan Hub', + imagesApproved: true, + initialImages: [ + { id: 'img1', type: 'front', url: 'https://picsum.photos/seed/bike-front/400/300', approved: true }, + { id: 'img2', type: 'back', url: 'https://picsum.photos/seed/bike-back/400/300', approved: true }, + { id: 'img3', type: 'left', url: 'https://picsum.photos/seed/bike-left/400/300', approved: true }, + { id: 'img4', type: 'right', url: 'https://picsum.photos/seed/bike-right/400/300', approved: true }, + { id: 'img5', type: 'battery', url: 'https://picsum.photos/seed/bike-battery/400/300', approved: true }, + ], + createdAt: '2024-01-15', + acceptedAt: '2024-01-15', + activatedAt: '2024-01-16', + lockHistory: [ + { id: 'lh1', action: 'locked', reason: 'First payment overdue - Day 1 penalty', performedBy: 'System', performedAt: '2024-02-01' }, + { id: 'lh2', action: 'unlocked', reason: 'Payment received', performedBy: 'Admin Manager', performedAt: '2024-02-03' }, + { id: 'lh3', action: 'locked', reason: 'Second payment overdue - Day 2 penalty', performedBy: 'System', performedAt: '2024-03-01' }, + { id: 'lh4', action: 'unlocked', reason: 'Payment cleared', performedBy: 'Admin Manager', performedAt: '2024-03-02' }, + ], }, { id: 'RNT-002', bikeId: 'BIKE-002', userId: 'USR-002', - type: 'shared', + userName: 'Karim Hasan', + userPhone: '+8801812345678', + bikeModel: 'Yadea DT3', + bikePlate: 'Dhaka Metro Ba-5521', + bikeBattery: 65, + type: 'single', status: 'pending', - startDate: '2024-02-01', + startDate: '2024-02-10', + contractMonths: 3, + subscriptionType: 'monthly', deposit: 3000, - dailyRate: 200, - totalPaid: 2000, - dueRental: 0, + depositPaymentMethod: 'bank', + depositPaid: true, + dailyRate: 150, + weeklyRate: 900, + monthlyRate: 3500, + totalPaid: 3000, + dueRental: 3500, + paymentStatus: 'partial', + penaltyLevel: 'none', + penaltyAmount: 0, hubId: 'HUB-002', - hubName: 'Banani Hub' + hubName: 'Banani Hub', + imagesApproved: false, + initialImages: [ + { id: 'img1', type: 'front', url: '', approved: false }, + { id: 'img2', type: 'back', url: '', approved: false }, + { id: 'img3', type: 'left', url: '', approved: false }, + { id: 'img4', type: 'right', url: '', approved: false }, + { id: 'img5', type: 'battery', url: '', approved: false }, + ], + createdAt: '2024-02-10', }, { id: 'RNT-003', bikeId: 'BIKE-003', - userId: 'USR-003', + userId: 'USR-001', + userName: 'Rahim Ahmed', + userPhone: '+8801712345678', + bikeModel: 'AIMA EM5', + bikePlate: 'Dhaka Metro Ko-1234', + bikeBattery: 92, type: 'rent-to-own', status: 'completed', startDate: '2023-06-01', endDate: '2023-12-01', + contractMonths: 6, + subscriptionType: 'monthly', deposit: 10000, + depositPaymentMethod: 'wallet', + depositPaid: true, dailyRate: 500, - totalPaid: 150000, + weeklyRate: 3000, + monthlyRate: 12000, + totalPaid: 82000, + dueRental: 0, + paymentStatus: 'paid', + penaltyLevel: 'none', + penaltyAmount: 0, hubId: 'HUB-001', - hubName: 'Gulshan Hub' - } + hubName: 'Gulshan Hub', + imagesApproved: true, + createdAt: '2023-06-01', + acceptedAt: '2023-06-01', + activatedAt: '2023-06-02', + }, + { + id: 'RNT-004', + bikeId: 'BIKE-005', + userId: 'USR-005', + userName: 'Farid Ahmed', + userPhone: '+8801612345678', + bikeModel: 'Yadea G5', + bikePlate: 'Dhaka Metro Ha-5678', + bikeBattery: 45, + type: 'shared', + status: 'locked', + startDate: '2024-01-20', + contractMonths: 1, + subscriptionType: 'weekly', + deposit: 2000, + depositPaymentMethod: 'cash', + depositPaid: true, + dailyRate: 100, + weeklyRate: 600, + monthlyRate: 2200, + totalPaid: 2600, + dueRental: 600, + paymentStatus: 'overdue', + penaltyLevel: 'day3', + penaltyAmount: 1000, + lockedAt: '2024-02-05', + lockedReason: 'Payment overdue - bike locked', + hubId: 'HUB-003', + hubName: 'Uttara Hub', + imagesApproved: true, + createdAt: '2024-01-20', + acceptedAt: '2024-01-20', + activatedAt: '2024-01-21', + lockHistory: [ + { id: 'lh1', action: 'locked', reason: 'Weekly payment overdue', performedBy: 'System', performedAt: '2024-02-05' }, + ], + }, ]; -const mockBikes: Record = { - 'BIKE-001': { - id: 'BIKE-001', - model: 'AIMA Lightning', - plate: 'Dhaka Metro Cha-9012', - status: 'active', - odometer: 3510, - batteryHealth: 85, - images: [ - { id: 'img1', type: 'front', url: '' }, - { id: 'img2', type: 'back', url: '' }, - { id: 'img3', type: 'left', url: '' }, - { id: 'img4', type: 'right', url: '' } - ] - }, - 'BIKE-002': { - id: 'BIKE-002', - model: 'Yadea DT3', - plate: 'Dhaka Metro Ba-5521', - status: 'active', - odometer: 2100, - batteryHealth: 92, - images: [] - } -}; - -const mockUsers: Record = { - 'USR-001': { - id: 'USR-001', - name: 'Rahim Ahmed', - phone: '+8801712345678', - email: 'rahim@example.com', - walletBalance: 2100, - membership: 'vip', - joinedFrom: 'Facebook', - kycStatus: 'verified', - insurance: 'active', - insuranceExpiry: '2024-12-01' - }, - 'USR-002': { - id: 'USR-002', - name: 'Karim Hasan', - phone: '+8801812345678', - email: 'karim@example.com', - walletBalance: 500, - membership: 'standard', - joinedFrom: 'Referral', - kycStatus: 'pending', - insurance: 'none' - } -}; - const mockHubs = [ - { id: 'HUB-001', name: 'Gulshan Hub', address: 'Gulshan 1, Dhaka' }, - { id: 'HUB-002', name: 'Banani Hub', address: 'Banani, Dhaka' }, - { id: 'HUB-003', name: 'Uttara Hub', address: 'Uttara, Dhaka' } + { id: 'HUB-001', name: 'Gulshan Hub' }, + { id: 'HUB-002', name: 'Banani Hub' }, + { id: 'HUB-003', name: 'Uttara Hub' }, + { id: 'HUB-004', name: 'Mirpur Hub' }, +]; + +const mockDamageHistory = [ + { id: 'DMG-001', date: '2024-02-10', description: 'Minor scratch on left mirror', severity: 'minor', status: 'resolved' }, + { id: 'DMG-002', date: '2024-03-05', description: 'Front fender dented', severity: 'moderate', status: 'reported' }, + { id: 'DMG-003', date: '2024-03-15', description: 'Rear tire puncture', severity: 'minor', status: 'resolved' }, +]; + +const mockDocuments = [ + { id: 'DOC-001', name: 'Rental Agreement', type: 'agreement', uploadedAt: '2024-01-15', downloaded: true }, + { id: 'DOC-002', name: 'Bike Delivery Form', type: 'form', uploadedAt: '2024-01-16', downloaded: true }, + { id: 'DOC-003', name: 'KYC Documents', type: 'kyc', uploadedAt: '2024-01-14', downloaded: true }, + { id: 'DOC-004', name: 'Insurance Policy', type: 'insurance', uploadedAt: '2024-01-15', downloaded: false }, +]; + +const mockMessages = [ + { id: 'MSG-001', date: '2024-03-15', text: 'Payment reminder sent', type: 'system' }, + { id: 'MSG-002', date: '2024-03-10', text: 'Bike inspection scheduled', type: 'system' }, + { id: 'MSG-003', date: '2024-02-20', text: 'User requested repair', type: 'user' }, ]; export default function RentalDetailPage() { const params = useParams(); - const router = useRouter(); const id = params.id as string; const [rental, setRental] = useState(null); - const [user, setUser] = useState(null); - const [bike, setBike] = useState(null); const [editMode, setEditMode] = useState(false); const [notes, setNotes] = useState([ - { id: 'n1', text: 'Initial rental started. Bike in good condition.', createdAt: '2024-01-15' }, - { id: 'n2', text: 'Battery replaced on 2024-01-20.', createdAt: '2024-01-20' } + { id: 'n1', text: 'Rental started successfully. Bike in good condition.', createdAt: '2024-01-15' }, + { id: 'n2', text: 'First monthly payment received.', createdAt: '2024-02-15' }, ]); const [newNote, setNewNote] = useState(''); const [editForm, setEditForm] = useState>({}); const [showLockModal, setShowLockModal] = useState(false); + const [showUnlockModal, setShowUnlockModal] = useState(false); + const [showCancelModal, setShowCancelModal] = useState(false); const [lockReason, setLockReason] = useState(''); - const [dueAmount, setDueAmount] = useState(0); - const [showDueModal, setShowDueModal] = useState(false); - const [showImageModal, setShowImageModal] = useState(false); - const [uploadImageType, setUploadImageType] = useState(''); + const [rejectNote, setRejectNote] = useState(''); + const [showRejectModal, setShowRejectModal] = useState(false); + + const [acceptPermission, setAcceptPermission] = useState(false); + const [rejectPermission, setRejectPermission] = useState(false); + const [cancelPermission, setCancelPermission] = useState(false); + const [editPermission, setEditPermission] = useState(false); + const [imageApprovePermission, setImageApprovePermission] = useState(false); + const [lockPermission, setLockPermission] = useState(false); + const [unlockPermission, setUnlockPermission] = useState(false); + + useEffect(() => { + setAcceptPermission(canRentalAccept()); + setRejectPermission(canRentalReject()); + setCancelPermission(canRentalCancel()); + setEditPermission(canRentalEdit()); + setImageApprovePermission(canRentalImageApprove()); + setLockPermission(canRentalLock()); + setUnlockPermission(canRentalUnlock()); + }, []); useEffect(() => { const found = mockRentals.find(r => r.id === id); if (found) { setRental(found); setEditForm(found); - setUser(mockUsers[found.userId as keyof typeof mockUsers] || null); - setBike(mockBikes[found.bikeId as keyof typeof mockBikes] || mockBikes['BIKE-001']); } }, [id]); @@ -201,17 +312,82 @@ export default function RentalDetailPage() {

Rental not found

- +
); } + const getStatusBadge = (status: RentalStatus) => { + const styles: Record = { + active: 'bg-green-100 text-green-700', + pending: 'bg-amber-100 text-amber-700', + accepted: 'bg-blue-100 text-blue-700', + completed: 'bg-indigo-100 text-indigo-700', + cancelled: 'bg-slate-100 text-slate-600', + locked: 'bg-red-100 text-red-700', + disputed: 'bg-orange-100 text-orange-700', + }; + const labels: Record = { + active: 'Active', + pending: 'Pending', + accepted: 'Accepted', + completed: 'Completed', + cancelled: 'Cancelled', + locked: 'Locked', + disputed: 'Disputed', + }; + return { style: styles[status], label: labels[status] || status }; + }; + + const getTypeBadge = (type: RentalType) => { + const styles: Record = { + single: 'bg-blue-100 text-blue-700', + shared: 'bg-purple-100 text-purple-700', + 'rent-to-own': 'bg-emerald-100 text-emerald-700', + }; + const labels: Record = { + single: 'Single Rent', + shared: 'Share EV', + 'rent-to-own': 'Rent to Own', + }; + return { style: styles[type], label: labels[type] || type }; + }; + + const getPaymentStatusBadge = (status: PaymentStatus) => { + const styles: Record = { + paid: 'bg-green-100 text-green-700', + partial: 'bg-amber-100 text-amber-700', + overdue: 'bg-red-100 text-red-700', + unpaid: 'bg-slate-100 text-slate-600', + }; + const labels: Record = { + paid: 'Paid', + partial: 'Partial', + overdue: 'Overdue', + unpaid: 'Unpaid', + }; + return { style: styles[status], label: labels[status] || status }; + }; + + const getPenaltyBadge = (level: PenaltyLevel) => { + const styles: Record = { + none: 'bg-slate-100 text-slate-500', + day1: 'bg-amber-100 text-amber-700', + day2: 'bg-orange-100 text-orange-700', + day3: 'bg-red-100 text-red-700', + }; + const labels: Record = { + none: 'None', + day1: '1st Day', + day2: '2nd Day', + day3: 'Bike Lock', + }; + return { style: styles[level], label: labels[level] || level }; + }; + const handleSaveEdit = () => { setRental(prev => prev ? { ...prev, ...editForm } : null); setEditMode(false); @@ -219,25 +395,40 @@ export default function RentalDetailPage() { const handleLockRental = () => { if (!lockReason.trim()) return; - setRental(prev => prev ? { ...prev, status: 'locked', lockedAt: new Date().toISOString(), lockedReason: lockReason } : null); + const newEvent: LockEvent = { id: `lh${Date.now()}`, action: 'locked', reason: lockReason, performedBy: 'Admin', performedAt: new Date().toISOString().split('T')[0] }; + setRental(prev => prev ? { ...prev, status: 'locked', lockedAt: new Date().toISOString().split('T')[0], lockedReason: lockReason, lockHistory: [...(prev.lockHistory || []), newEvent] } : null); setShowLockModal(false); setLockReason(''); }; const handleUnlockRental = () => { - setRental(prev => prev ? { ...prev, status: 'active', lockedAt: undefined, lockedReason: undefined } : null); + const newEvent: LockEvent = { id: `lh${Date.now()}`, action: 'unlocked', performedBy: 'Admin', performedAt: new Date().toISOString().split('T')[0] }; + setRental(prev => prev ? { ...prev, status: 'active', lockedAt: undefined, lockedReason: undefined, lockHistory: [...(prev.lockHistory || []), newEvent] } : null); + setShowUnlockModal(false); }; const handleCancelRental = () => { - if (confirm('Are you sure you want to cancel this rental? This action cannot be undone.')) { - setRental(prev => prev ? { ...prev, status: 'cancelled', endDate: new Date().toISOString() } : null); - } + setRental(prev => prev ? { ...prev, status: 'cancelled', endDate: new Date().toISOString().split('T')[0] } : null); + setShowCancelModal(false); }; - const handleAddDue = () => { - setRental(prev => prev ? { ...prev, dueRental: (prev.dueRental || 0) + dueAmount } : null); - setShowDueModal(false); - setDueAmount(0); + const handleAcceptRental = () => { + setRental(prev => prev ? { ...prev, status: 'accepted', acceptedAt: new Date().toISOString().split('T')[0] } : null); + }; + + const handleRejectRental = () => { + if (!rejectNote.trim()) return; + setRental(prev => prev ? { ...prev, status: 'cancelled', rejectNote } : null); + setShowRejectModal(false); + setRejectNote(''); + }; + + const handleApproveImages = () => { + setRental(prev => prev ? { ...prev, imagesApproved: true } : null); + }; + + const handleActivateRental = () => { + setRental(prev => prev ? { ...prev, status: 'active', activatedAt: new Date().toISOString().split('T')[0] } : null); }; const handleAddNote = () => { @@ -246,102 +437,80 @@ export default function RentalDetailPage() { setNewNote(''); }; - const handleUpdateOdometer = (value: number) => { - if (bike) setBike(prev => prev ? { ...prev, odometer: value } : null); + const statusBadge = getStatusBadge(rental.status); + const typeBadge = getTypeBadge(rental.type); + const paymentBadge = getPaymentStatusBadge(rental.paymentStatus); + const penaltyBadge = getPenaltyBadge(rental.penaltyLevel); + + const getBatteryColor = (level: number) => { + if (level > 70) return 'text-green-600'; + if (level > 40) return 'text-amber-600'; + return 'text-red-600'; }; - const handleUpdateBattery = (value: number) => { - if (bike) setBike(prev => prev ? { ...prev, batteryHealth: value } : null); - }; - - const handleUploadImage = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file || !bike) return; - const url = URL.createObjectURL(file); - setBike(prev => prev ? { - ...prev, - images: prev.images.map(img => img.type === uploadImageType ? { ...img, url } : img) - } : null); - setShowImageModal(false); - }; - - const statusColors = { - active: 'bg-green-100 text-green-700', - pending: 'bg-amber-100 text-amber-700', - completed: 'bg-blue-100 text-blue-700', - disputed: 'bg-red-100 text-red-700', - cancelled: 'bg-slate-100 text-slate-700', - locked: 'bg-red-100 text-red-700' - }; - const statusLabels: Record = { - active: 'Active', - pending: 'Pending', - completed: 'Completed', - disputed: 'Disputed', - cancelled: 'Cancelled', - locked: 'Locked' - }; + const timelineSteps = [ + { key: 'created', label: 'Created', date: rental.createdAt, completed: true }, + { key: 'accepted', label: 'Accepted', date: rental.acceptedAt, completed: !!rental.acceptedAt }, + { key: 'imagesApproved', label: 'Images Approved', date: rental.imagesApproved ? rental.acceptedAt : undefined, completed: rental.imagesApproved }, + { key: 'activated', label: 'Activated', date: rental.activatedAt, completed: !!rental.activatedAt }, + ]; return (
- +
-
+
-
-

{rental.id}

- - {statusLabels[rental.status] || rental.status} +
+

{rental.id}

+ + {statusBadge.label} + + + {typeBadge.label} - {rental.status === 'locked' && ( - - Locked - - )}
-

Started {rental.startDate} • From {rental.hubName || 'N/A'}

+

Created: {rental.createdAt}

{editMode ? ( <> - - ) : ( <> - - {rental.status === 'active' && ( - <> - - {rental.status === 'active' ? ( - - ) : rental.status === 'locked' ? ( - - ) : null} - + {/* {editPermission && ( + + )} */} + {cancelPermission && rental.status !== 'cancelled' && rental.status !== 'completed' && ( + )} - {rental.status !== 'cancelled' && rental.status !== 'completed' && ( - + )} + {unlockPermission && rental.status === 'locked' && ( + + )} + {rental.status === 'accepted' && ( + )} @@ -352,125 +521,175 @@ export default function RentalDetailPage() {
-
+
-

Total Spent

+

Total Paid

৳{rental.totalPaid.toLocaleString()}

-
-

Wallet Balance

-

৳{user?.walletBalance.toLocaleString() || 0}

+
+

Due Rental

+

৳{rental.dueRental.toLocaleString()}

-

Deposit Paid

-

৳{rental.deposit.toLocaleString()}

-
-
-

Due Rental

-

৳{(rental.dueRental || 0).toLocaleString()}

+

Payment Status

+ + {paymentBadge.label} +
-
-

- Rented Bike Details -

- {bike && ( -
-
-
Model{bike.model}
-
Plate{bike.plate}
-
Type{rental.type}
-
Daily Rate৳{rental.dailyRate}/day
+ {rental.penaltyLevel !== 'none' && ( +
+
+
+

Penalty

+

৳{rental.penaltyAmount.toLocaleString()}

+
+ + {penaltyBadge.label} + +
+
+ )} + +
+
+

+ Bike Info +

+
+ {editMode ? ( + setEditForm({ ...editForm, bikeModel: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Bike Model" + /> + ) : ( +
Model{rental.bikeModel}
+ )} +
Plate{rental.bikePlate}
+
+ Battery + {rental.bikeBattery}%
- )} -
- -
-

- Mileage Tracking -

-
-
- - handleUpdateOdometer(Number(e.target.value))} - className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm mt-1" - disabled={rental.status !== 'active'} - /> -
-
- -

{(bike?.odometer || 0).toLocaleString()} km

-
-
-
-

- Battery Health -

-
-
- - handleUpdateBattery(Number(e.target.value))} - className="w-full px-3 py-2 border border-green-200 rounded-lg text-sm mt-1" - disabled={rental.status !== 'active'} - /> -
-
- -

70 ? 'text-green-700' : (bike?.batteryHealth || 0) > 40 ? 'text-amber-700' : 'text-red-700'}`}> - {(bike?.batteryHealth || 0) > 70 ? 'Good' : (bike?.batteryHealth || 0) > 40 ? 'Fair' : 'Poor'} -

-

Estimated Range: {Math.round((bike?.batteryHealth || 0) * 1)} km

-
-
-
- -
-
-

- Bike Images +
+

+ User Info

- {rental.status === 'active' && ( - - )} +
+
Name{rental.userName}
+ + +
-
- {['front', 'back', 'left', 'right'].map(type => { - const img = bike?.images.find(i => i.type === type); - return ( -
- {img?.url ? ( - {type} - ) : ( -
- -

{type}

-
- )} -
- ); - })} + +
+

+ Hub Info +

+
+
Hub{rental.hubName}
+
+
+ +
+

+ Duration +

+
+
Start Date{rental.startDate}
+ {rental.endDate &&
End Date{rental.endDate}
} + {rental.contractMonths &&
Contract{rental.contractMonths} Months
} +
+
+ +
+

+ Subscription +

+
+
Type{rental.subscriptionType}
+
+ Rate + + ৳{rental.subscriptionType === 'daily' ? rental.dailyRate : rental.subscriptionType === 'weekly' ? rental.weeklyRate : rental.monthlyRate}/ + {rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'} + +
+
+
+ +
+

+ Deposit +

+
+
Amount৳{rental.deposit.toLocaleString()}
+
Method{rental.depositPaymentMethod}
+
+ Status + + {rental.depositPaid ? 'Paid' : 'Unpaid'} + +
+
+ {/* {(rental.status === 'pending' || rental.status === 'accepted') && rental.initialImages && ( */} + {rental.initialImages && ( +
+
+

+ Initial Condition Images +

+ {imageApprovePermission && !rental.imagesApproved && ( + + )} + {rental.imagesApproved && ( + + Approved + + )} +
+
+ {(['front', 'back', 'left', 'right', 'battery'] as const).map(type => { + const img = rental.initialImages?.find(i => i.type === type); + return ( +
+ {img?.url ? ( + {type} + ) : ( +
+ +

{type}

+
+ )} +
+ ); + })} +
+
+ )} + + +
-

+

Notes ({notes.length})

{notes.length > 0 ? ( @@ -485,94 +704,185 @@ export default function RentalDetailPage() { ) : (

No notes yet.

)} - {rental.status === 'active' && ( -
- setNewNote(e.target.value)} - placeholder="Add notes about the bike condition, issues, etc..." - className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> - -
- )} +
+ setNewNote(e.target.value)} + placeholder="Add a note..." + className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> + +

-
-

- User Info -

-
-
Name{user?.name}
-
Phone{user?.phone}
-
Email{user?.email || '-'}
- {user && ( - - )} -
-
- -
-

- Membership & Insurance -

-
-
Membership{user?.membership}
-
KYC Status{user?.kycStatus}
-
Insurance{user?.insurance}
- {user?.insuranceExpiry &&
Insurance Expiry{user.insuranceExpiry}
} -
-
- -
-

- Hub Info -

-
-
Hub{rental.hubName || '-'}
- {editMode ? ( - - ) : ( -
Joined From{user?.joinedFrom || '-'}
- )} -
-
- {rental.status === 'locked' && (

Locked Info

-
Locked At{rental.lockedAt?.split('T')[0]}
+
Locked At{rental.lockedAt}
Reason{rental.lockedReason}
)} + + +
+

+ Lock History +

+ {rental.lockHistory && rental.lockHistory.length > 0 ? ( +
+ {rental.lockHistory.map(event => ( +
+
+ {event.action === 'locked' ? ( + + ) : ( + + )} +
+
+
+ + {event.action === 'locked' ? 'Locked' : 'Unlocked'} + + {event.performedAt} +
+ {event.reason && ( +

{event.reason}

+ )} +

By: {event.performedBy}

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

No lock history available.

+ )} +
+ +
+
+

+ Damage History +

+ +
+
+ {mockDamageHistory.map(damage => ( +
+
+

{damage.description}

+

{damage.date}

+
+
+ + {damage.severity} + + + {damage.status} + +
+
+ ))} +
+
+ +
+
+

+ Rental Documents +

+ +
+
+ {mockDocuments.map(doc => ( +
+
+ +
+

{doc.name}

+

{doc.uploadedAt}

+
+
+ +
+ ))} +
+
+ + {rental.status === 'pending' && ( +
+

Biker Response

+
+ {acceptPermission && ( + + )} + {rejectPermission && ( + + )} +
+
+ )} + + {rental.status === 'accepted' && ( +
+
+ + Accepted on {rental.acceptedAt} +
+
+ )} + + {rental.status === 'cancelled' && rental.rejectNote && ( +
+
+ + Rejected +
+

Reason: {rental.rejectNote}

+
+ )} + +
+

+ Activity Timeline +

+
+ {timelineSteps.map((step, idx) => ( +
+
+ {step.completed ? : {idx + 1}} +
+
+

{step.label}

+ {step.date &&

{step.date}

} +
+
+ ))} +
+
@@ -604,92 +914,72 @@ export default function RentalDetailPage() {
)} - {showDueModal && ( + {showUnlockModal && (

- Add Due Rental + Unlock Rental

- +
- - setDueAmount(Math.max(0, Number(e.target.value)))} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" - placeholder="Enter amount..." - /> -

Current Due: ৳{rental.dueRental || 0}

+

Are you sure you want to unlock this rental? The bike will become active again.

- - + +
)} - {showImageModal && ( + {showCancelModal && (
-

Upload Bike Image

- +

+ Cancel Rental +

+
- - - {uploadImageType && ( -
- -
- )} +

Are you sure you want to cancel this rental? This action cannot be undone.

- + + +
+
+
+ )} + + {showRejectModal && ( +
+
+
+

+ Reject Rental +

+ +
+
+ +