Files
JML/src/app/admin/rentals/page.tsx

1222 lines
61 KiB
TypeScript
Raw Normal View History

'use client';
import { useState, useEffect } from 'react';
import {
FileText, Search, Filter, Bike, User, Calendar, DollarSign, Clock, MoreVertical,
Eye, Plus, Phone, MessageCircle, X, CreditCard, Wallet, Building, Download,
Printer, ChevronLeft, ChevronRight, CheckCircle, AlertTriangle
} from 'lucide-react';
import Link from 'next/link';
import {
canRentalCreate, canRentalCancel, canRentalEdit, canRentalLock, canRentalUnlock
} from '../../../lib/auth';
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;
url: string;
type: 'front' | 'back' | 'left' | 'right' | 'battery';
approved?: boolean;
}
interface Rental {
id: string;
bikeId: string;
userId: string;
userName: string;
userPhone: string;
bikeModel: string;
bikePlate: string;
bikeBattery: number;
batteryId?: string;
batteryName?: string;
batteryRent?: 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;
pendingRent?: number;
pendingRentDays?: number;
paymentStatus: PaymentStatus;
penaltyLevel: PenaltyLevel;
penaltyAmount: number;
lockedAt?: string;
lockedReason?: string;
hubId: string;
hubName: string;
initialImages?: BikeImage[];
imagesApproved: boolean;
bikerNote?: string;
rejectNote?: string;
createdAt: string;
acceptedAt?: string;
activatedAt?: string;
batteryHistory?: {
id: string;
batteryId: string;
batteryName: string;
assignedAt: string;
returnedAt?: string;
monthlyRent: number;
status: 'active' | 'returned';
}[];
}
interface Bike {
id: string;
model: string;
plate: string;
battery: number;
status: 'available' | 'rented' | 'maintenance';
}
interface UserData {
id: string;
name: string;
phone: string;
kycStatus: 'approved' | 'pending' | 'rejected';
hasActiveRental: boolean;
}
const mockBikes: Bike[] = [
{ id: 'BIKE-001', model: 'AIMA Lightning', plate: 'Dhaka Metro Cha-9012', battery: 87, status: 'available' },
{ id: 'BIKE-002', model: 'Yadea DT3', plate: 'Dhaka Metro Ba-5521', battery: 65, status: 'available' },
{ id: 'BIKE-003', model: 'AIMA EM5', plate: 'Dhaka Metro Ko-1234', battery: 92, status: 'available' },
{ id: 'BIKE-005', model: 'Yadea G5', plate: 'Dhaka Metro Ha-5678', battery: 45, status: 'available' },
];
const mockUsers: UserData[] = [
{ id: 'USR-001', name: 'Rahim Ahmed', phone: '+8801712345678', kycStatus: 'approved', hasActiveRental: false },
{ id: 'USR-002', name: 'Karim Hasan', phone: '+8801812345678', kycStatus: 'approved', hasActiveRental: false },
{ id: 'USR-003', name: 'Jamal Uddin', phone: '+8801912345678', kycStatus: 'approved', hasActiveRental: true },
{ id: 'USR-004', name: 'Rafiq Islam', phone: '+8801512345678', kycStatus: 'pending', hasActiveRental: false },
{ id: 'USR-005', name: 'Farid Ahmed', phone: '+8801612345678', kycStatus: 'approved', hasActiveRental: false },
];
const mockHubs = [
{ id: 'HUB-001', name: 'Gulshan Hub' },
{ id: 'HUB-002', name: 'Banani Hub' },
{ id: 'HUB-003', name: 'Uttara Hub' },
{ id: 'HUB-004', name: 'Mirpur Hub' },
];
interface Battery {
id: string;
brand: string;
model: string;
soc: number;
monthlyRent: number;
status: 'available' | 'in-use' | 'maintenance';
}
const mockBatteries: Battery[] = [
{ id: 'BAT-DH-001', brand: 'Galaxy', model: '72V 45Ah', soc: 85, monthlyRent: 1500, status: 'available' },
{ id: 'BAT-DH-002', brand: 'Titan', model: '72V 50Ah', soc: 92, monthlyRent: 1800, status: 'available' },
{ id: 'BAT-DH-003', brand: 'PowerMax', model: '60V 40Ah', soc: 78, monthlyRent: 1200, status: 'available' },
{ id: 'BAT-DH-004', brand: 'UltraCell', model: '72V 55Ah', soc: 88, monthlyRent: 2000, status: 'available' },
{ id: 'BAT-DH-005', brand: 'EcoVolt', model: '48V 30Ah', soc: 65, monthlyRent: 800, status: 'available' },
];
const rentalSettings = {
single: {
Premium: { deposit: 5000, contractMonths: [1, 3, 6, 12], dailyRate: 200, weeklyRate: 1200, monthlyRate: 5000 },
Standard: { deposit: 3000, contractMonths: [1, 3, 6, 12], dailyRate: 150, weeklyRate: 900, monthlyRate: 3500 },
Economy: { deposit: 2000, contractMonths: [1, 3, 6, 12], dailyRate: 100, weeklyRate: 600, monthlyRate: 2500 },
},
shared: {
Premium: { deposit: 4000, contractMonths: [1, 3, 6], dailyRate: 150, weeklyRate: 900, monthlyRate: 3500 },
Standard: { deposit: 2500, contractMonths: [1, 3, 6], dailyRate: 100, weeklyRate: 600, monthlyRate: 2200 },
Economy: { deposit: 2000, contractMonths: [1, 3, 6], dailyRate: 80, weeklyRate: 500, monthlyRate: 1800 },
},
'rent-to-own': {
Premium: { deposit: 15000, contractMonths: [12, 18, 24, 36], dailyRate: 350, weeklyRate: 2450, monthlyRate: 10500 },
Standard: { deposit: 12000, contractMonths: [12, 18, 24, 36], dailyRate: 250, weeklyRate: 1750, monthlyRate: 7000 },
Economy: { deposit: 10000, contractMonths: [12, 18, 24, 36], dailyRate: 200, weeklyRate: 1400, monthlyRate: 6000 },
},
};
const mockRentals: Rental[] = [
{
id: 'RNT-001',
bikeId: 'BIKE-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',
contractMonths: 12,
subscriptionType: 'monthly',
deposit: 3000,
depositPaymentMethod: 'cash',
depositPaid: true,
dailyRate: 150,
weeklyRate: 900,
monthlyRate: 3500,
totalPaid: 38500,
dueRental: 0,
pendingRent: 0,
pendingRentDays: 0,
paymentStatus: 'paid',
penaltyLevel: 'none',
penaltyAmount: 0,
hubId: 'HUB-001',
hubName: 'Gulshan Hub',
imagesApproved: true,
createdAt: '2024-01-15',
acceptedAt: '2024-01-15',
activatedAt: '2024-01-16',
},
{
id: 'RNT-002',
bikeId: 'BIKE-002',
userId: 'USR-002',
userName: 'Karim Hasan',
userPhone: '+8801812345678',
bikeModel: 'Yadea DT3',
bikePlate: 'Dhaka Metro Ba-5521',
bikeBattery: 65,
type: 'single',
status: 'pending',
startDate: '2024-02-10',
contractMonths: 3,
subscriptionType: 'monthly',
deposit: 3000,
depositPaymentMethod: 'bank',
depositPaid: true,
dailyRate: 150,
weeklyRate: 900,
monthlyRate: 3500,
totalPaid: 3000,
dueRental: 3500,
pendingRent: 3500,
pendingRentDays: 5,
paymentStatus: 'partial',
penaltyLevel: 'none',
penaltyAmount: 0,
hubId: 'HUB-002',
hubName: 'Banani Hub',
imagesApproved: false,
createdAt: '2024-02-10',
},
{
id: 'RNT-003',
bikeId: 'BIKE-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,
weeklyRate: 3000,
monthlyRate: 12000,
totalPaid: 82000,
dueRental: 0,
pendingRent: 0,
pendingRentDays: 0,
paymentStatus: 'paid',
penaltyLevel: 'none',
penaltyAmount: 0,
hubId: 'HUB-001',
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,
pendingRent: 600,
pendingRentDays: 3,
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',
},
];
export default function RentalsPage() {
const [rentals, setRentals] = useState<Rental[]>(mockRentals);
const [showCreateModal, setShowCreateModal] = useState(false);
const [statusFilter, setStatusFilter] = useState<string>('all');
const [searchQuery, setSearchQuery] = useState('');
const [hubFilter, setHubFilter] = useState<string>('all');
const [createPermission, setCreatePermission] = useState(false);
const [cancelPermission, setCancelPermission] = useState(false);
const [editPermission, setEditPermission] = useState(false);
const [lockPermission, setLockPermission] = useState(false);
const [unlockPermission, setUnlockPermission] = useState(false);
const [lockUnlockRental, setLockUnlockRental] = useState<{ id: string; action: 'lock' | 'unlock' } | null>(null);
const [planConditions, setPlanConditions] = useState<{
single: { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[]; evModels: string[] }[];
shared: { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[]; evModels: string[] }[];
'rent-to-own': { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[]; evModels: string[] }[];
}>({
single: [
{ name: 'Single Rent - Premium', deposit: 25000, dailyRate: 400, weeklyRate: 2800, monthlyRate: 12000, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Single Rent - Standard', deposit: 20000, dailyRate: 300, weeklyRate: 2100, monthlyRate: 9000, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Single Rent - Economy', deposit: 15000, dailyRate: 250, weeklyRate: 1750, monthlyRate: 7500, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
],
shared: [
{ name: 'Share an EV - Premium', deposit: 20000, dailyRate: 300, weeklyRate: 2100, monthlyRate: 8400, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Share an EV - Standard', deposit: 15000, dailyRate: 200, weeklyRate: 1400, monthlyRate: 5600, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Share an EV - Economy', deposit: 12000, dailyRate: 150, weeklyRate: 1050, monthlyRate: 4200, contractMonths: [1, 3, 6, 12], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
],
'rent-to-own': [
{ name: 'Rent to Own - Premium', deposit: 25000, dailyRate: 350, weeklyRate: 2450, monthlyRate: 10500, contractMonths: [12, 18, 24, 36], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Rent to Own - Standard', deposit: 18000, dailyRate: 250, weeklyRate: 1750, monthlyRate: 7000, contractMonths: [12, 18, 24, 36], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
{ name: 'Rent to Own - Economy', deposit: 15000, dailyRate: 200, weeklyRate: 1400, monthlyRate: 6000, contractMonths: [12, 18, 24, 36], evModels: ['Yadea G5', 'Yadea C1S', 'Himiwa 400', 'Himiwa 500', 'Himiwa 600', 'TVS iQube', 'Bexly 1S', 'Bexly 3S', 'Bexly 5E', 'Bolan E-Bike', 'Nova E-Bike', 'Super Eco', 'Other'] },
],
});
const [createStep, setCreateStep] = useState(1);
const [newRental, setNewRental] = useState<{
userId: string;
type: RentalType;
planConditionName: string;
evModel: string;
subscriptionType: 'daily' | 'weekly' | 'monthly';
contractMonths: number;
bikeId: string;
batteryId: string;
batteryRent: number;
startDate: string;
hubId: string;
depositAmount: number;
depositPaymentMethod: PaymentMethod;
}>({
userId: '',
type: 'single',
planConditionName: '',
evModel: '',
subscriptionType: 'daily',
contractMonths: 0,
bikeId: '',
batteryId: '',
batteryRent: 0,
startDate: new Date().toISOString().split('T')[0],
hubId: '',
depositAmount: 0,
depositPaymentMethod: 'cash',
});
const availableBatteries = mockBatteries.filter(b => b.status === 'available');
const [showJournalPreview, setShowJournalPreview] = useState(false);
useEffect(() => {
setCreatePermission(canRentalCreate());
setCancelPermission(canRentalCancel());
setEditPermission(canRentalEdit());
setLockPermission(canRentalLock());
setUnlockPermission(canRentalUnlock());
}, []);
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 getPaymentStatusBadge = (status: PaymentStatus, pendingRent?: number, pendingRentDays?: number) => {
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, pendingRent: pendingRent || 0, pendingRentDays: pendingRentDays || 0 };
};
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 };
};
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 filteredRentals = rentals.filter(r => {
if (statusFilter !== 'all' && r.status !== statusFilter) return false;
if (hubFilter !== 'all' && r.hubId !== hubFilter) return false;
if (searchQuery) {
const query = searchQuery.toLowerCase();
return (
r.id.toLowerCase().includes(query) ||
r.userName.toLowerCase().includes(query) ||
r.userPhone.includes(query) ||
r.bikeModel.toLowerCase().includes(query) ||
r.bikePlate.toLowerCase().includes(query)
);
}
return true;
});
const eligibleUsers = mockUsers.filter(u => u.kycStatus === 'approved' && !u.hasActiveRental);
const availableBikes = mockBikes.filter(b => b.status === 'available');
const selectedPlan = planConditions[newRental.type]?.find(p => p.name === newRental.planConditionName) || planConditions[newRental.type]?.[0];
const stats = {
active: rentals.filter(r => r.status === 'active').length,
pending: rentals.filter(r => r.status === 'pending').length,
overdue: rentals.filter(r => r.paymentStatus === 'overdue' || r.paymentStatus === 'partial').length,
locked: rentals.filter(r => r.status === 'locked').length,
};
const handleCreateRental = () => {
if (!newRental.userId || !newRental.bikeId || !newRental.hubId) return;
setShowJournalPreview(true);
};
const confirmCreateRental = () => {
const bike = mockBikes.find(b => b.id === newRental.bikeId);
const user = mockUsers.find(u => u.id === newRental.userId);
const hub = mockHubs.find(h => h.id === newRental.hubId);
const battery = mockBatteries.find(b => b.id === newRental.batteryId);
const settings = planConditions[newRental.type]?.find(p => p.name === newRental.planConditionName) || planConditions[newRental.type]?.[0];
const rental: Rental = {
id: `RNT-${String(rentals.length + 1).padStart(3, '0')}`,
bikeId: newRental.bikeId,
userId: newRental.userId,
userName: user?.name || '',
userPhone: user?.phone || '',
bikeModel: bike?.model || '',
bikePlate: bike?.plate || '',
bikeBattery: bike?.battery || 0,
batteryId: newRental.batteryId || undefined,
batteryName: battery ? `${battery.brand} ${battery.model}` : undefined,
batteryRent: newRental.batteryRent || undefined,
type: newRental.type,
status: 'pending',
startDate: newRental.startDate,
endDate: newRental.contractMonths > 0
? new Date(new Date(newRental.startDate).setMonth(new Date(newRental.startDate).getMonth() + newRental.contractMonths)).toISOString().split('T')[0]
: undefined,
contractMonths: newRental.contractMonths > 0 ? newRental.contractMonths : undefined,
subscriptionType: newRental.subscriptionType,
deposit: newRental.depositAmount > 0 ? newRental.depositAmount : settings.deposit,
depositPaymentMethod: newRental.depositPaymentMethod,
depositPaid: newRental.depositAmount > 0 || settings.deposit > 0,
dailyRate: settings.dailyRate,
weeklyRate: settings.weeklyRate,
monthlyRate: settings.monthlyRate,
totalPaid: settings.deposit,
dueRental: 0,
paymentStatus: settings.deposit > 0 ? 'paid' : 'unpaid',
penaltyLevel: 'none',
penaltyAmount: 0,
hubId: newRental.hubId,
hubName: hub?.name || '',
imagesApproved: false,
createdAt: new Date().toISOString().split('T')[0],
batteryHistory: newRental.batteryId ? [{
id: `BAT-RENT-${Date.now()}`,
batteryId: newRental.batteryId,
batteryName: battery ? `${battery.brand} ${battery.model}` : '',
assignedAt: new Date().toISOString().split('T')[0],
monthlyRent: newRental.batteryRent,
status: 'active' as const,
}] : undefined,
};
setRentals([...rentals, rental]);
setShowJournalPreview(false);
setShowCreateModal(false);
setCreateStep(1);
setNewRental({
userId: '',
type: 'single',
planConditionName: planConditions.single[1]?.name || '',
subscriptionType: 'daily',
contractMonths: 0,
bikeId: '',
batteryId: '',
batteryRent: 0,
evModel: '',
startDate: new Date().toISOString().split('T')[0],
hubId: '',
depositAmount: 0,
depositPaymentMethod: 'cash',
});
};
const selectedBike = mockBikes.find(b => b.id === newRental.bikeId);
const selectedUser = mockUsers.find(u => u.id === newRental.userId);
return (
<div className="p-4 lg:p-6">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
<div>
<h1 className="text-2xl lg:text-3xl font-extrabold text-slate-800">Rentals</h1>
<p className="text-sm text-slate-500 mt-1">Manage rental transactions</p>
</div>
<div className="grid grid-cols-2 gap-2 sm:flex sm:items-center sm:gap-2">
{createPermission && (
<button
onClick={() => setShowCreateModal(true)}
className="py-2 px-4 bg-emerald-600 text-white rounded-lg text-sm font-semibold hover:bg-emerald-700 flex items-center justify-center gap-2"
>
<Plus className="w-4 h-4" /> New Rental
</button>
)}
<button className="py-2 px-4 border border-slate-200 rounded-lg text-sm font-medium text-slate-600 hover:bg-slate-50 flex items-center justify-center gap-2">
<Download className="w-4 h-4" /> Export
</button>
</div>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<button onClick={() => setStatusFilter(statusFilter === 'active' ? 'all' : 'active')} className={`text-left bg-white rounded-xl p-4 shadow-sm border transition-all ${statusFilter === 'active' ? 'border-emerald-500 ring-2 ring-emerald-100' : 'border-slate-100 hover:border-emerald-200'}`}>
<p className="text-2xl font-extrabold text-emerald-600">{stats.active}</p>
<p className="text-sm text-slate-500">Active Rentals</p>
</button>
<button onClick={() => setStatusFilter(statusFilter === 'pending' ? 'all' : 'pending')} className={`text-left bg-white rounded-xl p-4 shadow-sm border transition-all ${statusFilter === 'pending' ? 'border-amber-500 ring-2 ring-amber-100' : 'border-slate-100 hover:border-amber-200'}`}>
<p className="text-2xl font-extrabold text-amber-600">{stats.pending}</p>
<p className="text-sm text-slate-500">Payment Due (Day1)</p>
</button>
<button onClick={() => setStatusFilter(statusFilter === 'overdue' ? 'all' : 'overdue')} className={`text-left bg-white rounded-xl p-4 shadow-sm border transition-all ${statusFilter === 'overdue' ? 'border-orange-500 ring-2 ring-orange-100' : 'border-slate-100 hover:border-orange-200'}`}>
<p className="text-2xl font-extrabold text-orange-600">{stats.overdue}</p>
<p className="text-sm text-slate-500">Payment Due (Day2)</p>
</button>
<button onClick={() => setStatusFilter(statusFilter === 'locked' ? 'all' : 'locked')} className={`text-left bg-white rounded-xl p-4 shadow-sm border transition-all ${statusFilter === 'locked' ? 'border-red-500 ring-2 ring-red-100' : 'border-slate-100 hover:border-red-200'}`}>
<p className="text-2xl font-extrabold text-red-600">{stats.locked}</p>
<p className="text-sm text-slate-500">EV Locked</p>
</button>
</div>
<div className="flex flex-col lg:flex-row gap-3 mb-4">
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="py-2 px-4 border border-slate-200 rounded-lg text-sm font-medium text-slate-600 hover:bg-slate-50"
>
<option value="all">All Status</option>
<option value="pending">Pending</option>
<option value="accepted">Accepted</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
<option value="locked">Locked</option>
<option value="disputed">Disputed</option>
</select>
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
placeholder="Search by ID, user, bike..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm text-slate-600"
/>
</div>
<select
value={hubFilter}
onChange={(e) => setHubFilter(e.target.value)}
className="py-2 px-4 border border-slate-200 rounded-lg text-sm font-medium text-slate-600 hover:bg-slate-50"
>
<option value="all">All Hubs</option>
{mockHubs.map(hub => (
<option key={hub.id} value={hub.id}>{hub.name}</option>
))}
</select>
</div>
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
<div className="hidden md:block overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">ID</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Bike</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">User</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Type</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Hub</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Deposit</th>
{/* <th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Subscription</th> */}
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Rent Payment</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{filteredRentals.map(rental => {
const statusBadge = getStatusBadge(rental.status);
const paymentBadge = getPaymentStatusBadge(rental.paymentStatus, rental.pendingRent, rental.pendingRentDays);
const typeBadge = getTypeBadge(rental.type);
return (
<tr key={rental.id} className="hover:bg-slate-50 transition-colors">
<td className="px-4 py-3">
<Link href={`/admin/rentals/${rental.id}`} className="text-sm font-medium text-emerald-600 hover:text-emerald-700">
{rental.id}
</Link>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<Bike className="w-4 h-4 text-slate-400" />
<div>
<span className="text-sm text-slate-600">{rental.bikeModel}</span>
<p className="text-xs text-slate-400">{rental.bikePlate}</p>
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-slate-400" />
<div>
<span className="text-sm text-slate-600">{rental.userName}</span>
<p className="text-xs text-slate-400">{rental.userPhone}</p>
</div>
</div>
</td>
<td className="px-4 py-3">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${typeBadge.style}`}>
{typeBadge.label}
</span>
</td>
<td className="px-4 py-3">
<span className="text-sm text-slate-600">{rental.hubName}</span>
</td>
<td className="px-4 py-3">
<div>
<span className="text-sm font-medium text-slate-700">{rental.deposit.toLocaleString()}</span>
<span className={`ml-2 text-xs px-1.5 py-0.5 rounded ${rental.depositPaid ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{rental.depositPaid ? 'Paid' : 'Unpaid'}
</span>
</div>
</td>
{/* <td className="px-4 py-3">
<span className="text-sm text-slate-600 capitalize">{rental.subscriptionType}</span>
</td> */}
<td className="px-4 py-3">
{(() => {
const rent = rental[rental.subscriptionType === 'daily' ? 'dailyRate' : rental.subscriptionType === 'weekly' ? 'weeklyRate' : 'monthlyRate'];
const due = rental.pendingRent || 0;
const penalty = rental.penaltyAmount || 0;
const totalDue = due + penalty;
if (totalDue > 0) {
return (
<div>
<span className="text-sm font-medium text-amber-600">{totalDue.toLocaleString()}</span>
<p className="text-xs text-slate-400">{rental.subscriptionType} {rent}/{(rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'wk' : 'mo')}</p>
{penalty > 0 && <span className="text-xs text-red-500">+{penalty} penalty</span>}
</div>
);
}
return (
<div>
<span className="text-sm font-medium text-slate-700">{rent.toLocaleString()}</span>
<p className="text-xs text-slate-400 capitalize">{rental.subscriptionType}</p>
</div>
);
})()}
</td>
<td className="px-4 py-3">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusBadge.style}`}>
{statusBadge.label}
</span>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-1">
<Link href={`/admin/rentals/${rental.id}`} className="p-1.5 hover:bg-slate-100 rounded-lg" title="View">
<Eye className="w-4 h-4 text-slate-500" />
</Link>
{lockPermission && rental.status === 'active' && (
<button onClick={() => setLockUnlockRental({ id: rental.id, action: 'lock' })} className="p-1.5 hover:bg-red-100 rounded-lg" title="Lock">
<Lock className="w-4 h-4 text-red-500" />
</button>
)}
{unlockPermission && rental.status === 'locked' && (
<button onClick={() => setLockUnlockRental({ id: rental.id, action: 'unlock' })} className="p-1.5 hover:bg-green-100 rounded-lg" title="Unlock">
<Unlock className="w-4 h-4 text-green-500" />
</button>
)}
<a href={`tel:${rental.userPhone}`} className="p-1.5 hover:bg-green-100 rounded-lg" title="Call">
<Phone className="w-4 h-4 text-green-500" />
</a>
<a href={`sms:${rental.userPhone}`} className="p-1.5 hover:bg-blue-100 rounded-lg" title="Message">
<MessageCircle className="w-4 h-4 text-blue-500" />
</a>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="md:hidden p-3 space-y-3">
{filteredRentals.map(rental => {
const statusBadge = getStatusBadge(rental.status);
const typeBadge = getTypeBadge(rental.type);
const rent = rental[rental.subscriptionType === 'daily' ? 'dailyRate' : rental.subscriptionType === 'weekly' ? 'weeklyRate' : 'monthlyRate'];
const due = rental.pendingRent || 0;
const penalty = rental.penaltyAmount || 0;
const totalDue = due + penalty;
return (
<div key={rental.id} className="bg-slate-50 rounded-lg p-3 space-y-2">
<div className="flex items-center justify-between">
<Link href={`/admin/rentals/${rental.id}`} className="text-sm font-medium text-emerald-600">
{rental.id}
</Link>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${statusBadge.style}`}>
{statusBadge.label}
</span>
</div>
<div className="flex items-center gap-2">
<Bike className="w-4 h-4 text-slate-400" />
<div className="flex-1 min-w-0">
<p className="text-sm text-slate-700 font-medium truncate">{rental.bikeModel}</p>
<p className="text-xs text-slate-400">{rental.bikePlate}</p>
</div>
</div>
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-slate-400" />
<div className="flex-1 min-w-0">
<p className="text-sm text-slate-700 truncate">{rental.userName}</p>
<p className="text-xs text-slate-400">{rental.userPhone}</p>
</div>
</div>
<div className="flex items-center gap-2 flex-wrap">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${typeBadge.style}`}>
{typeBadge.label}
</span>
<span className="text-xs text-slate-500 capitalize">{rental.subscriptionType}</span>
<span className="text-xs text-slate-400"></span>
<span className="text-xs text-slate-500 truncate">{rental.hubName}</span>
</div>
<div className="flex items-center justify-between pt-1 border-t border-slate-100">
<div>
<span className="text-xs text-slate-500">Deposit: </span>
<span className="text-sm font-medium text-slate-700">{rental.deposit.toLocaleString()}</span>
<span className={`ml-1.5 text-xs px-1 py-0.5 rounded ${rental.depositPaid ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{rental.depositPaid ? 'Paid' : 'Unpaid'}
</span>
</div>
{totalDue > 0 ? (
<div className="text-right">
<span className="text-sm font-medium text-amber-600">{totalDue.toLocaleString()}</span>
<p className="text-xs text-slate-400">{rental.subscriptionType} {rent}/{(rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'wk' : 'mo')}</p>
{penalty > 0 && <span className="text-xs text-red-500">+{penalty}</span>}
</div>
) : (
<div className="text-right">
<span className="text-sm font-medium text-slate-700">{rent.toLocaleString()}</span>
<p className="text-xs text-slate-400 capitalize">{rental.subscriptionType}</p>
</div>
)}
</div>
<div className="flex items-center gap-1.5 pt-1">
<Link href={`/admin/rentals/${rental.id}`} className="flex-1 flex items-center justify-center gap-1 p-2 bg-white hover:bg-slate-100 rounded-lg text-xs font-medium text-slate-600">
<Eye className="w-3.5 h-3.5" /> View
</Link>
{lockPermission && rental.status === 'active' && (
<button onClick={() => setLockUnlockRental({ id: rental.id, action: 'lock' })} className="flex-1 flex items-center justify-center gap-1 p-2 bg-white hover:bg-red-50 rounded-lg text-xs font-medium text-red-600">
<Lock className="w-3.5 h-3.5" /> Lock
</button>
)}
{unlockPermission && rental.status === 'locked' && (
<button onClick={() => setLockUnlockRental({ id: rental.id, action: 'unlock' })} className="flex-1 flex items-center justify-center gap-1 p-2 bg-white hover:bg-green-50 rounded-lg text-xs font-medium text-green-600">
<Unlock className="w-3.5 h-3.5" /> Unlock
</button>
)}
<a href={`tel:${rental.userPhone}`} className="flex-1 flex items-center justify-center gap-1 p-2 bg-white hover:bg-green-50 rounded-lg text-xs font-medium text-green-600">
<Phone className="w-3.5 h-3.5" /> Call
</a>
<a href={`sms:${rental.userPhone}`} className="flex-1 flex items-center justify-center gap-1 p-2 bg-white hover:bg-blue-50 rounded-lg text-xs font-medium text-blue-600">
<MessageCircle className="w-3.5 h-3.5" /> SMS
</a>
</div>
</div>
);
})}
</div>
</div>
{showCreateModal && (
<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-2xl max-h-[90vh] overflow-y-auto">
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
<h3 className="font-semibold text-slate-800">Create New Rental - Step {createStep} of 4</h3>
<button onClick={() => { setShowCreateModal(false); setCreateStep(1); }} className="text-slate-400 hover:text-slate-600">
<X className="w-5 h-5" />
</button>
</div>
<div className="p-4 space-y-4">
{createStep === 1 && (
<div className="space-y-4">
<h4 className="font-medium text-slate-700 mb-3">Step 1: Start Date & Hub</h4>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Start Date</label>
<input type="date" value={newRental.startDate} onChange={(e) => setNewRental({ ...newRental, startDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Hub</label>
<select value={newRental.hubId} onChange={(e) => setNewRental({ ...newRental, hubId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select Hub...</option>
{mockHubs.map(hub => (
<option key={hub.id} value={hub.id}>{hub.name}</option>
))}
</select>
</div>
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Select User</label>
<p className="text-xs text-slate-400 mb-2">Only KYC-approved users without active rentals are shown.</p>
<select value={newRental.userId} onChange={(e) => setNewRental({ ...newRental, userId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select User...</option>
{eligibleUsers.map(user => (
<option key={user.id} value={user.id}>{user.name} ({user.phone})</option>
))}
</select>
</div>
</div>
)}
{createStep === 2 && (
<div className="space-y-4">
<div>
<label className="text-sm text-slate-600">Rental Type</label>
<select
value={newRental.type}
onChange={(e) => setNewRental({ ...newRental, type: e.target.value as RentalType, planConditionName: planConditions[e.target.value as RentalType]?.[1]?.name || planConditions[e.target.value as RentalType]?.[0]?.name || '', subscriptionType: 'daily', contractMonths: 0 })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value="single">Single Rent</option>
<option value="shared">Share EV</option>
<option value="rent-to-own">Rent to Own</option>
</select>
</div>
<div>
<label className="text-sm text-slate-600">Plan Condition</label>
<select
value={newRental.planConditionName}
onChange={(e) => setNewRental(prev => {
const selectedPlan = planConditions[prev.type]?.find(p => p.name === e.target.value);
return { ...prev, planConditionName: e.target.value, depositAmount: selectedPlan?.deposit || prev.depositAmount, contractMonths: 0, subscriptionType: 'daily' };
})}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value="">Select Plan...</option>
{planConditions[newRental.type]?.map(plan => (
<option key={plan.name} value={plan.name}>{plan.name}</option>
))}
</select>
</div>
<div>
<label className="text-sm text-slate-600">EV Model</label>
<select
value={newRental.evModel}
onChange={(e) => setNewRental({ ...newRental, evModel: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value="">Select EV Model...</option>
{selectedPlan?.evModels.map(model => (
<option key={model} value={model}>{model}</option>
))}
</select>
</div>
<div>
<label className="text-sm text-slate-600">Contract Duration</label>
<select
value={newRental.contractMonths}
onChange={(e) => setNewRental({ ...newRental, contractMonths: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value={0}>Daily Basis</option>
{selectedPlan?.contractMonths.map(m => (
<option key={m} value={m}>{m} Months</option>
))}
</select>
{newRental.contractMonths > 0 && (
<p className="text-xs text-emerald-600 mt-1">
End Date: {new Date(new Date(newRental.startDate).setMonth(new Date(newRental.startDate).getMonth() + newRental.contractMonths)).toISOString().split('T')[0]}
</p>
)}
</div>
<div>
<label className="text-sm text-slate-600 mb-2 block">Subscription Type</label>
<div className="flex gap-2">
{(['daily', 'weekly', 'monthly'] as const).map(sub => (
<button
key={sub}
type="button"
onClick={() => setNewRental({ ...newRental, subscriptionType: sub })}
className={`flex-1 py-2 px-3 rounded-lg text-sm border ${newRental.subscriptionType === sub
? 'bg-emerald-100 border-emerald-300 text-emerald-700'
: 'border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
{sub === 'daily' ? 'Daily' : sub === 'weekly' ? 'Weekly' : 'Monthly'}
</button>
))}
</div>
</div>
<div className="bg-emerald-50 p-3 rounded-lg border border-emerald-100">
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-emerald-600 font-medium uppercase tracking-wide">{newRental.planConditionName}</span>
</div>
<p className="text-sm text-slate-600">Deposit: <span className="font-semibold text-slate-800">{selectedPlan?.deposit.toLocaleString()}</span></p>
<p className="text-sm text-slate-600">
Rate: <span className="font-semibold text-slate-800">{newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate}</span>
<span className="text-slate-500">/{newRental.subscriptionType === 'daily' ? 'day' : newRental.subscriptionType === 'weekly' ? 'week' : 'month'}</span>
</p>
</div>
</div>
)}
{createStep === 3 && (
<div className="space-y-4">
<h4 className="font-medium text-slate-700 mb-3">Step 3: Select Bike & Battery</h4>
<div>
<label className="text-sm text-slate-600 mb-1 block">Select Bike</label>
<select
value={newRental.bikeId}
onChange={(e) => setNewRental({ ...newRental, bikeId: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">Select Bike...</option>
{availableBikes.map(bike => (
<option key={bike.id} value={bike.id}>{bike.model} - {bike.plate}</option>
))}
</select>
{selectedBike && (
<div className="mt-2 flex items-center gap-2">
<span className="text-sm text-slate-600">Battery:</span>
<span className={`text-sm px-2 py-1 rounded-full ${selectedBike.battery > 70 ? 'bg-green-100 text-green-700' : selectedBike.battery > 40 ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}>
{selectedBike.battery}%
</span>
</div>
)}
</div>
<div>
<label className="text-sm text-slate-600 mb-1 block">Select Battery (Optional)</label>
<select
value={newRental.batteryId}
onChange={(e) => {
const bat = availableBatteries.find(b => b.id === e.target.value);
setNewRental({ ...newRental, batteryId: e.target.value, batteryRent: bat?.monthlyRent || 0 });
}}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">No Battery - Use Bike's Battery</option>
{availableBatteries.map(bat => (
<option key={bat.id} value={bat.id}>{bat.brand} {bat.model} - SOC: {bat.soc}% - Rent: {bat.monthlyRent}/month</option>
))}
</select>
{newRental.batteryId && (
<div className="mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg">
<p className="text-sm text-amber-700">Battery Monthly Rent: <span className="font-bold">{newRental.batteryRent}/month</span></p>
</div>
)}
</div>
</div>
)}
{createStep === 4 && (
<div className="space-y-4">
<h4 className="font-medium text-slate-700 mb-3">Step 4: Deposit Payment</h4>
<div className="bg-slate-50 p-3 rounded-lg">
<p className="text-sm text-slate-600">Deposit Amount: {selectedPlan?.deposit.toLocaleString()}</p>
<p className="text-sm text-slate-600">User: {selectedUser?.name}</p>
<p className="text-sm text-slate-600">Bike: {selectedBike?.model} ({selectedBike?.plate})</p>
{newRental.batteryId && (
<p className="text-sm text-slate-600">Battery: {availableBatteries.find(b => b.id === newRental.batteryId)?.brand} {availableBatteries.find(b => b.id === newRental.batteryId)?.model} (Rent: {newRental.batteryRent}/month)</p>
)}
<p className="text-sm text-slate-600">Hub: {mockHubs.find(h => h.id === newRental.hubId)?.name}</p>
{newRental.batteryId && (
<div className="mt-2 pt-2 border-t border-slate-200">
<p className="text-sm font-medium text-emerald-700">
Combined Monthly Rent: {(newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate) + newRental.batteryRent}/month
<span className="text-xs text-slate-500 ml-1">(Bike: {newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate} + Battery: {newRental.batteryRent})</span>
</p>
</div>
)}
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-slate-600 mb-2 block">Payment Method</label>
<div className="flex gap-2">
{(['cash', 'bank', 'wallet'] as const).map(method => (
<button
key={method}
type="button"
onClick={() => setNewRental({ ...newRental, depositPaymentMethod: method })}
className={`flex-1 py-2 px-3 rounded-lg text-sm border flex items-center justify-center gap-2 ${newRental.depositPaymentMethod === method
? 'bg-emerald-100 border-emerald-300 text-emerald-700'
: 'border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
{method === 'cash' && <Wallet className="w-4 h-4" />}
{method === 'bank' && <Building className="w-4 h-4" />}
{method === 'wallet' && <CreditCard className="w-4 h-4" />}
{method === 'cash' ? 'Cash' : method === 'bank' ? 'Bank' : 'Wallet'}
</button>
))}
</div>
</div>
</div>
{(newRental.depositAmount > 0 || selectedPlan?.deposit > 0) && (
<div className="bg-white border border-slate-200 rounded-lg overflow-hidden">
<div className="bg-slate-100 px-4 py-2 border-b border-slate-200">
<p className="text-sm font-semibold text-slate-700">Proforma Invoice</p>
</div>
<div className="p-3 space-y-1 text-xs">
<div className="flex justify-between"><span className="text-slate-500">Invoice No:</span><span className="text-slate-700">PI-{Date.now().toString().slice(-8)}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Date:</span><span className="text-slate-700">{new Date().toLocaleDateString()}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Customer:</span><span className="text-slate-700">{selectedUser?.name}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Bike:</span><span className="text-slate-700">{selectedBike?.model} ({selectedBike?.plate})</span></div>
</div>
<table className="w-full text-xs">
<thead className="bg-slate-50 border-t border-slate-200">
<tr>
<th className="px-3 py-2 text-left text-slate-500 font-medium">Description</th>
<th className="px-3 py-2 text-right text-slate-500 font-medium">Debit ()</th>
<th className="px-3 py-2 text-right text-slate-500 font-medium">Credit ()</th>
</tr>
</thead>
<tbody>
<tr className="border-t border-slate-100">
<td className="px-3 py-2">
{newRental.depositPaymentMethod === 'cash' && '1000 - Cash'}
{newRental.depositPaymentMethod === 'bank' && '1100 - Bank'}
{newRental.depositPaymentMethod === 'wallet' && '1200 - Biker Wallet'}
</td>
<td className="px-3 py-2 text-right">{newRental.depositAmount > 0 ? newRental.depositAmount.toLocaleString() : selectedPlan?.deposit.toLocaleString()}</td>
<td className="px-3 py-2 text-right">-</td>
</tr>
<tr className="border-t border-slate-100">
<td className="px-3 py-2">2100 - Deposit Received</td>
<td className="px-3 py-2 text-right">-</td>
<td className="px-3 py-2 text-right">{newRental.depositAmount > 0 ? newRental.depositAmount.toLocaleString() : selectedPlan?.deposit.toLocaleString()}</td>
</tr>
</tbody>
<tfoot className="bg-slate-50 border-t border-slate-200">
<tr>
<td className="px-3 py-2 font-semibold text-slate-700">Total</td>
<td className="px-3 py-2 text-right font-semibold">{newRental.depositAmount > 0 ? newRental.depositAmount.toLocaleString() : selectedPlan?.deposit.toLocaleString()}</td>
<td className="px-3 py-2 text-right font-semibold">{newRental.depositAmount > 0 ? newRental.depositAmount.toLocaleString() : selectedPlan?.deposit.toLocaleString()}</td>
</tr>
</tfoot>
</table>
</div>
)}
</div>
)}
</div>
<div className="p-4 border-t border-slate-100 flex justify-between">
<div className="flex gap-2">
{createStep > 1 && (
<button
onClick={() => setCreateStep(createStep - 1)}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm flex items-center gap-2"
>
<ChevronLeft className="w-4 h-4" /> Back
</button>
)}
</div>
<div className="flex gap-2">
{createStep < 4 ? (
<button
onClick={() => setCreateStep(createStep + 1)}
disabled={
(createStep === 1 && (!newRental.userId || !newRental.hubId)) ||
(createStep === 3 && !newRental.bikeId)
}
className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm disabled:opacity-50 flex items-center gap-2"
>
Next <ChevronRight className="w-4 h-4" />
</button>
) : (
<>
{(newRental.depositAmount > 0 || selectedPlan?.deposit > 0) && (
<button onClick={() => window.print()} className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm flex items-center gap-2">
<Printer className="w-4 h-4" /> Print Invoice
</button>
)}
<button
onClick={confirmCreateRental}
className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-700"
>
Confirm & Create
</button>
</>
)}
</div>
</div>
</div>
</div>
)}
{lockUnlockRental && (
<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-sm">
<div className="p-6 text-center">
<div className={`w-16 h-16 mx-auto mb-4 rounded-full flex items-center justify-center ${lockUnlockRental.action === 'lock' ? 'bg-red-100' : 'bg-green-100'}`}>
{lockUnlockRental.action === 'lock' ? <Lock className="w-8 h-8 text-red-600" /> : <Unlock className="w-8 h-8 text-green-600" />}
</div>
<h3 className="text-lg font-semibold text-slate-800 mb-2">
{lockUnlockRental.action === 'lock' ? 'Lock EV?' : 'Unlock EV?'}
</h3>
<p className="text-sm text-slate-500 mb-6">
{lockUnlockRental.action === 'lock'
? 'This will lock the EV and prevent the rider from starting it. Are you sure?'
: 'This will unlock the EV and allow the rider to start it. Are you sure?'}
</p>
<div className="flex gap-3">
<button
onClick={() => setLockUnlockRental(null)}
className="flex-1 py-2.5 px-4 border border-slate-200 rounded-lg text-sm font-medium text-slate-600 hover:bg-slate-50"
>
Cancel
</button>
<button
onClick={() => {
setRentals(prev => prev.map(r => r.id === lockUnlockRental.id ? { ...r, status: lockUnlockRental.action === 'lock' ? 'locked' : 'active' } : r));
setLockUnlockRental(null);
}}
className={`flex-1 py-2.5 px-4 rounded-lg text-sm font-semibold text-white ${lockUnlockRental.action === 'lock' ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700'}`}
>
Confirm
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
}
function Lock({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
);
}
function Unlock({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
</svg>
);
}