diff --git a/src/app/admin/fleet/[id]/page.tsx b/src/app/admin/fleet/[id]/page.tsx index 8b1ecc5..eef4b40 100644 --- a/src/app/admin/fleet/[id]/page.tsx +++ b/src/app/admin/fleet/[id]/page.tsx @@ -100,7 +100,9 @@ interface Bike { plateNumber: string; status: 'available' | 'rented' | 'maintenance' | 'retired'; batteryLevel: number; - location: string; + location?: string; // deprecated - use hubId/hubName + hubId?: string; + hubName?: string; assignedTo?: string; investorId?: string; investorName?: string; @@ -126,7 +128,7 @@ interface Bike { const mockBikes: Bike[] = [ { - id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: '', plateNumber: 'Dhaka Metro Cha-A-1234', status: 'rented', batteryLevel: 78, location: 'Gulshan 1', assignedTo: 'Rahim Ahmed', 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, 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', 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 }, @@ -157,7 +159,7 @@ const mockBikes: Bike[] = [ ] }, { - id: 'EV002', model: 'Yadea DT3', brand: 'Yadea', image: '', plateNumber: 'Dhaka Metro Cha-A-5678', status: 'available', batteryLevel: 95, location: 'Banani', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-02-01', purchasePrice: 118000, totalRides: 89, totalDistance: 1567, totalEarnings: 31150, lastService: '2024-03-15', nextService: '2024-04-15', insuranceExpiry: '2025-02-01', registrationExpiry: '2026-02-01', + id: 'EV002', model: 'Yadea DT3', brand: 'Yadea', image: '', plateNumber: 'Dhaka Metro Cha-A-5678', status: 'available', batteryLevel: 95, location: 'Banani', hubId: 'HUB-002', hubName: 'Banani Hub', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-02-01', purchasePrice: 118000, totalRides: 89, totalDistance: 1567, totalEarnings: 31150, lastService: '2024-03-15', nextService: '2024-04-15', insuranceExpiry: '2025-02-01', registrationExpiry: '2026-02-01', gpsDevice: { id: 'GPS002', phone: '01712345602', imei: '861234567890124', lastActive: '2024-03-21 15:00', signal: 92, battery: 88 }, documents: [ { type: 'registration', number: 'REG-EV002-2024', issueDate: '2024-02-01', expiryDate: '2026-02-01', verified: true }, @@ -173,7 +175,7 @@ const mockBikes: Bike[] = [ ] }, { - id: 'EV003', model: 'AIMA Lightning', brand: 'AIMA', image: '', plateNumber: 'Dhaka Metro Cha-A-9012', status: 'rented', batteryLevel: 62, location: 'Uttara', assignedTo: 'Karim Singh', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-01-20', purchasePrice: 132000, currentRent: 400, totalRides: 203, totalDistance: 3890, totalEarnings: 71100, lastService: '2024-03-10', nextService: '2024-04-10', insuranceExpiry: '2025-01-20', registrationExpiry: '2026-01-20', + id: 'EV003', model: 'AIMA Lightning', brand: 'AIMA', image: '', plateNumber: 'Dhaka Metro Cha-A-9012', status: 'rented', batteryLevel: 62, hubId: 'HUB-003', hubName: 'Uttara Hub', investorId: 'inv1', investorName: 'Mr. Hasan (Investor)', purchaseDate: '2024-01-20', purchasePrice: 132000, currentRent: 400, totalRides: 203, totalDistance: 3890, totalEarnings: 71100, lastService: '2024-03-10', nextService: '2024-04-10', insuranceExpiry: '2025-01-20', registrationExpiry: '2026-01-20', gpsDevice: { id: 'GPS003', phone: '01712345603', imei: '861234567890125', lastActive: '2024-03-21 14:45', signal: 78, battery: 55 }, documents: [ { type: 'registration', number: 'REG-EV003-2024', issueDate: '2024-01-20', expiryDate: '2026-01-20', verified: true }, @@ -184,7 +186,7 @@ const mockBikes: Bike[] = [ activityLog: [] }, { - id: 'EV004', model: 'TVS iQube', brand: 'TVS', image: '', plateNumber: 'Dhaka Metro Cha-A-3456', status: 'maintenance', batteryLevel: 45, location: 'Workshop - Banani', investorId: 'inv2', investorName: 'Mrs. Rita (Investor)', purchaseDate: '2023-12-10', purchasePrice: 145000, totalRides: 312, totalDistance: 5670, totalEarnings: 98000, lastService: '2024-03-20', nextService: '2024-03-25', insuranceExpiry: '2024-12-10', registrationExpiry: '2025-12-10', notes: 'Motor issue - awaiting parts', + id: 'EV004', model: 'TVS iQube', brand: 'TVS', image: '', plateNumber: 'Dhaka Metro Cha-A-3456', status: 'maintenance', batteryLevel: 45, hubId: 'HUB-002', hubName: 'Banani Hub', investorId: 'inv2', investorName: 'Mrs. Rita (Investor)', purchaseDate: '2023-12-10', purchasePrice: 145000, totalRides: 312, totalDistance: 5670, totalEarnings: 98000, lastService: '2024-03-20', nextService: '2024-03-25', insuranceExpiry: '2024-12-10', registrationExpiry: '2025-12-10', notes: 'Motor issue - awaiting parts', gpsDevice: { id: 'GPS004', phone: '01712345604', imei: '861234567890126', lastActive: '2024-03-20 10:00', signal: 0, battery: 12 }, documents: [ { type: 'registration', number: 'REG-EV004-2023', issueDate: '2023-12-10', expiryDate: '2025-12-10', verified: true }, @@ -194,7 +196,7 @@ const mockBikes: Bike[] = [ activityLog: [] }, { - id: 'EV005', model: 'Bajaj Chetak', brand: 'Bajaj', image: '', plateNumber: 'Dhaka Metro Cha-A-7890', status: 'available', batteryLevel: 100, location: 'Dhanmondi', investorId: 'inv2', investorName: 'Mrs. Rita (Investor)', purchaseDate: '2024-02-15', purchasePrice: 138000, totalRides: 67, totalDistance: 890, totalEarnings: 23450, lastService: '2024-03-18', nextService: '2024-04-18', insuranceExpiry: '2025-02-15', registrationExpiry: '2026-02-15', + id: 'EV005', model: 'Bajaj Chetak', brand: 'Bajaj', image: '', plateNumber: 'Dhaka Metro Cha-A-7890', status: 'available', batteryLevel: 100, hubId: 'HUB-001', hubName: 'JAIBEN Head Office', investorId: 'inv2', investorName: 'Mrs. Rita (Investor)', purchaseDate: '2024-02-15', purchasePrice: 138000, totalRides: 67, totalDistance: 890, totalEarnings: 23450, lastService: '2024-03-18', nextService: '2024-04-18', insuranceExpiry: '2025-02-15', registrationExpiry: '2026-02-15', gpsDevice: { id: 'GPS005', phone: '01712345605', imei: '861234567890127', lastActive: '2024-03-21 15:30', signal: 95, battery: 92 }, documents: [ { type: 'registration', number: 'REG-EV005-2024', issueDate: '2024-02-15', expiryDate: '2026-02-15', verified: true }, @@ -341,7 +343,7 @@ export default function FleetDetailPage({ params }: { params: Promise<{ id: stri const tabs = [ { id: 'overview', label: 'Overview', icon: Bike }, - { id: 'biker-assignment', label: 'Assign Bikers', icon: User }, + // { id: 'biker-assignment', label: 'Assign Bikers', icon: User }, { id: 'gps', label: 'GPS & Tracking', icon: Navigation2 }, { id: 'documents', label: 'Documents', icon: FileText }, { id: 'rental', label: 'Rental History', icon: History }, @@ -372,8 +374,8 @@ export default function FleetDetailPage({ params }: { params: Promise<{ id: stri key={tab.id} onClick={() => setActiveTab(tab.id)} className={`px-3 py-2 rounded-lg text-sm font-medium whitespace-nowrap flex items-center gap-2 ${activeTab === tab.id - ? 'bg-accent text-white' - : 'bg-white text-slate-600 border border-slate-200' + ? 'bg-accent text-white' + : 'bg-white text-slate-600 border border-slate-200' }`} > @@ -397,7 +399,7 @@ export default function FleetDetailPage({ params }: { params: Promise<{ id: stri

Damage History

- - - - - - -
-
-

{geofences.length}

-

Total Zones

-
-
-

41

-

Bikes in Zones

-
-
-

4

-

Active Zones

-
-
- -
-
-
- - -
-
- -
- - - - - - - - - - - - {geofences.map(zone => ( - - - - - - - - ))} - -
Zone NameTypeBikesStatusActions
-
- - {zone.name} -
-
- - {zone.type} - - - {zone.bikes} bikes - - - {zone.status} - - -
- - - -
-
-
-
- - ); -} \ No newline at end of file diff --git a/src/app/admin/hub/[id]/page.tsx b/src/app/admin/hub/[id]/page.tsx new file mode 100644 index 0000000..78d1c78 --- /dev/null +++ b/src/app/admin/hub/[id]/page.tsx @@ -0,0 +1,438 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { + ArrowLeft, MapPin, Phone, Clock, Bike, Plus, X, Edit, Save, Trash2, + Navigation, User, Wallet, DollarSign, CheckCircle, AlertTriangle +} from 'lucide-react'; + +interface Hub { + id: string; + name: string; + address: string; + phone: string; + managerName?: string; + bikeCount: number; + activeRentals: number; + status: 'active' | 'inactive'; + coordinates?: { lat: number; lng: number }; + openTime: string; + closeTime: string; + isHeadOffice?: boolean; +} + +interface BikeInfo { + id: string; + model: string; + plate: string; + status: 'available' | 'rented' | 'maintenance'; +} + +const mockHub: Hub = { + id: 'HUB-001', + name: 'JAIBEN Head Office', + address: 'House 12, Road 17, Gulshan 1, Dhaka', + phone: '+8801712345678', + managerName: 'Rahim Ahmed', + bikeCount: 25, + activeRentals: 12, + status: 'active', + coordinates: { lat: 23.7925, lng: 90.4174 }, + openTime: '06:00', + closeTime: '23:00', + isHeadOffice: true +}; + +const mockHubBikes: BikeInfo[] = [ + { id: 'BIKE-001', model: 'AIMA Lightning', plate: 'Dhaka Metro Cha-9012', status: 'available' }, + { id: 'BIKE-002', model: 'Yadea DT3', plate: 'Dhaka Metro Ba-5521', status: 'rented' }, + { id: 'BIKE-003', model: 'AIMA EM5', plate: 'Dhaka Metro Ko-1234', status: 'available' }, + { id: 'BIKE-004', model: 'AIMA Lightning', plate: 'Dhaka Metro Cha-9013', status: 'rented' }, + { id: 'BIKE-005', model: 'Yadea G5', plate: 'Dhaka Metro Ha-5678', status: 'maintenance' }, +]; + +interface RentalInfo { + id: string; + userName: string; + bike: string; + plate: string; + startDate: string; + type: string; + status: 'active' | 'pending' | 'completed'; + dailyRate: number; + totalPaid: number; +} + +const mockHubRentals: RentalInfo[] = [ + { id: 'RNT-001', userName: 'Rahim Ahmed', bike: 'AIMA Lightning', plate: 'Dhaka Metro Cha-9012', startDate: '2024-01-15', type: 'single', status: 'active', dailyRate: 300, totalPaid: 81900 }, + { id: 'RNT-002', userName: 'Karim Hasan', bike: 'Yadea DT3', plate: 'Dhaka Metro Ba-5521', startDate: '2024-01-20', type: 'single', status: 'active', dailyRate: 200, totalPaid: 12400 }, + { id: 'RNT-003', userName: 'Jamal Uddin', bike: 'AIMA EM5', plate: 'Dhaka Metro Ko-1234', startDate: '2024-02-01', type: 'shared', status: 'pending', dailyRate: 150, totalPaid: 450 }, +]; + +export default function HubDetailPage() { + const params = useParams(); + const router = useRouter(); + const id = params.id as string; + + const [hub, setHub] = useState(mockHub); + const [bikes, setBikes] = useState(mockHubBikes); + const [rentals, setRentals] = useState(mockHubRentals); + const [editMode, setEditMode] = useState(false); + const [editForm, setEditForm] = useState(hub); + const [activeTab, setActiveTab] = useState<'overview' | 'bikes' | 'rentals'>('overview'); + + const handleSaveEdit = () => { + setHub(editForm); + setEditMode(false); + }; + + if (!id || id !== 'HUB-001') { + return ( +
+
+ +

Hub not found

+ +
+
+ ); + } + + return ( +
+ + +
+
+
+
+
+

{hub.name}

+ + {hub.status} + + {hub.isHeadOffice && ( + + Head Office + + )} +
+

{hub.address}

+
+
+ {editMode ? ( + <> + + + + ) : ( + + )} +
+
+
+ +
+ +
+ +
+ {activeTab === 'overview' && ( +
+
+
+

+ Location Info +

+ {editMode ? ( +
+ setEditForm({ ...editForm, address: e.target.value })} + className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm" + placeholder="Address" + /> +
+ ) : ( +
+
+ Address + {hub.address} +
+ {hub.coordinates && ( +
+ Coordinates + {hub.coordinates.lat}, {hub.coordinates.lng} +
+ )} +
+ )} +
+ +
+

+ Contact Info +

+ {editMode ? ( +
+ setEditForm({ ...editForm, phone: e.target.value })} + className="w-full px-3 py-2 border border-purple-200 rounded-lg text-sm" + placeholder="Phone" + /> + setEditForm({ ...editForm, managerName: e.target.value })} + className="w-full px-3 py-2 border border-purple-200 rounded-lg text-sm" + placeholder="Manager Name" + /> +
+ ) : ( +
+
+ Phone + {hub.phone} +
+
+ Manager + {hub.managerName || '-'} +
+
+ )} +
+
+ +
+
+

+ Operating Hours +

+ {editMode ? ( +
+
+ + setEditForm({ ...editForm, openTime: e.target.value })} + className="w-full px-3 py-2 border border-green-200 rounded-lg text-sm mt-1" + /> +
+
+ + setEditForm({ ...editForm, closeTime: e.target.value })} + className="w-full px-3 py-2 border border-green-200 rounded-lg text-sm mt-1" + /> +
+
+ ) : ( +
+ Hours + {hub.openTime} - {hub.closeTime} +
+ )} +
+ +
+

+ Today's Earnings +

+
+
+ Rentals + {hub.activeRentals} +
+
+ Revenue + ৳{hub.activeRentals * 300} +
+
+
+
+ +
+
+

+ Bike Statistics +

+
+
+ Total Bikes + {hub.bikeCount} +
+
+ Available + + {bikes.filter(b => b.status === 'available').length} + +
+
+ Rented + + {bikes.filter(b => b.status === 'rented').length} + +
+
+ Maintenance + + {bikes.filter(b => b.status === 'maintenance').length} + +
+
+
+
+
+ )} + + {activeTab === 'bikes' && ( +
+
+

Hub Bikes ({bikes.length})

+ +
+
+ {bikes.map(bike => ( +
+
+ + + {bike.status} + +
+

{bike.model}

+

{bike.plate}

+

ID: {bike.id}

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

Hub Rentals ({rentals.length})

+ +
+
+ + + + + + + + + + + + + + + {rentals.map(rental => ( + + + + + + + + + + + ))} + +
Rental IDUserBikeStart DateTypeDaily RateTotal PaidStatus
+ {rental.id} + + {rental.userName} + +
+ {rental.bike} +

{rental.plate}

+
+
+ {rental.startDate} + + {rental.type} + + ৳{rental.dailyRate} + + ৳{rental.totalPaid.toLocaleString()} + + + {rental.status} + +
+
+
+ )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/admin/hub/page.tsx b/src/app/admin/hub/page.tsx new file mode 100644 index 0000000..36ce3b0 --- /dev/null +++ b/src/app/admin/hub/page.tsx @@ -0,0 +1,371 @@ +'use client'; + +import { useState } from 'react'; +import { MapPin, Plus, Search, Eye, Edit, Trash2, Bike, X, Navigation, Phone, Clock } from 'lucide-react'; +import Link from 'next/link'; + +interface Hub { + id: string; + name: string; + address: string; + phone: string; + managerName?: string; + bikeCount: number; + activeRentals: number; + status: 'active' | 'inactive'; + coordinates?: { lat: number; lng: number }; + openTime: string; + closeTime: string; + isHeadOffice?: boolean; +} + +const mockHubs: Hub[] = [ + { + id: 'HUB-001', + name: 'JAIBEN Head Office', + address: 'House 12, Road 17, Gulshan 1, Dhaka', + phone: '+8801712345678', + managerName: 'Rahim Ahmed', + bikeCount: 25, + activeRentals: 12, + status: 'active', + coordinates: { lat: 23.7925, lng: 90.4174 }, + openTime: '06:00', + closeTime: '23:00', + isHeadOffice: true + }, + { + id: 'HUB-002', + name: 'Banani Hub', + address: 'House 5, Road 11, Banani, Dhaka', + phone: '+8801812345678', + managerName: 'Karim Hasan', + bikeCount: 18, + activeRentals: 8, + status: 'active', + coordinates: { lat: 23.7785, lng: 90.4190 }, + openTime: '06:00', + closeTime: '23:00' + }, + { + id: 'HUB-003', + name: 'Uttara Hub', + address: 'Sector 11, Uttara, Dhaka', + phone: '+8801912345678', + managerName: 'Jamal Uddin', + bikeCount: 30, + activeRentals: 15, + status: 'active', + coordinates: { lat: 23.8657, lng: 90.4027 }, + openTime: '06:00', + closeTime: '23:00' + }, + { + id: 'HUB-004', + name: 'Mirpur Hub', + address: 'Section 10, Mirpur, Dhaka', + phone: '+8801512345678', + bikeCount: 0, + activeRentals: 0, + status: 'inactive', + openTime: '06:00', + closeTime: '23:00' + } +]; + +export default function HubsPage() { + const [hubs, setHubs] = useState(mockHubs); + const [search, setSearch] = useState(''); + const [showCreateModal, setShowCreateModal] = useState(false); + const [editingHub, setEditingHub] = useState(null); + + const [formData, setFormData] = useState({ + name: '', + address: '', + phone: '', + managerName: '', + openTime: '06:00', + closeTime: '23:00', + isHeadOffice: false, + }); + + const filteredHubs = hubs.filter(h => + h.name.toLowerCase().includes(search.toLowerCase()) || + h.address.toLowerCase().includes(search.toLowerCase()) + ); + + const handleSave = () => { + if (!formData.name || !formData.address) return; + + if (editingHub) { + setHubs(hubs.map(h => h.id === editingHub.id ? { + ...h, + ...formData, + status: editingHub.status, + bikeCount: editingHub.bikeCount, + activeRentals: editingHub.activeRentals, + } : h)); + } else { + const newHub: Hub = { + id: `HUB-${String(hubs.length + 1).padStart(3, '0')}`, + ...formData, + bikeCount: 0, + activeRentals: 0, + status: 'active', + }; + setHubs([...hubs, newHub]); + } + + setShowCreateModal(false); + setEditingHub(null); + setFormData({ name: '', address: '', phone: '', managerName: '', openTime: '06:00', closeTime: '23:00', isHeadOffice: false }); + }; + + const handleDelete = (id: string) => { + if (confirm('Are you sure you want to delete this hub?')) { + setHubs(hubs.filter(h => h.id !== id)); + } + }; + + const openEdit = (hub: Hub) => { + setEditingHub(hub); + setFormData({ + name: hub.name, + address: hub.address, + phone: hub.phone, + managerName: hub.managerName || '', + openTime: hub.openTime, + closeTime: hub.closeTime, + isHeadOffice: hub.isHeadOffice || false, + }); + setShowCreateModal(true); + }; + + return ( +
+
+
+

Hubs

+

Manage hub locations and branches

+
+ +
+ +
+
+

{hubs.length}

+

Total Hubs

+
+
+

{hubs.filter(h => h.status === 'active').length}

+

Active Hubs

+
+
+

{hubs.reduce((a, h) => a + h.bikeCount, 0)}

+

Total Bikes

+
+
+

{hubs.reduce((a, h) => a + h.activeRentals, 0)}

+

Active Rentals

+
+
+ +
+
+
+ + setSearch(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent" + /> +
+
+ +
+ + + + + + + + + + + + + + + + {filteredHubs.map(hub => ( + + + + + + + + + + + + ))} + +
Hub NameAddressPhoneManagerBikesRentalsHoursStatusActions
+ + + {hub.name} + + + {hub.address} + + {hub.phone} + + {hub.managerName || '-'} + + {hub.bikeCount} + + {hub.activeRentals} + + {hub.openTime} - {hub.closeTime} + + + {hub.status} + + {hub.isHeadOffice && ( + + Head Office + + )} + +
+ + + + + +
+
+
+
+ + {showCreateModal && ( +
+
+
+

{editingHub ? 'Edit Hub' : 'Add New Hub'}

+ +
+
+
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" + placeholder="Gulshan Hub" + /> +
+
+ +