2804 lines
164 KiB
TypeScript
2804 lines
164 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/navigation';
|
|
import {
|
|
User, Phone, Mail, MapPin, Calendar, Heart, Briefcase, Car, Navigation,
|
|
FileText, Clock, TrendingUp, CreditCard, Shield, Award, Star, Activity,
|
|
Eye, ArrowLeft, PhoneCall, MessageCircle, CheckCircle, XCircle,
|
|
AlertTriangle, DollarSign, Wallet, Bike as BikeIcon, Wrench, Ban, Copy,
|
|
ExternalLink, Download, Upload, Bell, MessageSquare, Send, RefreshCcw, Image,
|
|
Plus, X, Check, AlertCircle, Map, Camera, FileCheck, FileX, Users,
|
|
Bike, AlertOctagon, ShieldAlert, History, Banknote, Building2,
|
|
ChevronDown, ChevronRight, MapPinHouse, Home, Hash, Globe, PhoneIncoming,
|
|
Tag, Save, Trash2, EyeOff, LayoutList, Grid3X3, MoreHorizontal,
|
|
Package, Fuel, Gauge, Timer, Coins, Receipt, ArrowRightLeft,
|
|
Truck, Battery, Zap, Settings2, Siren, Printer, Clock3, UserCheck,
|
|
Globe2, Building, HeartPulse, UsersRound, MessageSquareDashed, BellRing,
|
|
ListTodo, ClipboardList, BugOff, Info, SendHorizontal, PhoneOutgoing, Edit, Pencil
|
|
} from 'lucide-react';
|
|
import toast from 'react-hot-toast';
|
|
|
|
interface KYCData {
|
|
fullName: string;
|
|
phone: string;
|
|
alternatePhone?: string;
|
|
email: string;
|
|
nid: string;
|
|
dateOfBirth: string;
|
|
gender: string;
|
|
bloodGroup: string;
|
|
maritalStatus: string;
|
|
religion: string;
|
|
nationality: string;
|
|
presentAddress: {
|
|
houseFlat: string;
|
|
floor: string;
|
|
road: string;
|
|
block: string;
|
|
area: string;
|
|
thana: string;
|
|
district: string;
|
|
division: string;
|
|
postalCode: string;
|
|
landmark?: string;
|
|
};
|
|
permanentAddress: {
|
|
houseFlat: string;
|
|
floor: string;
|
|
road: string;
|
|
block: string;
|
|
area: string;
|
|
thana: string;
|
|
district: string;
|
|
division: string;
|
|
postalCode: string;
|
|
};
|
|
isPermanentSame: boolean;
|
|
drivingLicense: {
|
|
number: string;
|
|
licenseType: string;
|
|
expiryDate: string;
|
|
issueDate: string;
|
|
issuingAuthority: string;
|
|
frontImage?: string;
|
|
backImage?: string;
|
|
};
|
|
employment: {
|
|
status: string;
|
|
companyName: string;
|
|
department: string;
|
|
designation: string;
|
|
monthlyIncome: string;
|
|
yearsOfExperience: string;
|
|
businessAddress: string;
|
|
};
|
|
nominee: {
|
|
name: string;
|
|
relationship: string;
|
|
nid: string;
|
|
phone: string;
|
|
email: string;
|
|
address: string;
|
|
dateOfBirth: string;
|
|
bloodGroup: string;
|
|
sharePercentage: string;
|
|
photo?: string;
|
|
nidFront?: string;
|
|
nidBack?: string;
|
|
};
|
|
emergencyContact: {
|
|
name: string;
|
|
relationship: string;
|
|
phone: string;
|
|
email: string;
|
|
address: string;
|
|
};
|
|
nidFrontImage?: string;
|
|
nidBackImage?: string;
|
|
kycTimeline: Array<{
|
|
stage: string;
|
|
status: string;
|
|
timestamp: string;
|
|
adminName: string;
|
|
notes?: string;
|
|
}>;
|
|
}
|
|
|
|
interface Rental {
|
|
id: string;
|
|
planName: string;
|
|
subscriptionType: string;
|
|
startDate: string;
|
|
endDate?: string;
|
|
dailyRate: number;
|
|
weeklyRate?: number;
|
|
monthlyRate?: number;
|
|
depositAmount: number;
|
|
depositStatus: string;
|
|
dueAmount: number;
|
|
pendingDays: number;
|
|
rentPaymentStatus: string;
|
|
evModel: string;
|
|
batteryPercent: number;
|
|
currentLocation: string;
|
|
odometerReading: number;
|
|
contractEndDate: string;
|
|
initialConditionImages: string[];
|
|
status: string;
|
|
totalAmount: number;
|
|
penalty: number;
|
|
plate: string;
|
|
}
|
|
|
|
interface Transaction {
|
|
id: string;
|
|
date: string;
|
|
type: string;
|
|
amount: number;
|
|
method: string;
|
|
status: string;
|
|
description: string;
|
|
}
|
|
|
|
interface ActivityLog {
|
|
id: string;
|
|
type: string;
|
|
action: string;
|
|
timestamp: string;
|
|
adminName?: string;
|
|
details?: string;
|
|
icon: any;
|
|
}
|
|
|
|
interface DamageReport {
|
|
id: string;
|
|
date: string;
|
|
rentalId: string;
|
|
bike: string;
|
|
damageType: string;
|
|
severity: string;
|
|
estimatedCost: number;
|
|
actualCost?: number;
|
|
status: string;
|
|
resolution?: string;
|
|
description: string;
|
|
location: string;
|
|
images: string[];
|
|
}
|
|
|
|
interface MessageHistory {
|
|
id: string;
|
|
direction: 'sent' | 'received';
|
|
channel: 'sms' | 'push' | 'in_app';
|
|
message: string;
|
|
timestamp: string;
|
|
status: string;
|
|
}
|
|
|
|
interface Note {
|
|
id: string;
|
|
text: string;
|
|
createdAt: string;
|
|
createdBy: string;
|
|
}
|
|
|
|
interface Biker {
|
|
id: string;
|
|
kyc: KYCData;
|
|
gpsDeviceId: string;
|
|
gpsStatus: string;
|
|
lastLocation: string;
|
|
location?: string;
|
|
currentRental: Rental;
|
|
previousRentals: Rental[];
|
|
bikeSpecs: {
|
|
model: string;
|
|
brand: string;
|
|
year: string;
|
|
color: string;
|
|
vin: string;
|
|
engineNumber: string;
|
|
plateNumber: string;
|
|
batteryCapacity: string;
|
|
maxRange: string;
|
|
chargingTime: string;
|
|
lastServiceDate: string;
|
|
nextServiceDue: string;
|
|
totalKmRun: number;
|
|
};
|
|
bikes: {
|
|
current: {
|
|
id: string;
|
|
model: string;
|
|
brand: string;
|
|
plate: string;
|
|
vin: string;
|
|
color: string;
|
|
year: string;
|
|
batteryCapacity: string;
|
|
maxRange: string;
|
|
totalKmRun: number;
|
|
lastService: string;
|
|
nextService: string;
|
|
};
|
|
batteries: Array<{
|
|
id: string;
|
|
name: string;
|
|
percent: number;
|
|
status: 'active' | 'available' | 'charging' | 'swapped';
|
|
swappedAt: string;
|
|
location: string;
|
|
odometer: number;
|
|
}>;
|
|
};
|
|
transactions: Transaction[];
|
|
activityLog: ActivityLog[];
|
|
damageReports: DamageReport[];
|
|
messageHistory: MessageHistory[];
|
|
notes: Note[];
|
|
walletBalance: number;
|
|
totalPaid: number;
|
|
totalDue: number;
|
|
pendingAmount: number;
|
|
totalPenaltiesPaid: number;
|
|
bankAccount: {
|
|
bankName: string;
|
|
accountNumber: string;
|
|
routingNumber: string;
|
|
};
|
|
billingAddress: {
|
|
houseFlat: string;
|
|
floor: string;
|
|
road: string;
|
|
block: string;
|
|
area: string;
|
|
thana: string;
|
|
district: string;
|
|
division: string;
|
|
postalCode: string;
|
|
};
|
|
status: string;
|
|
kycStatus: string;
|
|
membershipType: string;
|
|
rating: number;
|
|
totalRatings: number;
|
|
depositAmount: number;
|
|
profileImage?: string;
|
|
role: string;
|
|
totalRentals: number;
|
|
activeRentals: number;
|
|
completedRentals: number;
|
|
cancelledRentals: number;
|
|
}
|
|
|
|
const mockBiker: Biker = {
|
|
id: 'B001',
|
|
status: 'active',
|
|
role: 'rider',
|
|
totalRentals: 45,
|
|
activeRentals: 1,
|
|
completedRentals: 42,
|
|
cancelledRentals: 2,
|
|
location: 'Gulshan 1, Dhaka',
|
|
kycStatus: 'verified',
|
|
membershipType: 'Premium',
|
|
rating: 4.8,
|
|
totalRatings: 156,
|
|
depositAmount: 5000,
|
|
profileImage: 'https://picsum.photos/200/200?random=profile',
|
|
kyc: {
|
|
fullName: 'Mohammad Rafiqul Islam',
|
|
phone: '01712345678',
|
|
alternatePhone: '01987654321',
|
|
email: 'rafiqul.islam@gmail.com',
|
|
nid: '19951234567890123',
|
|
dateOfBirth: '1995-06-15',
|
|
gender: 'Male',
|
|
bloodGroup: 'O+',
|
|
maritalStatus: 'Married',
|
|
religion: 'Islam',
|
|
nationality: 'Bangladeshi',
|
|
presentAddress: {
|
|
houseFlat: 'House 42',
|
|
floor: '3rd Floor',
|
|
road: 'Road 11',
|
|
block: 'Block D',
|
|
area: 'Gulshan 1',
|
|
thana: 'Gulshan',
|
|
district: 'Dhaka',
|
|
division: 'Dhaka',
|
|
postalCode: '1212',
|
|
landmark: 'Near BIBM',
|
|
},
|
|
permanentAddress: {
|
|
houseFlat: 'House 42',
|
|
floor: '3rd Floor',
|
|
road: 'Road 11',
|
|
block: 'Block D',
|
|
area: 'Gulshan 1',
|
|
thana: 'Gulshan',
|
|
district: 'Dhaka',
|
|
division: 'Dhaka',
|
|
postalCode: '1212',
|
|
},
|
|
isPermanentSame: true,
|
|
drivingLicense: {
|
|
number: 'DL-2020-DH-1234567',
|
|
licenseType: 'Motorcycle',
|
|
expiryDate: '2030-06-14',
|
|
issueDate: '2020-06-15',
|
|
issuingAuthority: 'Bangladesh Road Transport Authority (BRTA), Dhaka',
|
|
frontImage: 'https://picsum.photos/400/250?random=dl1',
|
|
backImage: 'https://picsum.photos/400/250?random=dl2',
|
|
},
|
|
employment: {
|
|
status: 'Self Employed',
|
|
companyName: 'FoodPanda Bangladesh',
|
|
department: 'Delivery Operations',
|
|
designation: 'Delivery Rider',
|
|
monthlyIncome: '৳35,000',
|
|
yearsOfExperience: '4 years',
|
|
businessAddress: 'House 12, Road 5, Gulshan 1, Dhaka',
|
|
},
|
|
nominee: {
|
|
name: 'Fatema Begum',
|
|
relationship: 'Wife',
|
|
nid: '19952345678901234',
|
|
phone: '01812345678',
|
|
email: 'fatema.begum@yahoo.com',
|
|
address: 'House 42, Road 11, Gulshan 1, Dhaka',
|
|
dateOfBirth: '1998-03-20',
|
|
bloodGroup: 'A+',
|
|
sharePercentage: '100',
|
|
photo: 'https://picsum.photos/200/200?random=nominee',
|
|
nidFront: 'https://picsum.photos/400/250?random=nf1',
|
|
nidBack: 'https://picsum.photos/400/250?random=nf2',
|
|
},
|
|
emergencyContact: {
|
|
name: 'Abdul Karim',
|
|
relationship: 'Brother',
|
|
phone: '01798765432',
|
|
email: 'abdul.karim@outlook.com',
|
|
address: 'House 15, Road 8, Banani, Dhaka',
|
|
},
|
|
nidFrontImage: 'https://picsum.photos/400/250?random=nid1',
|
|
nidBackImage: 'https://picsum.photos/400/250?random=nid2',
|
|
kycTimeline: [
|
|
{ stage: 'Application Submitted', status: 'completed', timestamp: '2024-01-10 09:30 AM', adminName: 'System' },
|
|
{ stage: 'ID Verification', status: 'completed', timestamp: '2024-01-11 10:15 AM', adminName: 'Asaduzzaman', notes: 'NID verified successfully' },
|
|
{ stage: 'Address Verification', status: 'completed', timestamp: '2024-01-12 02:30 PM', adminName: 'Rahima Begum', notes: 'Address confirmed via phone call' },
|
|
{ stage: 'Interview Scheduled', status: 'completed', timestamp: '2024-01-13 09:00 AM', adminName: 'System' },
|
|
{ stage: 'Interview Completed', status: 'completed', timestamp: '2024-01-15 11:30 AM', adminName: 'Karim Ahmed', notes: 'All documents verified, approved for onboarding' },
|
|
{ stage: 'KYC Approved', status: 'completed', timestamp: '2024-01-16 03:45 PM', adminName: 'Supervisor Rahman' },
|
|
],
|
|
},
|
|
gpsDeviceId: 'GPS-DH-2024-001234',
|
|
gpsStatus: 'Online',
|
|
lastLocation: '23.8103, 90.4125',
|
|
currentRental: {
|
|
id: 'RNT-2024-0045',
|
|
planName: 'Monthly Rental',
|
|
subscriptionType: 'Premium',
|
|
startDate: '2024-03-01',
|
|
contractEndDate: '2024-04-30',
|
|
dailyRate: 0,
|
|
monthlyRate: 4500,
|
|
depositAmount: 5000,
|
|
depositStatus: 'Paid',
|
|
dueAmount: 0,
|
|
pendingDays: 0,
|
|
rentPaymentStatus: 'Paid',
|
|
evModel: 'AIMA Lightning EV',
|
|
batteryPercent: 78,
|
|
currentLocation: '23.7521, 90.3756',
|
|
odometerReading: 2456,
|
|
initialConditionImages: [
|
|
'https://picsum.photos/400/300?random=cond1',
|
|
'https://picsum.photos/400/300?random=cond2',
|
|
'https://picsum.photos/400/300?random=cond3',
|
|
'https://picsum.photos/400/300?random=cond4',
|
|
],
|
|
status: 'Active',
|
|
totalAmount: 4500,
|
|
penalty: 0,
|
|
plate: 'Dhaka Metro Cha-4521',
|
|
},
|
|
previousRentals: [
|
|
{
|
|
id: 'RNT-2024-0020',
|
|
planName: 'Weekly Rental',
|
|
subscriptionType: 'Basic',
|
|
startDate: '2024-02-01',
|
|
contractEndDate: '2024-02-07',
|
|
endDate: '2024-02-28',
|
|
dailyRate: 0,
|
|
weeklyRate: 1200,
|
|
depositAmount: 3000,
|
|
depositStatus: 'Returned',
|
|
dueAmount: 0,
|
|
pendingDays: 0,
|
|
rentPaymentStatus: 'Paid',
|
|
evModel: 'Yadea DT3',
|
|
batteryPercent: 0,
|
|
currentLocation: '',
|
|
odometerReading: 1850,
|
|
initialConditionImages: [],
|
|
status: 'Completed',
|
|
totalAmount: 4800,
|
|
penalty: 0,
|
|
plate: 'Dhaka Metro Cha-3890',
|
|
},
|
|
{
|
|
id: 'RNT-2024-0010',
|
|
planName: 'Monthly Rental',
|
|
subscriptionType: 'Standard',
|
|
startDate: '2024-01-01',
|
|
contractEndDate: '2024-01-31',
|
|
endDate: '2024-01-31',
|
|
dailyRate: 0,
|
|
monthlyRate: 3500,
|
|
depositAmount: 3000,
|
|
depositStatus: 'Returned',
|
|
dueAmount: 500,
|
|
pendingDays: 0,
|
|
rentPaymentStatus: 'Paid with Penalty',
|
|
evModel: 'Etron ET50',
|
|
batteryPercent: 0,
|
|
currentLocation: '',
|
|
odometerReading: 1200,
|
|
initialConditionImages: [],
|
|
status: 'Completed',
|
|
totalAmount: 4000,
|
|
penalty: 500,
|
|
plate: 'Dhaka Metro Cha-2156',
|
|
},
|
|
{
|
|
id: 'RNT-2023-0045',
|
|
planName: 'Weekly Rental',
|
|
subscriptionType: 'Basic',
|
|
startDate: '2023-12-15',
|
|
contractEndDate: '2023-12-21',
|
|
endDate: '2023-12-22',
|
|
dailyRate: 0,
|
|
weeklyRate: 1200,
|
|
depositAmount: 3000,
|
|
depositStatus: 'Returned',
|
|
dueAmount: 0,
|
|
pendingDays: 0,
|
|
rentPaymentStatus: 'Cancelled',
|
|
evModel: 'AIMA Lightning EV',
|
|
batteryPercent: 0,
|
|
currentLocation: '',
|
|
odometerReading: 180,
|
|
initialConditionImages: [],
|
|
status: 'Cancelled',
|
|
totalAmount: 1200,
|
|
penalty: 0,
|
|
plate: 'Dhaka Metro Cha-4521',
|
|
},
|
|
],
|
|
bikeSpecs: {
|
|
model: 'AIMA Lightning EV',
|
|
brand: 'AIMA',
|
|
year: '2024',
|
|
color: 'Matte Black',
|
|
vin: 'VIN1234567890ABCD',
|
|
engineNumber: 'ENG-2024-AIMA-5678',
|
|
plateNumber: 'Dhaka Metro Cha-4521',
|
|
batteryCapacity: '72V 40Ah',
|
|
maxRange: '120 km',
|
|
chargingTime: '4-6 hours',
|
|
lastServiceDate: '2024-03-15',
|
|
nextServiceDue: '2024-04-15',
|
|
totalKmRun: 2456,
|
|
},
|
|
bikes: {
|
|
current: {
|
|
id: 'BIKE-001',
|
|
model: 'AIMA Lightning EV',
|
|
brand: 'AIMA',
|
|
plate: 'Dhaka Metro Cha-4521',
|
|
vin: 'VIN123456789ABCDE',
|
|
color: 'Black',
|
|
year: '2023',
|
|
batteryCapacity: '72V 45Ah',
|
|
maxRange: '120 km',
|
|
totalKmRun: 2456,
|
|
lastService: '2024-03-15',
|
|
nextService: '2024-06-15',
|
|
},
|
|
batteries: [
|
|
{ id: 'BAT-DH-001', name: 'Battery A', percent: 78, status: 'active', swappedAt: '2024-04-15 10:30', location: 'Swap Station - Gulshan', odometer: 2456 },
|
|
{ id: 'BAT-DH-002', name: 'Battery B', percent: 92, status: 'available', swappedAt: '2024-04-10 08:00', location: 'Swap Station - Banani', odometer: 1890 },
|
|
{ id: 'BAT-DH-003', name: 'Battery C', percent: 15, status: 'charging', swappedAt: '2024-04-05 14:00', location: 'Swap Station - Gulshan', odometer: 1200 },
|
|
],
|
|
},
|
|
transactions: [
|
|
{ id: 'TXN-001', date: '2024-03-28', type: 'Rent Payment', amount: 4500, method: 'bKash', status: 'Completed', description: 'March 2024 monthly rent' },
|
|
{ id: 'TXN-002', date: '2024-03-01', type: 'Deposit', amount: 5000, method: 'bKash', status: 'Completed', description: 'Security deposit for new rental' },
|
|
{ id: 'TXN-003', date: '2024-02-28', type: 'Rent Payment', amount: 1200, method: 'Rocket', status: 'Completed', description: 'Final week rent - February' },
|
|
{ id: 'TXN-004', date: '2024-02-21', type: 'Rent Payment', amount: 1200, method: 'bKash', status: 'Completed', description: 'Week 4 rent - February' },
|
|
{ id: 'TXN-005', date: '2024-02-14', type: 'Rent Payment', amount: 1200, method: 'bKash', status: 'Completed', description: 'Week 3 rent - February' },
|
|
{ id: 'TXN-006', date: '2024-02-07', type: 'Rent Payment', amount: 1200, method: 'Rocket', status: 'Completed', description: 'Week 2 rent - February' },
|
|
{ id: 'TXN-007', date: '2024-02-01', type: 'Deposit Return', amount: -3000, method: 'Bank Transfer', status: 'Completed', description: 'Deposit returned from previous rental' },
|
|
{ id: 'TXN-008', date: '2024-01-31', type: 'Penalty', amount: 500, method: 'Wallet', status: 'Completed', description: 'Late payment penalty - January' },
|
|
{ id: 'TXN-009', date: '2024-01-28', type: 'Rent Payment', amount: 3500, method: 'bKash', status: 'Completed', description: 'January 2024 monthly rent' },
|
|
{ id: 'TXN-010', date: '2024-01-15', type: 'Wallet Top-up', amount: 5000, method: 'bKash', status: 'Completed', description: 'Wallet recharge' },
|
|
{ id: 'TXN-011', date: '2024-01-01', type: 'Deposit', amount: 3000, method: 'Cash', status: 'Completed', description: 'Security deposit' },
|
|
{ id: 'TXN-012', date: '2023-12-28', type: 'Rent Payment', amount: 800, method: 'bKash', status: 'Completed', description: 'Partial rent - December' },
|
|
],
|
|
activityLog: [
|
|
{ id: 'ACT-001', type: 'login', action: 'App login from Android device', timestamp: '2024-03-28 08:15 AM', icon: User },
|
|
{ id: 'ACT-002', type: 'rental', action: 'Battery swap at Gulshan Swap Station', timestamp: '2024-03-28 07:45 AM', icon: Battery, details: 'Swapped from 35% to 92%' },
|
|
{ id: 'ACT-003', type: 'payment', action: 'Monthly rent payment completed', timestamp: '2024-03-28 07:30 AM', adminName: 'System', icon: DollarSign },
|
|
{ id: 'ACT-004', type: 'rental', action: 'Rental started - AIMA Lightning EV', timestamp: '2024-03-01 10:00 AM', icon: BikeIcon },
|
|
{ id: 'ACT-005', type: 'kyc', action: 'KYC verification completed', timestamp: '2024-01-16 03:45 PM', adminName: 'Supervisor Rahman', icon: Shield },
|
|
{ id: 'ACT-006', type: 'document', action: 'Driving license image uploaded', timestamp: '2024-01-10 11:20 AM', icon: FileText },
|
|
{ id: 'ACT-007', type: 'penalty', action: 'Late payment penalty applied', timestamp: '2024-01-31 11:59 PM', adminName: 'System', icon: AlertTriangle, details: '৳500 for late rent payment' },
|
|
{ id: 'ACT-008', type: 'bike', action: 'Bike changed from Etron ET50 to AIMA Lightning', timestamp: '2024-03-01 09:30 AM', adminName: 'Staff: Kamal', icon: ArrowRightLeft },
|
|
{ id: 'ACT-009', type: 'message', action: 'Received SMS: Rent reminder', timestamp: '2024-02-28 09:00 AM', icon: MessageSquare },
|
|
{ id: 'ACT-010', type: 'login', action: 'App login from iOS device', timestamp: '2024-02-15 06:30 PM', icon: User },
|
|
{ id: 'ACT-011', type: 'rental', action: 'Battery swap at Banani Swap Station', timestamp: '2024-02-15 05:45 PM', icon: Battery, details: 'Swapped from 28% to 95%' },
|
|
{ id: 'ACT-012', type: 'document', action: 'NID image uploaded', timestamp: '2024-01-10 09:45 AM', icon: FileText },
|
|
],
|
|
damageReports: [
|
|
{
|
|
id: 'DMG-001',
|
|
date: '2024-02-10',
|
|
rentalId: 'RNT-2024-0020',
|
|
bike: 'Yadea DT3',
|
|
damageType: 'Body Damage',
|
|
severity: 'Medium',
|
|
estimatedCost: 2000,
|
|
actualCost: 1800,
|
|
status: 'Resolved',
|
|
resolution: 'Panel repaired and repainted',
|
|
description: 'Minor scratch on left rear fender noticed during routine inspection',
|
|
location: 'Gulshan 1, Dhaka',
|
|
images: ['https://picsum.photos/400/300?random=dm1', 'https://picsum.photos/400/300?random=dm2'],
|
|
},
|
|
{
|
|
id: 'DMG-002',
|
|
date: '2024-01-20',
|
|
rentalId: 'RNT-2024-0010',
|
|
bike: 'Etron ET50',
|
|
damageType: 'Tire Damage',
|
|
severity: 'Low',
|
|
estimatedCost: 800,
|
|
actualCost: 750,
|
|
status: 'Resolved',
|
|
resolution: 'Rear tire replaced',
|
|
description: 'Puncture in rear tire caused by sharp object on road',
|
|
location: 'Dhanmondi 32, Dhaka',
|
|
images: ['https://picsum.photos/400/300?random=dm3'],
|
|
},
|
|
{
|
|
id: 'DMG-003',
|
|
date: '2024-03-25',
|
|
rentalId: 'RNT-2024-0045',
|
|
bike: 'AIMA Lightning EV',
|
|
damageType: 'Battery Damage',
|
|
severity: 'High',
|
|
estimatedCost: 5000,
|
|
status: 'Pending',
|
|
description: 'Battery not holding charge properly, discharging faster than normal',
|
|
location: 'Uttara Sector 10, Dhaka',
|
|
images: ['https://picsum.photos/400/300?random=dm4', 'https://picsum.photos/400/300?random=dm5'],
|
|
},
|
|
],
|
|
messageHistory: [
|
|
{ id: 'MSG-001', direction: 'sent', channel: 'sms', message: 'Your monthly rent of ৳4,500 is due on March 28. Please ensure sufficient balance in your wallet.', timestamp: '2024-03-25 10:00 AM', status: 'Delivered' },
|
|
{ id: 'MSG-002', direction: 'received', channel: 'sms', message: 'Okay, I will pay today.', timestamp: '2024-03-25 10:15 AM', status: 'Received' },
|
|
{ id: 'MSG-003', direction: 'sent', channel: 'sms', message: 'Thank you. You can make payment via bKash or Rocket to 01712345678.', timestamp: '2024-03-25 10:20 AM', status: 'Delivered' },
|
|
{ id: 'MSG-004', direction: 'received', channel: 'sms', message: 'Payment done. Transaction ID: BKP123456789.', timestamp: '2024-03-28 07:25 AM', status: 'Received' },
|
|
{ id: 'MSG-005', direction: 'sent', channel: 'push', message: 'Your rent payment has been received. Thank you!', timestamp: '2024-03-28 07:30 AM', status: 'Delivered' },
|
|
{ id: 'MSG-006', direction: 'sent', channel: 'sms', message: 'Reminder: Your bike rental contract ends on April 30. Please renew or return the bike.', timestamp: '2024-03-20 09:00 AM', status: 'Delivered' },
|
|
{ id: 'MSG-007', direction: 'received', channel: 'sms', message: 'I want to renew the contract. What are the new rates?', timestamp: '2024-03-20 02:30 PM', status: 'Received' },
|
|
{ id: 'MSG-008', direction: 'sent', channel: 'sms', message: 'Great! Current rates are ৳4,500/month for the same plan. We will send renewal documents soon.', timestamp: '2024-03-20 03:00 PM', status: 'Delivered' },
|
|
],
|
|
notes: [
|
|
{ id: 'NOTE-001', text: 'Very reliable biker, always pays rent on time. Low maintenance issues.', createdAt: '2024-03-15 11:30 AM', createdBy: 'Karim (Staff)' },
|
|
{ id: 'NOTE-002', text: 'Battery performance issue reported on March 25. Needs inspection.', createdAt: '2024-03-25 04:00 PM', createdBy: 'Rahim (Manager)' },
|
|
{ id: 'NOTE-003', text: 'VIP customer - priority support required.', createdAt: '2024-02-01 09:00 AM', createdBy: 'Supervisor Rahman' },
|
|
],
|
|
walletBalance: 1250,
|
|
totalPaid: 27800,
|
|
totalDue: 0,
|
|
pendingAmount: 0,
|
|
totalPenaltiesPaid: 500,
|
|
bankAccount: {
|
|
bankName: 'Dutch-Bangla Bank Limited',
|
|
accountNumber: '1511234567890',
|
|
routingNumber: '9032612',
|
|
},
|
|
billingAddress: {
|
|
houseFlat: 'House 42',
|
|
floor: '3rd Floor',
|
|
road: 'Road 11',
|
|
block: 'Block D',
|
|
area: 'Gulshan 1',
|
|
thana: 'Gulshan',
|
|
district: 'Dhaka',
|
|
division: 'Dhaka',
|
|
postalCode: '1212',
|
|
},
|
|
};
|
|
|
|
const tabs = [
|
|
{ id: 'personal', label: 'Personal', icon: User },
|
|
{ id: 'license', label: 'License & GPS', icon: Car },
|
|
{ id: 'documents', label: 'Documents', icon: FileCheck },
|
|
{ id: 'bikes', label: 'Bikes', icon: BikeIcon },
|
|
{ id: 'rent', label: 'Rent & Account', icon: CreditCard },
|
|
{ id: 'messages', label: 'Messages', icon: MessageSquare },
|
|
{ id: 'notes', label: 'Notes', icon: ClipboardList },
|
|
{ id: 'activity', label: 'Activity', icon: Activity },
|
|
{ id: 'damage', label: 'Damage', icon: AlertOctagon },
|
|
];
|
|
|
|
const statusColors: Record<string, string> = {
|
|
active: 'bg-green-100 text-green-700',
|
|
pending: 'bg-amber-100 text-amber-700',
|
|
inactive: 'bg-slate-100 text-slate-500',
|
|
blocked: 'bg-red-100 text-red-700',
|
|
completed: 'bg-green-100 text-green-700',
|
|
cancelled: 'bg-red-100 text-red-700',
|
|
resolved: 'bg-green-100 text-green-700',
|
|
};
|
|
|
|
const severityColors: Record<string, string> = {
|
|
Low: 'bg-blue-100 text-blue-700',
|
|
Medium: 'bg-amber-100 text-amber-700',
|
|
High: 'bg-orange-100 text-orange-700',
|
|
Critical: 'bg-red-100 text-red-700',
|
|
};
|
|
|
|
const activityTypeColors: Record<string, string> = {
|
|
login: 'bg-blue-100 text-blue-600',
|
|
rental: 'bg-green-100 text-green-600',
|
|
payment: 'bg-purple-100 text-purple-600',
|
|
kyc: 'bg-cyan-100 text-cyan-600',
|
|
document: 'bg-amber-100 text-amber-600',
|
|
penalty: 'bg-red-100 text-red-600',
|
|
bike: 'bg-indigo-100 text-indigo-600',
|
|
message: 'bg-pink-100 text-pink-600',
|
|
};
|
|
|
|
const damageTypes = [
|
|
'Battery Damage',
|
|
'Body Damage',
|
|
'Tire Damage',
|
|
'Engine Damage',
|
|
'Accident',
|
|
'Theft',
|
|
'Other',
|
|
];
|
|
|
|
const severityLevels = ['Low', 'Medium', 'High', 'Critical'];
|
|
|
|
const smsTemplates = [
|
|
{ id: 'rent_due', label: 'Monthly rent due', message: 'Monthly rent due. Please pay ৳{amount} within 3 days.' },
|
|
{ id: 'contract_end', label: 'Contract ending', message: 'Your bike rental contract ends on {date}. Please renew or return.' },
|
|
{ id: 'kyc_approved', label: 'KYC approved', message: 'Congratulations! Your KYC has been approved. You can now rent bikes.' },
|
|
{ id: 'penalty', label: 'Penalty warning', message: 'Warning: Your account has a penalty of ৳{amount}. Please clear it to avoid service suspension.' },
|
|
{ id: 'docs_upload', label: 'Document request', message: 'Please upload your documents for verification. Missing documents: {docs}' },
|
|
{ id: 'battery_reminder', label: 'Battery swap reminder', message: 'Your battery is running low. Please swap at nearest station.' },
|
|
];
|
|
|
|
function AddressCard({ title, address, icon: Icon, bgColor }: { title: string; address: any; icon: any; bgColor: string }) {
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className={`${bgColor} px-4 py-3 flex items-center gap-2`}>
|
|
<Icon className="w-5 h-5" />
|
|
<h3 className="font-semibold text-slate-800">{title}</h3>
|
|
</div>
|
|
<div className="p-4 space-y-2">
|
|
{address.houseFlat && <InfoRow label="House/Flat" value={address.houseFlat} />}
|
|
{address.floor && <InfoRow label="Floor" value={address.floor} />}
|
|
{address.road && <InfoRow label="Road" value={address.road} />}
|
|
{address.block && <InfoRow label="Block" value={address.block} />}
|
|
{address.area && <InfoRow label="Area" value={address.area} />}
|
|
{address.thana && <InfoRow label="Thana" value={address.thana} />}
|
|
{address.district && <InfoRow label="District" value={address.district} />}
|
|
{address.division && <InfoRow label="Division" value={address.division} />}
|
|
{address.postalCode && <InfoRow label="Postal Code" value={address.postalCode} />}
|
|
{address.landmark && <InfoRow label="Landmark" value={address.landmark} />}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function InfoRow({ label, value }: { label: string; value: string }) {
|
|
return value ? (
|
|
<div className="flex justify-between">
|
|
<span className="text-xs text-slate-500">{label}</span>
|
|
<span className="text-sm font-medium text-slate-700">{value}</span>
|
|
</div>
|
|
) : null;
|
|
}
|
|
|
|
function ProfileField({ label, value, editing, onChange }: { label: string; value: string; editing?: boolean; onChange?: (v: string) => void }) {
|
|
if (editing) {
|
|
return (
|
|
<div className="flex justify-between items-start gap-4">
|
|
<span className="text-xs text-slate-500 whitespace-nowrap pt-2">{label}</span>
|
|
<input
|
|
type="text"
|
|
value={value}
|
|
onChange={(e) => onChange?.(e.target.value)}
|
|
className="w-40 px-2 py-1.5 border border-slate-200 rounded text-xs text-slate-800 bg-white"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<div className="flex justify-between items-start gap-4">
|
|
<span className="text-xs text-slate-500 whitespace-nowrap">{label}</span>
|
|
<span className="text-xs font-medium text-slate-800 text-right">{value}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SectionCard({ title, icon: Icon, children, headerBg = 'bg-slate-50', editKey, editingSection, setEditingSection, onEdit, editForm, setEditForm, biker }: { title: string; icon: any; children: React.ReactNode; headerBg?: string; editKey?: string; editingSection?: string | null; setEditingSection?: (s: string | null) => void; onEdit?: () => void; editForm?: any; setEditForm?: any; biker?: any }) {
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className={`${headerBg} px-4 py-3 flex items-center justify-between`}>
|
|
<div className="flex items-center gap-2">
|
|
<Icon className="w-5 h-5 text-accent" />
|
|
<h3 className="font-semibold text-slate-800">{title}</h3>
|
|
</div>
|
|
{editKey && setEditingSection ? (
|
|
editingSection !== editKey ? (
|
|
<button onClick={() => { setEditingSection(editKey); onEdit?.(); }} className="p-1.5 hover:bg-white rounded-lg transition-colors">
|
|
{/* <Edit className="w-4 h-4 text-slate-500" /> */}
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)
|
|
) : null}
|
|
</div>
|
|
<div className="p-4">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TimelineItem({ stage, status, timestamp, adminName, notes, isLast }: { stage: string; status: string; timestamp: string; adminName: string; notes?: string; isLast: boolean }) {
|
|
return (
|
|
<div className="flex gap-3">
|
|
<div className="flex flex-col items-center">
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${status === 'completed' ? 'bg-green-100' : 'bg-amber-100'}`}>
|
|
{status === 'completed' ? <Check className="w-4 h-4 text-green-600" /> : <Clock className="w-4 h-4 text-amber-600" />}
|
|
</div>
|
|
{!isLast && <div className="w-0.5 h-full bg-slate-200 mt-2" />}
|
|
</div>
|
|
<div className="flex-1 pb-4">
|
|
<p className="font-medium text-slate-700">{stage}</p>
|
|
<p className="text-xs text-slate-500 mt-0.5">{timestamp} • {adminName}</p>
|
|
{notes && <p className="text-xs text-slate-400 mt-1 italic">{notes}</p>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function BikerDetailPage() {
|
|
const router = useRouter();
|
|
const [activeTab, setActiveTab] = useState('personal');
|
|
const [showMessageModal, setShowMessageModal] = useState(false);
|
|
const [showDamageModal, setShowDamageModal] = useState(false);
|
|
const [showDocumentUpload, setShowDocumentUpload] = useState(false);
|
|
const [messageTab, setMessageTab] = useState<'sms' | 'push' | 'template'>('sms');
|
|
const [messageText, setMessageText] = useState('');
|
|
const [selectedTemplate, setSelectedTemplate] = useState('');
|
|
const [newNote, setNewNote] = useState('');
|
|
const [damageForm, setDamageForm] = useState({
|
|
bike: '',
|
|
damageType: '',
|
|
description: '',
|
|
severity: '',
|
|
estimatedCost: '',
|
|
location: '',
|
|
images: [] as string[],
|
|
});
|
|
const [rentPage, setRentPage] = useState(1);
|
|
const [transactionPage, setTransactionPage] = useState(1);
|
|
const [editingSection, setEditingSection] = useState<string | null>(null);
|
|
const [editForm, setEditForm] = useState<any>({});
|
|
|
|
const biker = mockBiker;
|
|
const perPage = 10;
|
|
|
|
const paginatedRentHistory = [...biker.currentRental ? [biker.currentRental] : [], ...biker.previousRentals].slice((rentPage - 1) * perPage, rentPage * perPage);
|
|
const totalRentPages = Math.ceil((1 + biker.previousRentals.length) / perPage);
|
|
|
|
const paginatedTransactions = biker.transactions.slice((transactionPage - 1) * perPage, transactionPage * perPage);
|
|
const totalTransactionPages = Math.ceil(biker.transactions.length / perPage);
|
|
|
|
const handleSendMessage = () => {
|
|
if (!messageText.trim()) return;
|
|
toast.success(`Message sent to ${biker.kyc.phone}`);
|
|
setMessageText('');
|
|
setShowMessageModal(false);
|
|
};
|
|
|
|
const handleAddNote = () => {
|
|
if (!newNote.trim()) return;
|
|
toast.success('Note added successfully');
|
|
setNewNote('');
|
|
};
|
|
|
|
const handleSendTemplate = (template: typeof smsTemplates[0]) => {
|
|
let message = template.message;
|
|
message = message.replace('{amount}', biker.currentRental?.monthlyRate?.toString() || '4500');
|
|
message = message.replace('{date}', biker.currentRental?.contractEndDate || '2024-04-30');
|
|
message = message.replace('{docs}', 'Driving License, NID');
|
|
setMessageText(message);
|
|
setMessageTab('sms');
|
|
};
|
|
|
|
const handleSubmitDamage = () => {
|
|
if (!damageForm.damageType || !damageForm.description) {
|
|
toast.error('Please fill all required fields');
|
|
return;
|
|
}
|
|
toast.success('Damage report submitted successfully');
|
|
setShowDamageModal(false);
|
|
setDamageForm({ bike: '', damageType: '', description: '', severity: '', estimatedCost: '', location: '', images: [] });
|
|
};
|
|
|
|
const formatAddress = (addr: any) => {
|
|
return [addr.houseFlat, addr.floor, addr.road, addr.block, addr.area, addr.thana, addr.district, addr.division, addr.postalCode].filter(Boolean).join(', ');
|
|
};
|
|
|
|
return (
|
|
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
|
<button onClick={() => router.push('/admin/bikers')} className="flex items-center gap-2 text-slate-600 hover:text-slate-800 mb-4">
|
|
<ArrowLeft className="w-4 h-4" /> Back to Bikers
|
|
</button>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-8">
|
|
<div className="p-4 lg:p-6 flex flex-col lg:flex-row lg:items-center gap-4">
|
|
<div className="relative group">
|
|
{biker.profileImage ? (
|
|
<img src={biker.profileImage} alt={biker.kyc.fullName} className="w-20 h-20 rounded-full object-cover border-2 border-slate-200" />
|
|
) : (
|
|
<div className="w-20 h-20 rounded-full bg-blue-100 flex items-center justify-center border-2 border-slate-200">
|
|
<span className="text-3xl font-bold text-blue-600">{biker.kyc.fullName.charAt(0)}</span>
|
|
</div>
|
|
)}
|
|
<label className="absolute bottom-0 right-0 w-7 h-7 bg-blue-600 rounded-full flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity shadow-sm border-2 border-white">
|
|
<Camera className="w-3.5 h-3.5 text-white" />
|
|
<input type="file" accept="image/*" className="hidden" onChange={(e) => { toast.success('Profile image updated'); }} />
|
|
</label>
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<h2 className="text-xl lg:text-2xl font-bold text-slate-800">{biker.kyc.fullName}</h2>
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[biker.status]}`}>
|
|
{biker.status === 'active' && <Activity className="w-3 h-3" />}
|
|
{biker.status === 'pending' && <Clock className="w-3 h-3" />}
|
|
{biker.status === 'blocked' && <Ban className="w-3 h-3" />}
|
|
{biker.status.charAt(0).toUpperCase() + biker.status.slice(1)}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-slate-500">ID: {biker.id} • {biker.location}</p>
|
|
<div className="flex flex-wrap items-center gap-2 mt-2">
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${biker.kycStatus === 'verified' ? 'bg-green-100 text-green-700' : biker.kycStatus === 'pending' ? 'bg-amber-100 text-amber-700' : 'bg-slate-100 text-slate-500'}`}>
|
|
{biker.kycStatus === 'verified' && <CheckCircle className="w-3 h-3" />}
|
|
{biker.kycStatus === 'pending' && <Clock className="w-3 h-3" />}
|
|
KYC: {biker.kycStatus}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-purple-100 text-purple-700 border border-purple-200">
|
|
{editingSection === 'membership' ? (
|
|
<>
|
|
<select autoFocus value={editForm.membershipType || ''} onChange={(e) => setEditForm({ ...editForm, membershipType: e.target.value })} className="bg-transparent border-none text-xs font-medium p-0 focus:outline-none text-purple-700">
|
|
<option value="Basic">Basic</option>
|
|
<option value="Standard">Standard</option>
|
|
<option value="Premium">Premium</option>
|
|
<option value="Platinum">Platinum</option>
|
|
</select>
|
|
<button onClick={() => { toast.success('Membership updated'); setEditingSection(null); }} className="hover:bg-purple-200 p-0.5 rounded">
|
|
<Check className="w-3 h-3 text-green-700" />
|
|
</button>
|
|
<button onClick={() => setEditingSection(null)} className="hover:bg-purple-200 p-0.5 rounded">
|
|
<X className="w-3 h-3 text-red-700" />
|
|
</button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<button onClick={() => { setEditingSection('membership'); setEditForm({ membershipType: biker.membershipType }); }} className="hover:bg-purple-200 p-0.5 rounded">
|
|
<Pencil className="w-3 h-3" />
|
|
</button>
|
|
<Award className="w-3 h-3" /> {biker.membershipType}
|
|
</>
|
|
)}
|
|
</span>
|
|
{biker.rating > 0 && (
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full bg-amber-100 text-amber-700">
|
|
<Star className="w-3 h-3" /> {biker.rating} ({biker.totalRatings})
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<button className="flex-1 lg:flex-none px-4 py-2 bg-blue-600 text-white rounded-lg font-semibold text-sm hover:bg-blue-700 flex items-center justify-center gap-2">
|
|
<PhoneCall className="w-4 h-4" />
|
|
<span className="hidden sm:inline">Call</span>
|
|
</button>
|
|
<a href={`sms:${biker.kyc.phone}`} className="px-3 lg:px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-50 flex items-center gap-2">
|
|
<PhoneOutgoing className="w-4 h-4" />
|
|
<span className="hidden sm:inline">SMS</span>
|
|
</a>
|
|
<button onClick={() => setShowMessageModal(true)} className="px-3 lg:px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 flex items-center gap-2">
|
|
<MessageSquare className="w-4 h-4" />
|
|
<span className="hidden sm:inline">Message</span>
|
|
</button>
|
|
<button onClick={() => setShowDamageModal(true)} className="px-3 lg:px-4 py-2 bg-red-600 text-white rounded-lg text-sm font-medium hover:bg-red-700 flex items-center gap-2">
|
|
<AlertOctagon className="w-4 h-4" />
|
|
<span className="hidden sm:inline">Damage</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-4 mb-4">
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-slate-100">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<History className="w-4 h-4 text-blue-600" />
|
|
<span className="text-xs text-slate-500">Total Rentals</span>
|
|
</div>
|
|
<p className="text-2xl font-extrabold text-slate-800">{biker.totalRentals}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-slate-100">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<BikeIcon className="w-4 h-4 text-green-600" />
|
|
<span className="text-xs text-slate-500">Active Rentals</span>
|
|
</div>
|
|
<p className="text-2xl font-extrabold text-green-600">{biker.activeRentals}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-slate-100">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<CheckCircle className="w-4 h-4 text-purple-600" />
|
|
<span className="text-xs text-slate-500">Completed</span>
|
|
</div>
|
|
<p className="text-2xl font-extrabold text-purple-600">{biker.completedRentals}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-slate-100">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<XCircle className="w-4 h-4 text-red-600" />
|
|
<span className="text-xs text-slate-500">Cancelled</span>
|
|
</div>
|
|
<p className="text-2xl font-extrabold text-red-600">{biker.cancelledRentals}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs Container */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-4">
|
|
<div className="border-b border-slate-100 overflow-x-auto">
|
|
<nav className="flex whitespace-nowrap">
|
|
{tabs.map(tab => {
|
|
const Icon = tab.icon;
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 transition-colors ${activeTab === tab.id ? 'border-accent text-accent' : 'border-transparent text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
<Icon className="w-4 h-4 inline mr-1" />
|
|
{tab.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
|
|
<div className="p-4 lg:p-6">
|
|
{activeTab === 'personal' && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
{/* Application Details */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-blue-50 border-b border-blue-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<User className="w-4 h-4 text-blue-600" /> Application Details
|
|
</h3>
|
|
{editingSection !== 'application' ? (
|
|
<button onClick={() => { setEditingSection('application'); setEditForm({ fullName: biker.kyc.fullName, phone: biker.kyc.phone, alternatePhone: biker.kyc.alternatePhone || '', email: biker.kyc.email, nid: biker.kyc.nid, dateOfBirth: biker.kyc.dateOfBirth, gender: biker.kyc.gender, bloodGroup: biker.kyc.bloodGroup, maritalStatus: biker.kyc.maritalStatus, religion: biker.kyc.religion, nationality: biker.kyc.nationality }); }} className="p-1.5 hover:bg-blue-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-blue-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Application details updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'application' ? (
|
|
<>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Full Name <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.fullName} onChange={(e) => setEditForm({ ...editForm, fullName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Phone <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.phone} onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Alternate Phone</label>
|
|
<input type="text" value={editForm.alternatePhone} onChange={(e) => setEditForm({ ...editForm, alternatePhone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Email</label>
|
|
<input type="email" value={editForm.email} onChange={(e) => setEditForm({ ...editForm, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">NID <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.nid} onChange={(e) => setEditForm({ ...editForm, nid: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Date of Birth</label>
|
|
<input type="date" value={editForm.dateOfBirth} onChange={(e) => setEditForm({ ...editForm, dateOfBirth: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Gender</label>
|
|
<select value={editForm.gender} onChange={(e) => setEditForm({ ...editForm, gender: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="Male">Male</option>
|
|
<option value="Female">Female</option>
|
|
<option value="Other">Other</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Blood Group</label>
|
|
<select value={editForm.bloodGroup} onChange={(e) => setEditForm({ ...editForm, bloodGroup: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="A+">A+</option>
|
|
<option value="A-">A-</option>
|
|
<option value="B+">B+</option>
|
|
<option value="B-">B-</option>
|
|
<option value="AB+">AB+</option>
|
|
<option value="AB-">AB-</option>
|
|
<option value="O+">O+</option>
|
|
<option value="O-">O-</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Marital Status</label>
|
|
<select value={editForm.maritalStatus} onChange={(e) => setEditForm({ ...editForm, maritalStatus: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="Single">Single</option>
|
|
<option value="Married">Married</option>
|
|
<option value="Divorced">Divorced</option>
|
|
<option value="Widowed">Widowed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Religion</label>
|
|
<input type="text" value={editForm.religion} onChange={(e) => setEditForm({ ...editForm, religion: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Nationality</label>
|
|
<input type="text" value={editForm.nationality} onChange={(e) => setEditForm({ ...editForm, nationality: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Full Name</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.kyc.fullName}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Phone</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.phone}</p>
|
|
</div>
|
|
{biker.kyc.alternatePhone && (
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Alternate Phone</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.alternatePhone}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Email</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.email}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">NID</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nid}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Date of Birth</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.dateOfBirth}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Gender</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.gender}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Blood Group</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.bloodGroup}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Marital Status</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.maritalStatus}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Religion</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.religion}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Nationality</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nationality}</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Nominee Details */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-orange-50 border-b border-orange-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<Users className="w-4 h-4 text-orange-600" /> Nominee Details
|
|
</h3>
|
|
{editingSection !== 'nominee' ? (
|
|
<button onClick={() => { setEditingSection('nominee'); setEditForm({ name: biker.kyc.nominee.name, relationship: biker.kyc.nominee.relationship, nid: biker.kyc.nominee.nid, phone: biker.kyc.nominee.phone, email: biker.kyc.nominee.email, sharePercentage: biker.kyc.nominee.sharePercentage, bloodGroup: biker.kyc.nominee.bloodGroup, address: biker.kyc.nominee.address }); }} className="p-1.5 hover:bg-orange-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-orange-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Nominee details updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'nominee' ? (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Nominee Name <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Relationship <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.relationship} onChange={(e) => setEditForm({ ...editForm, relationship: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">NID <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.nid} onChange={(e) => setEditForm({ ...editForm, nid: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Phone <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.phone} onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Email</label>
|
|
<input type="email" value={editForm.email} onChange={(e) => setEditForm({ ...editForm, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Share % <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.sharePercentage} onChange={(e) => setEditForm({ ...editForm, sharePercentage: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Blood Group</label>
|
|
<select value={editForm.bloodGroup} onChange={(e) => setEditForm({ ...editForm, bloodGroup: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="A+">A+</option><option value="A-">A-</option><option value="B+">B+</option><option value="B-">B-</option><option value="AB+">AB+</option><option value="AB-">AB-</option><option value="O+">O+</option><option value="O-">O-</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address</label>
|
|
<input type="text" value={editForm.address} onChange={(e) => setEditForm({ ...editForm, address: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Nominee Name</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.kyc.nominee.name}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Relationship</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.relationship}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">NID</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.nid}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Phone</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.phone}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Email</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.email}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Share %</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.sharePercentage}%</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Blood Group</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.bloodGroup}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.nominee.address}</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Employment Information */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-purple-50 border-b border-purple-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<Briefcase className="w-4 h-4 text-purple-600" /> Employment Information
|
|
</h3>
|
|
{editingSection !== 'employment' ? (
|
|
<button onClick={() => { setEditingSection('employment'); setEditForm({ status: biker.kyc.employment.status, companyName: biker.kyc.employment.companyName, department: biker.kyc.employment.department, designation: biker.kyc.employment.designation, monthlyIncome: biker.kyc.employment.monthlyIncome, yearsOfExperience: biker.kyc.employment.yearsOfExperience, businessAddress: biker.kyc.employment.businessAddress }); }} className="p-1.5 hover:bg-purple-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-purple-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Employment info updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'employment' ? (
|
|
<>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Status <span className="text-red-500">*</span></label>
|
|
<select value={editForm.status} onChange={(e) => setEditForm({ ...editForm, status: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="Employed">Employed</option>
|
|
<option value="Self Employed">Self Employed</option>
|
|
<option value="Student">Student</option>
|
|
<option value="Unemployed">Unemployed</option>
|
|
<option value="Retired">Retired</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Company / Business Name</label>
|
|
<input type="text" value={editForm.companyName} onChange={(e) => setEditForm({ ...editForm, companyName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Department</label>
|
|
<input type="text" value={editForm.department} onChange={(e) => setEditForm({ ...editForm, department: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Designation</label>
|
|
<input type="text" value={editForm.designation} onChange={(e) => setEditForm({ ...editForm, designation: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Monthly Income</label>
|
|
<input type="text" value={editForm.monthlyIncome} onChange={(e) => setEditForm({ ...editForm, monthlyIncome: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Experience</label>
|
|
<input type="text" value={editForm.yearsOfExperience} onChange={(e) => setEditForm({ ...editForm, yearsOfExperience: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Business Address</label>
|
|
<input type="text" value={editForm.businessAddress} onChange={(e) => setEditForm({ ...editForm, businessAddress: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Status</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.status}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Monthly Income</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.kyc.employment.monthlyIncome}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Company / Business</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.companyName}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Department</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.department}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Designation</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.designation}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Experience</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.yearsOfExperience}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Business Address</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.employment.businessAddress}</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Emergency Contact */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-red-50 border-b border-red-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<PhoneIncoming className="w-4 h-4 text-red-600" /> Emergency Contact
|
|
</h3>
|
|
{editingSection !== 'emergency' ? (
|
|
<button onClick={() => { setEditingSection('emergency'); setEditForm({ name: biker.kyc.emergencyContact.name, relationship: biker.kyc.emergencyContact.relationship, phone: biker.kyc.emergencyContact.phone, email: biker.kyc.emergencyContact.email, address: biker.kyc.emergencyContact.address }); }} className="p-1.5 hover:bg-red-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-red-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Emergency contact updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'emergency' ? (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Contact Name <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Relationship <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.relationship} onChange={(e) => setEditForm({ ...editForm, relationship: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Phone <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.phone} onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Email</label>
|
|
<input type="email" value={editForm.email} onChange={(e) => setEditForm({ ...editForm, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address</label>
|
|
<input type="text" value={editForm.address} onChange={(e) => setEditForm({ ...editForm, address: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Contact Name</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.kyc.emergencyContact.name}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Relationship</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.emergencyContact.relationship}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Phone</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.emergencyContact.phone}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Email</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.emergencyContact.email}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.emergencyContact.address}</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Present Address */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-green-50 border-b border-green-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<MapPinHouse className="w-4 h-4 text-green-600" /> Present Address
|
|
</h3>
|
|
{editingSection !== 'presentAddress' ? (
|
|
<button onClick={() => { setEditingSection('presentAddress'); setEditForm({ addressLine1: `${biker.kyc.presentAddress.houseFlat}, ${biker.kyc.presentAddress.road}`, addressLine2: biker.kyc.presentAddress.block ? `${biker.kyc.presentAddress.block}, ${biker.kyc.presentAddress.area}` : biker.kyc.presentAddress.area, division: biker.kyc.presentAddress.division, district: biker.kyc.presentAddress.district, thana: biker.kyc.presentAddress.thana, zip: biker.kyc.presentAddress.postalCode, landmark: biker.kyc.presentAddress.landmark || '' }); }} className="p-1.5 hover:bg-green-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-green-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Address updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'presentAddress' ? (
|
|
<>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address Line 1 <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.addressLine1} onChange={(e) => setEditForm({ ...editForm, addressLine1: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="House No, Road, Area" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address Line 2</label>
|
|
<input type="text" value={editForm.addressLine2} onChange={(e) => setEditForm({ ...editForm, addressLine2: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="Additional info" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Division <span className="text-red-500">*</span></label>
|
|
<select value={editForm.division} onChange={(e) => setEditForm({ ...editForm, division: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="">Select Division</option>
|
|
<option value="Dhaka">Dhaka</option>
|
|
<option value="Chittagong">Chittagong</option>
|
|
<option value="Rajshahi">Rajshahi</option>
|
|
<option value="Sylhet">Sylhet</option>
|
|
<option value="Khulna">Khulna</option>
|
|
<option value="Barisal">Barisal</option>
|
|
<option value="Rangpur">Rangpur</option>
|
|
<option value="Mymensingh">Mymensingh</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">District <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.district} onChange={(e) => setEditForm({ ...editForm, district: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Thana / Upazila</label>
|
|
<input type="text" value={editForm.thana} onChange={(e) => setEditForm({ ...editForm, thana: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Zip Code <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.zip} onChange={(e) => setEditForm({ ...editForm, zip: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Landmark</label>
|
|
<input type="text" value={editForm.landmark} onChange={(e) => setEditForm({ ...editForm, landmark: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="Near landmark" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Country <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.country || 'Bangladesh'} onChange={(e) => setEditForm({ ...editForm, country: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address Line 1</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.houseFlat}, {biker.kyc.presentAddress.road}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address Line 2</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.block ? `${biker.kyc.presentAddress.block}, ` : ''}{biker.kyc.presentAddress.area}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Division</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.division}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">District</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.district}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Thana / Upazila</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.thana}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Zip Code</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.postalCode}</p>
|
|
</div>
|
|
</div>
|
|
{biker.kyc.presentAddress.landmark && (
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Landmark</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.presentAddress.landmark}</p>
|
|
</div>
|
|
)}
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Country</p>
|
|
<p className="text-xs font-medium text-slate-700">Bangladesh</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Permanent Address */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="px-5 py-4 bg-emerald-50 border-b border-emerald-100 flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-slate-800 flex items-center gap-2">
|
|
<Home className="w-4 h-4 text-emerald-600" /> Permanent Address
|
|
</h3>
|
|
<div className="flex items-center gap-3">
|
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
|
<input type="checkbox" checked={biker.kyc.isPermanentSame} onChange={() => { }} className="w-3.5 h-3.5 rounded border-slate-300" />
|
|
<span className="text-xs text-slate-600">Same as Present</span>
|
|
</label>
|
|
{editingSection !== 'permanentAddress' ? (
|
|
<button onClick={() => { setEditingSection('permanentAddress'); setEditForm({ addressLine1: `${biker.kyc.permanentAddress.houseFlat}, ${biker.kyc.permanentAddress.road}`, addressLine2: biker.kyc.permanentAddress.block ? `${biker.kyc.permanentAddress.block}, ${biker.kyc.permanentAddress.area}` : biker.kyc.permanentAddress.area, division: biker.kyc.permanentAddress.division, district: biker.kyc.permanentAddress.district, thana: biker.kyc.permanentAddress.thana || '', zip: biker.kyc.permanentAddress.postalCode, landmark: '', country: 'Bangladesh' }); }} className="p-1.5 hover:bg-emerald-100 rounded-lg transition-colors">
|
|
<Edit className="w-4 h-4 text-emerald-600" />
|
|
</button>
|
|
) : (
|
|
<div className="flex gap-1">
|
|
<button onClick={() => { toast.success('Address updated'); setEditingSection(null); }} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium hover:bg-green-700">Save</button>
|
|
<button onClick={() => setEditingSection(null)} className="px-3 py-1.5 border border-slate-200 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-50">Cancel</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
{editingSection === 'permanentAddress' ? (
|
|
<>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address Line 1 <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.addressLine1} onChange={(e) => setEditForm({ ...editForm, addressLine1: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="House No, Road, Area" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Address Line 2</label>
|
|
<input type="text" value={editForm.addressLine2} onChange={(e) => setEditForm({ ...editForm, addressLine2: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="Additional info" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Division <span className="text-red-500">*</span></label>
|
|
<select value={editForm.division} onChange={(e) => setEditForm({ ...editForm, division: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="">Select Division</option>
|
|
<option value="Dhaka">Dhaka</option>
|
|
<option value="Chittagong">Chittagong</option>
|
|
<option value="Rajshahi">Rajshahi</option>
|
|
<option value="Sylhet">Sylhet</option>
|
|
<option value="Khulna">Khulna</option>
|
|
<option value="Barisal">Barisal</option>
|
|
<option value="Rangpur">Rangpur</option>
|
|
<option value="Mymensingh">Mymensingh</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">District <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.district} onChange={(e) => setEditForm({ ...editForm, district: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Thana / Upazila</label>
|
|
<input type="text" value={editForm.thana} onChange={(e) => setEditForm({ ...editForm, thana: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Zip Code <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.zip} onChange={(e) => setEditForm({ ...editForm, zip: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Landmark</label>
|
|
<input type="text" value={editForm.landmark} onChange={(e) => setEditForm({ ...editForm, landmark: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" placeholder="Near landmark" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Country <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.country || 'Bangladesh'} onChange={(e) => setEditForm({ ...editForm, country: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address Line 1</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.houseFlat}, {biker.kyc.permanentAddress.road}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Address Line 2</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.block ? `${biker.kyc.permanentAddress.block}, ` : ''}{biker.kyc.permanentAddress.area}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Division</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.division}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">District</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.district}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Thana / Upazila</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.thana || '-'}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Zip Code</p>
|
|
<p className="text-xs font-medium text-slate-700">{biker.kyc.permanentAddress.postalCode}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Landmark</p>
|
|
<p className="text-xs font-medium text-slate-700">{'-'}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Country</p>
|
|
<p className="text-xs font-medium text-slate-700">Bangladesh</p>
|
|
</div>
|
|
{biker.kyc.isPermanentSame && (
|
|
<div className="flex items-center gap-2 p-2 bg-green-50 rounded-lg border border-green-100">
|
|
<Check className="w-3 h-3 text-green-600" />
|
|
<span className="text-xs text-green-700 font-medium">Same as Present Address</span>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'license' && (
|
|
<div className="space-y-6">
|
|
<SectionCard title="Driving License Details" icon={Car} editKey="drivingLicense" editingSection={editingSection} setEditingSection={setEditingSection} onEdit={() => { setEditForm({ licenseNumber: biker.kyc.drivingLicense.number, licenseType: biker.kyc.drivingLicense.licenseType, licenseIssueDate: biker.kyc.drivingLicense.issueDate, licenseExpiryDate: biker.kyc.drivingLicense.expiryDate, issuingAuthority: biker.kyc.drivingLicense.issuingAuthority }); }} editForm={editForm} setEditForm={setEditForm} biker={biker}>
|
|
<div className="space-y-3">
|
|
{editingSection === 'drivingLicense' ? (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">License Number <span className="text-red-500">*</span></label>
|
|
<input type="text" value={editForm.licenseNumber} onChange={(e) => setEditForm({ ...editForm, licenseNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">License Type <span className="text-red-500">*</span></label>
|
|
<select value={editForm.licenseType} onChange={(e) => setEditForm({ ...editForm, licenseType: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="">Select Type</option>
|
|
<option value="Non-Transport">Non-Transport</option>
|
|
<option value="Transport">Transport</option>
|
|
<option value="Motorcycle">Motorcycle</option>
|
|
<option value="Heavy Vehicle">Heavy Vehicle</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Issue Date</label>
|
|
<input type="date" value={editForm.licenseIssueDate} onChange={(e) => setEditForm({ ...editForm, licenseIssueDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Expiry Date</label>
|
|
<input type="date" value={editForm.licenseExpiryDate} onChange={(e) => setEditForm({ ...editForm, licenseExpiryDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Issuing Authority</label>
|
|
<input type="text" value={editForm.issuingAuthority} onChange={(e) => setEditForm({ ...editForm, issuingAuthority: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">License Number</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.drivingLicense.number}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">License Type</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.drivingLicense.licenseType}</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Issue Date</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.drivingLicense.issueDate}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Expiry Date</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.drivingLicense.expiryDate}</p>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Issuing Authority</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.kyc.drivingLicense.issuingAuthority}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4 mt-2">
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<p className="text-xs text-slate-500 mb-2">Front Image</p>
|
|
{biker.kyc.drivingLicense.frontImage ? (
|
|
<img src={biker.kyc.drivingLicense.frontImage} alt="License Front" className="w-full h-40 object-cover rounded-lg" />
|
|
) : (
|
|
<div className="h-40 bg-slate-50 rounded-lg flex items-center justify-center">
|
|
<Upload className="w-8 h-8 text-slate-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<p className="text-xs text-slate-500 mb-2">Back Image</p>
|
|
{biker.kyc.drivingLicense.backImage ? (
|
|
<img src={biker.kyc.drivingLicense.backImage} alt="License Back" className="w-full h-40 object-cover rounded-lg" />
|
|
) : (
|
|
<div className="h-40 bg-slate-50 rounded-lg flex items-center justify-center">
|
|
<Upload className="w-8 h-8 text-slate-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard title="GPS Device Information" icon={Navigation} editKey="gps" editingSection={editingSection} setEditingSection={setEditingSection} onEdit={() => { setEditForm({ gpsDeviceId: biker.gpsDeviceId, gpsStatus: biker.gpsStatus, lastLocation: biker.lastLocation }); }} editForm={editForm} setEditForm={setEditForm} biker={biker}>
|
|
<div className="space-y-3">
|
|
{editingSection === 'gps' ? (
|
|
<>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Device ID</label>
|
|
<input type="text" value={editForm.gpsDeviceId} onChange={(e) => setEditForm({ ...editForm, gpsDeviceId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Status</label>
|
|
<select value={editForm.gpsStatus} onChange={(e) => setEditForm({ ...editForm, gpsStatus: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="Online">Online</option>
|
|
<option value="Offline">Offline</option>
|
|
<option value="Inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Last Location</label>
|
|
<input type="text" value={editForm.lastLocation} onChange={(e) => setEditForm({ ...editForm, lastLocation: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Device ID</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.gpsDeviceId}</p>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-xs text-slate-500 mb-1">Status</p>
|
|
<span className={`inline-flex items-center gap-1 text-sm font-medium ${biker.gpsStatus === 'Online' ? 'text-green-600' : 'text-red-600'}`}>
|
|
<span className={`w-2 h-2 rounded-full ${biker.gpsStatus === 'Online' ? 'bg-green-500' : 'bg-red-500'}`} />
|
|
{biker.gpsStatus}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-slate-500 mb-1">Last Location</p>
|
|
<p className="text-sm font-medium text-slate-700">{biker.lastLocation}</p>
|
|
</div>
|
|
<a href={`https://www.google.com/maps?q=${biker.lastLocation}`} target="_blank" rel="noopener noreferrer" className="p-1 hover:bg-slate-100 rounded-lg">
|
|
<Map className="w-4 h-4 text-blue-500" />
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'documents' && (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-lg font-semibold text-slate-800">Documents</h3>
|
|
<button onClick={() => setShowDocumentUpload(true)} className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm font-medium hover:bg-emerald-700 flex items-center gap-2">
|
|
<Upload className="w-4 h-4" /> Upload Documents
|
|
</button>
|
|
</div>
|
|
<SectionCard title="KYC Documents" icon={FileCheck}>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-700 mb-3">National ID</p>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm font-medium text-slate-700">NID Front</p>
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">
|
|
<CheckCircle className="w-3 h-3" /> Approved
|
|
</span>
|
|
</div>
|
|
{biker.kyc.nidFrontImage && (
|
|
<img src={biker.kyc.nidFrontImage} alt="NID Front" className="w-full h-40 object-cover rounded-lg" />
|
|
)}
|
|
</div>
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm font-medium text-slate-700">NID Back</p>
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">
|
|
<CheckCircle className="w-3 h-3" /> Approved
|
|
</span>
|
|
</div>
|
|
{biker.kyc.nidBackImage && (
|
|
<img src={biker.kyc.nidBackImage} alt="NID Back" className="w-full h-40 object-cover rounded-lg" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-700 mb-3">Driving License</p>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm font-medium text-slate-700">License Front</p>
|
|
{biker.kyc.drivingLicense.frontImage ? (
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">
|
|
<CheckCircle className="w-3 h-3" /> Uploaded
|
|
</span>
|
|
) : (
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-red-100 text-red-700">Missing</span>
|
|
)}
|
|
</div>
|
|
{biker.kyc.drivingLicense.frontImage && (
|
|
<img src={biker.kyc.drivingLicense.frontImage} alt="License Front" className="w-full h-40 object-cover rounded-lg" />
|
|
)}
|
|
</div>
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm font-medium text-slate-700">License Back</p>
|
|
{biker.kyc.drivingLicense.backImage ? (
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">
|
|
<CheckCircle className="w-3 h-3" /> Uploaded
|
|
</span>
|
|
) : (
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-red-100 text-red-700">Missing</span>
|
|
)}
|
|
</div>
|
|
{biker.kyc.drivingLicense.backImage && (
|
|
<img src={biker.kyc.drivingLicense.backImage} alt="License Back" className="w-full h-40 object-cover rounded-lg" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-700 mb-3">Nominee Documents</p>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<p className="text-xs text-slate-500 mb-2">Nominee Photo</p>
|
|
{biker.kyc.nominee.photo ? (
|
|
<img src={biker.kyc.nominee.photo} alt="Nominee" className="w-full h-32 object-cover rounded-lg" />
|
|
) : (
|
|
<div className="h-32 bg-slate-50 rounded-lg flex items-center justify-center">
|
|
<User className="w-6 h-6 text-slate-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<p className="text-xs text-slate-500 mb-2">Nominee NID Front</p>
|
|
{biker.kyc.nominee.nidFront ? (
|
|
<img src={biker.kyc.nominee.nidFront} alt="NID Front" className="w-full h-32 object-cover rounded-lg" />
|
|
) : (
|
|
<div className="h-32 bg-slate-50 rounded-lg flex items-center justify-center">
|
|
<Image className="w-6 h-6 text-slate-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="border border-slate-200 rounded-lg p-3">
|
|
<p className="text-xs text-slate-500 mb-2">Nominee NID Back</p>
|
|
{biker.kyc.nominee.nidBack ? (
|
|
<img src={biker.kyc.nominee.nidBack} alt="NID Back" className="w-full h-32 object-cover rounded-lg" />
|
|
) : (
|
|
<div className="h-32 bg-slate-50 rounded-lg flex items-center justify-center">
|
|
<Image className="w-6 h-6 text-slate-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard title="KYC Timeline" icon={Clock3}>
|
|
<div className="space-y-0">
|
|
{biker.kyc.kycTimeline.map((item, idx) => (
|
|
<TimelineItem
|
|
key={idx}
|
|
stage={item.stage}
|
|
status={item.status}
|
|
timestamp={item.timestamp}
|
|
adminName={item.adminName}
|
|
notes={item.notes}
|
|
isLast={idx === biker.kyc.kycTimeline.length - 1}
|
|
/>
|
|
))}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'bikes' && (
|
|
<div className="space-y-6">
|
|
{/* Current Bike */}
|
|
<SectionCard title="Current Bike" icon={BikeIcon}>
|
|
<div className="space-y-4">
|
|
{/* Bike info row */}
|
|
<div className="flex items-center gap-4 p-4 bg-slate-50 rounded-lg">
|
|
<div className="w-16 h-16 bg-blue-100 rounded-xl flex items-center justify-center">
|
|
<BikeIcon className="w-8 h-8 text-blue-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="text-lg font-bold text-slate-800">{biker.bikes.current.model}</p>
|
|
<p className="text-sm text-slate-500">{biker.bikes.current.brand} • {biker.bikes.current.year} • {biker.bikes.current.color}</p>
|
|
<p className="text-sm text-slate-400">{biker.bikes.current.plate}</p>
|
|
</div>
|
|
<div>
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-3 py-1.5 rounded-full bg-green-100 text-green-700">
|
|
<Activity className="w-3 h-3" /> Active
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Battery swap info */}
|
|
<div className="flex items-center gap-4 p-3 bg-slate-50 rounded-lg">
|
|
<div className={`w-14 h-14 rounded-full flex items-center justify-center ${biker.bikes.batteries[0]?.percent > 50 ? 'bg-green-100' : biker.bikes.batteries[0]?.percent > 20 ? 'bg-amber-100' : 'bg-red-100'}`}>
|
|
<Battery className={`w-7 h-7 ${biker.bikes.batteries[0]?.percent > 50 ? 'text-green-600' : biker.bikes.batteries[0]?.percent > 20 ? 'text-amber-600' : 'text-red-600'}`} />
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-500">Current Battery</p>
|
|
<p className={`text-xl font-bold ${biker.bikes.batteries[0]?.percent > 50 ? 'text-green-600' : biker.bikes.batteries[0]?.percent > 20 ? 'text-amber-600' : 'text-red-600'}`}>{biker.bikes.batteries[0]?.percent}%</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-500">Battery ID</p>
|
|
<p className="text-sm font-semibold text-slate-700 font-mono">{biker.bikes.batteries[0]?.id}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-500">Location</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.batteries[0]?.location}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Battery info grid */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Battery Capacity</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.current.batteryCapacity}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Max Range</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.current.maxRange}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Odometer</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.current.totalKmRun.toLocaleString()} km</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">VIN</p>
|
|
<p className="text-sm font-semibold text-slate-700 font-mono">{biker.bikes.current.vin}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Last Service</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.current.lastService}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Next Service</p>
|
|
<p className="text-sm font-semibold text-slate-700">{biker.bikes.current.nextService}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Initial Condition Images */}
|
|
{biker.currentRental.initialConditionImages.length > 0 && (
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-700 mb-2">Initial Condition Photos</p>
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{biker.currentRental.initialConditionImages.map((img, idx) => (
|
|
<img key={idx} src={img} alt={`Condition ${idx + 1}`} className="w-full h-20 object-cover rounded-lg" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
|
|
{/* Battery History */}
|
|
<SectionCard title="Battery History" icon={Battery} editKey="batteryHistory" editingSection={editingSection} setEditingSection={setEditingSection} onEdit={() => { setEditForm({}); }} editForm={editForm} setEditForm={setEditForm} biker={biker}>
|
|
<div className="space-y-3">
|
|
{editingSection === 'batteryHistory' ? (
|
|
<>
|
|
{editForm.editingIndex !== undefined ? (
|
|
<>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-xs font-semibold text-slate-500">Editing: {biker.bikes.batteries[editForm.editingIndex]?.name}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Battery Name</label>
|
|
<input type="text" value={editForm.batName || ''} onChange={(e) => setEditForm({ ...editForm, batName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Battery ID</label>
|
|
<input type="text" value={editForm.batId || ''} onChange={(e) => setEditForm({ ...editForm, batId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Charge %</label>
|
|
<input type="number" value={editForm.batPercent || ''} onChange={(e) => setEditForm({ ...editForm, batPercent: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Status</label>
|
|
<select value={editForm.batStatus || ''} onChange={(e) => setEditForm({ ...editForm, batStatus: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="active">Active</option>
|
|
<option value="available">Available</option>
|
|
<option value="charging">Charging</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Location</label>
|
|
<input type="text" value={editForm.batLocation || ''} onChange={(e) => setEditForm({ ...editForm, batLocation: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Swapped At</label>
|
|
<input type="text" value={editForm.batSwappedAt || ''} onChange={(e) => setEditForm({ ...editForm, batSwappedAt: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Odometer</label>
|
|
<input type="number" value={editForm.batOdometer || ''} onChange={(e) => setEditForm({ ...editForm, batOdometer: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
|
<p className="text-sm text-slate-600">Click edit button on existing batteries to update them.</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
) : (
|
|
<>
|
|
{biker.bikes.batteries.map((bat, idx) => (
|
|
<div key={bat.id} className={`p-4 rounded-lg border ${bat.status === 'active' ? 'bg-green-50 border-green-200' : bat.status === 'charging' ? 'bg-amber-50 border-amber-200' : 'bg-slate-50 border-slate-200'}`}>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${bat.percent > 50 ? 'bg-green-100' : bat.percent > 20 ? 'bg-amber-100' : 'bg-red-100'}`}>
|
|
<Battery className={`w-5 h-5 ${bat.percent > 50 ? 'text-green-600' : bat.percent > 20 ? 'text-amber-600' : 'text-red-600'}`} />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-slate-700">{bat.name} <span className="text-xs text-slate-400">({bat.id})</span></p>
|
|
<p className="text-sm text-slate-500">{bat.location} • Swapped: {bat.swappedAt}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`text-lg font-bold ${bat.percent > 50 ? 'text-green-600' : bat.percent > 20 ? 'text-amber-600' : 'text-red-600'}`}>{bat.percent}%</span>
|
|
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${bat.status === 'active' ? 'bg-green-100 text-green-700' : bat.status === 'charging' ? 'bg-amber-100 text-amber-700' : 'bg-blue-100 text-blue-700'}`}>
|
|
{bat.status.charAt(0).toUpperCase() + bat.status.slice(1)}
|
|
</span>
|
|
<button onClick={() => { setEditForm({ batId: bat.id, batName: bat.name, batPercent: bat.percent, batStatus: bat.status, batLocation: bat.location, batSwappedAt: bat.swappedAt, batOdometer: bat.odometer, editingIndex: idx }); setEditingSection('batteryHistory'); }} className="p-1.5 hover:bg-white rounded-lg">
|
|
<Edit className="w-4 h-4 text-slate-400 hover:text-blue-600" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
|
|
{/* Previous Rentals */}
|
|
<SectionCard title="Previous Rentals" icon={History}>
|
|
<div className="space-y-3">
|
|
{biker.previousRentals.length > 0 ? (
|
|
<>
|
|
<div className="flex justify-end mb-2">
|
|
<button onClick={() => { setEditingSection('previousRentals'); setEditForm({}); }} className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-xs font-medium hover:bg-blue-700 flex items-center gap-1">
|
|
<Plus className="w-3 h-3" /> Add Rental
|
|
</button>
|
|
</div>
|
|
{biker.previousRentals.map((rental, idx) => (
|
|
<div key={rental.id} className="p-4 rounded-lg border border-slate-200 bg-slate-50">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div>
|
|
<p className="font-medium text-slate-700">{rental.evModel}</p>
|
|
<p className="text-xs text-slate-400">{rental.plate} • {rental.id}</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${rental.status === 'Completed' ? 'bg-blue-100 text-blue-700' : rental.status === 'Cancelled' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-500'}`}>
|
|
{rental.status}
|
|
</span>
|
|
<button onClick={() => { setEditForm({ rentalEvModel: rental.evModel, rentalPlate: rental.plate, rentalPlanName: rental.planName, rentalStartDate: rental.startDate, rentalEndDate: rental.endDate || '', rentalTotalAmount: rental.totalAmount, rentalStatus: rental.status, rentalPenalty: rental.penalty, editingIndex: idx }); setEditingSection('previousRentals'); }} className="p-1.5 hover:bg-white rounded-lg">
|
|
<Edit className="w-4 h-4 text-slate-400 hover:text-blue-600" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{editingSection === 'previousRentals' && editForm.editingIndex === idx ? (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">EV Model</label>
|
|
<input type="text" value={editForm.rentalEvModel || ''} onChange={(e) => setEditForm({ ...editForm, rentalEvModel: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Plate</label>
|
|
<input type="text" value={editForm.rentalPlate || ''} onChange={(e) => setEditForm({ ...editForm, rentalPlate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Plan Name</label>
|
|
<select value={editForm.rentalPlanName || ''} onChange={(e) => setEditForm({ ...editForm, rentalPlanName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="">Select Plan</option>
|
|
<option value="Daily Rental">Daily Rental</option>
|
|
<option value="Weekly Rental">Weekly Rental</option>
|
|
<option value="Monthly Rental">Monthly Rental</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Status</label>
|
|
<select value={editForm.rentalStatus || ''} onChange={(e) => setEditForm({ ...editForm, rentalStatus: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs bg-white">
|
|
<option value="Completed">Completed</option>
|
|
<option value="Active">Active</option>
|
|
<option value="Cancelled">Cancelled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Start Date</label>
|
|
<input type="date" value={editForm.rentalStartDate || ''} onChange={(e) => setEditForm({ ...editForm, rentalStartDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">End Date</label>
|
|
<input type="date" value={editForm.rentalEndDate || ''} onChange={(e) => setEditForm({ ...editForm, rentalEndDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Total Amount</label>
|
|
<input type="number" value={editForm.rentalTotalAmount || ''} onChange={(e) => setEditForm({ ...editForm, rentalTotalAmount: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Penalty</label>
|
|
<input type="number" value={editForm.rentalPenalty || 0} onChange={(e) => setEditForm({ ...editForm, rentalPenalty: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-xs" />
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
|
<div className="p-2 bg-white rounded-lg">
|
|
<p className="text-xs text-slate-500">Plan</p>
|
|
<p className="text-xs font-medium text-slate-700">{rental.planName}</p>
|
|
</div>
|
|
<div className="p-2 bg-white rounded-lg">
|
|
<p className="text-xs text-slate-500">Start</p>
|
|
<p className="text-xs font-medium text-slate-700">{rental.startDate}</p>
|
|
</div>
|
|
<div className="p-2 bg-white rounded-lg">
|
|
<p className="text-xs text-slate-500">End</p>
|
|
<p className="text-xs font-medium text-slate-700">{rental.endDate || '-'}</p>
|
|
</div>
|
|
<div className="p-2 bg-white rounded-lg">
|
|
<p className="text-xs text-slate-500">Total</p>
|
|
<p className="text-xs font-semibold text-green-600">৳{rental.totalAmount.toLocaleString()}{rental.penalty > 0 ? <span className="text-red-400 ml-1">(+৳{rental.penalty})</span> : null}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</>
|
|
) : (
|
|
<p className="text-sm text-slate-400">No previous rentals</p>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'rent' && (
|
|
<div className="space-y-6">
|
|
{/* Financial Summary */}
|
|
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
|
|
<div className="bg-green-50 p-4 rounded-xl border border-green-200">
|
|
<p className="text-xs text-green-600">Total Paid</p>
|
|
<p className="text-xl font-bold text-green-700">৳{biker.totalPaid.toLocaleString()}</p>
|
|
</div>
|
|
<div className="bg-red-50 p-4 rounded-xl border border-red-200">
|
|
<p className="text-xs text-red-600">Total Due</p>
|
|
<p className="text-xl font-bold text-red-700">৳{biker.totalDue.toLocaleString()}</p>
|
|
</div>
|
|
<div className="bg-amber-50 p-4 rounded-xl border border-amber-200">
|
|
<p className="text-xs text-amber-600">Pending</p>
|
|
<p className="text-xl font-bold text-amber-700">৳{biker.pendingAmount.toLocaleString()}</p>
|
|
</div>
|
|
<div className="bg-blue-50 p-4 rounded-xl border border-blue-200">
|
|
<p className="text-xs text-blue-600">Deposit</p>
|
|
<p className="text-xl font-bold text-blue-700">৳{biker.depositAmount.toLocaleString()}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-4 rounded-xl border border-slate-200">
|
|
<p className="text-xs text-slate-600">Penalties Paid</p>
|
|
<p className="text-xl font-bold text-slate-700">৳{biker.totalPenaltiesPaid.toLocaleString()}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Wallet */}
|
|
<SectionCard title="Wallet" icon={Wallet}>
|
|
<div className="text-center py-4">
|
|
<p className="text-xs text-slate-500 mb-1">Balance</p>
|
|
<p className="text-4xl font-extrabold text-green-600">৳{biker.walletBalance.toLocaleString()}</p>
|
|
<p className="text-xs text-slate-400 mt-2">Last updated: March 28, 2024</p>
|
|
</div>
|
|
</SectionCard>
|
|
|
|
{/* Payment History */}
|
|
<SectionCard title="Payment History" icon={Banknote}>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-slate-50">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-500">Date</th>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-500">Type</th>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-500">Amount</th>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-500">Method</th>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-500">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{paginatedTransactions.map(txn => (
|
|
<tr key={txn.id} className="hover:bg-slate-50">
|
|
<td className="px-4 py-2 text-sm text-slate-600">{txn.date}</td>
|
|
<td className="px-4 py-2 text-sm text-slate-700 capitalize">{txn.type}</td>
|
|
<td className="px-4 py-2 text-sm font-semibold text-green-600">৳{Math.abs(txn.amount).toLocaleString()}</td>
|
|
<td className="px-4 py-2 text-sm text-slate-600 capitalize">{txn.method}</td>
|
|
<td className="px-4 py-2 text-xs">
|
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-100 text-green-700">{txn.status}</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className="flex items-center justify-between mt-4 pt-4 border-t border-slate-100">
|
|
<p className="text-sm text-slate-500">Showing {((transactionPage - 1) * perPage) + 1}-{Math.min(transactionPage * perPage, biker.transactions.length)} of {biker.transactions.length}</p>
|
|
<div className="flex gap-2">
|
|
<button onClick={() => setTransactionPage(p => Math.max(1, p - 1))} disabled={transactionPage === 1} className="px-3 py-1.5 border border-slate-200 rounded-lg text-sm disabled:opacity-50 hover:bg-slate-50">Previous</button>
|
|
<button onClick={() => setTransactionPage(p => Math.min(totalTransactionPages, p + 1))} disabled={transactionPage === totalTransactionPages} className="px-3 py-1.5 border border-slate-200 rounded-lg text-sm disabled:opacity-50 hover:bg-slate-50">Next</button>
|
|
</div>
|
|
</div>
|
|
</SectionCard>
|
|
|
|
{/* Rent History Table */}
|
|
<SectionCard title="Rent History" icon={History}>
|
|
<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">Date</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Bike</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Plan</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Duration</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Rent</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Penalty</th>
|
|
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Total</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">
|
|
{biker.currentRental && (
|
|
<tr className="bg-green-50">
|
|
<td className="px-3 py-2.5 text-xs text-slate-600">{biker.currentRental.startDate}</td>
|
|
<td className="px-3 py-2.5">
|
|
<p className="text-xs font-medium text-slate-700">{biker.currentRental.evModel}</p>
|
|
<p className="text-xs text-slate-400">{biker.currentRental.plate}</p>
|
|
</td>
|
|
<td className="px-3 py-2.5 text-xs text-slate-600">{biker.currentRental.planName}</td>
|
|
<td className="px-3 py-2.5 text-xs text-slate-600 capitalize">{biker.currentRental.subscriptionType}</td>
|
|
<td className="px-3 py-2.5 text-xs font-medium text-slate-700">৳{(biker.currentRental.monthlyRate || biker.currentRental.weeklyRate || biker.currentRental.dailyRate).toLocaleString()}</td>
|
|
<td className="px-3 py-2.5 text-xs text-slate-400">-</td>
|
|
<td className="px-3 py-2.5 text-xs font-semibold text-green-600">৳{biker.currentRental.totalAmount.toLocaleString()}</td>
|
|
<td className="px-3 py-2.5">
|
|
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">Active</span>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{biker.previousRentals.map(rental => (
|
|
<tr key={rental.id} className="hover:bg-slate-50">
|
|
<td className="px-3 py-2.5 text-xs text-slate-600">{rental.startDate}</td>
|
|
<td className="px-3 py-2.5">
|
|
<p className="text-xs font-medium text-slate-700">{rental.evModel}</p>
|
|
<p className="text-xs text-slate-400">{rental.plate}</p>
|
|
</td>
|
|
<td className="px-3 py-2.5 text-xs text-slate-600">{rental.planName}</td>
|
|
<td className="px-3 py-2.5 text-xs text-slate-600">{rental.endDate || '-'}</td>
|
|
<td className="px-3 py-2.5 text-xs font-medium text-slate-700">৳{((rental.monthlyRate || rental.weeklyRate) || 0).toLocaleString()}</td>
|
|
<td className="px-3 py-2.5 text-xs text-red-500">{rental.penalty > 0 ? `৳${rental.penalty.toLocaleString()}` : '-'}</td>
|
|
<td className="px-3 py-2.5 text-xs font-semibold text-green-600">৳{rental.totalAmount.toLocaleString()}</td>
|
|
<td className="px-3 py-2.5">
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${rental.status === 'Completed' ? 'bg-blue-100 text-blue-700' : rental.status === 'Cancelled' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-500'}`}>
|
|
{rental.status}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className="flex items-center justify-between mt-4 pt-4 border-t border-slate-100">
|
|
<p className="text-sm text-slate-500">Showing page {rentPage} of {totalRentPages}</p>
|
|
<div className="flex gap-2">
|
|
<button onClick={() => setRentPage(p => Math.max(1, p - 1))} disabled={rentPage === 1} className="px-3 py-1.5 border border-slate-200 rounded-lg text-sm disabled:opacity-50 hover:bg-slate-50">Previous</button>
|
|
<button onClick={() => setRentPage(p => Math.min(totalRentPages, p + 1))} disabled={rentPage === totalRentPages} className="px-3 py-1.5 border border-slate-200 rounded-lg text-sm disabled:opacity-50 hover:bg-slate-50">Next</button>
|
|
</div>
|
|
</div>
|
|
</SectionCard>
|
|
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'activity' && (
|
|
<div className="space-y-6">
|
|
<SectionCard title="Activity Log" icon={Activity}>
|
|
<div className="space-y-3">
|
|
{biker.activityLog.map(log => {
|
|
const Icon = log.icon;
|
|
return (
|
|
<div key={log.id} className="flex items-start gap-3 p-3 bg-slate-50 rounded-lg">
|
|
<div className={`p-2 rounded-lg ${activityTypeColors[log.type] || 'bg-slate-100 text-slate-600'}`}>
|
|
<Icon className="w-4 h-4" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="text-sm font-medium text-slate-700">{log.action}</p>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<span className="text-xs text-slate-400">{log.timestamp}</span>
|
|
{log.adminName && <span className="text-xs text-slate-400">• {log.adminName}</span>}
|
|
</div>
|
|
{log.details && <p className="text-xs text-slate-500 mt-1">{log.details}</p>}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'messages' && (
|
|
<div className="space-y-6">
|
|
<SectionCard title="SMS / Message History" icon={MessageSquareDashed}>
|
|
<div className="space-y-3 max-h-96 overflow-y-auto mb-4">
|
|
{biker.messageHistory.filter(m => m.channel === 'sms' || m.channel === 'in_app').map(msg => (
|
|
<div key={msg.id} className={`p-3 rounded-lg ${msg.direction === 'sent' ? 'bg-blue-50 border border-blue-100' : 'bg-green-50 border border-green-100'}`}>
|
|
<div className="flex items-center justify-between mb-1">
|
|
<div className="flex items-center gap-2">
|
|
{msg.direction === 'sent' ? <SendHorizontal className="w-3 h-3 text-blue-500" /> : <PhoneIncoming className="w-3 h-3 text-green-500" />}
|
|
<span className="text-xs font-medium text-slate-600">{msg.direction === 'sent' ? 'Sent' : 'Received'}</span>
|
|
<span className="text-xs text-slate-400 capitalize">({msg.channel})</span>
|
|
</div>
|
|
<span className="text-xs text-slate-400">{msg.timestamp}</span>
|
|
</div>
|
|
<p className="text-sm text-slate-700">{msg.message}</p>
|
|
<span className={`inline-flex items-center gap-1 text-xs mt-2 ${msg.status === 'Delivered' || msg.status === 'Received' ? 'text-green-600' : 'text-slate-500'}`}>
|
|
{msg.status === 'Delivered' && <Check className="w-3 h-3" />}
|
|
{msg.status}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<button onClick={() => setShowMessageModal(true)} className="w-full py-2 border border-dashed border-slate-300 rounded-lg text-sm text-slate-500 hover:border-accent hover:text-accent flex items-center justify-center gap-2">
|
|
<Send className="w-4 h-4" /> Send New Message
|
|
</button>
|
|
</SectionCard>
|
|
|
|
<SectionCard title="Push Notification History" icon={BellRing}>
|
|
<div className="space-y-3">
|
|
{biker.messageHistory.filter(m => m.channel === 'push').length > 0 ? (
|
|
biker.messageHistory.filter(m => m.channel === 'push').map(msg => (
|
|
<div key={msg.id} className="p-3 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="text-xs font-medium text-blue-600">Push Notification</span>
|
|
<span className="text-xs text-slate-400">{msg.timestamp}</span>
|
|
</div>
|
|
<p className="text-sm text-slate-700">{msg.message}</p>
|
|
<span className={`inline-flex items-center gap-1 text-xs mt-2 ${msg.status === 'Delivered' ? 'text-green-600' : 'text-slate-500'}`}>
|
|
{msg.status === 'Delivered' && <Check className="w-3 h-3" />}
|
|
{msg.status}
|
|
</span>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-sm text-slate-400">No push notifications sent</p>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'notes' && (
|
|
<div className="space-y-6">
|
|
<SectionCard title="Notes" icon={ClipboardList}>
|
|
<div className="space-y-3 mb-4">
|
|
{biker.notes.length > 0 ? (
|
|
biker.notes.map(note => (
|
|
<div key={note.id} className="p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-sm text-slate-700">{note.text}</p>
|
|
<div className="flex items-center gap-2 mt-2">
|
|
<span className="text-xs text-slate-400">{note.createdAt}</span>
|
|
<span className="text-xs text-slate-400">•</span>
|
|
<span className="text-xs text-slate-500">{note.createdBy}</span>
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-sm text-slate-400">No notes yet</p>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={newNote}
|
|
onChange={(e) => setNewNote(e.target.value)}
|
|
placeholder="Add a note..."
|
|
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
/>
|
|
<button onClick={handleAddNote} className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark">
|
|
Add Note
|
|
</button>
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'damage' && (
|
|
<div className="space-y-6">
|
|
<SectionCard title="Report Damage" icon={AlertOctagon}>
|
|
<button onClick={() => setShowDamageModal(true)} className="w-full py-3 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 flex items-center justify-center gap-2">
|
|
<Plus className="w-4 h-4" /> Report New Damage
|
|
</button>
|
|
</SectionCard>
|
|
|
|
<SectionCard title="Damage History" icon={ClipboardList}>
|
|
{biker.damageReports.length > 0 ? (
|
|
<div className="space-y-4">
|
|
{biker.damageReports.map(report => (
|
|
<div key={report.id} className="border border-slate-200 rounded-lg p-4">
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div>
|
|
<p className="font-medium text-slate-700">{report.damageType}</p>
|
|
<p className="text-xs text-slate-400">{report.date} • {report.rentalId}</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${severityColors[report.severity]}`}>
|
|
{report.severity}
|
|
</span>
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[report.status]}`}>
|
|
{report.status === 'Resolved' && <CheckCircle className="w-3 h-3" />}
|
|
{report.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-slate-600 mb-3">{report.description}</p>
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-3">
|
|
<div className="bg-slate-50 p-2 rounded-lg">
|
|
<p className="text-xs text-slate-500">Bike</p>
|
|
<p className="text-sm font-medium text-slate-700">{report.bike}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-2 rounded-lg">
|
|
<p className="text-xs text-slate-500">Location</p>
|
|
<p className="text-sm font-medium text-slate-700">{report.location}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-2 rounded-lg">
|
|
<p className="text-xs text-slate-500">Estimated</p>
|
|
<p className="text-sm font-semibold text-amber-600">৳{report.estimatedCost}</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-2 rounded-lg">
|
|
<p className="text-xs text-slate-500">Actual</p>
|
|
<p className="text-sm font-semibold text-green-600">৳{report.actualCost || '-'}</p>
|
|
</div>
|
|
</div>
|
|
{report.resolution && (
|
|
<div className="bg-green-50 p-2 rounded-lg">
|
|
<p className="text-xs text-green-600">Resolution</p>
|
|
<p className="text-sm text-green-700">{report.resolution}</p>
|
|
</div>
|
|
)}
|
|
{report.images.length > 0 && (
|
|
<div className="flex gap-2 mt-3 overflow-x-auto">
|
|
{report.images.map((img, idx) => (
|
|
<img key={idx} src={img} alt={`Damage ${idx + 1}`} className="w-20 h-20 object-cover rounded-lg flex-shrink-0" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-slate-400">No damage reports</p>
|
|
)}
|
|
</SectionCard>
|
|
|
|
<SectionCard title="Damage Photos" icon={Camera}>
|
|
<div className="space-y-4">
|
|
{biker.damageReports.filter(r => r.images.length > 0).map(report => (
|
|
<div key={report.id}>
|
|
<p className="text-sm font-medium text-slate-600 mb-2">{report.date} - {report.damageType}</p>
|
|
<div className="flex gap-2 overflow-x-auto pb-2">
|
|
{report.images.map((img, idx) => (
|
|
<img key={idx} src={img} alt={`Damage ${report.id}-${idx}`} className="w-32 h-32 object-cover rounded-lg flex-shrink-0" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{biker.damageReports.filter(r => r.images.length > 0).length === 0 && (
|
|
<p className="text-sm text-slate-400">No damage photos</p>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{showMessageModal && (
|
|
<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 flex flex-col">
|
|
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
|
<h3 className="font-semibold text-slate-800">Send Message to {biker.kyc.fullName}</h3>
|
|
<button onClick={() => setShowMessageModal(false)} className="text-slate-400 hover:text-slate-600">
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="border-b border-slate-100">
|
|
<div className="flex">
|
|
<button onClick={() => setMessageTab('sms')} className={`flex-1 px-4 py-3 text-sm font-medium ${messageTab === 'sms' ? 'border-b-2 border-accent text-accent' : 'text-slate-500'}`}>
|
|
SMS
|
|
</button>
|
|
<button onClick={() => setMessageTab('push')} className={`flex-1 px-4 py-3 text-sm font-medium ${messageTab === 'push' ? 'border-b-2 border-accent text-accent' : 'text-slate-500'}`}>
|
|
Push Notification
|
|
</button>
|
|
<button onClick={() => setMessageTab('template')} className={`flex-1 px-4 py-3 text-sm font-medium ${messageTab === 'template' ? 'border-b-2 border-accent text-accent' : 'text-slate-500'}`}>
|
|
Template
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="p-4 flex-1 overflow-y-auto">
|
|
{messageTab === 'template' ? (
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-slate-500 mb-3">Select a template to use:</p>
|
|
{smsTemplates.map(tmpl => (
|
|
<button
|
|
key={tmpl.id}
|
|
onClick={() => handleSendTemplate(tmpl)}
|
|
className="w-full text-left p-3 border border-slate-200 rounded-lg hover:border-accent hover:bg-accent/5 transition-colors"
|
|
>
|
|
<p className="text-sm font-medium text-slate-700">{tmpl.label}</p>
|
|
<p className="text-xs text-slate-500 mt-1">{tmpl.message.length > 60 ? tmpl.message.slice(0, 60) + '...' : tmpl.message}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-2 block">Message</label>
|
|
<textarea
|
|
value={messageText}
|
|
onChange={(e) => setMessageText(e.target.value)}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
rows={5}
|
|
placeholder="Type your message..."
|
|
/>
|
|
<p className="text-xs text-slate-400 mt-1">{messageText.length} characters</p>
|
|
</div>
|
|
<div className="bg-slate-50 p-3 rounded-lg">
|
|
<p className="text-xs text-slate-500">Recipient: {biker.kyc.phone}</p>
|
|
<p className="text-xs text-slate-500">Channel: {messageTab === 'sms' ? 'SMS' : 'Push Notification'}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 border-t border-slate-100 flex justify-between items-center">
|
|
<div className="text-xs text-slate-400">
|
|
Recent: {biker.messageHistory.filter(m => m.direction === 'sent').slice(-2).map(m => m.message.slice(0, 30) + '...').join(', ')}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<button onClick={() => setShowMessageModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm">Cancel</button>
|
|
<button onClick={handleSendMessage} disabled={!messageText.trim()} className="px-4 py-2 bg-accent text-white rounded-lg text-sm disabled:opacity-50 flex items-center gap-2">
|
|
<Send className="w-4 h-4" /> Send
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{showDamageModal && (
|
|
<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 flex flex-col">
|
|
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
|
<h3 className="font-semibold text-slate-800">Report Damage</h3>
|
|
<button onClick={() => setShowDamageModal(false)} className="text-slate-400 hover:text-slate-600">
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="p-4 flex-1 overflow-y-auto space-y-4">
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Bike *</label>
|
|
<select
|
|
value={damageForm.bike}
|
|
onChange={(e) => setDamageForm({ ...damageForm, bike: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
>
|
|
<option value="">Select bike</option>
|
|
<option value="current">{biker.currentRental.evModel} ({biker.currentRental.plate})</option>
|
|
{biker.previousRentals.map(r => (
|
|
<option key={r.id} value={r.id}>{r.evModel} ({r.plate})</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Damage Type *</label>
|
|
<select
|
|
value={damageForm.damageType}
|
|
onChange={(e) => setDamageForm({ ...damageForm, damageType: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
>
|
|
<option value="">Select type</option>
|
|
{damageTypes.map(t => (
|
|
<option key={t} value={t}>{t}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Description *</label>
|
|
<textarea
|
|
value={damageForm.description}
|
|
onChange={(e) => setDamageForm({ ...damageForm, description: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
rows={3}
|
|
placeholder="Describe the damage..."
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Severity</label>
|
|
<select
|
|
value={damageForm.severity}
|
|
onChange={(e) => setDamageForm({ ...damageForm, severity: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
>
|
|
<option value="">Select severity</option>
|
|
{severityLevels.map(s => (
|
|
<option key={s} value={s}>{s}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Estimated Cost (৳)</label>
|
|
<input
|
|
type="number"
|
|
value={damageForm.estimatedCost}
|
|
onChange={(e) => setDamageForm({ ...damageForm, estimatedCost: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
placeholder="0"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Location</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={damageForm.location}
|
|
onChange={(e) => setDamageForm({ ...damageForm, location: e.target.value })}
|
|
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
|
placeholder="Enter location"
|
|
/>
|
|
<button className="px-3 py-2 border border-slate-200 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-1">
|
|
<MapPin className="w-4 h-4" /> Current
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Damage Images</label>
|
|
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent transition-colors cursor-pointer">
|
|
<Upload className="w-8 h-8 text-slate-300 mx-auto mb-2" />
|
|
<p className="text-sm text-slate-500">Click to upload or drag and drop</p>
|
|
<p className="text-xs text-slate-400 mt-1">PNG, JPG up to 10MB</p>
|
|
</div>
|
|
{damageForm.images.length > 0 && (
|
|
<div className="flex gap-2 mt-3">
|
|
{damageForm.images.map((img, idx) => (
|
|
<img key={idx} src={img} alt={`Upload ${idx}`} className="w-16 h-16 object-cover rounded-lg" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
|
<button onClick={() => setShowDamageModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm">Cancel</button>
|
|
<button onClick={handleSubmitDamage} className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700 flex items-center gap-2">
|
|
<AlertOctagon className="w-4 h-4" /> Submit Report
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{showDocumentUpload && (
|
|
<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 flex flex-col">
|
|
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
|
<h3 className="font-semibold text-slate-800">Upload Documents</h3>
|
|
<button onClick={() => setShowDocumentUpload(false)} className="text-slate-400 hover:text-slate-600">
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="p-4 flex-1 overflow-y-auto space-y-4">
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Document Type</label>
|
|
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
|
<option value="">Select type</option>
|
|
<option value="nid">National ID</option>
|
|
<option value="license">Driving License</option>
|
|
<option value="nominee_nid">Nominee NID</option>
|
|
<option value="nominee_photo">Nominee Photo</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Document Title</label>
|
|
<input type="text" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Enter document title" />
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Upload File</label>
|
|
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-emerald-400 transition-colors cursor-pointer">
|
|
<Upload className="w-8 h-8 text-slate-300 mx-auto mb-2" />
|
|
<p className="text-sm text-slate-500">Click to upload or drag and drop</p>
|
|
<p className="text-xs text-slate-400 mt-1">PNG, JPG, PDF up to 10MB</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
|
<button onClick={() => setShowDocumentUpload(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm">Cancel</button>
|
|
<button onClick={() => { toast.success('Document uploaded'); setShowDocumentUpload(false); }} className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-700 flex items-center gap-2">
|
|
<Upload className="w-4 h-4" /> Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |