diff --git a/next.config.ts b/next.config.ts index e9ffa30..36f5f2f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,15 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'images.unsplash.com', + pathname: '/**', + }, + ], + }, }; -export default nextConfig; +export default nextConfig; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 33d9ead..cec640f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "jaiben-ui", "version": "0.1.0", "dependencies": { + "lucide-react": "^1.8.0", "next": "16.2.4", "react": "19.2.4", "react-dom": "19.2.4" @@ -4858,6 +4859,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz", + "integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index 4d8f2c2..5e649f2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "lucide-react": "^1.8.0", "next": "16.2.4", "react": "19.2.4", "react-dom": "19.2.4" diff --git a/src/app/admin/bikers/[id]/page.tsx b/src/app/admin/bikers/[id]/page.tsx new file mode 100644 index 0000000..99dd444 --- /dev/null +++ b/src/app/admin/bikers/[id]/page.tsx @@ -0,0 +1,505 @@ +'use client'; + +import { useState, use } 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, Edit, Trash2, ArrowLeft, PhoneCall, MessageCircle, CheckCircle, XCircle, + AlertTriangle, DollarSign, Wallet, Bike as BikeIcon, Wrench, Ban, MoreHorizontal, Copy, + ExternalLink, Download, Upload, Bell, MessageSquare, Send, RefreshCcw +} from 'lucide-react'; + +interface DrivingLicense { + number: string; + issueDate: string; + expiryDate: string; + class: string; + status: 'valid' | 'expired' | 'suspended'; +} + +interface Document { + type: 'nid' | 'passport' | 'driving_license' | 'other'; + number: string; + verified: boolean; +} + +interface Biker { + id: string; + name: string; + email: string; + phone: string; + alternatePhone?: string; + status: 'active' | 'pending' | 'inactive' | 'blocked'; + createdAt: string; + location: string; + address: string; + dateOfBirth: string; + gender: 'male' | 'female' | 'other'; + bloodGroup: string; + occupation: string; + emergencyContact: string; + emergencyPhone: string; + emergencyRelation: string; + drivingLicense: DrivingLicense; + documents: Document[]; + gpsDeviceId?: string; + gpsPhone?: string; + totalRides: number; + totalDistance: number; + totalRideHours: number; + totalSpent: number; + currentBike?: string; + bikePlate?: string; + depositPaid: number; + walletBalance: number; + rating: number; + totalRatings: number; + responseTime: number; + cancellationRate: number; + kycStatus: 'verified' | 'pending' | 'rejected'; + membershipType: 'free' | 'basic' | 'premium' | 'vip'; + insuranceStatus: 'active' | 'expired' | 'none'; + insuranceExpiry?: string; + notes?: string; + lastRideAt?: string; + firstRideAt?: string; + joinedFrom: string; +} + +const mockBikers: Biker[] = [ + { + id: 'B001', name: 'Rahim Ahmed', email: 'rahim@email.com', phone: '01712345678', alternatePhone: '01912345678', + status: 'active', createdAt: '2024-01-15', location: 'Gulshan, Dhaka', + address: 'House 12, Road 5, Gulshan 1, Dhaka 1212', dateOfBirth: '1995-03-15', gender: 'male', bloodGroup: 'O+', + occupation: 'Student', emergencyContact: 'Karim Ahmed', emergencyPhone: '01712345679', emergencyRelation: 'Brother', + drivingLicense: { number: 'DL2024001234', issueDate: '2023-01-15', expiryDate: '2033-01-14', class: 'M', status: 'valid' }, + documents: [{ type: 'nid', number: '1234567890', verified: true }], + gpsDeviceId: 'GP-001234', gpsPhone: '01712345678', + totalRides: 156, totalDistance: 2340, totalRideHours: 468, totalSpent: 54500, + currentBike: 'Etron ET50', bikePlate: 'Dhaka Metro Cha-1234', depositPaid: 5000, walletBalance: 1250, + rating: 4.8, totalRatings: 156, responseTime: 2.5, cancellationRate: 2.1, + kycStatus: 'verified', membershipType: 'premium', insuranceStatus: 'active', insuranceExpiry: '2025-01-14', + notes: 'Reliable rider, frequently uses premium service', lastRideAt: '2024-03-21', firstRideAt: '2024-01-15', joinedFrom: 'App Store', + }, + { + id: 'B002', name: 'Karim Hasan', email: 'karim@email.com', phone: '01712345679', + status: 'active', createdAt: '2024-02-20', location: 'Banani, Dhaka', + address: 'House 5, Road 11, Banani, Dhaka 1213', dateOfBirth: '1990-07-22', gender: 'male', bloodGroup: 'B+', + occupation: 'Business', emergencyContact: 'Rahim Hasan', emergencyPhone: '01712345678', emergencyRelation: 'Friend', + drivingLicense: { number: 'DL2024005678', issueDate: '2023-06-20', expiryDate: '2033-06-19', class: 'M', status: 'valid' }, + documents: [{ type: 'nid', number: '1234567891', verified: true }], + gpsDeviceId: 'GP-001235', + totalRides: 89, totalDistance: 1335, totalRideHours: 267, totalSpent: 31200, + currentBike: 'Yadea DT3', bikePlate: 'Dhaka Metro Cha-5678', depositPaid: 5000, walletBalance: 800, + rating: 4.5, totalRatings: 89, responseTime: 3.2, cancellationRate: 4.5, + kycStatus: 'verified', membershipType: 'basic', insuranceStatus: 'active', insuranceExpiry: '2025-02-19', joinedFrom: 'Website', + }, + { + id: 'B003', name: 'Jamal Mahmud', email: 'jamal@email.com', phone: '01712345680', + status: 'pending', createdAt: '2024-03-18', location: 'Uttara, Dhaka', + address: 'Sector 10, Uttara, Dhaka 1230', dateOfBirth: '1988-11-05', gender: 'male', bloodGroup: 'A+', + occupation: 'Job Holder', emergencyContact: 'Mahmud Ali', emergencyPhone: '01712345681', emergencyRelation: 'Father', + drivingLicense: { number: '', issueDate: '', expiryDate: '', class: 'M', status: 'valid' }, + documents: [{ type: 'nid', number: '1234567892', verified: false }], + totalRides: 0, totalDistance: 0, totalRideHours: 0, totalSpent: 0, + depositPaid: 0, walletBalance: 0, rating: 0, totalRatings: 0, responseTime: 0, cancellationRate: 0, + kycStatus: 'pending', membershipType: 'free', insuranceStatus: 'none', joinedFrom: 'Referral', + }, + { + id: 'B004', name: 'Ali Rahman', email: 'ali@email.com', phone: '01712345681', + status: 'active', createdAt: '2023-12-01', location: 'Dhanmondi, Dhaka', + address: 'House 27, Road 8, Dhanmondi, Dhaka 1205', dateOfBirth: '1992-06-10', gender: 'male', bloodGroup: 'AB+', + occupation: 'Engineer', emergencyContact: 'Rahman Ali', emergencyPhone: '01712345682', emergencyRelation: 'Brother', + drivingLicense: { number: 'DL202301234', issueDate: '2023-05-10', expiryDate: '2033-05-09', class: 'M', status: 'valid' }, + documents: [{ type: 'nid', number: '1234567893', verified: true }], + gpsDeviceId: 'GP-001236', + totalRides: 234, totalDistance: 3510, totalRideHours: 702, totalSpent: 81900, + currentBike: 'AIMA Lightning', bikePlate: 'Dhaka Metro Cha-9012', depositPaid: 5000, walletBalance: 2100, + rating: 4.9, totalRatings: 234, responseTime: 1.8, cancellationRate: 1.2, + kycStatus: 'verified', membershipType: 'vip', insuranceStatus: 'active', insuranceExpiry: '2024-12-01', joinedFrom: 'Facebook', + }, + { + id: 'B005', name: 'Mostafa Kamal', email: 'mostafa@email.com', phone: '01712345682', + status: 'inactive', createdAt: '2023-08-15', location: 'Mirpur, Dhaka', + address: 'Mirpur 1, Dhaka 1216', dateOfBirth: '1997-02-28', gender: 'male', bloodGroup: 'O-', + occupation: 'Teacher', emergencyContact: 'Kamal Mostafa', emergencyPhone: '01712345683', emergencyRelation: 'Father', + drivingLicense: { number: 'DL2022009876', issueDate: '2022-08-15', expiryDate: '2024-08-14', class: 'M', status: 'expired' }, + documents: [{ type: 'nid', number: '1234567894', verified: true }], + totalRides: 45, totalDistance: 675, totalRideHours: 135, totalSpent: 15750, + depositPaid: 5000, walletBalance: 0, rating: 3.8, totalRatings: 45, responseTime: 4.5, cancellationRate: 8.9, + kycStatus: 'verified', membershipType: 'free', insuranceStatus: 'expired', insuranceExpiry: '2024-01-14', joinedFrom: 'App Store', + }, +]; + +const statusColors: Record = { + 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', +}; + +const kycColors: Record = { + verified: 'bg-green-100 text-green-700', + pending: 'bg-amber-100 text-amber-700', + rejected: 'bg-red-100 text-red-700', +}; + +const membershipColors: Record = { + free: 'bg-slate-100 text-slate-600', + basic: 'bg-blue-100 text-blue-700', + premium: 'bg-purple-100 text-purple-700', + vip: 'bg-amber-100 text-amber-700', +}; + +interface PageProps { + params: Promise<{ id: string }>; +} + +export default function BikerDetailPage({ params }: PageProps) { + const resolvedParams = use(params); + const router = useRouter(); + const [activeTab, setActiveTab] = useState('personal'); + + const biker = mockBikers.find(b => b.id === resolvedParams.id) || mockBikers[0]; + + const tabs = [ + { id: 'personal', label: 'Personal', icon: User }, + { id: 'license', label: 'License & GPS', icon: Car }, + { id: 'documents', label: 'Documents', icon: FileText }, + { id: 'reviews', label: 'Reviews', icon: MessageCircle }, + { id: 'stats', label: 'Statistics', icon: TrendingUp }, + { id: 'account', label: 'Account', icon: CreditCard }, + { id: 'activity', label: 'Activity', icon: Activity }, + ]; + + return ( +
+
+ + + + +
+

Biker Details

+

View and manage biker profile

+
+
+ + +
+
+ +
+
+
+ {biker.name.charAt(0)} +
+
+
+

{biker.name}

+ + {biker.status === 'active' && } + {biker.status === 'pending' && } + {biker.status === 'blocked' && } + {biker.status} + +
+

ID: {biker.id} • {biker.location}

+
+ + {biker.kycStatus === 'verified' && } + {biker.kycStatus === 'pending' && } + KYC: {biker.kycStatus} + + + {biker.membershipType} + + {biker.rating > 0 && ( + + {biker.rating} ({biker.totalRatings}) + + )} +
+
+
+ + +
+
+ +
+ +
+
+ +
+ {activeTab === 'personal' && ( +
+
+
+

+ Personal Information +

+
+ + + + + + + + +
+
+

Address

+

{biker.address}

+
+
+
+

+ Emergency Contact +

+
+ + + +
+
+
+
+ )} + + {activeTab === 'license' && ( +
+
+

+ Driving License +

+
+ + + + + +
+
+
+

+ GPS Tracking Device +

+
+ + +
+
+
+ )} + + {activeTab === 'documents' && ( +
+

+ Uploaded Documents +

+
+ {biker.documents.map((doc, idx) => ( +
+
+
+

{doc.type}

+

Number: {doc.number || 'N/A'}

+
+ + {doc.verified ? : } + {doc.verified ? 'Verified' : 'Pending'} + +
+
+ ))} +
+
+ )} + + {activeTab === 'reviews' && ( +
+

+ Biker Reviews +

+
+ {[ + { rider: 'Tashrif', rating: 5, comment: 'Great service! Very polite and quick.', date: '2024-03-20', rideId: 'R001' }, + { rider: 'Mahir', rating: 4, comment: 'Good ride, arrived on time.', date: '2024-03-19', rideId: 'R002' }, + { rider: 'Raisa', rating: 5, comment: 'Excellent experience. Would recommend!', date: '2024-03-18', rideId: 'R003' }, + { rider: 'Anika', rating: 3, comment: 'Ride was okay but a bit slow.', date: '2024-03-17', rideId: 'R004' }, + { rider: 'Ovi', rating: 5, comment: 'Best biker ever! Helped with luggage.', date: '2024-03-16', rideId: 'R005' }, + ].map((review, idx) => ( +
+
+
+
+ {review.rider.charAt(0)} +
+
+

{review.rider}

+

Ride: {review.rideId}

+
+
+ + {Array.from({ length: review.rating }).map((_, i) => ( + + ))} + +
+

{review.comment}

+

{review.date}

+
+ ))} +
+
+ )} + + {activeTab === 'stats' && ( +
+
+ + + + +
+
+ + 5 ? 'red' : ''} /> + + +
+
+ )} + + {activeTab === 'account' && ( +
+
+
+

+ Financial +

+
+ + + + +
+
+
+

+ Verification & Insurance +

+
+ + + + +
+
+
+
+ )} + + {activeTab === 'activity' && ( +
+

+ Activity Log +

+
+ {[ + { action: 'Profile Updated', date: '2024-03-21', icon: Edit }, + { action: 'Ride Completed', date: '2024-03-21', icon: BikeIcon }, + { action: 'Wallet Top Up - ৳2000', date: '2024-03-20', icon: Wallet }, + { action: 'KYC Verified', date: '2024-01-15', icon: CheckCircle }, + ].map((log, idx) => { + const Icon = log.icon; + return ( +
+ +
+

{log.action}

+

{log.date}

+
+
+ ); + })} +
+
+ )} +
+ + {/*
+
+ + +
+
*/} + +
+ ); +} + +function InfoCard({ label, value, highlight }: { label: string; value: string; highlight?: string }) { + return ( +
+

{label}

+

{value}

+
+ ); +} + +function StatCard({ label, value, icon: Icon, color }: { label: string; value: string; icon: any; color: string }) { + return ( +
+ +

{value}

+

{label}

+
+ ); +} \ No newline at end of file diff --git a/src/app/admin/bikers/page.tsx b/src/app/admin/bikers/page.tsx new file mode 100644 index 0000000..dbf0b60 --- /dev/null +++ b/src/app/admin/bikers/page.tsx @@ -0,0 +1,1298 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { + Users, + Search, + Filter, + Plus, + MoreVertical, + Phone, + Mail, + MapPin, + Clock, + Shield, + Bike, + Eye, + Edit, + Trash2, + X, + ChevronDown, + ChevronUp, + Download, + Upload, + MoreHorizontal, + CheckCircle, + XCircle, + AlertTriangle, + User, + Calendar, + FileText, + CreditCard, + MessageSquare, + Bell, + Activity, + TrendingUp, + Wrench, + Car, + FileCheck, + FileX, + MapPinned, + Navigation, + Map, + Award, + Heart, + Briefcase, + Image, + Clock3, + DollarSign, + Star, + UsersRound, + PhoneCall, + MessageCircle, + Send, + RefreshCcw, + Copy, + ExternalLink, + Info, + AlertCircle, + CheckSquare, + Ban, + LayoutGrid, + List +} from 'lucide-react'; + +interface DrivingLicense { + number: string; + issueDate: string; + expiryDate: string; + class: string; + status: 'valid' | 'expired' | 'suspended'; + imageUrl?: string; +} + +interface Document { + type: 'nid' | 'passport' | 'driving_license' | 'other'; + number: string; + imageFront?: string; + imageBack?: string; + verified: boolean; + verifiedAt?: string; +} + +interface Biker { + id: string; + name: string; + email: string; + phone: string; + alternatePhone?: string; + status: 'active' | 'pending' | 'inactive' | 'blocked'; + createdAt: string; + location: string; + address: string; + dateOfBirth: string; + gender: 'male' | 'female' | 'other'; + bloodGroup: 'A+' | 'A-' | 'B+' | 'B-' | 'O+' | 'O-' | 'AB+' | 'AB-'; + occupation: string; + emergencyContact: string; + emergencyPhone: string; + emergencyRelation: string; + + drivingLicense: DrivingLicense; + documents: Document[]; + + gpsDeviceId?: string; + gpsPhone?: string; + + totalRides: number; + totalDistance: number; + totalRideHours: number; + totalSpent: number; + currentBike?: string; + bikePlate?: string; + depositPaid: number; + walletBalance: number; + + rating: number; + totalRatings: number; + responseTime: number; + cancellationRate: number; + + kycStatus: 'verified' | 'pending' | 'rejected'; + kycRejectReason?: string; + kycVerifiedAt?: string; + + membershipType: 'free' | 'basic' | 'premium' | 'vip'; + referralCode?: string; + referredBy?: string; + + insuranceStatus: 'active' | 'expired' | 'none'; + insuranceExpiry?: string; + + notes?: string; + tags?: string[]; + + lastRideAt?: string; + firstRideAt?: string; + joinedFrom: string; +} + +const mockBikers: Biker[] = [ + { + id: 'B001', + name: 'Rahim Ahmed', + email: 'rahim@email.com', + phone: '01712345678', + alternatePhone: '01912345678', + status: 'active', + createdAt: '2024-01-15', + location: 'Gulshan, Dhaka', + address: 'House 12, Road 5, Gulshan 1, Dhaka 1212', + dateOfBirth: '1995-03-15', + gender: 'male', + bloodGroup: 'O+', + occupation: 'Student', + emergencyContact: 'Karim Ahmed', + emergencyPhone: '01712345679', + emergencyRelation: 'Brother', + drivingLicense: { + number: 'DL2024001234', + issueDate: '2023-01-15', + expiryDate: '2033-01-14', + class: 'M', + status: 'valid' + }, + documents: [ + { type: 'nid', number: '1234567890', verified: true, verifiedAt: '2024-01-15' } + ], + gpsDeviceId: 'GP-001234', + gpsPhone: '01712345678', + totalRides: 156, + totalDistance: 2340, + totalRideHours: 468, + totalSpent: 54500, + currentBike: 'Etron ET50', + bikePlate: 'Dhaka Metro Cha-1234', + depositPaid: 5000, + walletBalance: 1250, + rating: 4.8, + totalRatings: 156, + responseTime: 2.5, + cancellationRate: 2.1, + kycStatus: 'verified', + kycVerifiedAt: '2024-01-15', + membershipType: 'premium', + referralCode: 'RAHIM50', + referredBy: 'Ali007', + insuranceStatus: 'active', + insuranceExpiry: '2025-01-14', + tags: ['reliable', 'frequent'], + lastRideAt: '2024-03-21', + firstRideAt: '2024-01-15', + joinedFrom: 'App Store', + }, + { + id: 'B002', + name: 'Karim Hasan', + email: 'karim@email.com', + phone: '01712345679', + status: 'active', + createdAt: '2024-02-20', + location: 'Banani, Dhaka', + address: 'House 5, Road 11, Banani, Dhaka 1213', + dateOfBirth: '1990-07-22', + gender: 'male', + bloodGroup: 'B+', + occupation: 'Business', + emergencyContact: 'Rahim Hasan', + emergencyPhone: '01712345678', + emergencyRelation: 'Friend', + drivingLicense: { + number: 'DL2024005678', + issueDate: '2023-06-20', + expiryDate: '2033-06-19', + class: 'M', + status: 'valid' + }, + documents: [ + { type: 'nid', number: '1234567891', verified: true, verifiedAt: '2024-02-20' } + ], + gpsDeviceId: 'GP-001235', + totalRides: 89, + totalDistance: 1335, + totalRideHours: 267, + totalSpent: 31200, + currentBike: 'Yadea DT3', + bikePlate: 'Dhaka Metro Cha-5678', + depositPaid: 5000, + walletBalance: 800, + rating: 4.5, + totalRatings: 89, + responseTime: 3.2, + cancellationRate: 4.5, + kycStatus: 'verified', + membershipType: 'basic', + insuranceStatus: 'active', + insuranceExpiry: '2025-02-19', + joinedFrom: 'Website', + }, + { + id: 'B003', + name: 'Jamal Mahmud', + email: 'jamal@email.com', + phone: '01712345680', + status: 'pending', + createdAt: '2024-03-18', + location: 'Uttara, Dhaka', + address: 'Sector 10, Uttara, Dhaka 1230', + dateOfBirth: '1988-11-05', + gender: 'male', + bloodGroup: 'A+', + occupation: 'Job Holder', + emergencyContact: 'Mahmud Ali', + emergencyPhone: '01712345681', + emergencyRelation: 'Father', + drivingLicense: { + number: '', + issueDate: '', + expiryDate: '', + class: 'M', + status: 'valid' + }, + documents: [ + { type: 'nid', number: '1234567892', verified: false } + ], + totalRides: 0, + totalDistance: 0, + totalRideHours: 0, + totalSpent: 0, + depositPaid: 0, + walletBalance: 0, + rating: 0, + totalRatings: 0, + responseTime: 0, + cancellationRate: 0, + kycStatus: 'pending', + membershipType: 'free', + insuranceStatus: 'none', + joinedFrom: 'Referral', + }, + { + id: 'B004', + name: 'Ali Rahman', + email: 'ali@email.com', + phone: '01712345681', + status: 'active', + createdAt: '2023-12-01', + location: 'Dhanmondi, Dhaka', + address: 'House 27, Road 8, Dhanmondi, Dhaka 1205', + dateOfBirth: '1992-06-10', + gender: 'male', + bloodGroup: 'AB+', + occupation: 'Engineer', + emergencyContact: 'Rahman Ali', + emergencyPhone: '01712345682', + emergencyRelation: 'Brother', + drivingLicense: { + number: 'DL202301234', + issueDate: '2023-05-10', + expiryDate: '2033-05-09', + class: 'M', + status: 'valid' + }, + documents: [ + { type: 'nid', number: '1234567893', verified: true, verifiedAt: '2023-12-01' } + ], + gpsDeviceId: 'GP-001236', + totalRides: 234, + totalDistance: 3510, + totalRideHours: 702, + totalSpent: 81900, + currentBike: 'AIMA Lightning', + bikePlate: 'Dhaka Metro Cha-9012', + depositPaid: 5000, + walletBalance: 2100, + rating: 4.9, + totalRatings: 234, + responseTime: 1.8, + cancellationRate: 1.2, + kycStatus: 'verified', + membershipType: 'vip', + insuranceStatus: 'active', + insuranceExpiry: '2024-12-01', + tags: ['top_rider', 'vip'], + joinedFrom: 'Facebook', + }, + { + id: 'B005', + name: 'Mostafa Kamal', + email: 'mostafa@email.com', + phone: '01712345682', + status: 'inactive', + createdAt: '2023-08-15', + location: 'Mirpur, Dhaka', + address: 'Mirpur 1, Dhaka 1216', + dateOfBirth: '1997-02-28', + gender: 'male', + bloodGroup: 'O-', + occupation: 'Teacher', + emergencyContact: 'Kamal Mostafa', + emergencyPhone: '01712345683', + emergencyRelation: 'Father', + drivingLicense: { + number: 'DL2022009876', + issueDate: '2022-08-15', + expiryDate: '2024-08-14', + class: 'M', + status: 'expired' + }, + documents: [ + { type: 'nid', number: '1234567894', verified: true, verifiedAt: '2023-08-15' } + ], + totalRides: 45, + totalDistance: 675, + totalRideHours: 135, + totalSpent: 15750, + depositPaid: 5000, + walletBalance: 0, + rating: 3.8, + totalRatings: 45, + responseTime: 4.5, + cancellationRate: 8.9, + kycStatus: 'verified', + membershipType: 'free', + insuranceStatus: 'expired', + insuranceExpiry: '2024-01-14', + joinedFrom: 'App Store', + }, +]; + +const statusColors: Record = { + 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', +}; + +const kycColors: Record = { + verified: 'bg-green-100 text-green-700', + pending: 'bg-amber-100 text-amber-700', + rejected: 'bg-red-100 text-red-700', +}; + +const membershipColors: Record = { + free: 'bg-slate-100 text-slate-600', + basic: 'bg-blue-100 text-blue-700', + premium: 'bg-purple-100 text-purple-700', + vip: 'bg-amber-100 text-amber-700', +}; + +export default function BikersPage() { + const [bikers, setBikers] = useState(mockBikers); + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [kycFilter, setKycFilter] = useState('all'); + const [selectedBiker, setSelectedBiker] = useState(null); + const [showModal, setShowModal] = useState(false); + const [showDetailsModal, setShowDetailsModal] = useState(false); + const [editingBiker, setEditingBiker] = useState(null); + const [activeTab, setActiveTab] = useState('personal'); + const [sortField, setSortField] = useState('name'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); + + const filteredBikers = bikers.filter(biker => { + const matchesSearch = biker.name.toLowerCase().includes(searchQuery.toLowerCase()) || + biker.email.toLowerCase().includes(searchQuery.toLowerCase()) || + biker.phone.includes(searchQuery) || + biker.id.toLowerCase().includes(searchQuery.toLowerCase()) || + biker.drivingLicense.number.toLowerCase().includes(searchQuery.toLowerCase()) || + biker.gpsDeviceId?.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesStatus = statusFilter === 'all' || biker.status === statusFilter; + const matchesKyc = kycFilter === 'all' || biker.kycStatus === kycFilter; + return matchesSearch && matchesStatus && matchesKyc; + }); + + const sortedBikers = [...filteredBikers].sort((a, b) => { + const aVal = String(a[sortField as keyof Biker] || ''); + const bVal = String(b[sortField as keyof Biker] || ''); + if (sortOrder === 'asc') return aVal.localeCompare(bVal); + return bVal.localeCompare(aVal); + }); + + const handleSort = (field: string) => { + if (sortField === field) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortOrder('asc'); + } + }; + + const handleAddBiker = () => { + setEditingBiker({ + id: `B${String(bikers.length + 1).padStart(3, '0')}`, + name: '', + email: '', + phone: '', + status: 'pending', + createdAt: new Date().toISOString().split('T')[0], + location: '', + address: '', + dateOfBirth: '', + gender: 'male', + bloodGroup: 'O+', + occupation: '', + emergencyContact: '', + emergencyPhone: '', + emergencyRelation: '', + drivingLicense: { number: '', issueDate: '', expiryDate: '', class: 'M', status: 'valid' }, + documents: [{ type: 'nid', number: '', verified: false }], + totalRides: 0, + totalDistance: 0, + totalRideHours: 0, + totalSpent: 0, + depositPaid: 0, + walletBalance: 0, + rating: 0, + totalRatings: 0, + responseTime: 0, + cancellationRate: 0, + kycStatus: 'pending', + membershipType: 'free', + insuranceStatus: 'none', + joinedFrom: 'Direct', + }); + setShowModal(true); + }; + + const handleEditBiker = (biker: Biker) => { + setEditingBiker(JSON.parse(JSON.stringify(biker))); + setShowModal(true); + }; + + const handleDeleteBiker = (id: string) => { + if (confirm('Are you sure you want to delete this biker?')) { + setBikers(bikers.filter(b => b.id !== id)); + } + }; + + const handleSaveBiker = () => { + if (editingBiker) { + const existingIndex = bikers.findIndex(b => b.id === editingBiker.id); + if (existingIndex >= 0) { + const newBikers = [...bikers]; + newBikers[existingIndex] = editingBiker; + setBikers(newBikers); + } else { + setBikers([...bikers, editingBiker]); + } + } + setShowModal(false); + setEditingBiker(null); + }; + + const handleViewDetails = (biker: Biker) => { + setSelectedBiker(biker); + setShowDetailsModal(true); + setActiveTab('personal'); + }; + + const activeCount = bikers.filter(b => b.status === 'active').length; + const pendingCount = bikers.filter(b => b.status === 'pending').length; + const blockedCount = bikers.filter(b => b.status === 'blocked').length; + const verifiedKyc = bikers.filter(b => b.kycStatus === 'verified').length; + + return ( +
+
+
+

Bikers Management

+

Manage registered biker accounts with full details

+
+ +
+ +
+
+
+ + Total +
+

{bikers.length}

+
+
+
+ + Active +
+

{activeCount}

+
+
+
+ + Pending +
+

{pendingCount}

+
+
+
+ + Blocked +
+

{blockedCount}

+
+
+
+ + Verified KYC +
+

{verifiedKyc}

+
+
+
+ + GPS Devices +
+

{bikers.filter(b => b.gpsDeviceId).length}

+
+
+ +
+
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent" + /> +
+
+ + + + + +
+
+ + {viewMode === 'table' ? ( +
+ + + + + + + + + + + + + + + + {sortedBikers.map(biker => ( + + + + + + + + + + + + ))} + +
BikerLicense & GPSLocationRides & DistanceFinancialMemberKYCStatusActions
+ +
+ {biker.name.charAt(0)} +
+
+

{biker.name}

+

{biker.phone}

+
+ +
+

+ {biker.drivingLicense.number || 'No License'} +

+

+ GPS: {biker.gpsDeviceId || 'No GPS'} +

+

Class: {biker.drivingLicense.class || 'N/A'}

+
+

{biker.location}

+
+

{biker.totalRides} rides

+

{biker.totalDistance.toLocaleString()} km

+
+

৳{biker.totalSpent.toLocaleString()}

+

Wallet: ৳{biker.walletBalance}

+
+ + {biker.membershipType} + + + + {biker.kycStatus === 'verified' && } + {biker.kycStatus === 'pending' && } + {biker.kycStatus === 'rejected' && } + {biker.kycStatus} + + + + {biker.status === 'active' && } + {biker.status === 'pending' && } + {biker.status === 'blocked' && } + {biker.status} + + {biker.rating > 0 && ( +

★ {biker.rating} ({biker.totalRatings})

+ )} +
+
+ + + +
+
+
+ ) : ( +
+ {sortedBikers.map(biker => ( + +
+
+
+ {biker.name.charAt(0)} +
+
+

{biker.name}

+

{biker.phone}

+
+
+ +
+
+ + {biker.status} + + + {biker.kycStatus} + + + {biker.membershipType} + + {biker.rating > 0 && ( + + ★ {biker.rating} + + )} +
+
+
+

Rides

+

{biker.totalRides}

+
+
+

Distance

+

{biker.totalDistance.toLocaleString()} km

+
+
+

Wallet

+

৳{biker.walletBalance}

+
+
+

Location

+

{biker.location}

+
+
+ + ))} +
+ )} + +
+

+ Showing 1 to {sortedBikers.length} of {sortedBikers.length} results +

+
+ + + +
+
+
+ + {/* Add/Edit Modal with Tabs */} + {showModal && editingBiker && ( +
+
+
+

+ {bikers.find(b => b.id === editingBiker.id) ? 'Edit Biker' : 'Add New Biker'} +

+ +
+ +
+ + + + + +
+ +
+ {activeTab === 'personal' && ( +
+
+
+ + setEditingBiker({ ...editingBiker, name: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Enter full name" /> +
+
+ + setEditingBiker({ ...editingBiker, phone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="01XXXXXXXXX" /> +
+
+
+
+ + setEditingBiker({ ...editingBiker, alternatePhone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Optional" /> +
+
+ + setEditingBiker({ ...editingBiker, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="email@example.com" /> +
+
+
+
+ + setEditingBiker({ ...editingBiker, dateOfBirth: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + +
+
+
+
+ + +
+
+ + setEditingBiker({ ...editingBiker, occupation: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Job title" /> +
+
+
+ + setEditingBiker({ ...editingBiker, address: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Full address" /> +
+
+
+ + setEditingBiker({ ...editingBiker, emergencyContact: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + setEditingBiker({ ...editingBiker, emergencyPhone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+
+
+ + setEditingBiker({ ...editingBiker, emergencyRelation: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g. Brother, Father" /> +
+
+ + +
+
+
+ )} + + {activeTab === 'license' && ( +
+

Driving License

+
+
+ + setEditingBiker({ ...editingBiker, drivingLicense: { ...editingBiker.drivingLicense, number: e.target.value } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="DL2024XXXXXX" /> +
+
+ + +
+
+
+
+ + setEditingBiker({ ...editingBiker, drivingLicense: { ...editingBiker.drivingLicense, issueDate: e.target.value } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + setEditingBiker({ ...editingBiker, drivingLicense: { ...editingBiker.drivingLicense, expiryDate: e.target.value } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+
+ + +
+ +

GPS Tracking Device

+
+
+ + setEditingBiker({ ...editingBiker, gpsDeviceId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="GP-XXXXXX" /> +
+
+ + setEditingBiker({ ...editingBiker, gpsPhone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Linked phone number" /> +
+
+
+ )} + + {activeTab === 'documents' && ( +
+

Documents

+ {editingBiker.documents.map((doc, index) => ( +
+
+
+ + +
+
+ + { + const newDocs = [...editingBiker.documents]; + newDocs[index] = { ...newDocs[index], number: e.target.value }; + setEditingBiker({ ...editingBiker, documents: newDocs }); + }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Document number" /> +
+
+
+ + {doc.verified ? : } + {doc.verified ? 'Verified' : 'Pending Verification'} + + +
+
+ ))} +
+ )} + + {activeTab === 'stats' && ( +
+
+
+

Total Rides

+ setEditingBiker({ ...editingBiker, totalRides: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+

Total Distance (km)

+ setEditingBiker({ ...editingBiker, totalDistance: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+

Total Ride Hours

+ setEditingBiker({ ...editingBiker, totalRideHours: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+
+
+

Rating

+ setEditingBiker({ ...editingBiker, rating: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+

Total Ratings

+ setEditingBiker({ ...editingBiker, totalRatings: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+

Response Time (min)

+ setEditingBiker({ ...editingBiker, responseTime: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+
+

Cancellation Rate (%)

+ setEditingBiker({ ...editingBiker, cancellationRate: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" /> +
+
+ )} + + {activeTab === 'account' && ( +
+
+
+ + +
+
+ + +
+
+
+
+ + setEditingBiker({ ...editingBiker, totalSpent: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + setEditingBiker({ ...editingBiker, walletBalance: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+
+
+ + setEditingBiker({ ...editingBiker, depositPaid: Number(e.target.value) })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + +
+
+
+
+ + setEditingBiker({ ...editingBiker, referralCode: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + setEditingBiker({ ...editingBiker, referredBy: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+
+ +