2026-04-22 01:02:45 +06:00
'use client' ;
import { useState , use } from 'react' ;
import Link from 'next/link' ;
import { useRouter } from 'next/navigation' ;
import {
Bike , MapPin , Battery , User , Wrench , Eye , Edit , Trash2 , X , ArrowLeft , PhoneCall ,
MessageCircle , Calendar , DollarSign , Clock , Navigation , Car , FileText , Shield , Zap ,
GaugeCircle , CheckCircle , AlertTriangle , Activity , Award , TrendingUp , Wallet ,
MoreHorizontal , Map , Navigation2 , Satellite , FileCheck , FileX , Clock3 ,
2026-04-26 14:56:12 +06:00
History , CreditCard , User2 , Phone , Mail , MapPinned , ExternalLink , Plus ,
2026-05-16 20:54:17 +06:00
AlertCircle , Image as ImageIcon , Camera
2026-04-22 01:02:45 +06:00
} from 'lucide-react' ;
interface GPSDevice {
id : string ;
phone : string ;
imei : string ;
lastActive : string ;
signal : number ;
battery : number ;
}
interface BikeDocument {
type : 'registration' | 'insurance' | 'fitness' | 'permit' | 'other' ;
number : string ;
issueDate : string ;
expiryDate : string ;
verified : boolean ;
}
interface RentalHistory {
id : string ;
bikerId : string ;
bikerName : string ;
type : 'single' | 'shared' | 'rent-to-own' ;
status : 'active' | 'completed' | 'disputed' ;
startDate : string ;
endDate? : string ;
dailyRate : number ;
totalPaid : number ;
rideCount : number ;
}
interface ActivityLog {
id : string ;
action : string ;
details : string ;
date : string ;
by : string ;
}
2026-04-26 14:56:12 +06:00
interface BikeAssignment {
id : string ;
bikeId : string ;
bikerId : string ;
bikerName : string ;
assignedAt : string ;
assignedBy : string ;
unassignedAt? : string ;
unassignedBy? : string ;
reason? : string ;
status : 'active' | 'completed' ;
notes? : string ;
}
interface DamageRecord {
id : string ;
date : string ;
type : 'accident' | 'theft' | 'natural' | 'wear_tear' | 'other' ;
description : string ;
reportedBy : string ;
reportedAt : string ;
estimatedCost? : number ;
actualCost? : number ;
status : 'reported' | 'under_repair' | 'repaired' | 'claim_rejected' ;
2026-05-16 20:19:23 +06:00
hubId? : string ;
hubName? : string ;
2026-04-26 14:56:12 +06:00
images? : string [ ] ;
billImage? : string ;
resolvedAt? : string ;
notes? : string ;
}
interface MaintenanceRecord {
id : string ;
date : string ;
type : 'routine' | 'battery' | 'tire' | 'brake' | 'engine' | 'electrical' | 'other' ;
description : string ;
performedBy : string ;
cost : number ;
nextDueDate? : string ;
status : 'scheduled' | 'in_progress' | 'completed' ;
2026-05-16 20:19:23 +06:00
hubId? : string ;
hubName? : string ;
2026-04-26 14:56:12 +06:00
notes? : string ;
}
2026-05-16 20:44:19 +06:00
interface BatteryHistory {
id : string ;
batteryId : string ;
brand : string ;
model : string ;
serialNumber : string ;
assignedDate : string ;
returnedDate? : string ;
2026-05-16 20:54:17 +06:00
swappedToBatteryId? : string ;
status : 'active' | 'returned' | 'swapped' ;
2026-05-16 20:44:19 +06:00
socStart : number ;
socEnd? : number ;
monthlyRent? : number ;
}
2026-04-22 01:02:45 +06:00
interface Bike {
id : string ;
model : string ;
brand : string ;
image : string ;
plateNumber : string ;
status : 'available' | 'rented' | 'maintenance' | 'retired' ;
batteryLevel : number ;
2026-05-16 20:44:19 +06:00
currentBatteryId? : string ;
currentBatteryBrand? : string ;
currentBatteryModel? : string ;
location? : string ;
2026-04-26 16:02:53 +06:00
hubId? : string ;
hubName? : string ;
2026-04-22 01:02:45 +06:00
assignedTo? : string ;
2026-05-16 20:44:19 +06:00
renterPhone? : string ;
renterNid? : string ;
rentalStartDate? : string ;
subscriptionType ? : 'daily' | 'weekly' | 'monthly' ;
weeklyRent? : number ;
monthlyRent? : number ;
2026-04-22 01:02:45 +06:00
investorId? : string ;
investorName? : string ;
purchaseDate? : string ;
purchasePrice? : number ;
currentRent? : number ;
totalRides? : number ;
totalDistance? : number ;
totalEarnings? : number ;
lastService? : string ;
nextService? : string ;
insuranceExpiry? : string ;
registrationExpiry? : string ;
notes? : string ;
gpsDevice? : GPSDevice ;
documents? : BikeDocument [ ] ;
rentalHistory? : RentalHistory [ ] ;
activityLog? : ActivityLog [ ] ;
2026-04-26 14:56:12 +06:00
assignmentHistory? : BikeAssignment [ ] ;
damageHistory? : DamageRecord [ ] ;
maintenanceHistory? : MaintenanceRecord [ ] ;
2026-05-16 20:44:19 +06:00
batteryHistory? : BatteryHistory [ ] ;
2026-05-16 20:54:17 +06:00
bikeImages ? : {
front? : string ;
back? : string ;
left? : string ;
right? : string ;
} ;
2026-04-22 01:02:45 +06:00
}
const mockBikes : Bike [ ] = [
2026-04-26 14:56:12 +06:00
{
2026-05-16 22:34:44 +06:00
id : 'EV001' , model : 'Etron ET50' , brand : 'Etron' , image : '' , plateNumber : 'Dhaka Metro Cha-A-1234' , status : 'rented' , batteryLevel : 78 , currentBatteryId : 'BAT-001' , currentBatteryBrand : 'EVE Energy' , currentBatteryModel : 'Li-Ion 60V50Ah' , location : 'Gulshan 1' , assignedTo : 'Rahim Ahmed' , renterPhone : '01712345678' , renterNid : '1234567890' , rentalStartDate : '2024-03-01' , subscriptionType : 'weekly' , weeklyRent : 2400 , hubId : 'HUB-001' , hubName : 'JAIBEN Head Office' , investorId : 'inv1' , investorName : 'Mr. Hasan (Investor)' , purchaseDate : '2024-01-15' , purchasePrice : 125000 , currentRent : 350 , totalRides : 156 , totalDistance : 2340 , totalEarnings : 54600 , lastService : '2024-03-01' , nextService : '2024-04-01' , insuranceExpiry : '2025-01-15' , registrationExpiry : '2026-01-15' ,
2026-04-22 01:02:45 +06:00
gpsDevice : { id : 'GPS001' , phone : '01712345601' , imei : '861234567890123' , lastActive : '2024-03-21 14:30' , signal : 85 , battery : 72 } ,
documents : [
{ type : 'registration' , number : 'REG-EV001-2024' , issueDate : '2024-01-15' , expiryDate : '2026-01-15' , verified : true } ,
{ type : 'insurance' , number : 'INS-EV001-2024' , issueDate : '2024-01-15' , expiryDate : '2025-01-15' , verified : true } ,
{ type : 'fitness' , number : 'FIT-EV001-2024' , issueDate : '2024-01-15' , expiryDate : '2025-01-15' , verified : true } ,
{ type : 'permit' , number : 'PMT-EV001-2024' , issueDate : '2024-01-15' , expiryDate : '2026-01-15' , verified : true } ,
] ,
rentalHistory : [
{ id : 'R001' , bikerId : 'B001' , bikerName : 'Rahim Ahmed' , type : 'single' , status : 'active' , startDate : '2024-03-01' , dailyRate : 350 , totalPaid : 7350 , rideCount : 21 } ,
{ id : 'R002' , bikerId : 'B003' , bikerName : 'Jamal Khan' , type : 'rent-to-own' , status : 'completed' , startDate : '2024-02-01' , endDate : '2024-02-28' , dailyRate : 400 , totalPaid : 11200 , rideCount : 28 } ,
{ id : 'R003' , bikerId : 'B005' , bikerName : 'Mahir Islam' , type : 'shared' , status : 'completed' , startDate : '2024-01-15' , endDate : '2024-01-31' , dailyRate : 60 , totalPaid : 2700 , rideCount : 45 } ,
] ,
activityLog : [
{ id : 'A001' , action : 'Rental Started' , details : 'Single rental by Rahim Ahmed' , date : '2024-03-01 08:00' , by : 'System' } ,
{ id : 'A002' , action : 'Service' , details : 'Regular maintenance completed' , date : '2024-03-01' , by : 'Admin' } ,
{ id : 'A003' , action : 'GPS Update' , details : 'New GPS device installed' , date : '2024-02-15' , by : 'Admin' } ,
{ id : 'A004' , action : 'Rental Completed' , details : 'Rent-to-own by Jamal Khan' , date : '2024-02-28 23:59' , by : 'System' } ,
{ id : 'A005' , action : 'Insurance Renewed' , details : 'Insurance renewed for 1 year' , date : '2024-01-15' , by : 'Admin' } ,
2026-04-26 14:56:12 +06:00
] ,
damageHistory : [
2026-05-16 20:19:23 +06:00
{ id : 'DMG001' , date : '2024-02-10' , type : 'accident' , description : 'Minor collision at Mirpur intersection' , reportedBy : 'Jamal Khan' , reportedAt : '2024-02-10 14:30' , estimatedCost : 5000 , actualCost : 4500 , status : 'repaired' , resolvedAt : '2024-02-15' , hubId : 'HUB-001' , hubName : 'Gulshan Hub' } ,
{ id : 'DMG002' , date : '2024-03-15' , type : 'wear_tear' , description : 'Front tire wear - replaced' , reportedBy : 'Rahim Ahmed' , reportedAt : '2024-03-15 09:00' , estimatedCost : 2500 , actualCost : 2200 , status : 'repaired' , resolvedAt : '2024-03-16' , hubId : 'HUB-002' , hubName : 'Banani Hub' } ,
2026-04-26 14:56:12 +06:00
] ,
maintenanceHistory : [
2026-05-16 20:19:23 +06:00
{ id : 'MNT001' , date : '2024-03-01' , type : 'routine' , description : 'Full service - oil change, brake check, tire rotation' , performedBy : 'Service Center' , cost : 1500 , nextDueDate : '2024-04-01' , status : 'completed' , hubId : 'HUB-001' , hubName : 'Gulshan Hub' } ,
{ id : 'MNT002' , date : '2024-02-15' , type : 'battery' , description : 'Battery health check and terminal cleaning' , performedBy : 'Service Center' , cost : 500 , nextDueDate : '2024-05-15' , status : 'completed' , hubId : 'HUB-003' , hubName : 'Uttara Hub' } ,
{ id : 'MNT003' , date : '2024-01-20' , type : 'tire' , description : 'Tire pressure check and inflation' , performedBy : 'Service Center' , cost : 300 , nextDueDate : '2024-04-20' , status : 'completed' , hubId : 'HUB-004' , hubName : 'Mirpur Hub' } ,
2026-05-16 20:44:19 +06:00
] ,
batteryHistory : [
{ id : 'BH001' , batteryId : 'BAT-001' , brand : 'EVE Energy' , model : 'Li-Ion 60V50Ah' , serialNumber : 'SN-2024-00001' , assignedDate : '2024-03-15' , status : 'active' , socStart : 85 , monthlyRent : 1500 } ,
{ id : 'BH002' , batteryId : 'BAT-005' , brand : 'Samsung SDI' , model : 'Li-Ion 60V45Ah' , serialNumber : 'SN-2023-00045' , assignedDate : '2024-01-10' , returnedDate : '2024-03-14' , status : 'returned' , socStart : 80 , socEnd : 45 , monthlyRent : 1200 } ,
2026-05-16 20:54:17 +06:00
{ id : 'BH003' , batteryId : 'BAT-008' , brand : 'LG Chem' , model : 'Li-Ion 48V40Ah' , serialNumber : 'SN-2023-00012' , assignedDate : '2023-11-05' , returnedDate : '2024-01-09' , status : 'swapped' , socStart : 90 , socEnd : 60 , monthlyRent : 1000 } ,
{ id : 'BH004' , batteryId : 'BAT-003' , brand : 'Panasonic' , model : 'Li-Ion 60V50Ah' , serialNumber : 'SN-2023-00078' , assignedDate : '2023-08-20' , returnedDate : '2023-11-04' , status : 'swapped' , socStart : 88 , socEnd : 55 , monthlyRent : 1500 } ,
2026-05-16 20:44:19 +06:00
{ id : 'BH005' , batteryId : 'BAT-002' , brand : 'Sony' , model : 'Li-Ion 48V35Ah' , serialNumber : 'SN-2023-00034' , assignedDate : '2023-05-15' , returnedDate : '2023-08-19' , status : 'returned' , socStart : 75 , socEnd : 40 , monthlyRent : 900 } ,
{ id : 'BH006' , batteryId : 'BAT-012' , brand : 'BYD' , model : 'LiFePO4 60V40Ah' , serialNumber : 'SN-2023-00056' , assignedDate : '2024-04-20' , returnedDate : '2024-06-15' , status : 'returned' , socStart : 92 , socEnd : 35 , monthlyRent : 1300 } ,
{ id : 'BH007' , batteryId : 'BAT-015' , brand : 'CATL' , model : 'Li-Ion 72V50Ah' , serialNumber : 'SN-2024-00089' , assignedDate : '2024-06-20' , status : 'active' , socStart : 88 , monthlyRent : 1800 } ,
{ id : 'BH008' , batteryId : 'BAT-009' , brand : 'Tongsheng' , model : 'Li-Ion 48V45Ah' , serialNumber : 'SN-2023-00023' , assignedDate : '2023-02-10' , returnedDate : '2023-05-14' , status : 'returned' , socStart : 82 , socEnd : 50 , monthlyRent : 1100 } ,
{ id : 'BH009' , batteryId : 'BAT-011' , brand : 'Binek' , model : 'Li-Ion 60V48Ah' , serialNumber : 'SN-2023-00067' , assignedDate : '2024-02-01' , returnedDate : '2024-04-18' , status : 'returned' , socStart : 78 , socEnd : 42 , monthlyRent : 1400 } ,
{ id : 'BH010' , batteryId : 'BAT-007' , brand : 'Kexin' , model : 'Li-Ion 48V36Ah' , serialNumber : 'SN-2022-00045' , assignedDate : '2022-12-05' , returnedDate : '2023-02-08' , status : 'returned' , socStart : 85 , socEnd : 55 , monthlyRent : 850 } ,
{ id : 'BH011' , batteryId : 'BAT-004' , brand : 'Faraday' , model : 'LiFePO4 48V42Ah' , serialNumber : 'SN-2022-00089' , assignedDate : '2022-09-15' , returnedDate : '2022-12-04' , status : 'returned' , socStart : 90 , socEnd : 48 , monthlyRent : 1200 } ,
{ id : 'BH012' , batteryId : 'BAT-006' , brand : 'Reliance' , model : 'Lead Acid 48V32Ah' , serialNumber : 'SN-2022-00034' , assignedDate : '2022-06-20' , returnedDate : '2022-09-14' , status : 'returned' , socStart : 95 , socEnd : 30 , monthlyRent : 600 } ,
2026-05-16 20:54:17 +06:00
{ id : 'BH013' , batteryId : 'BAT-020' , brand : 'Maxell' , model : 'Li-Ion 60V45Ah' , serialNumber : 'SN-2024-00123' , assignedDate : '2024-07-10' , swappedToBatteryId : 'BAT-025' , status : 'swapped' , socStart : 75 , socEnd : 65 , monthlyRent : 1400 } ,
{ id : 'BH014' , batteryId : 'BAT-018' , brand : 'Nikola' , model : 'LiFePO4 48V40Ah' , serialNumber : 'SN-2023-00078' , assignedDate : '2024-05-05' , swappedToBatteryId : 'BAT-022' , status : 'swapped' , socStart : 82 , socEnd : 55 , monthlyRent : 1150 } ,
] ,
bikeImages : {
front : 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop' ,
back : 'https://images.unsplash.com/photo-1591637333184-19aa84de3fbd?w=400&h=300&fit=crop' ,
left : 'https://images.unsplash.com/photo-1622185135505-2d795043906a?w=400&h=300&fit=crop' ,
right : 'https://images.unsplash.com/photo-1609630875171-b1321377ee53?w=400&h=300&fit=crop' ,
}
2026-04-22 01:02:45 +06:00
} ,
2026-04-26 14:56:12 +06:00
{
2026-04-26 16:02:53 +06:00
id : 'EV002' , model : 'Yadea DT3' , brand : 'Yadea' , image : '' , plateNumber : 'Dhaka Metro Cha-A-5678' , status : 'available' , batteryLevel : 95 , location : 'Banani' , hubId : 'HUB-002' , hubName : 'Banani Hub' , investorId : 'inv1' , investorName : 'Mr. Hasan (Investor)' , purchaseDate : '2024-02-01' , purchasePrice : 118000 , totalRides : 89 , totalDistance : 1567 , totalEarnings : 31150 , lastService : '2024-03-15' , nextService : '2024-04-15' , insuranceExpiry : '2025-02-01' , registrationExpiry : '2026-02-01' ,
2026-04-22 01:02:45 +06:00
gpsDevice : { id : 'GPS002' , phone : '01712345602' , imei : '861234567890124' , lastActive : '2024-03-21 15:00' , signal : 92 , battery : 88 } ,
documents : [
{ type : 'registration' , number : 'REG-EV002-2024' , issueDate : '2024-02-01' , expiryDate : '2026-02-01' , verified : true } ,
{ type : 'insurance' , number : 'INS-EV002-2024' , issueDate : '2024-02-01' , expiryDate : '2025-02-01' , verified : true } ,
{ type : 'fitness' , number : 'FIT-EV002-2024' , issueDate : '2024-02-01' , expiryDate : '2025-02-01' , verified : true } ,
] ,
rentalHistory : [
{ id : 'R004' , bikerId : 'B002' , bikerName : 'Karim Singh' , type : 'single' , status : 'completed' , startDate : '2024-02-15' , endDate : '2024-02-28' , dailyRate : 350 , totalPaid : 4900 , rideCount : 14 } ,
] ,
activityLog : [
{ id : 'A006' , action : 'Service' , details : 'Regular maintenance' , date : '2024-03-15' , by : 'Admin' } ,
{ id : 'A007' , action : 'Added to Fleet' , details : 'Bike registered in system' , date : '2024-02-01' , by : 'Admin' } ,
]
} ,
2026-04-26 14:56:12 +06:00
{
2026-04-26 16:02:53 +06:00
id : 'EV003' , model : 'AIMA Lightning' , brand : 'AIMA' , image : '' , plateNumber : 'Dhaka Metro Cha-A-9012' , status : 'rented' , batteryLevel : 62 , hubId : 'HUB-003' , hubName : 'Uttara Hub' , investorId : 'inv1' , investorName : 'Mr. Hasan (Investor)' , purchaseDate : '2024-01-20' , purchasePrice : 132000 , currentRent : 400 , totalRides : 203 , totalDistance : 3890 , totalEarnings : 71100 , lastService : '2024-03-10' , nextService : '2024-04-10' , insuranceExpiry : '2025-01-20' , registrationExpiry : '2026-01-20' ,
2026-04-22 01:02:45 +06:00
gpsDevice : { id : 'GPS003' , phone : '01712345603' , imei : '861234567890125' , lastActive : '2024-03-21 14:45' , signal : 78 , battery : 55 } ,
documents : [
{ type : 'registration' , number : 'REG-EV003-2024' , issueDate : '2024-01-20' , expiryDate : '2026-01-20' , verified : true } ,
{ type : 'insurance' , number : 'INS-EV003-2024' , issueDate : '2024-01-20' , expiryDate : '2025-01-20' , verified : true } ,
{ type : 'fitness' , number : 'FIT-EV003-2024' , issueDate : '2024-01-20' , expiryDate : '2025-01-20' , verified : true } ,
] ,
rentalHistory : [ ] ,
activityLog : [ ]
} ,
2026-04-26 14:56:12 +06:00
{
2026-04-26 16:02:53 +06:00
id : 'EV004' , model : 'TVS iQube' , brand : 'TVS' , image : '' , plateNumber : 'Dhaka Metro Cha-A-3456' , status : 'maintenance' , batteryLevel : 45 , hubId : 'HUB-002' , hubName : 'Banani Hub' , investorId : 'inv2' , investorName : 'Mrs. Rita (Investor)' , purchaseDate : '2023-12-10' , purchasePrice : 145000 , totalRides : 312 , totalDistance : 5670 , totalEarnings : 98000 , lastService : '2024-03-20' , nextService : '2024-03-25' , insuranceExpiry : '2024-12-10' , registrationExpiry : '2025-12-10' , notes : 'Motor issue - awaiting parts' ,
2026-04-22 01:02:45 +06:00
gpsDevice : { id : 'GPS004' , phone : '01712345604' , imei : '861234567890126' , lastActive : '2024-03-20 10:00' , signal : 0 , battery : 12 } ,
documents : [
{ type : 'registration' , number : 'REG-EV004-2023' , issueDate : '2023-12-10' , expiryDate : '2025-12-10' , verified : true } ,
{ type : 'insurance' , number : 'INS-EV004-2023' , issueDate : '2023-12-10' , expiryDate : '2024-12-10' , verified : true } ,
] ,
rentalHistory : [ ] ,
activityLog : [ ]
} ,
2026-04-26 14:56:12 +06:00
{
2026-04-26 16:02:53 +06:00
id : 'EV005' , model : 'Bajaj Chetak' , brand : 'Bajaj' , image : '' , plateNumber : 'Dhaka Metro Cha-A-7890' , status : 'available' , batteryLevel : 100 , hubId : 'HUB-001' , hubName : 'JAIBEN Head Office' , investorId : 'inv2' , investorName : 'Mrs. Rita (Investor)' , purchaseDate : '2024-02-15' , purchasePrice : 138000 , totalRides : 67 , totalDistance : 890 , totalEarnings : 23450 , lastService : '2024-03-18' , nextService : '2024-04-18' , insuranceExpiry : '2025-02-15' , registrationExpiry : '2026-02-15' ,
2026-04-22 01:02:45 +06:00
gpsDevice : { id : 'GPS005' , phone : '01712345605' , imei : '861234567890127' , lastActive : '2024-03-21 15:30' , signal : 95 , battery : 92 } ,
documents : [
{ type : 'registration' , number : 'REG-EV005-2024' , issueDate : '2024-02-15' , expiryDate : '2026-02-15' , verified : true } ,
{ type : 'insurance' , number : 'INS-EV005-2024' , issueDate : '2024-02-15' , expiryDate : '2025-02-15' , verified : true } ,
] ,
rentalHistory : [ ] ,
activityLog : [ ]
} ,
] ;
function getBikeById ( id : string ) : Bike | undefined {
return mockBikes . find ( b = > b . id === id ) ;
}
const statusColors : Record < string , string > = {
available : 'bg-green-100 text-green-700' ,
rented : 'bg-blue-100 text-blue-700' ,
maintenance : 'bg-amber-100 text-amber-700' ,
retired : 'bg-slate-100 text-slate-500' ,
} ;
const docTypeLabels : Record < string , string > = {
registration : 'Registration Certificate' ,
insurance : 'Insurance' ,
fitness : 'Fitness Certificate' ,
permit : 'Road Permit' ,
other : 'Other' ,
} ;
export default function FleetDetailPage ( { params } : { params : Promise < { id : string } > } ) {
const resolvedParams = use ( params ) ;
const router = useRouter ( ) ;
const bikeData = getBikeById ( resolvedParams . id ) ;
2026-04-26 14:56:12 +06:00
2026-04-22 01:02:45 +06:00
const [ activeTab , setActiveTab ] = useState ( 'overview' ) ;
2026-04-26 14:56:12 +06:00
const [ bikes , setBikes ] = useState < Bike [ ] > ( mockBikes ) ;
const [ showDamageModal , setShowDamageModal ] = useState ( false ) ;
const [ showMaintenanceModal , setShowMaintenanceModal ] = useState ( false ) ;
const [ editingDamage , setEditingDamage ] = useState < DamageRecord | null > ( null ) ;
const [ editingMaintenance , setEditingMaintenance ] = useState < MaintenanceRecord | null > ( null ) ;
const bike = bikes . find ( b = > b . id === resolvedParams . id ) || bikes [ 0 ] ;
const damageTypes = [
{ value : 'accident' , label : 'Accident' } ,
{ value : 'theft' , label : 'Theft' } ,
{ value : 'natural' , label : 'Natural Disaster' } ,
{ value : 'wear_tear' , label : 'Wear & Tear' } ,
{ value : 'other' , label : 'Other' } ,
] ;
const maintenanceTypes = [
{ value : 'routine' , label : 'Routine Service' } ,
{ value : 'battery' , label : 'Battery' } ,
{ value : 'tire' , label : 'Tire' } ,
{ value : 'brake' , label : 'Brake' } ,
{ value : 'engine' , label : 'Engine' } ,
{ value : 'electrical' , label : 'Electrical' } ,
{ value : 'other' , label : 'Other' } ,
] ;
2026-05-16 20:19:23 +06:00
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' } ,
] ;
2026-04-26 14:56:12 +06:00
const handleAddDamage = ( damage : DamageRecord ) = > {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return { . . . b , damageHistory : [ . . . ( b . damageHistory || [ ] ) , damage ] } ;
}
return b ;
} ) ) ;
setShowDamageModal ( false ) ;
} ;
const handleUpdateDamage = ( damage : DamageRecord ) = > {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return {
. . . b ,
damageHistory : ( b . damageHistory || [ ] ) . map ( d = > d . id === damage . id ? damage : d )
} ;
}
return b ;
} ) ) ;
setShowDamageModal ( false ) ;
setEditingDamage ( null ) ;
} ;
const handleDeleteDamage = ( damageId : string ) = > {
if ( confirm ( 'Are you sure you want to delete this damage record?' ) ) {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return { . . . b , damageHistory : ( b . damageHistory || [ ] ) . filter ( d = > d . id !== damageId ) } ;
}
return b ;
} ) ) ;
}
} ;
const handleAddMaintenance = ( maintenance : MaintenanceRecord ) = > {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return { . . . b , maintenanceHistory : [ . . . ( b . maintenanceHistory || [ ] ) , maintenance ] } ;
}
return b ;
} ) ) ;
setShowMaintenanceModal ( false ) ;
} ;
const handleUpdateMaintenance = ( maintenance : MaintenanceRecord ) = > {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return {
. . . b ,
maintenanceHistory : ( b . maintenanceHistory || [ ] ) . map ( m = > m . id === maintenance . id ? maintenance : m )
} ;
}
return b ;
} ) ) ;
setShowMaintenanceModal ( false ) ;
setEditingMaintenance ( null ) ;
} ;
2026-04-22 01:02:45 +06:00
2026-04-26 14:56:12 +06:00
const handleDeleteMaintenance = ( maintenanceId : string ) = > {
if ( confirm ( 'Are you sure you want to delete this maintenance record?' ) ) {
setBikes ( bikes . map ( b = > {
if ( b . id === bike . id ) {
return { . . . b , maintenanceHistory : ( b . maintenanceHistory || [ ] ) . filter ( m = > m . id !== maintenanceId ) } ;
}
return b ;
} ) ) ;
}
} ;
if ( ! bike ) {
2026-04-22 01:02:45 +06:00
return (
< div className = "p-4 lg:p-6" >
< div className = "bg-white rounded-xl p-8 text-center" >
< Bike className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< h2 className = "text-lg font-bold text-slate-700" > Bike Not Found < / h2 >
< p className = "text-sm text-slate-500 mb-4" > The bike ID "{resolvedParams.id}" was not found . < / p >
< Link href = "/admin/fleet" className = "text-accent hover:underline" > Back to Fleet < / Link >
< / div >
< / div >
) ;
}
const tabs = [
{ id : 'overview' , label : 'Overview' , icon : Bike } ,
2026-04-26 16:02:53 +06:00
// { id: 'biker-assignment', label: 'Assign Bikers', icon: User },
2026-04-22 01:02:45 +06:00
{ id : 'gps' , label : 'GPS & Tracking' , icon : Navigation2 } ,
{ id : 'documents' , label : 'Documents' , icon : FileText } ,
{ id : 'rental' , label : 'Rental History' , icon : History } ,
2026-04-26 14:56:12 +06:00
{ id : 'damage' , label : 'Damage History' , icon : AlertTriangle } ,
{ id : 'maintenance' , label : 'Maintenance' , icon : Wrench } ,
2026-04-22 01:02:45 +06:00
{ id : 'activity' , label : 'Activity Log' , icon : Clock3 } ,
{ id : 'investor' , label : 'Investor Info' , icon : User2 } ,
] ;
return (
< div className = "p-4 lg:p-6 min-h-screen" >
< div className = "flex items-center gap-3 mb-4" >
< button onClick = { ( ) = > router . back ( ) } className = "p-2 hover:bg-slate-100 rounded-lg lg:hidden" >
< ArrowLeft className = "w-5 h-5 text-slate-600" / >
< / button >
< Link href = "/admin/fleet" className = "p-2 hover:bg-slate-100 rounded-lg hidden lg:block" >
< ArrowLeft className = "w-5 h-5 text-slate-600" / >
< / Link >
< div className = "flex-1" >
< h1 className = "text-xl lg:text-2xl font-extrabold text-slate-800" > Bike Details < / h1 >
< p className = "text-sm text-slate-500" > ID : { bike . id } < / p >
< / div >
< / div >
< div className = "flex flex-wrap gap-2 mb-4 overflow-x-auto pb-2" >
{ tabs . map ( tab = > (
< button
key = { tab . id }
onClick = { ( ) = > setActiveTab ( tab . id ) }
2026-04-26 14:56:12 +06:00
className = { ` px-3 py-2 rounded-lg text-sm font-medium whitespace-nowrap flex items-center gap-2 ${ activeTab === tab . id
2026-04-26 16:02:53 +06:00
? 'bg-accent text-white'
: 'bg-white text-slate-600 border border-slate-200'
2026-04-26 14:56:12 +06:00
} ` }
2026-04-22 01:02:45 +06:00
>
< tab.icon className = "w-4 h-4" / >
{ tab . label }
< / button >
) ) }
< / div >
{ activeTab === 'overview' && < OverviewTab bike = { bike } / > }
2026-04-26 14:56:12 +06:00
{ activeTab === 'biker-assignment' && < BikerAssignmentTab bike = { bike } / > }
2026-04-22 01:02:45 +06:00
{ activeTab === 'gps' && < GPSTab bike = { bike } / > }
{ activeTab === 'documents' && < DocumentsTab bike = { bike } / > }
{ activeTab === 'rental' && < RentalTab bike = { bike } / > }
{ activeTab === 'activity' && < ActivityTab bike = { bike } / > }
{ activeTab === 'investor' && < InvestorTab bike = { bike } / > }
2026-04-26 14:56:12 +06:00
{ activeTab === 'damage' && (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< 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-accent" / > Damage History
< / h3 >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > { setEditingDamage ( null ) ; setShowDamageModal ( true ) ; } }
className = "px-4 py-2 bg-accent text-white text-sm rounded-lg hover:bg-accent-dark flex items-center gap-2"
>
< Plus className = "w-4 h-4" / > Add Damage
< / button >
< / div >
{ ( bike . damageHistory || [ ] ) . length > 0 ? (
< div className = "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" > Date < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Type < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Description < / th >
2026-05-16 20:19:23 +06:00
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Hub < / th >
2026-04-26 14:56:12 +06:00
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Reported By < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Est . Cost < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Actual Cost < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Status < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Actions < / th >
< / tr >
< / thead >
< tbody className = "divide-y divide-slate-100" >
{ bike . damageHistory ? . map ( damage = > (
< tr key = { damage . id } className = "hover:bg-slate-50" >
< td className = "px-4 py-3 text-sm text-slate-600" > { damage . date } < / td >
< td className = "px-4 py-3" >
< span className = "text-sm text-slate-700 capitalize" > { damage . type . replace ( '_' , ' ' ) } < / span >
< / td >
< td className = "px-4 py-3 text-sm text-slate-600 max-w-xs truncate" > { damage . description } < / td >
2026-05-16 20:19:23 +06:00
< td className = "px-4 py-3 text-sm text-slate-600" > { damage . hubName || '-' } < / td >
2026-04-26 14:56:12 +06:00
< td className = "px-4 py-3 text-sm text-slate-600" > { damage . reportedBy } < / td >
< td className = "px-4 py-3 text-sm text-slate-600" > ৳ { damage . estimatedCost || 0 } < / td >
< td className = "px-4 py-3 text-sm font-medium text-slate-700" > ৳ { damage . actualCost || '-' } < / td >
< td className = "px-4 py-3" >
2026-04-26 16:02:53 +06:00
< span className = { ` inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${ damage . status === 'repaired' ? 'bg-green-100 text-green-700' :
2026-05-16 20:54:17 +06:00
damage . status === 'under_repair' ? 'bg-amber-100 text-amber-700' :
damage . status === 'claim_rejected' ? 'bg-red-100 text-red-700' :
'bg-slate-100 text-slate-700'
2026-04-26 16:02:53 +06:00
} ` }>
2026-04-26 14:56:12 +06:00
{ damage . status . replace ( '_' , ' ' ) }
< / span >
< / td >
< td className = "px-4 py-3" >
< div className = "flex items-center gap-1" >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > { setEditingDamage ( damage ) ; setShowDamageModal ( true ) ; } }
className = "p-2 hover:bg-slate-100 rounded-lg"
title = "Edit"
>
< Edit className = "w-4 h-4 text-slate-400" / >
< / button >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > handleDeleteDamage ( damage . id ) }
className = "p-2 hover:bg-red-50 rounded-lg"
title = "Delete"
>
< Trash2 className = "w-4 h-4 text-red-400" / >
< / button >
< / div >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) : (
< div className = "text-center py-12" >
< AlertTriangle className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-slate-500" > No damage records found < / p >
< / div >
) }
< / div >
2026-04-22 01:02:45 +06:00
< / div >
2026-04-26 14:56:12 +06:00
) }
{ activeTab === 'maintenance' && (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< div className = "flex items-center justify-between mb-4" >
< h3 className = "font-semibold text-slate-700 flex items-center gap-2" >
< Wrench className = "w-5 h-5 text-accent" / > Maintenance History
< / h3 >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > { setEditingMaintenance ( null ) ; setShowMaintenanceModal ( true ) ; } }
className = "px-4 py-2 bg-accent text-white text-sm rounded-lg hover:bg-accent-dark flex items-center gap-2"
>
< Plus className = "w-4 h-4" / > Add Maintenance
< / button >
< / div >
{ ( bike . maintenanceHistory || [ ] ) . length > 0 ? (
< div className = "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" > Date < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Type < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Description < / th >
2026-05-16 20:19:23 +06:00
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Hub < / th >
2026-04-26 14:56:12 +06:00
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Performed By < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Cost < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Next Due < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Status < / th >
< th className = "px-4 py-3 text-left text-xs font-semibold text-slate-500 uppercase" > Actions < / th >
< / tr >
< / thead >
< tbody className = "divide-y divide-slate-100" >
{ bike . maintenanceHistory ? . map ( maintenance = > (
< tr key = { maintenance . id } className = "hover:bg-slate-50" >
< td className = "px-4 py-3 text-sm text-slate-600" > { maintenance . date } < / td >
< td className = "px-4 py-3" >
< span className = "text-sm text-slate-700 capitalize" > { maintenance . type } < / span >
< / td >
< td className = "px-4 py-3 text-sm text-slate-600 max-w-xs truncate" > { maintenance . description } < / td >
2026-05-16 20:19:23 +06:00
< td className = "px-4 py-3 text-sm text-slate-600" > { maintenance . hubName || '-' } < / td >
2026-04-26 14:56:12 +06:00
< td className = "px-4 py-3 text-sm text-slate-600" > { maintenance . performedBy } < / td >
< td className = "px-4 py-3 text-sm font-medium text-slate-700" > ৳ { maintenance . cost } < / td >
< td className = "px-4 py-3 text-sm text-slate-600" > { maintenance . nextDueDate || '-' } < / td >
< td className = "px-4 py-3" >
2026-04-26 16:02:53 +06:00
< span className = { ` inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${ maintenance . status === 'completed' ? 'bg-green-100 text-green-700' :
2026-05-16 20:54:17 +06:00
maintenance . status === 'in_progress' ? 'bg-amber-100 text-amber-700' :
'bg-slate-100 text-slate-700'
2026-04-26 16:02:53 +06:00
} ` }>
2026-04-26 14:56:12 +06:00
{ maintenance . status . replace ( '_' , ' ' ) }
< / span >
< / td >
< td className = "px-4 py-3" >
< div className = "flex items-center gap-1" >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > { setEditingMaintenance ( maintenance ) ; setShowMaintenanceModal ( true ) ; } }
className = "p-2 hover:bg-slate-100 rounded-lg"
title = "Edit"
>
< Edit className = "w-4 h-4 text-slate-400" / >
< / button >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { ( ) = > handleDeleteMaintenance ( maintenance . id ) }
className = "p-2 hover:bg-red-50 rounded-lg"
title = "Delete"
>
< Trash2 className = "w-4 h-4 text-red-400" / >
< / button >
< / div >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) : (
< div className = "text-center py-12" >
< Wrench className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-slate-500" > No maintenance records found < / p >
< / div >
) }
< / div >
< / div >
) }
{ showDamageModal && (
< DamageModal
bike = { bike }
damage = { editingDamage }
onClose = { ( ) = > { setShowDamageModal ( false ) ; setEditingDamage ( null ) ; } }
onSave = { ( damage ) = > {
if ( editingDamage ) {
handleUpdateDamage ( damage ) ;
} else {
handleAddDamage ( damage ) ;
}
} }
/ >
) }
{ showMaintenanceModal && (
< MaintenanceModal
bike = { bike }
maintenance = { editingMaintenance }
onClose = { ( ) = > { setShowMaintenanceModal ( false ) ; setEditingMaintenance ( null ) ; } }
onSave = { ( maintenance ) = > {
if ( editingMaintenance ) {
handleUpdateMaintenance ( maintenance ) ;
} else {
handleAddMaintenance ( maintenance ) ;
}
} }
/ >
) }
2026-04-22 01:02:45 +06:00
< / div >
) ;
}
function OverviewTab ( { bike } : { bike : Bike } ) {
2026-05-16 20:44:19 +06:00
const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
const itemsPerPage = 5 ;
const batteryHistory = bike . batteryHistory || [ ] ;
const totalPages = Math . ceil ( batteryHistory . length / itemsPerPage ) ;
const paginatedHistory = batteryHistory . slice (
( currentPage - 1 ) * itemsPerPage ,
currentPage * itemsPerPage
) ;
2026-04-22 01:02:45 +06:00
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< div className = "flex items-start gap-4 mb-4" >
< div className = "w-16 h-16 rounded-xl bg-blue-50 flex items-center justify-center" >
< Bike className = "w-8 h-8 text-blue-600" / >
< / div >
< div className = "flex-1" >
< h2 className = "text-xl font-bold text-slate-800" > { bike . model } < / h2 >
< p className = "text-sm text-slate-500" > { bike . brand } • { bike . id } < / p >
< div className = "flex flex-wrap gap-2 mt-2" >
< span className = { ` inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${ statusColors [ bike . status ] } ` } >
{ bike . status }
< / span >
< span className = "inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full bg-slate-100 text-slate-600" >
2026-04-26 16:02:53 +06:00
< MapPin className = "w-3 h-3" / > { bike . hubName || 'Not Assigned' }
2026-04-22 01:02:45 +06:00
< / span >
< / div >
< / div >
< / div >
2026-04-26 14:56:12 +06:00
2026-04-22 01:02:45 +06:00
< div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
2026-05-16 20:44:19 +06:00
< p className = "text-xs text-slate-500" > Bike < / p >
< p className = "font-semibold text-slate-700" > { bike . brand } { bike . model } < / p >
< p className = "text-xs text-slate-500 mt-1" > { bike . plateNumber } < / p >
2026-04-22 01:02:45 +06:00
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Battery < / p >
2026-05-16 20:44:19 +06:00
< p className = { ` font-semibold text-lg ${ bike . batteryLevel > 50 ? 'text-green-600' : bike . batteryLevel > 20 ? 'text-amber-600' : 'text-red-600' } ` } > { bike . batteryLevel } % < / p >
{ bike . currentBatteryId && (
< p className = "text-xs font-medium text-slate-700 mt-1" > { bike . currentBatteryId } < / p >
) }
{ bike . currentBatteryBrand && (
< p className = "text-xs text-slate-500" > { bike . currentBatteryBrand } { bike . currentBatteryModel } < / p >
) }
2026-04-22 01:02:45 +06:00
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Current Renter < / p >
< p className = "font-semibold text-slate-700" > { bike . assignedTo || 'Available' } < / p >
2026-05-16 20:44:19 +06:00
{ bike . renterPhone && < p className = "text-xs text-slate-500 mt-1" > { bike . renterPhone } < / p > }
{ bike . rentalStartDate && < p className = "text-xs text-slate-400 mt-1" > Since : { bike . rentalStartDate } < / p > }
2026-04-22 01:02:45 +06:00
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
2026-05-16 20:44:19 +06:00
< p className = "text-xs text-slate-500" > Subscription < / p >
< p className = "font-semibold text-slate-700 capitalize" > { bike . subscriptionType || 'Daily' } < / p >
{ bike . subscriptionType === 'weekly' && bike . weeklyRent && (
< p className = "text-xs text-green-600 mt-1" > ৳ { bike . weeklyRent } / week < / p >
) }
{ bike . subscriptionType === 'monthly' && bike . monthlyRent && (
< p className = "text-xs text-green-600 mt-1" > ৳ { bike . monthlyRent } / month < / p >
) }
{ bike . subscriptionType === 'daily' && (
< p className = "text-xs text-green-600 mt-1" > ৳ { bike . currentRent || 0 } / day < / p >
) }
2026-04-22 01:02:45 +06:00
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Performance Metrics < / h3 >
< div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Total Rides < / p >
< p className = "text-xl font-bold text-slate-700" > { bike . totalRides || 0 } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Distance < / p >
< p className = "text-xl font-bold text-slate-700" > { ( bike . totalDistance || 0 ) . toLocaleString ( ) } km < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Total Earnings < / p >
< p className = "text-xl font-bold text-green-600" > ৳ { bike . totalEarnings ? . toLocaleString ( ) || 0 } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Purchase Price < / p >
< p className = "text-xl font-bold text-slate-700" > ৳ { bike . purchasePrice ? . toLocaleString ( ) || 0 } < / p >
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Maintenance < / h3 >
< div className = "grid grid-cols-2 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Last Service < / p >
< p className = "font-medium text-slate-700" > { bike . lastService || 'N/A' } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Next Service < / p >
< p className = "font-medium text-slate-700" > { bike . nextService || 'N/A' } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Insurance Expiry < / p >
< p className = "font-medium text-slate-700" > { bike . insuranceExpiry || 'N/A' } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Registration Expiry < / p >
< p className = "font-medium text-slate-700" > { bike . registrationExpiry || 'N/A' } < / p >
< / div >
< / div >
{ bike . notes && (
< div className = "mt-3 p-3 bg-amber-50 rounded-lg" >
< p className = "text-xs text-amber-600 font-medium" > Note < / p >
< p className = "text-sm text-amber-800" > { bike . notes } < / p >
< / div >
) }
2026-05-16 20:44:19 +06:00
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-4" > Battery History < / h3 >
{ batteryHistory . length > 0 ? (
< >
< div className = "overflow-x-auto" >
< table className = "w-full" >
< thead className = "bg-slate-50" >
< tr >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Battery ID < / th >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Brand / Model < / th >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Assigned Date < / th >
2026-05-16 20:54:17 +06:00
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Returned / Swapped < / th >
2026-05-16 20:44:19 +06:00
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Start SOC < / th >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > End SOC < / th >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Monthly Rent < / th >
< th className = "px-3 py-2 text-left text-xs font-semibold text-slate-500" > Status < / th >
< / tr >
< / thead >
< tbody className = "divide-y divide-slate-100" >
{ paginatedHistory . map ( bh = > (
< tr key = { bh . id } className = "hover:bg-slate-50" >
< td className = "px-3 py-2 text-sm text-slate-700 font-medium" > { bh . batteryId } < / td >
< td className = "px-3 py-2 text-sm text-slate-600" > { bh . brand } { bh . model } < / td >
< td className = "px-3 py-2 text-sm text-slate-600" > { bh . assignedDate } < / td >
2026-05-16 20:54:17 +06:00
< td className = "px-3 py-2 text-sm text-slate-600" >
{ bh . status === 'swapped' ? (
< >
< span className = "text-blue-600 block" > Swapped to { bh . swappedToBatteryId } < / span >
< span className = "text-blue-600 text-[10px] block opacity-75" > { bh . returnedDate } < / span >
< / >
) : bh . returnedDate || '-' }
< / td >
2026-05-16 20:44:19 +06:00
< td className = "px-3 py-2 text-sm text-slate-600" > { bh . socStart } % < / td >
< td className = "px-3 py-2 text-sm text-slate-600" > { bh . socEnd ? ` ${ bh . socEnd } % ` : '-' } < / td >
< td className = "px-3 py-2 text-sm text-green-600" > ৳ { bh . monthlyRent } < / td >
< td className = "px-3 py-2" >
2026-05-16 20:54:17 +06:00
< span className = { ` text-xs font-medium px-2 py-1 rounded-full ${ bh . status === 'active' ? 'bg-green-100 text-green-700' : bh . status === 'swapped' ? 'bg-blue-100 text-blue-700' : 'bg-slate-100 text-slate-500' } ` } >
{ bh . status === 'active' ? 'Active' : bh . status === 'swapped' ? 'Swapped' : 'Returned' }
2026-05-16 20:44:19 +06:00
< / span >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
{ totalPages > 1 && (
< div className = "flex items-center justify-between mt-4 pt-4 border-t border-slate-100" >
< p className = "text-sm text-slate-500" >
Showing { ( currentPage - 1 ) * itemsPerPage + 1 } to { Math . min ( currentPage * itemsPerPage , batteryHistory . length ) } of { batteryHistory . length } batteries
< / p >
< div className = "flex items-center gap-2" >
< button
onClick = { ( ) = > setCurrentPage ( p = > Math . max ( 1 , p - 1 ) ) }
disabled = { currentPage === 1 }
className = "px-3 py-1 text-sm border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Previous
< / button >
{ Array . from ( { length : totalPages } , ( _ , i ) = > i + 1 ) . map ( page = > (
< button
key = { page }
onClick = { ( ) = > setCurrentPage ( page ) }
className = { ` px-3 py-1 text-sm rounded-lg ${ currentPage === page ? 'bg-accent text-white' : 'border border-slate-200 hover:bg-slate-50' } ` }
>
{ page }
< / button >
) ) }
< button
onClick = { ( ) = > setCurrentPage ( p = > Math . min ( totalPages , p + 1 ) ) }
disabled = { currentPage === totalPages }
className = "px-3 py-1 text-sm border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
< / button >
< / div >
< / div >
) }
< / >
) : (
< div className = "text-center py-8 text-slate-500" >
No battery history found
< / div >
) }
2026-04-22 01:02:45 +06:00
< / div >
< / div >
) ;
}
function GPSTab ( { bike } : { bike : Bike } ) {
const gps = bike . gpsDevice ;
if ( ! gps ) {
return (
< div className = "bg-white rounded-xl p-6 shadow-sm border border-slate-100 text-center" >
< Satellite className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< h3 className = "font-semibold text-slate-700" > No GPS Device < / h3 >
< p className = "text-sm text-slate-500" > This bike doesn ' t have a GPS device installed . < / p >
< / div >
) ;
}
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< div className = "flex items-center gap-3 mb-4" >
< div className = "w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center" >
< Satellite className = "w-6 h-6 text-purple-600" / >
< / div >
< div >
< h3 className = "font-semibold text-slate-700" > GPS Device < / h3 >
< p className = "text-sm text-slate-500" > ID : { gps . id } < / p >
< / div >
< / div >
< div className = "grid grid-cols-2 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Phone Number < / p >
< p className = "font-semibold text-slate-700 flex items-center gap-2" >
< Phone className = "w-4 h-4 text-slate-400" / > { gps . phone }
< / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > IMEI < / p >
< p className = "font-semibold text-slate-700 font-mono text-sm" > { gps . imei } < / p >
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Status < / h3 >
< div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Last Active < / p >
< p className = "font-medium text-slate-700" > { gps . lastActive } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Signal Strength < / p >
< p className = { ` font-semibold ${ gps . signal > 70 ? 'text-green-600' : gps . signal > 40 ? 'text-amber-600' : 'text-red-600' } ` } >
{ gps . signal } %
< / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > GPS Battery < / p >
< p className = { ` font-semibold ${ gps . battery > 50 ? 'text-green-600' : gps . battery > 20 ? 'text-amber-600' : 'text-red-600' } ` } >
{ gps . battery } %
< / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Status < / p >
< p className = "font-semibold text-green-600 flex items-center gap-1" >
< CheckCircle className = "w-4 h-4" / > Active
< / p >
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Live Location < / h3 >
< div className = "bg-slate-100 rounded-lg h-48 flex items-center justify-center" >
< div className = "text-center" >
< MapPinned className = "w-8 h-8 text-slate-400 mx-auto mb-2" / >
< p className = "text-sm text-slate-500" > { bike . location } < / p >
< / div >
< / div >
< / div >
< / div >
) ;
}
function DocumentsTab ( { bike } : { bike : Bike } ) {
const docs = bike . documents || [ ] ;
2026-05-16 20:54:17 +06:00
const [ images , setImages ] = useState ( bike . bikeImages || { front : '' , back : '' , left : '' , right : '' } ) ;
const [ uploading , setUploading ] = useState < string | null > ( null ) ;
const handleImageUpload = ( view : 'front' | 'back' | 'left' | 'right' , e : React.ChangeEvent < HTMLInputElement > ) = > {
const file = e . target . files ? . [ 0 ] ;
if ( file ) {
setUploading ( view ) ;
const reader = new FileReader ( ) ;
reader . onloadend = ( ) = > {
setImages ( prev = > ( { . . . prev , [ view ] : reader . result as string } ) ) ;
setUploading ( null ) ;
} ;
reader . readAsDataURL ( file ) ;
}
} ;
2026-04-22 01:02:45 +06:00
return (
< div className = "space-y-4" >
2026-05-16 20:54:17 +06:00
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-4" > Bike Images < / h3 >
< div className = "grid grid-cols-2 md:grid-cols-4 gap-4" >
{ ( [ 'front' , 'back' , 'left' , 'right' ] as const ) . map ( view = > (
< div key = { view } className = "space-y-2" >
< p className = "text-xs font-medium text-slate-500 capitalize" > { view } View < / p >
< div className = "relative aspect-video bg-slate-100 rounded-lg overflow-hidden border border-slate-200" >
{ images [ view ] ? (
< img src = { images [ view ] } alt = { ` ${ view } view ` } className = "w-full h-full object-cover" / >
) : (
< div className = "w-full h-full flex items-center justify-center" >
< Camera className = "w-8 h-8 text-slate-300" / >
< / div >
) }
< label className = "absolute inset-0 cursor-pointer hover:bg-black/20 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity" >
< span className = "bg-white px-3 py-1 rounded-full text-xs font-medium text-slate-700 shadow" >
{ uploading === view ? 'Uploading...' : 'Upload' }
< / span >
< input type = "file" accept = "image/*" className = "hidden" onChange = { ( e ) = > handleImageUpload ( view , e ) } / >
< / label >
< / div >
< / div >
) ) }
< / div >
< / div >
2026-04-22 01:02:45 +06:00
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-4" > Bike Documents < / h3 >
{ docs . length === 0 ? (
< div className = "text-center py-8" >
< FileText className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-sm text-slate-500" > No documents uploaded . < / p >
< / div >
) : (
< div className = "space-y-3" >
{ docs . map ( ( doc , idx ) = > (
< div key = { idx } className = "flex items-center justify-between p-4 border border-slate-200 rounded-lg" >
< div className = "flex items-center gap-3" >
< div className = "w-10 h-10 rounded-lg bg-blue-50 flex items-center justify-center" >
< FileText className = "w-5 h-5 text-blue-600" / >
< / div >
< div >
< p className = "font-medium text-slate-700" > { docTypeLabels [ doc . type ] } < / p >
< p className = "text-xs text-slate-500" > Number : { doc . number } < / p >
< p className = "text-xs text-slate-400" > Issued : { doc . issueDate } • Expires : { doc . expiryDate } < / p >
< / div >
< / div >
2026-04-26 14:56:12 +06:00
< span className = { ` inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${ doc . verified ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'
} ` }>
2026-04-22 01:02:45 +06:00
{ doc . verified ? < CheckCircle className = "w-3 h-3" / > : < Clock3 className = "w-3 h-3" / > }
{ doc . verified ? 'Verified' : 'Pending' }
< / span >
< / div >
) ) }
< / div >
) }
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Add New Document < / h3 >
< div className = "grid grid-cols-1 md:grid-cols-2 gap-3" >
< select className = "px-3 py-2 border border-slate-200 rounded-lg text-sm" >
< option value = "" > Select Document Type < / option >
< option value = "registration" > Registration Certificate < / option >
< option value = "insurance" > Insurance < / option >
< option value = "fitness" > Fitness Certificate < / option >
< option value = "permit" > Road Permit < / option >
< option value = "other" > Other < / option >
< / select >
< input type = "text" placeholder = "Document Number" className = "px-3 py-2 border border-slate-200 rounded-lg text-sm" / >
< input type = "date" placeholder = "Issue Date" className = "px-3 py-2 border border-slate-200 rounded-lg text-sm" / >
< input type = "date" placeholder = "Expiry Date" className = "px-3 py-2 border border-slate-200 rounded-lg text-sm" / >
< / div >
< button className = "w-full mt-3 py-2 bg-accent text-white rounded-lg font-semibold text-sm hover:bg-accent-dark" >
Upload Document
< / button >
< / div >
< / div >
) ;
}
function RentalTab ( { bike } : { bike : Bike } ) {
const history = bike . rentalHistory || [ ] ;
const getRateDisplay = ( type : string , rate : number ) = > {
switch ( type ) {
case 'single' : return ` ৳ ${ rate } /day ` ;
2026-04-26 14:56:12 +06:00
case 'shared' : return ` ৳ ${ rate / 2 } + ${ rate / 2 } (2 person) ` ;
2026-04-22 01:02:45 +06:00
case 'rent-to-own' : return ` ৳ ${ rate } /day ` ;
default : return ` ৳ ${ rate } ` ;
}
} ;
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Rental History < / h3 >
{ history . length === 0 ? (
< div className = "text-center py-8" >
< History className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-sm text-slate-500" > No rental history yet . < / p >
< / div >
) : (
< div className = "space-y-3" >
{ history . map ( rental = > (
< div key = { rental . id } className = "p-4 border border-slate-200 rounded-lg" >
< div className = "flex items-start justify-between mb-2" >
< div >
< p className = "font-medium text-slate-700" > { rental . bikerName } < / p >
< p className = "text-xs text-slate-500" > ID : { rental . id } < / p >
< / div >
2026-04-26 14:56:12 +06:00
< span className = { ` inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${ rental . status === 'active' ? 'bg-green-100 text-green-700' :
2026-04-26 16:02:53 +06:00
rental . status === 'completed' ? 'bg-blue-100 text-blue-700' :
'bg-red-100 text-red-700'
2026-04-26 14:56:12 +06:00
} ` }>
2026-04-22 01:02:45 +06:00
{ rental . status }
< / span >
< / div >
< div className = "flex flex-wrap gap-3 text-xs" >
< span className = "bg-slate-100 px-2 py-1 rounded text-slate-600" >
2026-04-26 14:56:12 +06:00
{ rental . type === 'single' ? 'Single (৳350/day)' :
rental . type === 'shared' ? 'Shared (৳60/day)' :
'Rent-to-Own (৳450/day)' }
2026-04-22 01:02:45 +06:00
< / span >
< span className = "text-slate-500" >
{ rental . startDate } { rental . endDate && ` to ${ rental . endDate } ` }
< / span >
< / div >
< div className = "flex justify-between mt-2 pt-2 border-t border-slate-100" >
< span className = "text-xs text-slate-500" > { rental . rideCount } rides < / span >
< span className = "text-sm font-semibold text-green-600" > ৳ { rental . totalPaid . toLocaleString ( ) } < / span >
< / div >
< / div >
) ) }
< / div >
) }
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Rental Rates Info < / h3 >
< div className = "space-y-2" >
< div className = "flex items-center justify-between p-3 bg-green-50 rounded-lg" >
< div className = "flex items-center gap-2" >
< div className = "w-8 h-8 rounded-full bg-green-100 flex items-center justify-center" >
< span className = "text-xs font-bold text-green-600" > 1 < / span >
< / div >
< span className = "font-medium text-slate-700" > Single < / span >
< / div >
< span className = "font-semibold text-green-600" > ৳ 350 / day < / span >
< / div >
< div className = "flex items-center justify-between p-3 bg-blue-50 rounded-lg" >
< div className = "flex items-center gap-2" >
< div className = "w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center" >
< span className = "text-xs font-bold text-blue-600" > 2 < / span >
< / div >
< span className = "font-medium text-slate-700" > Shared ( 2 Person ) < / span >
< / div >
< span className = "font-semibold text-green-600" > ৳ 60 / day ( ৳ 30 + ৳ 30 ) < / span >
< / div >
< div className = "flex items-center justify-between p-3 bg-purple-50 rounded-lg" >
< div className = "flex items-center gap-2" >
< div className = "w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center" >
< span className = "text-xs font-bold text-purple-600" > 3 < / span >
< / div >
< span className = "font-medium text-slate-700" > Rent - to - Own < / span >
< / div >
< span className = "font-semibold text-green-600" > ৳ 450 / day < / span >
< / div >
< / div >
< / div >
< / div >
) ;
}
function ActivityTab ( { bike } : { bike : Bike } ) {
const logs = bike . activityLog || [ ] ;
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Activity Log < / h3 >
{ logs . length === 0 ? (
< div className = "text-center py-8" >
< Clock3 className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-sm text-slate-500" > No activity yet . < / p >
< / div >
) : (
< div className = "space-y-3" >
{ logs . map ( log = > (
< div key = { log . id } className = "flex items-start gap-3 p-3 bg-slate-50 rounded-lg" >
< div className = "w-8 h-8 rounded-full bg-white flex items-center justify-center" >
< Activity className = "w-4 h-4 text-slate-400" / >
< / div >
< div className = "flex-1" >
< p className = "text-sm font-medium text-slate-700" > { log . action } < / p >
< p className = "text-xs text-slate-500" > { log . details } < / p >
< p className = "text-xs text-slate-400" > { log . date } by { log . by } < / p >
< / div >
< / div >
) ) }
< / div >
) }
< / div >
< / div >
) ;
}
2026-04-26 14:56:12 +06:00
function BikerAssignmentTab ( { bike } : { bike : Bike } ) {
const [ showAssignModal , setShowAssignModal ] = useState ( false ) ;
const [ rentalPlan , setRentalPlan ] = useState < 'single' | 'shared' | 'rent-to-own' > ( 'single' ) ;
const [ assignedBikers , setAssignedBikers ] = useState < any [ ] > ( [ ] ) ;
const [ selectedBikerId , setSelectedBikerId ] = useState ( '' ) ;
const [ showAddBikerModal , setShowAddBikerModal ] = useState ( false ) ;
const [ newBikerDetails , setNewBikerDetails ] = useState ( {
name : '' , phone : '' , email : '' , licenseNumber : '' , nidNumber : '' , address : '' , dailyRate : 0 , notes : ''
} ) ;
const assignmentHistory = bike . assignmentHistory || [ ] ;
const activeAssignments = assignmentHistory . filter ( a = > a . status === 'active' ) ;
const availableBikers = [
{ id : 'u1' , name : 'Karim Ahmed' , phone : '01712345678' , email : 'karim@email.com' , license : 'DL123456' , nid : '1234567890' } ,
{ id : 'u2' , name : 'Sofiq Rahman' , phone : '01722345678' , email : 'sofiq@email.com' , license : 'DL234567' , nid : '2345678901' } ,
{ id : 'u3' , name : 'Rahim Khan' , phone : '01732345678' , email : 'rahim@email.com' , license : 'DL345678' , nid : '3456789012' } ,
{ id : 'u4' , name : 'Jamal Hossain' , phone : '01742345678' , email : 'jamal@email.com' , license : 'DL456789' , nid : '4567890123' } ,
{ id : 'u5' , name : 'Ripon Mia' , phone : '01752345678' , email : 'ripon@email.com' , license : 'DL567890' , nid : '5678901234' } ,
{ id : 'u6' , name : 'Mizanur Rahman' , phone : '01762345678' , email : 'mizan@email.com' , license : 'DL678901' , nid : '6789012345' } ,
] ;
const planDetails = {
single : { name : 'Rental (Single)' , price : '৳50/day' , maxBikers : 1 } ,
shared : { name : 'Rental (2 Person Shared)' , price : '৳30+30=৳60/day' , maxBikers : 2 } ,
'rent-to-own' : { name : 'Rent-to-Own' , price : '৳45/day' , maxBikers : 1 } ,
} ;
const handleAddBiker = ( ) = > {
if ( ! selectedBikerId ) return ;
const biker = availableBikers . find ( b = > b . id === selectedBikerId ) ;
if ( biker && assignedBikers . length < planDetails [ rentalPlan ] . maxBikers ) {
setAssignedBikers ( [ . . . assignedBikers , { . . . biker , dailyRate : rentalPlan === 'single' ? 50 : rentalPlan === 'shared' ? 30 : 45 } ] ) ;
setSelectedBikerId ( '' ) ;
}
} ;
const handleRemoveBiker = ( id : string ) = > {
setAssignedBikers ( assignedBikers . filter ( b = > b . id !== id ) ) ;
} ;
const handleSubmitAssignment = ( ) = > {
alert ( ` Assigned ${ assignedBikers . length } biker(s) under ${ planDetails [ rentalPlan ] . name } ! \ nTotal Daily Rate: ৳ ${ assignedBikers . reduce ( ( sum , b ) = > sum + b . dailyRate , 0 ) } ` ) ;
setShowAssignModal ( false ) ;
setAssignedBikers ( [ ] ) ;
setRentalPlan ( 'single' ) ;
} ;
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< div className = "flex items-center justify-between mb-4" >
< h3 className = "font-semibold text-slate-700" > Current Biker Assignments < / h3 >
< button
onClick = { ( ) = > setShowAssignModal ( true ) }
className = "py-2 px-4 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark flex items-center gap-2"
>
< User className = "w-4 h-4" / > Assign Bikers
< / button >
< / div >
{ activeAssignments . length > 0 ? (
< div className = "space-y-3" >
{ activeAssignments . map ( ( assignment , idx ) = > (
< div key = { idx } className = "p-4 bg-green-50 border border-green-200 rounded-lg" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-3" >
< div className = "w-12 h-12 rounded-full bg-green-100 flex items-center justify-center" >
< User className = "w-6 h-6 text-green-600" / >
< / div >
< div >
< p className = "font-semibold text-slate-700" > { assignment . bikerName } < / p >
< p className = "text-sm text-slate-500" > ID : { assignment . bikerId } < / p >
< p className = "text-xs text-slate-400" > Assigned : { new Date ( assignment . assignedAt ) . toLocaleString ( ) } < / p >
< / div >
< / div >
< span className = "px-3 py-1 bg-green-100 text-green-700 text-sm font-medium rounded-full" > Active < / span >
< / div >
{ assignment . notes && < p className = "mt-2 text-sm text-slate-600" > { assignment . notes } < / p > }
< / div >
) ) }
< / div >
) : (
< div className = "text-center py-8 border-2 border-dashed border-slate-200 rounded-lg" >
< User className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-slate-500" > No biker currently assigned < / p >
< button onClick = { ( ) = > setShowAssignModal ( true ) } className = "mt-2 text-accent hover:underline" > Assign bikers < / button >
< / div >
) }
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-4" > Assignment History < / h3 >
{ assignmentHistory . length > 0 ? (
< div className = "space-y-3" >
{ assignmentHistory . map ( ( assignment , idx ) = > (
< div key = { idx } className = { ` p-4 rounded-lg border ${ assignment . status === 'active' ? 'bg-green-50 border-green-200' : 'bg-slate-50 border-slate-200' } ` } >
< div className = "flex items-start justify-between" >
< div className = "flex items-center gap-3" >
< div className = { ` w-10 h-10 rounded-full flex items-center justify-center ${ assignment . status === 'active' ? 'bg-green-100' : 'bg-slate-200' } ` } >
< User className = { ` w-5 h-5 ${ assignment . status === 'active' ? 'text-green-600' : 'text-slate-500' } ` } / >
< / div >
< div >
< p className = "font-medium text-slate-700" > { assignment . bikerName } < / p >
< p className = "text-sm text-slate-500" > ID : { assignment . bikerId } < / p >
< / div >
< / div >
< span className = { ` px-2 py-1 text-xs font-medium rounded-full ${ assignment . status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-200 text-slate-600' } ` } >
{ assignment . status === 'active' ? 'Active' : 'Completed' }
< / span >
< / div >
< div className = "mt-2 text-sm text-slate-500 grid grid-cols-2 gap-2" >
< p > Assigned : { new Date ( assignment . assignedAt ) . toLocaleString ( ) } < / p >
{ assignment . unassignedAt && < p > Unassigned : { new Date ( assignment . unassignedAt ) . toLocaleString ( ) } < / p > }
< / div >
{ assignment . reason && < p className = "mt-1 text-sm text-slate-400" > Reason : { assignment . reason } < / p > }
< / div >
) ) }
< / div >
) : (
< div className = "text-center py-8 text-slate-400" >
< History className = "w-10 h-10 mx-auto mb-2 opacity-50" / >
< p > No assignment history < / p >
< / div >
) }
< / div >
{ showAssignModal && (
< 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-3xl max-h-[90vh] overflow-hidden" >
< div className = "p-5 border-b border-slate-100 flex items-center justify-between" >
< h2 className = "text-lg font-bold text-slate-800" > Assign Bikers to { bike . id } < / h2 >
< button onClick = { ( ) = > setShowAssignModal ( false ) } className = "p-2 hover:bg-slate-100 rounded-lg" >
< X className = "w-5 h-5 text-slate-400" / >
< / button >
< / div >
< div className = "p-5 overflow-y-auto max-h-[70vh]" >
< div className = "mb-6" >
< label className = "text-sm font-medium text-slate-600 mb-2 block" > Choose Rental Plan < / label >
< div className = "grid grid-cols-3 gap-3" >
{ Object . entries ( planDetails ) . map ( ( [ key , plan ] ) = > (
< button
key = { key }
onClick = { ( ) = > { setRentalPlan ( key as any ) ; setAssignedBikers ( [ ] ) ; } }
className = { ` p-4 rounded-lg border text-left transition-all ${ rentalPlan === key
2026-04-26 16:02:53 +06:00
? 'border-accent bg-accent/5'
: 'border-slate-200 hover:border-accent/50'
2026-04-26 14:56:12 +06:00
} ` }
>
< p className = "font-semibold text-slate-700" > { plan . name } < / p >
< p className = "text-sm text-green-600 font-medium" > { plan . price } < / p >
< p className = "text-xs text-slate-500 mt-1" > Max { plan . maxBikers } biker { plan . maxBikers > 1 ? 's' : '' } < / p >
< / button >
) ) }
< / div >
< / div >
< div className = "mb-4" >
< label className = "text-sm font-medium text-slate-600 mb-2 block" >
Add Biker { rentalPlan === 'shared' ? 's (Shared Rental)' : '' } ( { assignedBikers . length } / { planDetails [ rentalPlan ] . maxBikers } )
< / label >
{ assignedBikers . length < planDetails [ rentalPlan ] . maxBikers ? (
< div className = "flex gap-2" >
< select
value = { selectedBikerId }
onChange = { ( e ) = > setSelectedBikerId ( e . target . value ) }
className = "flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
< option value = "" > Select biker < / option >
{ availableBikers . filter ( b = > ! assignedBikers . some ( ab = > ab . id === b . id ) ) . map ( b = > (
< option key = { b . id } value = { b . id } > { b . name } - { b . phone } < / option >
) ) }
< / select >
< button
onClick = { handleAddBiker }
disabled = { ! selectedBikerId }
className = "px-4 py-2 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark disabled:opacity-50"
>
Add
< / button >
< / div >
) : (
< p className = "text-sm text-green-600" > Maximum bikers assigned for this plan < / p >
) }
< / div >
{ assignedBikers . length > 0 && (
< div className = "space-y-3 mb-4" >
{ assignedBikers . map ( ( biker , idx ) = > (
< div key = { idx } className = "p-4 bg-slate-50 rounded-lg border border-slate-200" >
< div className = "flex items-start justify-between" >
< div className = "flex items-center gap-3" >
< div className = "w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center" >
< User className = "w-6 h-6 text-blue-600" / >
< / div >
< div >
< p className = "font-semibold text-slate-700" > { biker . name } < / p >
< p className = "text-sm text-slate-500" > { biker . phone } < / p >
< p className = "text-xs text-slate-400" > { biker . email } < / p >
< / div >
< / div >
< button onClick = { ( ) = > handleRemoveBiker ( biker . id ) } className = "p-1 hover:bg-red-50 rounded" >
< X className = "w-4 h-4 text-red-500" / >
< / button >
< / div >
< div className = "mt-3 grid grid-cols-2 gap-2 text-sm" >
< div >
< label className = "text-xs text-slate-400" > Daily Rate < / label >
< input
type = "number"
value = { biker . dailyRate }
onChange = { ( e ) = > {
const updated = [ . . . assignedBikers ] ;
updated [ idx ] . dailyRate = Number ( e . target . value ) ;
setAssignedBikers ( updated ) ;
} }
className = "w-full px-2 py-1 border border-slate-200 rounded text-sm"
/ >
< / div >
< div >
< label className = "text-xs text-slate-400" > License < / label >
< p className = "text-slate-600" > { biker . license } < / p >
< / div >
< / div >
< div className = "mt-2" >
< label className = "text-xs text-slate-400 block" > Notes < / label >
< input
type = "text"
placeholder = "Additional notes..."
className = "w-full px-2 py-1 border border-slate-200 rounded text-sm"
/ >
< / div >
< / div >
) ) }
< / div >
) }
{ assignedBikers . length > 0 && (
< div className = "bg-green-50 border border-green-200 rounded-lg p-4" >
< div className = "flex justify-between items-center" >
< span className = "font-semibold text-slate-700" > Total Daily Rate : < / span >
< span className = "text-xl font-bold text-green-600" >
৳ { assignedBikers . reduce ( ( sum , b ) = > sum + b . dailyRate , 0 ) } / day
< / span >
< / div >
< / div >
) }
< / div >
< div className = "p-5 border-t border-slate-100 flex justify-end gap-3" >
< button onClick = { ( ) = > setShowAssignModal ( false ) } className = "px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50" > Cancel < / button >
2026-04-26 16:02:53 +06:00
< button
2026-04-26 14:56:12 +06:00
onClick = { handleSubmitAssignment }
disabled = { assignedBikers . length === 0 }
className = "px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark disabled:opacity-50"
>
Assign { assignedBikers . length } Biker { assignedBikers . length !== 1 ? 's' : '' }
< / button >
< / div >
< / div >
< / div >
) }
< / div >
) ;
}
2026-04-22 01:02:45 +06:00
function InvestorTab ( { bike } : { bike : Bike } ) {
return (
< div className = "space-y-4" >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Investor Information < / h3 >
{ ! bike . investorId ? (
< div className = "text-center py-8" >
< User2 className = "w-12 h-12 text-slate-300 mx-auto mb-4" / >
< p className = "text-sm text-slate-500" > No investor assigned . < / p >
< / div >
) : (
< div className = "space-y-4" >
< div className = "flex items-center gap-4 p-4 bg-purple-50 rounded-lg" >
< div className = "w-12 h-12 rounded-full bg-purple-100 flex items-center justify-center" >
< User2 className = "w-6 h-6 text-purple-600" / >
< / div >
2026-04-26 14:56:12 +06:00
< div className = "flex-1" >
2026-04-22 01:02:45 +06:00
< p className = "font-semibold text-slate-700" > { bike . investorName || 'Investor' } < / p >
< p className = "text-sm text-slate-500" > ID : { bike . investorId } < / p >
< / div >
2026-04-26 16:02:53 +06:00
< Link
2026-04-26 14:56:12 +06:00
href = { ` /admin/investors/ ${ bike . investorId } ` }
className = "px-4 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 flex items-center gap-2"
>
< ExternalLink className = "w-4 h-4" / > View Investor
< / Link >
2026-04-22 01:02:45 +06:00
< / div >
< div className = "grid grid-cols-2 gap-3" >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Purchase Date < / p >
< p className = "font-semibold text-slate-700" > { bike . purchaseDate || 'N/A' } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Purchase Price < / p >
< p className = "font-semibold text-green-600" > ৳ { bike . purchasePrice ? . toLocaleString ( ) || 0 } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > Total Earnings < / p >
< p className = "font-semibold text-green-600" > ৳ { bike . totalEarnings ? . toLocaleString ( ) || 0 } < / p >
< / div >
< div className = "bg-slate-50 rounded-lg p-3" >
< p className = "text-xs text-slate-500" > ROI < / p >
< p className = "font-semibold text-amber-600" >
2026-04-26 14:56:12 +06:00
{ bike . purchasePrice && bike . totalEarnings
? ( ( bike . totalEarnings / bike . purchasePrice ) * 100 ) . toFixed ( 1 )
2026-04-22 01:02:45 +06:00
: 0 } %
< / p >
< / div >
< / div >
< / div >
) }
< / div >
< div className = "bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100" >
< h3 className = "font-semibold text-slate-700 mb-3" > Payment Summary < / h3 >
< div className = "space-y-2" >
< div className = "flex items-center justify-between p-3 bg-slate-50 rounded-lg" >
< span className = "text-sm text-slate-600" > Total Paid by Renters < / span >
< span className = "font-semibold text-green-600" > ৳ { bike . totalEarnings ? . toLocaleString ( ) || 0 } < / span >
< / div >
< div className = "flex items-center justify-between p-3 bg-slate-50 rounded-lg" >
< span className = "text-sm text-slate-600" > Investor Share ( 80 % ) < / span >
< span className = "font-semibold text-purple-600" > ৳ { Math . round ( ( bike . totalEarnings || 0 ) * 0.8 ) . toLocaleString ( ) } < / span >
< / div >
< div className = "flex items-center justify-between p-3 bg-slate-50 rounded-lg" >
< span className = "text-sm text-slate-600" > Company Share ( 20 % ) < / span >
< span className = "font-semibold text-blue-600" > ৳ { Math . round ( ( bike . totalEarnings || 0 ) * 0.2 ) . toLocaleString ( ) } < / span >
< / div >
< / div >
< / div >
< / div >
) ;
2026-04-26 14:56:12 +06:00
}
function DamageModal ( { bike , damage , onClose , onSave } : { bike : Bike ; damage : DamageRecord | null ; onClose : ( ) = > void ; onSave : ( damage : DamageRecord ) = > void } ) {
const [ formData , setFormData ] = useState ( {
id : damage?.id || ` DMG ${ Date . now ( ) } ` ,
date : damage?.date || new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ,
type : damage ? . type || 'accident' ,
description : damage?.description || '' ,
reportedBy : damage?.reportedBy || '' ,
reportedAt : damage?.reportedAt || new Date ( ) . toISOString ( ) . replace ( 'T' , ' ' ) . slice ( 0 , 16 ) ,
estimatedCost : damage?.estimatedCost || 0 ,
actualCost : damage?.actualCost || 0 ,
status : damage?.status || 'reported' ,
2026-05-16 20:19:23 +06:00
hubId : damage?.hubId || '' ,
hubName : damage?.hubName || '' ,
2026-04-26 14:56:12 +06:00
} ) ;
2026-05-16 20:19:23 +06:00
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' } ,
] ;
2026-04-26 14:56:12 +06:00
const damageTypes = [
{ value : 'accident' , label : 'Accident' } ,
{ value : 'theft' , label : 'Theft' } ,
{ value : 'natural' , label : 'Natural Disaster' } ,
{ value : 'wear_tear' , label : 'Wear & Tear' } ,
{ value : 'other' , label : 'Other' } ,
] ;
const statusOptions = [
{ value : 'reported' , label : 'Reported' } ,
{ value : 'under_repair' , label : 'Under Repair' } ,
{ value : 'repaired' , label : 'Repaired' } ,
{ value : 'claim_rejected' , label : 'Claim Rejected' } ,
] ;
const handleSubmit = ( e : React.FormEvent ) = > {
e . preventDefault ( ) ;
onSave ( formData as DamageRecord ) ;
} ;
return (
< 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-lg max-h-[90vh] overflow-hidden" >
< div className = "p-5 border-b border-slate-100 flex items-center justify-between" >
< h2 className = "text-lg font-bold text-slate-800" > { damage ? 'Edit Damage Record' : 'Add Damage Record' } < / h2 >
< button onClick = { onClose } className = "p-2 hover:bg-slate-100 rounded-lg" >
< X className = "w-5 h-5 text-slate-400" / >
< / button >
< / div >
< form onSubmit = { handleSubmit } className = "p-5 overflow-y-auto max-h-[70vh] space-y-4" >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Date < / label >
< input
type = "date"
value = { formData . date }
onChange = { ( e ) = > setFormData ( { . . . formData , date : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
required
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Type < / label >
< select
value = { formData . type }
onChange = { ( e ) = > setFormData ( { . . . formData , type : e . target . value as any } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
{ damageTypes . map ( t = > (
< option key = { t . value } value = { t . value } > { t . label } < / option >
) ) }
< / select >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Description < / label >
< textarea
value = { formData . description }
onChange = { ( e ) = > setFormData ( { . . . formData , description : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
rows = { 3 }
required
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Reported By < / label >
< input
type = "text"
value = { formData . reportedBy }
onChange = { ( e ) = > setFormData ( { . . . formData , reportedBy : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
required
/ >
< / div >
2026-05-16 20:19:23 +06:00
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Hub < / label >
< select
value = { formData . hubId }
onChange = { ( e ) = > setFormData ( { . . . formData , hubId : e.target.value , hubName : mockHubs.find ( h = > h . id === e . target . value ) ? . name || '' } ) }
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 >
2026-04-26 14:56:12 +06:00
< div className = "grid grid-cols-2 gap-3" >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Estimated Cost ( ৳ ) < / label >
< input
type = "number"
value = { formData . estimatedCost }
onChange = { ( e ) = > setFormData ( { . . . formData , estimatedCost : Number ( e . target . value ) } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Actual Cost ( ৳ ) < / label >
< input
type = "number"
value = { formData . actualCost }
onChange = { ( e ) = > setFormData ( { . . . formData , actualCost : Number ( e . target . value ) } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/ >
< / div >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Status < / label >
< select
value = { formData . status }
onChange = { ( e ) = > setFormData ( { . . . formData , status : e.target.value as any } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
{ statusOptions . map ( s = > (
< option key = { s . value } value = { s . value } > { s . label } < / option >
) ) }
< / select >
< / div >
< div className = "flex justify-end gap-3 pt-4" >
< button type = "button" onClick = { onClose } className = "px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50" >
Cancel
< / button >
< button type = "submit" className = "px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark" >
{ damage ? 'Update' : 'Add' } Record
< / button >
< / div >
< / form >
< / div >
< / div >
) ;
}
function MaintenanceModal ( { bike , maintenance , onClose , onSave } : { bike : Bike ; maintenance : MaintenanceRecord | null ; onClose : ( ) = > void ; onSave : ( maintenance : MaintenanceRecord ) = > void } ) {
const [ formData , setFormData ] = useState ( {
id : maintenance?.id || ` MNT ${ Date . now ( ) } ` ,
date : maintenance?.date || new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ,
type : maintenance ? . type || 'routine' ,
description : maintenance?.description || '' ,
performedBy : maintenance?.performedBy || '' ,
cost : maintenance?.cost || 0 ,
nextDueDate : maintenance?.nextDueDate || '' ,
status : maintenance?.status || 'completed' ,
2026-05-16 20:19:23 +06:00
hubId : maintenance?.hubId || '' ,
hubName : maintenance?.hubName || '' ,
2026-04-26 14:56:12 +06:00
} ) ;
2026-05-16 20:19:23 +06:00
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' } ,
] ;
2026-04-26 14:56:12 +06:00
const maintenanceTypes = [
{ value : 'routine' , label : 'Routine Service' } ,
{ value : 'battery' , label : 'Battery' } ,
{ value : 'tire' , label : 'Tire' } ,
{ value : 'brake' , label : 'Brake' } ,
{ value : 'engine' , label : 'Engine' } ,
{ value : 'electrical' , label : 'Electrical' } ,
{ value : 'other' , label : 'Other' } ,
] ;
const statusOptions = [
{ value : 'scheduled' , label : 'Scheduled' } ,
{ value : 'in_progress' , label : 'In Progress' } ,
{ value : 'completed' , label : 'Completed' } ,
] ;
const handleSubmit = ( e : React.FormEvent ) = > {
e . preventDefault ( ) ;
onSave ( formData as MaintenanceRecord ) ;
} ;
return (
< 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-lg max-h-[90vh] overflow-hidden" >
< div className = "p-5 border-b border-slate-100 flex items-center justify-between" >
< h2 className = "text-lg font-bold text-slate-800" > { maintenance ? 'Edit Maintenance Record' : 'Add Maintenance Record' } < / h2 >
< button onClick = { onClose } className = "p-2 hover:bg-slate-100 rounded-lg" >
< X className = "w-5 h-5 text-slate-400" / >
< / button >
< / div >
< form onSubmit = { handleSubmit } className = "p-5 overflow-y-auto max-h-[70vh] space-y-4" >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Date < / label >
< input
type = "date"
value = { formData . date }
onChange = { ( e ) = > setFormData ( { . . . formData , date : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
required
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Type < / label >
< select
value = { formData . type }
onChange = { ( e ) = > setFormData ( { . . . formData , type : e . target . value as any } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
{ maintenanceTypes . map ( t = > (
< option key = { t . value } value = { t . value } > { t . label } < / option >
) ) }
< / select >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Description < / label >
< textarea
value = { formData . description }
onChange = { ( e ) = > setFormData ( { . . . formData , description : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
rows = { 3 }
required
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Performed By < / label >
< input
type = "text"
value = { formData . performedBy }
onChange = { ( e ) = > setFormData ( { . . . formData , performedBy : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
required
/ >
< / div >
2026-05-16 20:19:23 +06:00
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Hub < / label >
< select
value = { formData . hubId }
onChange = { ( e ) = > setFormData ( { . . . formData , hubId : e.target.value , hubName : mockHubs.find ( h = > h . id === e . target . value ) ? . name || '' } ) }
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 >
2026-04-26 14:56:12 +06:00
< div className = "grid grid-cols-2 gap-3" >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Cost ( ৳ ) < / label >
< input
type = "number"
value = { formData . cost }
onChange = { ( e ) = > setFormData ( { . . . formData , cost : Number ( e . target . value ) } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
required
/ >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Next Due Date < / label >
< input
type = "date"
value = { formData . nextDueDate }
onChange = { ( e ) = > setFormData ( { . . . formData , nextDueDate : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/ >
< / div >
< / div >
< div >
< label className = "text-sm font-medium text-slate-600 mb-1 block" > Status < / label >
< select
value = { formData . status }
onChange = { ( e ) = > setFormData ( { . . . formData , status : e.target.value as any } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
{ statusOptions . map ( s = > (
< option key = { s . value } value = { s . value } > { s . label } < / option >
) ) }
< / select >
< / div >
< div className = "flex justify-end gap-3 pt-4" >
< button type = "button" onClick = { onClose } className = "px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50" >
Cancel
< / button >
< button type = "submit" className = "px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark" >
{ maintenance ? 'Update' : 'Add' } Record
< / button >
< / div >
< / form >
< / div >
< / div >
) ;
2026-04-22 01:02:45 +06:00
}