2026-04-26 14:56:12 +06:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
2026-05-10 01:22:17 +06:00
|
|
|
|
import { useParams } from 'next/navigation';
|
|
|
|
|
|
import Link from 'next/link';
|
2026-04-26 14:56:12 +06:00
|
|
|
|
import {
|
|
|
|
|
|
ArrowLeft, Bike, User, Calendar, DollarSign, Wallet, Shield, CheckCircle, XCircle,
|
|
|
|
|
|
Clock, Edit, Save, Plus, Trash2, Image, Upload, Lock, Unlock, AlertTriangle, MessageSquare, MapPin,
|
2026-05-10 01:22:17 +06:00
|
|
|
|
Phone, MessageCircle, Play, Check, X, FileText, Download
|
2026-04-26 14:56:12 +06:00
|
|
|
|
} from 'lucide-react';
|
2026-05-10 01:22:17 +06:00
|
|
|
|
import {
|
|
|
|
|
|
canRentalAccept, canRentalReject, canRentalCancel, canRentalEdit,
|
|
|
|
|
|
canRentalImageApprove, canRentalLock, canRentalUnlock
|
|
|
|
|
|
} from '../../../../lib/auth';
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
type RentalStatus = 'pending' | 'accepted' | 'active' | 'completed' | 'cancelled' | 'locked' | 'disputed';
|
2026-04-26 14:56:12 +06:00
|
|
|
|
type RentalType = 'single' | 'shared' | 'rent-to-own';
|
2026-05-10 01:22:17 +06:00
|
|
|
|
type PaymentMethod = 'cash' | 'bank' | 'wallet';
|
|
|
|
|
|
type PaymentStatus = 'paid' | 'partial' | 'overdue' | 'unpaid';
|
|
|
|
|
|
type PenaltyLevel = 'none' | 'day1' | 'day2' | 'day3';
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
|
|
|
|
|
interface BikeImage {
|
|
|
|
|
|
id: string;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
url: string;
|
|
|
|
|
|
type: 'front' | 'back' | 'left' | 'right' | 'battery';
|
|
|
|
|
|
approved?: boolean;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Note {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Rental {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
bikeId: string;
|
|
|
|
|
|
userId: string;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
userName: string;
|
|
|
|
|
|
userPhone: string;
|
|
|
|
|
|
bikeModel: string;
|
|
|
|
|
|
bikePlate: string;
|
|
|
|
|
|
bikeBattery: number;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
type: RentalType;
|
|
|
|
|
|
status: RentalStatus;
|
|
|
|
|
|
startDate: string;
|
|
|
|
|
|
endDate?: string;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
contractMonths?: number;
|
|
|
|
|
|
subscriptionType: 'daily' | 'weekly' | 'monthly';
|
2026-04-26 14:56:12 +06:00
|
|
|
|
deposit: number;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
depositPaymentMethod: PaymentMethod;
|
|
|
|
|
|
depositPaid: boolean;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
dailyRate: number;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
weeklyRate: number;
|
|
|
|
|
|
monthlyRate: number;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
totalPaid: number;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
dueRental: number;
|
|
|
|
|
|
paymentStatus: PaymentStatus;
|
|
|
|
|
|
penaltyLevel: PenaltyLevel;
|
|
|
|
|
|
penaltyAmount: number;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
lockedAt?: string;
|
|
|
|
|
|
lockedReason?: string;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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;
|
2026-04-26 14:56:12 +06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const mockRentals: Rental[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'RNT-001',
|
|
|
|
|
|
bikeId: 'BIKE-001',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
userId: 'USR-003',
|
|
|
|
|
|
userName: 'Jamal Uddin',
|
|
|
|
|
|
userPhone: '+8801912345678',
|
|
|
|
|
|
bikeModel: 'AIMA Lightning',
|
|
|
|
|
|
bikePlate: 'Dhaka Metro Cha-9012',
|
|
|
|
|
|
bikeBattery: 87,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
type: 'single',
|
|
|
|
|
|
status: 'active',
|
|
|
|
|
|
startDate: '2024-01-15',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
contractMonths: 12,
|
|
|
|
|
|
subscriptionType: 'monthly',
|
|
|
|
|
|
deposit: 3000,
|
|
|
|
|
|
depositPaymentMethod: 'cash',
|
|
|
|
|
|
depositPaid: true,
|
|
|
|
|
|
dailyRate: 150,
|
|
|
|
|
|
weeklyRate: 900,
|
|
|
|
|
|
monthlyRate: 3500,
|
|
|
|
|
|
totalPaid: 38500,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
dueRental: 0,
|
2026-05-10 01:22:17 +06:00
|
|
|
|
paymentStatus: 'paid',
|
|
|
|
|
|
penaltyLevel: 'none',
|
|
|
|
|
|
penaltyAmount: 0,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
hubId: 'HUB-001',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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' },
|
|
|
|
|
|
],
|
2026-04-26 14:56:12 +06:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'RNT-002',
|
|
|
|
|
|
bikeId: 'BIKE-002',
|
|
|
|
|
|
userId: 'USR-002',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
userName: 'Karim Hasan',
|
|
|
|
|
|
userPhone: '+8801812345678',
|
|
|
|
|
|
bikeModel: 'Yadea DT3',
|
|
|
|
|
|
bikePlate: 'Dhaka Metro Ba-5521',
|
|
|
|
|
|
bikeBattery: 65,
|
|
|
|
|
|
type: 'single',
|
2026-04-26 14:56:12 +06:00
|
|
|
|
status: 'pending',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
startDate: '2024-02-10',
|
|
|
|
|
|
contractMonths: 3,
|
|
|
|
|
|
subscriptionType: 'monthly',
|
2026-04-26 14:56:12 +06:00
|
|
|
|
deposit: 3000,
|
2026-05-10 01:22:17 +06:00
|
|
|
|
depositPaymentMethod: 'bank',
|
|
|
|
|
|
depositPaid: true,
|
|
|
|
|
|
dailyRate: 150,
|
|
|
|
|
|
weeklyRate: 900,
|
|
|
|
|
|
monthlyRate: 3500,
|
|
|
|
|
|
totalPaid: 3000,
|
|
|
|
|
|
dueRental: 3500,
|
|
|
|
|
|
paymentStatus: 'partial',
|
|
|
|
|
|
penaltyLevel: 'none',
|
|
|
|
|
|
penaltyAmount: 0,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
hubId: 'HUB-002',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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',
|
2026-04-26 14:56:12 +06:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'RNT-003',
|
|
|
|
|
|
bikeId: 'BIKE-003',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
userId: 'USR-001',
|
|
|
|
|
|
userName: 'Rahim Ahmed',
|
|
|
|
|
|
userPhone: '+8801712345678',
|
|
|
|
|
|
bikeModel: 'AIMA EM5',
|
|
|
|
|
|
bikePlate: 'Dhaka Metro Ko-1234',
|
|
|
|
|
|
bikeBattery: 92,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
type: 'rent-to-own',
|
|
|
|
|
|
status: 'completed',
|
|
|
|
|
|
startDate: '2023-06-01',
|
|
|
|
|
|
endDate: '2023-12-01',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
contractMonths: 6,
|
|
|
|
|
|
subscriptionType: 'monthly',
|
2026-04-26 14:56:12 +06:00
|
|
|
|
deposit: 10000,
|
2026-05-10 01:22:17 +06:00
|
|
|
|
depositPaymentMethod: 'wallet',
|
|
|
|
|
|
depositPaid: true,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
dailyRate: 500,
|
2026-05-10 01:22:17 +06:00
|
|
|
|
weeklyRate: 3000,
|
|
|
|
|
|
monthlyRate: 12000,
|
|
|
|
|
|
totalPaid: 82000,
|
|
|
|
|
|
dueRental: 0,
|
|
|
|
|
|
paymentStatus: 'paid',
|
|
|
|
|
|
penaltyLevel: 'none',
|
|
|
|
|
|
penaltyAmount: 0,
|
2026-04-26 14:56:12 +06:00
|
|
|
|
hubId: 'HUB-001',
|
2026-05-10 01:22:17 +06:00
|
|
|
|
hubName: 'Gulshan Hub',
|
|
|
|
|
|
imagesApproved: true,
|
|
|
|
|
|
createdAt: '2023-06-01',
|
|
|
|
|
|
acceptedAt: '2023-06-01',
|
|
|
|
|
|
activatedAt: '2023-06-02',
|
2026-04-26 14:56:12 +06:00
|
|
|
|
},
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{
|
|
|
|
|
|
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' },
|
|
|
|
|
|
],
|
2026-04-26 14:56:12 +06:00
|
|
|
|
},
|
2026-05-10 01:22:17 +06:00
|
|
|
|
];
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
|
|
|
|
|
const mockHubs = [
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{ 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' },
|
2026-04-26 14:56:12 +06:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export default function RentalDetailPage() {
|
|
|
|
|
|
const params = useParams();
|
|
|
|
|
|
const id = params.id as string;
|
|
|
|
|
|
|
|
|
|
|
|
const [rental, setRental] = useState<Rental | null>(null);
|
|
|
|
|
|
const [editMode, setEditMode] = useState(false);
|
|
|
|
|
|
const [notes, setNotes] = useState<Note[]>([
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{ 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' },
|
2026-04-26 14:56:12 +06:00
|
|
|
|
]);
|
|
|
|
|
|
const [newNote, setNewNote] = useState('');
|
|
|
|
|
|
const [editForm, setEditForm] = useState<Partial<Rental>>({});
|
|
|
|
|
|
const [showLockModal, setShowLockModal] = useState(false);
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const [showUnlockModal, setShowUnlockModal] = useState(false);
|
|
|
|
|
|
const [showCancelModal, setShowCancelModal] = useState(false);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
const [lockReason, setLockReason] = useState('');
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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());
|
|
|
|
|
|
}, []);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const found = mockRentals.find(r => r.id === id);
|
|
|
|
|
|
if (found) {
|
|
|
|
|
|
setRental(found);
|
|
|
|
|
|
setEditForm(found);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [id]);
|
|
|
|
|
|
|
|
|
|
|
|
if (!rental) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="p-6 flex items-center justify-center min-h-[50vh]">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<Bike className="w-16 h-16 text-slate-300 mx-auto mb-4" />
|
|
|
|
|
|
<p className="text-slate-500">Rental not found</p>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<Link href="/admin/rentals" className="mt-4 px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm inline-block">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
Back to Rentals
|
2026-05-10 01:22:17 +06:00
|
|
|
|
</Link>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const getStatusBadge = (status: RentalStatus) => {
|
|
|
|
|
|
const styles: Record<RentalStatus, string> = {
|
|
|
|
|
|
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<RentalStatus, string> = {
|
|
|
|
|
|
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<RentalType, string> = {
|
|
|
|
|
|
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<RentalType, string> = {
|
|
|
|
|
|
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<PaymentStatus, string> = {
|
|
|
|
|
|
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<PaymentStatus, string> = {
|
|
|
|
|
|
paid: 'Paid',
|
|
|
|
|
|
partial: 'Partial',
|
|
|
|
|
|
overdue: 'Overdue',
|
|
|
|
|
|
unpaid: 'Unpaid',
|
|
|
|
|
|
};
|
|
|
|
|
|
return { style: styles[status], label: labels[status] || status };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getPenaltyBadge = (level: PenaltyLevel) => {
|
|
|
|
|
|
const styles: Record<PenaltyLevel, string> = {
|
|
|
|
|
|
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<PenaltyLevel, string> = {
|
|
|
|
|
|
none: 'None',
|
|
|
|
|
|
day1: '1st Day',
|
|
|
|
|
|
day2: '2nd Day',
|
|
|
|
|
|
day3: 'Bike Lock',
|
|
|
|
|
|
};
|
|
|
|
|
|
return { style: styles[level], label: labels[level] || level };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-26 14:56:12 +06:00
|
|
|
|
const handleSaveEdit = () => {
|
|
|
|
|
|
setRental(prev => prev ? { ...prev, ...editForm } : null);
|
|
|
|
|
|
setEditMode(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleLockRental = () => {
|
|
|
|
|
|
if (!lockReason.trim()) return;
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
setShowLockModal(false);
|
|
|
|
|
|
setLockReason('');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleUnlockRental = () => {
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleCancelRental = () => {
|
2026-05-10 01:22:17 +06:00
|
|
|
|
setRental(prev => prev ? { ...prev, status: 'cancelled', endDate: new Date().toISOString().split('T')[0] } : null);
|
|
|
|
|
|
setShowCancelModal(false);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const handleAcceptRental = () => {
|
|
|
|
|
|
setRental(prev => prev ? { ...prev, status: 'accepted', acceptedAt: new Date().toISOString().split('T')[0] } : null);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const handleRejectRental = () => {
|
|
|
|
|
|
if (!rejectNote.trim()) return;
|
|
|
|
|
|
setRental(prev => prev ? { ...prev, status: 'cancelled', rejectNote } : null);
|
|
|
|
|
|
setShowRejectModal(false);
|
|
|
|
|
|
setRejectNote('');
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const handleApproveImages = () => {
|
|
|
|
|
|
setRental(prev => prev ? { ...prev, imagesApproved: true } : null);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const handleActivateRental = () => {
|
|
|
|
|
|
setRental(prev => prev ? { ...prev, status: 'active', activatedAt: new Date().toISOString().split('T')[0] } : null);
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
const handleAddNote = () => {
|
|
|
|
|
|
if (!newNote.trim()) return;
|
|
|
|
|
|
setNotes(prev => [...prev, { id: `n${Date.now()}`, text: newNote, createdAt: new Date().toISOString().split('T')[0] }]);
|
|
|
|
|
|
setNewNote('');
|
2026-04-26 14:56:12 +06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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';
|
2026-04-26 18:32:52 +06:00
|
|
|
|
};
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
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 },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-04-26 14:56:12 +06:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<Link href="/admin/rentals" className="flex items-center gap-2 text-slate-600 hover:text-slate-800 mb-4">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<ArrowLeft className="w-4 h-4" /> Back to Rentals
|
2026-05-10 01:22:17 +06:00
|
|
|
|
</Link>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
|
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="p-6 bg-gradient-to-r from-emerald-600 to-emerald-700 text-white">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
|
|
|
|
|
|
<div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="flex items-center gap-3 flex-wrap">
|
|
|
|
|
|
<h1 className="text-2xl font-extrabold">{rental.id}</h1>
|
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusBadge.style}`}>
|
|
|
|
|
|
{statusBadge.label}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${typeBadge.style}`}>
|
|
|
|
|
|
{typeBadge.label}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<p className="text-emerald-100 mt-1">Created: {rental.createdAt}</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex gap-2 flex-wrap">
|
|
|
|
|
|
{editMode ? (
|
|
|
|
|
|
<>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<button onClick={handleSaveEdit} className="px-4 py-2 bg-white text-emerald-700 rounded-lg text-sm hover:bg-emerald-50 flex items-center gap-2">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<Save className="w-4 h-4" /> Save
|
|
|
|
|
|
</button>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<button onClick={() => { setEditForm(rental); setEditMode(false); }} className="px-4 py-2 border border-white/30 text-white rounded-lg text-sm hover:bg-white/10">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{/* {editPermission && (
|
|
|
|
|
|
<button onClick={() => setEditMode(true)} className="px-4 py-2 bg-white text-emerald-700 rounded-lg text-sm hover:bg-emerald-50 flex items-center gap-2">
|
|
|
|
|
|
<Edit className="w-4 h-4" /> Edit
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)} */}
|
|
|
|
|
|
{cancelPermission && rental.status !== 'cancelled' && rental.status !== 'completed' && (
|
|
|
|
|
|
<button onClick={() => setShowCancelModal(true)} className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm hover:bg-red-600 flex items-center gap-2">
|
|
|
|
|
|
<XCircle className="w-4 h-4" /> Cancel
|
|
|
|
|
|
</button>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
)}
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{lockPermission && rental.status === 'active' && (
|
|
|
|
|
|
<button onClick={() => setShowLockModal(true)} className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700 flex items-center gap-2">
|
|
|
|
|
|
<Lock className="w-4 h-4" /> Lock
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{unlockPermission && rental.status === 'locked' && (
|
|
|
|
|
|
<button onClick={() => setShowUnlockModal(true)} className="px-4 py-2 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600 flex items-center gap-2">
|
|
|
|
|
|
<Unlock className="w-4 h-4" /> Unlock
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{rental.status === 'accepted' && (
|
|
|
|
|
|
<button onClick={handleActivateRental} className="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600 flex items-center gap-2">
|
|
|
|
|
|
<Play className="w-4 h-4" /> Activate
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
|
|
<div className="lg:col-span-2 space-y-6">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="bg-blue-50 p-4 rounded-xl border border-blue-100">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<p className="text-sm text-blue-600 font-medium">Total Paid</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<p className="text-xl font-bold text-blue-800">৳{rental.totalPaid.toLocaleString()}</p>
|
|
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="bg-amber-50 p-4 rounded-xl border border-amber-100">
|
|
|
|
|
|
<p className="text-sm text-amber-600 font-medium">Due Rental</p>
|
|
|
|
|
|
<p className="text-xl font-bold text-amber-800">৳{rental.dueRental.toLocaleString()}</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<p className="text-sm text-purple-600 font-medium">Payment Status</p>
|
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${paymentBadge.style}`}>
|
|
|
|
|
|
{paymentBadge.label}
|
|
|
|
|
|
</span>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{rental.penaltyLevel !== 'none' && (
|
|
|
|
|
|
<div className="bg-red-50 p-4 rounded-xl border border-red-100">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm text-red-600 font-medium">Penalty</p>
|
|
|
|
|
|
<p className="text-lg font-bold text-red-800">৳{rental.penaltyAmount.toLocaleString()}</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${penaltyBadge.style}`}>
|
|
|
|
|
|
{penaltyBadge.label}
|
|
|
|
|
|
</span>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<Bike className="w-5 h-5 text-blue-500" /> Bike Info
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{editMode ? (
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={editForm.bikeModel || ''}
|
|
|
|
|
|
onChange={(e) => setEditForm({ ...editForm, bikeModel: e.target.value })}
|
|
|
|
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
|
|
|
|
placeholder="Bike Model"
|
|
|
|
|
|
/>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Model</span><span className="text-sm font-medium text-slate-800">{rental.bikeModel}</span></div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Plate</span><span className="text-sm font-medium text-slate-800">{rental.bikePlate}</span></div>
|
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
|
<span className="text-sm text-slate-600">Battery</span>
|
|
|
|
|
|
<span className={`text-sm font-medium ${getBatteryColor(rental.bikeBattery)}`}>{rental.bikeBattery}%</span>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<User className="w-5 h-5 text-green-500" /> User Info
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Name</span><span className="text-sm font-medium text-slate-800">{rental.userName}</span></div>
|
|
|
|
|
|
<div className="flex justify-between items-center"><span className="text-sm text-slate-600">Phone</span><a href={`tel:${rental.userPhone}`} className="text-sm font-medium text-emerald-600 hover:text-emerald-700">{rental.userPhone}</a></div>
|
|
|
|
|
|
<div className="flex gap-2 mt-2 pt-2 border-t border-slate-100">
|
|
|
|
|
|
<a href={`tel:${rental.userPhone}`} className="flex-1 py-1.5 bg-green-500 text-white rounded-lg text-xs text-center hover:bg-green-600 flex items-center justify-center gap-1">
|
|
|
|
|
|
<Phone className="w-3 h-3" /> Call
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<a href={`sms:${rental.userPhone}`} className="flex-1 py-1.5 bg-blue-500 text-white rounded-lg text-xs text-center hover:bg-blue-600 flex items-center justify-center gap-1">
|
|
|
|
|
|
<MessageCircle className="w-3 h-3" /> SMS
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<MapPin className="w-5 h-5 text-purple-500" /> Hub Info
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Hub</span><span className="text-sm font-medium text-slate-800">{rental.hubName}</span></div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<Calendar className="w-5 h-5 text-amber-500" /> Duration
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Start Date</span><span className="text-sm font-medium text-slate-800">{rental.startDate}</span></div>
|
|
|
|
|
|
{rental.endDate && <div className="flex justify-between"><span className="text-sm text-slate-600">End Date</span><span className="text-sm font-medium text-slate-800">{rental.endDate}</span></div>}
|
|
|
|
|
|
{rental.contractMonths && <div className="flex justify-between"><span className="text-sm text-slate-600">Contract</span><span className="text-sm font-medium text-slate-800">{rental.contractMonths} Months</span></div>}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<Clock className="w-5 h-5 text-indigo-500" /> Subscription
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</h3>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Type</span><span className="text-sm font-medium text-slate-800 capitalize">{rental.subscriptionType}</span></div>
|
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
|
<span className="text-sm text-slate-600">Rate</span>
|
|
|
|
|
|
<span className="text-sm font-medium text-slate-800">
|
|
|
|
|
|
৳{rental.subscriptionType === 'daily' ? rental.dailyRate : rental.subscriptionType === 'weekly' ? rental.weeklyRate : rental.monthlyRate}/
|
|
|
|
|
|
{rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<Wallet className="w-5 h-5 text-emerald-500" /> Deposit
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Amount</span><span className="text-sm font-medium text-slate-800">৳{rental.deposit.toLocaleString()}</span></div>
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-slate-600">Method</span><span className="text-sm font-medium text-slate-800 capitalize">{rental.depositPaymentMethod}</span></div>
|
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
|
<span className="text-sm text-slate-600">Status</span>
|
|
|
|
|
|
<span className={`text-xs px-2 py-0.5 rounded ${rental.depositPaid ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
|
|
|
|
|
{rental.depositPaid ? 'Paid' : 'Unpaid'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{/* {(rental.status === 'pending' || rental.status === 'accepted') && rental.initialImages && ( */}
|
|
|
|
|
|
{rental.initialImages && (
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
|
|
|
|
|
|
<Image className="w-5 h-5 text-purple-500" /> Initial Condition Images
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{imageApprovePermission && !rental.imagesApproved && (
|
|
|
|
|
|
<button onClick={handleApproveImages} className="px-3 py-1.5 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-700 flex items-center gap-2">
|
|
|
|
|
|
<Check className="w-4 h-4" /> Approve Images
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{rental.imagesApproved && (
|
|
|
|
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full bg-green-100 text-green-700">
|
|
|
|
|
|
<CheckCircle className="w-3 h-3" /> Approved
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="grid grid-cols-5 gap-2">
|
|
|
|
|
|
{(['front', 'back', 'left', 'right', 'battery'] as const).map(type => {
|
|
|
|
|
|
const img = rental.initialImages?.find(i => i.type === type);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={type} className="aspect-video bg-slate-50 rounded-lg border border-slate-200 flex items-center justify-center overflow-hidden">
|
|
|
|
|
|
{img?.url ? (
|
|
|
|
|
|
<img src={img.url} alt={type} className="w-full h-full object-cover" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="text-center p-2">
|
|
|
|
|
|
<Image className="w-6 h-6 text-slate-300 mx-auto" />
|
|
|
|
|
|
<p className="text-xs text-slate-500 capitalize mt-1">{type}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<MessageSquare className="w-5 h-5" /> Notes ({notes.length})
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{notes.length > 0 ? (
|
|
|
|
|
|
<div className="space-y-2 mb-3">
|
|
|
|
|
|
{notes.map(note => (
|
|
|
|
|
|
<div key={note.id} className="bg-white p-3 rounded-lg">
|
|
|
|
|
|
<p className="text-sm text-slate-700">{note.text}</p>
|
|
|
|
|
|
<p className="text-xs text-slate-400 mt-1">{note.createdAt}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<p className="text-sm text-slate-500 mb-3">No notes yet.</p>
|
|
|
|
|
|
)}
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={newNote}
|
|
|
|
|
|
onChange={(e) => setNewNote(e.target.value)}
|
|
|
|
|
|
placeholder="Add a note..."
|
|
|
|
|
|
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button onClick={handleAddNote} disabled={!newNote.trim()} className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm disabled:opacity-50">
|
|
|
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-4">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{rental.status === 'locked' && (
|
|
|
|
|
|
<div className="bg-red-50 p-4 rounded-xl border border-red-100">
|
|
|
|
|
|
<h3 className="font-semibold text-red-800 mb-3 flex items-center gap-2">
|
|
|
|
|
|
<Lock className="w-5 h-5" /> Locked Info
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-red-600">Locked At</span><span className="text-sm font-medium text-red-800">{rental.lockedAt}</span></div>
|
|
|
|
|
|
<div className="flex justify-between"><span className="text-sm text-red-600">Reason</span><span className="text-sm font-medium text-red-800">{rental.lockedReason}</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-4 flex items-center gap-2">
|
|
|
|
|
|
<Lock className="w-5 h-5 text-red-500" /> Lock History
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</h3>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{rental.lockHistory && rental.lockHistory.length > 0 ? (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{rental.lockHistory.map(event => (
|
|
|
|
|
|
<div key={event.id} className="flex items-start gap-3 p-3 bg-slate-50 rounded-lg">
|
|
|
|
|
|
<div className={`p-1.5 rounded-full ${event.action === 'locked' ? 'bg-red-100' : 'bg-green-100'}`}>
|
|
|
|
|
|
{event.action === 'locked' ? (
|
|
|
|
|
|
<Lock className="w-4 h-4 text-red-600" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Unlock className="w-4 h-4 text-green-600" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${event.action === 'locked' ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`}>
|
|
|
|
|
|
{event.action === 'locked' ? 'Locked' : 'Unlocked'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-xs text-slate-400">{event.performedAt}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{event.reason && (
|
|
|
|
|
|
<p className="text-sm text-slate-600 mt-1">{event.reason}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<p className="text-xs text-slate-400 mt-1">By: {event.performedBy}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<p className="text-sm text-slate-400 text-center py-4">No lock history available.</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
|
|
|
|
|
|
<AlertTriangle className="w-5 h-5 text-red-500" /> Damage History
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button className="px-3 py-1.5 bg-red-500 text-white rounded-lg text-sm hover:bg-red-600 flex items-center gap-2">
|
|
|
|
|
|
<Plus className="w-4 h-4" /> Report Damage
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="space-y-2">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{mockDamageHistory.map(damage => (
|
|
|
|
|
|
<div key={damage.id} className="p-3 bg-slate-50 rounded-lg flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm font-medium text-slate-700">{damage.description}</p>
|
|
|
|
|
|
<p className="text-xs text-slate-500">{damage.date}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className={`text-xs px-2 py-1 rounded-full ${damage.severity === 'minor' ? 'bg-yellow-100 text-yellow-700' :
|
|
|
|
|
|
damage.severity === 'moderate' ? 'bg-orange-100 text-orange-700' :
|
|
|
|
|
|
'bg-red-100 text-red-700'
|
|
|
|
|
|
}`}>
|
|
|
|
|
|
{damage.severity}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className={`text-xs px-2 py-1 rounded-full ${damage.status === 'resolved' ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'
|
|
|
|
|
|
}`}>
|
|
|
|
|
|
{damage.status}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
))}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
|
|
|
|
|
|
<FileText className="w-5 h-5 text-blue-500" /> Rental Documents
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button className="px-3 py-1.5 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600 flex items-center gap-2">
|
|
|
|
|
|
<Upload className="w-4 h-4" /> Upload Document
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="space-y-2">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{mockDocuments.map(doc => (
|
|
|
|
|
|
<div key={doc.id} className="p-3 bg-slate-50 rounded-lg flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
|
<FileText className="w-5 h-5 text-slate-400" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm font-medium text-slate-700">{doc.name}</p>
|
|
|
|
|
|
<p className="text-xs text-slate-500">{doc.uploadedAt}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button className="px-3 py-1.5 text-blue-600 hover:bg-blue-50 rounded-lg text-sm flex items-center gap-1">
|
|
|
|
|
|
<Download className="w-4 h-4" /> View
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{rental.status === 'pending' && (
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-4">Biker Response</h3>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
{acceptPermission && (
|
|
|
|
|
|
<button onClick={handleAcceptRental} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2">
|
|
|
|
|
|
<CheckCircle className="w-4 h-4" /> Accept
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{rejectPermission && (
|
|
|
|
|
|
<button onClick={() => setShowRejectModal(true)} className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm hover:bg-red-600 flex items-center gap-2">
|
|
|
|
|
|
<XCircle className="w-4 h-4" /> Reject
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
)}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{rental.status === 'accepted' && (
|
|
|
|
|
|
<div className="bg-green-50 p-4 rounded-xl border border-green-100">
|
|
|
|
|
|
<div className="flex items-center gap-2 text-green-700">
|
|
|
|
|
|
<CheckCircle className="w-5 h-5" />
|
|
|
|
|
|
<span className="font-medium">Accepted on {rental.acceptedAt}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{rental.status === 'cancelled' && rental.rejectNote && (
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<div className="bg-red-50 p-4 rounded-xl border border-red-100">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<div className="flex items-center gap-2 text-red-700 mb-2">
|
|
|
|
|
|
<XCircle className="w-5 h-5" />
|
|
|
|
|
|
<span className="font-medium">Rejected</span>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<p className="text-sm text-red-600">Reason: {rental.rejectNote}</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-05-10 01:22:17 +06:00
|
|
|
|
|
|
|
|
|
|
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-700 mb-4 flex items-center gap-2">
|
|
|
|
|
|
<Clock className="w-5 h-5 text-slate-500" /> Activity Timeline
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
{timelineSteps.map((step, idx) => (
|
|
|
|
|
|
<div key={step.key} className="flex items-center gap-3">
|
|
|
|
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${step.completed ? 'bg-emerald-100 text-emerald-600' : 'bg-slate-100 text-slate-400'}`}>
|
|
|
|
|
|
{step.completed ? <Check className="w-4 h-4" /> : <span className="text-xs font-medium">{idx + 1}</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<p className={`text-sm font-medium ${step.completed ? 'text-slate-800' : 'text-slate-400'}`}>{step.label}</p>
|
|
|
|
|
|
{step.date && <p className="text-xs text-slate-500">{step.date}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{showLockModal && (
|
|
|
|
|
|
<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-4 border-b border-slate-100 flex justify-between items-center">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
|
|
|
|
|
<Lock className="w-5 h-5" /> Lock Rental
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button onClick={() => setShowLockModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
|
<label className="text-sm text-slate-600">Reason for locking</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={lockReason}
|
|
|
|
|
|
onChange={(e) => setLockReason(e.target.value)}
|
|
|
|
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="Enter reason..."
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
|
|
|
|
|
<button onClick={() => setShowLockModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
|
|
|
|
|
<button onClick={handleLockRental} disabled={!lockReason.trim()} className="px-4 py-2 bg-red-600 text-white rounded-lg disabled:opacity-50">Lock Rental</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{showUnlockModal && (
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<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-4 border-b border-slate-100 flex justify-between items-center">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<Unlock className="w-5 h-5" /> Unlock Rental
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</h3>
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<button onClick={() => setShowUnlockModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<p className="text-sm text-slate-600">Are you sure you want to unlock this rental? The bike will become active again.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
|
|
|
|
|
<button onClick={() => setShowUnlockModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
|
|
|
|
|
<button onClick={handleUnlockRental} className="px-4 py-2 bg-green-600 text-white rounded-lg">Unlock Rental</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{showCancelModal && (
|
|
|
|
|
|
<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-4 border-b border-slate-100 flex justify-between items-center">
|
|
|
|
|
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
|
|
|
|
|
<XCircle className="w-5 h-5" /> Cancel Rental
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button onClick={() => setShowCancelModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
|
<p className="text-sm text-slate-600">Are you sure you want to cancel this rental? This action cannot be undone.</p>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<button onClick={() => setShowCancelModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
|
|
|
|
|
<button onClick={handleCancelRental} className="px-4 py-2 bg-red-600 text-white rounded-lg">Confirm Cancel</button>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-05-10 01:22:17 +06:00
|
|
|
|
{showRejectModal && (
|
2026-04-26 14:56:12 +06:00
|
|
|
|
<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-4 border-b border-slate-100 flex justify-between items-center">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
|
|
|
|
|
<XCircle className="w-5 h-5" /> Reject Rental
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button onClick={() => setShowRejectModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<label className="text-sm text-slate-600">Rejection Reason</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={rejectNote}
|
|
|
|
|
|
onChange={(e) => setRejectNote(e.target.value)}
|
2026-04-26 14:56:12 +06:00
|
|
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
2026-05-10 01:22:17 +06:00
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="Enter reason for rejection..."
|
|
|
|
|
|
/>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
2026-05-10 01:22:17 +06:00
|
|
|
|
<button onClick={() => setShowRejectModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
|
|
|
|
|
<button onClick={handleRejectRental} disabled={!rejectNote.trim()} className="px-4 py-2 bg-red-600 text-white rounded-lg disabled:opacity-50">Reject Rental</button>
|
2026-04-26 14:56:12 +06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|