diff --git a/src/app/admin/batteries/[id]/page.tsx b/src/app/admin/batteries/[id]/page.tsx index 45335fd..1dadfed 100644 --- a/src/app/admin/batteries/[id]/page.tsx +++ b/src/app/admin/batteries/[id]/page.tsx @@ -52,6 +52,8 @@ interface DamageRecord { estimatedCost: number; actualCost?: number; status: 'reported' | 'in-progress' | 'resolved'; + hubId?: string; + hubName?: string; } interface MaintenanceRecord { @@ -63,6 +65,8 @@ interface MaintenanceRecord { performedBy: string; nextDueDate?: string; status: 'completed' | 'pending' | 'overdue'; + hubId?: string; + hubName?: string; } interface Battery { diff --git a/src/app/admin/fleet/[id]/page.tsx b/src/app/admin/fleet/[id]/page.tsx index b6ad357..1733e5b 100644 --- a/src/app/admin/fleet/[id]/page.tsx +++ b/src/app/admin/fleet/[id]/page.tsx @@ -163,7 +163,7 @@ interface Bike { const mockBikes: Bike[] = [ { - id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: '', plateNumber: 'Dhaka Metro Cha-A-1234', status: 'rented', batteryLevel: 78, currentBatteryId: 'BAT-001', currentBatteryBrand: 'EVE Energy', currentBatteryModel: 'Li-Ion 60V50Ah', location: 'Gulshan 1', assignedTo: 'Rahim Ahmed', renterPhone: '01712345678', renterNid: '1234567890', rentalStartDate: '2024-03-01', subscriptionType: 'weekly', weeklyRent: 2400, location: 'Gulshan 1', assignedTo: 'Rahim Ahmed', hubId: 'HUB-001', hubName: 'JAIBEN Head Office', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-01-15', purchasePrice: 125000, currentRent: 350, totalRides: 156, totalDistance: 2340, totalEarnings: 54600, lastService: '2024-03-01', nextService: '2024-04-01', insuranceExpiry: '2025-01-15', registrationExpiry: '2026-01-15', + id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: '', plateNumber: 'Dhaka Metro Cha-A-1234', status: 'rented', batteryLevel: 78, currentBatteryId: 'BAT-001', currentBatteryBrand: 'EVE Energy', currentBatteryModel: 'Li-Ion 60V50Ah', location: 'Gulshan 1', assignedTo: 'Rahim Ahmed', renterPhone: '01712345678', renterNid: '1234567890', rentalStartDate: '2024-03-01', subscriptionType: 'weekly', weeklyRent: 2400, hubId: 'HUB-001', hubName: 'JAIBEN Head Office', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-01-15', purchasePrice: 125000, currentRent: 350, totalRides: 156, totalDistance: 2340, totalEarnings: 54600, lastService: '2024-03-01', nextService: '2024-04-01', insuranceExpiry: '2025-01-15', registrationExpiry: '2026-01-15', gpsDevice: { id: 'GPS001', phone: '01712345601', imei: '861234567890123', lastActive: '2024-03-21 14:30', signal: 85, battery: 72 }, documents: [ { type: 'registration', number: 'REG-EV001-2024', issueDate: '2024-01-15', expiryDate: '2026-01-15', verified: true }, diff --git a/src/app/admin/rentals/map/page.tsx b/src/app/admin/rentals/map/page.tsx new file mode 100644 index 0000000..0d78a20 --- /dev/null +++ b/src/app/admin/rentals/map/page.tsx @@ -0,0 +1,382 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + ArrowLeft, Search, Bike, User, MapPin, Battery, + Phone, MessageCircle, X, Navigation, Clock, RefreshCw +} from 'lucide-react'; + +interface Rental { + id: string; + bikeId: string; + userId: string; + userName: string; + userPhone: string; + bikeModel: string; + bikePlate: string; + bikeBattery: number; + status: 'pending' | 'accepted' | 'active' | 'completed' | 'cancelled' | 'locked'; + type: 'single' | 'shared' | 'rent-to-own'; + hubId: string; + hubName: string; + location?: { + lat: number; + lng: number; + address?: string; + lastUpdate?: string; + speed?: number; + heading?: number; + }; +} + +const mockRentals: Rental[] = [ + { + id: 'RNT-001', + bikeId: 'BIKE-001', + userId: 'USR-003', + userName: 'Jamal Uddin', + userPhone: '+8801912345678', + bikeModel: 'AIMA Lightning', + bikePlate: 'Dhaka Metro Cha-9012', + bikeBattery: 87, + status: 'active', + type: 'single', + hubId: 'HUB-001', + hubName: 'Gulshan Hub', + location: { lat: 23.7925, lng: 90.4074, address: 'Gulshan 1, Dhaka', lastUpdate: '2024-03-28 14:30:00', speed: 0, heading: 180 }, + }, + { + id: 'RNT-002', + bikeId: 'BIKE-002', + userId: 'USR-004', + userName: 'Rafiq Islam', + userPhone: '+8801512345678', + bikeModel: 'Yadea DT3', + bikePlate: 'Dhaka Metro Ba-5521', + bikeBattery: 65, + status: 'active', + type: 'shared', + hubId: 'HUB-002', + hubName: 'Banani Hub', + location: { lat: 23.8041, lng: 90.4152, address: 'Banani, Dhaka', lastUpdate: '2024-03-28 14:28:00', speed: 15, heading: 90 }, + }, + { + id: 'RNT-003', + bikeId: 'BIKE-003', + userId: 'USR-001', + userName: 'Rahim Ahmed', + userPhone: '+8801712345678', + bikeModel: 'AIMA EM5', + bikePlate: 'Dhaka Metro Ko-1234', + bikeBattery: 92, + status: 'active', + type: 'rent-to-own', + hubId: 'HUB-003', + hubName: 'Uttara Hub', + location: { lat: 23.8776, lng: 90.4014, address: 'Uttara Sector 11, Dhaka', lastUpdate: '2024-03-28 14:25:00', speed: 25, heading: 270 }, + }, + { + id: 'RNT-004', + bikeId: 'BIKE-005', + userId: 'USR-005', + userName: 'Farid Ahmed', + userPhone: '+8801612345678', + bikeModel: 'Yadea G5', + bikePlate: 'Dhaka Metro Ha-5678', + bikeBattery: 45, + status: 'active', + type: 'single', + hubId: 'HUB-004', + hubName: 'Mirpur Hub', + location: { lat: 23.8222, lng: 90.3639, address: 'Mirpur 10, Dhaka', lastUpdate: '2024-03-28 14:20:00', speed: 0, heading: 0 }, + }, + { + id: 'RNT-005', + bikeId: 'BIKE-001', + userId: 'USR-002', + userName: 'Karim Hasan', + userPhone: '+8801812345678', + bikeModel: 'AIMA Lightning', + bikePlate: 'Dhaka Metro Cha-9012', + bikeBattery: 78, + status: 'pending', + type: 'single', + hubId: 'HUB-001', + hubName: 'Gulshan Hub', + location: { lat: 23.7889, lng: 90.4025, address: 'Dhanmondi 27, Dhaka', lastUpdate: '2024-03-28 14:15:00', speed: 8, heading: 45 }, + }, +]; + +const statusColors: Record = { + active: { bg: 'bg-green-100', text: 'text-green-700' }, + pending: { bg: 'bg-amber-100', text: 'text-amber-700' }, + accepted: { bg: 'bg-blue-100', text: 'text-blue-700' }, + completed: { bg: 'bg-indigo-100', text: 'text-indigo-700' }, + cancelled: { bg: 'bg-slate-100', text: 'text-slate-600' }, + locked: { bg: 'bg-red-100', text: 'text-red-700' }, +}; + +export default function RentalMapPage() { + const [rentals, setRentals] = useState(mockRentals); + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [selectedRental, setSelectedRental] = useState(null); + const [lastRefresh, setLastRefresh] = useState(new Date()); + const [liveLocations, setLiveLocations] = useState>({}); + + const filteredRentals = rentals.filter(r => { + const matchesSearch = r.id.toLowerCase().includes(searchQuery.toLowerCase()) || + r.userName.toLowerCase().includes(searchQuery.toLowerCase()) || + r.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesStatus = statusFilter === 'all' || r.status === statusFilter; + return matchesSearch && matchesStatus && r.location; + }); + + const simulateLiveUpdate = () => { + const newLocations: Record = {}; + rentals.forEach(rental => { + if (rental.location && rental.status === 'active') { + const movement = Math.random() * 0.002 - 0.001; + newLocations[rental.id] = { + lat: rental.location.lat + movement, + lng: rental.location.lng + movement, + speed: Math.floor(Math.random() * 30), + }; + } + }); + setLiveLocations(newLocations); + setLastRefresh(new Date()); + }; + + useEffect(() => { + const interval = setInterval(simulateLiveUpdate, 5000); + return () => clearInterval(interval); + }, [rentals]); + + const getMarkerPosition = (rental: Rental) => { + if (liveLocations[rental.id]) { + return { lat: liveLocations[rental.id].lat, lng: liveLocations[rental.id].lng }; + } + return { lat: rental.location?.lat || 0, lng: rental.location?.lng || 0 }; + }; + + return ( +
+
+
+ + + +
+

Live Rental Map

+
+ +
+ +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm text-slate-600" + /> +
+ +
+
+ +
+
+
+
+
+
+ +

Live Map View

+

+ Showing {filteredRentals.length} rentals on map +

+

+ Last updated: {lastRefresh.toLocaleTimeString()} +

+
+
+ + {filteredRentals.map((rental, index) => { + const pos = getMarkerPosition(rental); + const x = ((pos.lng - 90.35) / 0.15) * 100; + const y = ((23.95 - pos.lat) / 0.2) * 100; + return ( +
setSelectedRental(rental)} + > +
+ + + {index + 1} + +
+
+ {rental.id} +
+
+ ); + })} +
+
+
+ +
+
+

+ Rental List ({filteredRentals.length}) +

+
+
+ Live +
+
+
+ {filteredRentals.map((rental, index) => ( +
setSelectedRental(rental)} + className={`p-3 rounded-lg border cursor-pointer transition-all ${selectedRental?.id === rental.id ? 'border-emerald-500 bg-emerald-50' : 'border-slate-200 hover:border-emerald-300'}`} + > +
+
+ + {index + 1} + + + {rental.id} + +
+ + {rental.status} + +
+ +
+ + {rental.bikeModel} +
+ +
+ + {rental.userName} +
+ +
+
+ + {rental.location?.address || rental.hubName} +
+
+ + {rental.bikeBattery}% +
+
+ + {liveLocations[rental.id] && ( +
+ + Speed: {liveLocations[rental.id].speed} km/h + + + {rental.location?.lastUpdate} + +
+ )} +
+ ))} +
+
+
+ + {selectedRental && ( +
setSelectedRental(null)}> +
e.stopPropagation()}> +
+

Rental Details

+ +
+
+
+ {selectedRental.id} + + {selectedRental.status} + +
+ +
+
+

Bike

+

{selectedRental.bikeModel}

+

{selectedRental.bikePlate}

+
+
+

Battery

+

50 ? 'text-green-600' : selectedRental.bikeBattery > 20 ? 'text-amber-600' : 'text-red-600'}`}> + {selectedRental.bikeBattery}% +

+
+
+ +
+

Renter

+

{selectedRental.userName}

+

{selectedRental.userPhone}

+
+ +
+

Location

+

{selectedRental.location?.address}

+

Lat: {selectedRental.location?.lat.toFixed(4)}, Lng: {selectedRental.location?.lng.toFixed(4)}

+ {selectedRental.location?.lastUpdate && ( +

Last Update: {selectedRental.location.lastUpdate}

+ )} +
+ + + + + View Full Details + +
+
+
+ )} +
+ ); +} \ No newline at end of file