From a4ff86b9533870441121f2928e43a641ffb3184d Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Sun, 17 May 2026 20:24:47 +0600 Subject: [PATCH] feat: add battery management tab with support for viewing, adding, editing, and deleting batteries in hub dashboard --- src/app/admin/hub/[id]/page.tsx | 348 ++++++++++++++++++++++++++++++-- 1 file changed, 330 insertions(+), 18 deletions(-) diff --git a/src/app/admin/hub/[id]/page.tsx b/src/app/admin/hub/[id]/page.tsx index 78d1c78..731bd9f 100644 --- a/src/app/admin/hub/[id]/page.tsx +++ b/src/app/admin/hub/[id]/page.tsx @@ -4,7 +4,7 @@ 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 + Navigation, User, Wallet, DollarSign, CheckCircle, AlertTriangle, Battery } from 'lucide-react'; interface Hub { @@ -29,6 +29,17 @@ interface BikeInfo { status: 'available' | 'rented' | 'maintenance'; } +interface BatteryInfo { + id: string; + brand: string; + model: string; + serialNumber: string; + status: 'available' | 'charging' | 'deployed' | 'maintenance'; + chargeLevel: number; + assignedBike?: string; + assignedDate?: string; +} + const mockHub: Hub = { id: 'HUB-001', name: 'JAIBEN Head Office', @@ -52,6 +63,15 @@ const mockHubBikes: BikeInfo[] = [ { id: 'BIKE-005', model: 'Yadea G5', plate: 'Dhaka Metro Ha-5678', status: 'maintenance' }, ]; +const mockHubBatteries: BatteryInfo[] = [ + { id: 'BAT-001', brand: 'Lithium', model: '60V/30Ah', serialNumber: 'LTH-2024-001', status: 'available', chargeLevel: 95 }, + { id: 'BAT-002', brand: 'Lithium', model: '60V/30Ah', serialNumber: 'LTH-2024-002', status: 'charging', chargeLevel: 75 }, + { id: 'BAT-003', brand: 'Lithium', model: '60V/40Ah', serialNumber: 'LTH-2024-003', status: 'deployed', chargeLevel: 45, assignedBike: 'BIKE-002', assignedDate: '2024-03-15' }, + { id: 'BAT-004', brand: 'Lithium', model: '60V/30Ah', serialNumber: 'LTH-2024-004', status: 'available', chargeLevel: 88 }, + { id: 'BAT-005', brand: 'Lithium', model: '48V/25Ah', serialNumber: 'LTH-2024-005', status: 'maintenance', chargeLevel: 0 }, + { id: 'BAT-006', brand: 'Lithium', model: '60V/30Ah', serialNumber: 'LTH-2024-006', status: 'deployed', chargeLevel: 62, assignedBike: 'BIKE-004', assignedDate: '2024-03-18' }, +]; + interface RentalInfo { id: string; userName: string; @@ -77,10 +97,20 @@ export default function HubDetailPage() { const [hub, setHub] = useState(mockHub); const [bikes, setBikes] = useState(mockHubBikes); + const [batteries, setBatteries] = useState(mockHubBatteries); const [rentals, setRentals] = useState(mockHubRentals); const [editMode, setEditMode] = useState(false); const [editForm, setEditForm] = useState(hub); - const [activeTab, setActiveTab] = useState<'overview' | 'bikes' | 'rentals'>('overview'); + const [activeTab, setActiveTab] = useState<'overview' | 'bikes' | 'batteries' | 'rentals'>('overview'); + const [assignModal, setAssignModal] = useState(null); + const [selectedBike, setSelectedBike] = useState(''); + const [addBikeModal, setAddBikeModal] = useState(false); + const [addBatteryModal, setAddBatteryModal] = useState(false); + const [editingBike, setEditingBike] = useState(null); + const [editingBattery, setEditingBattery] = useState(null); + const [bikeForm, setBikeForm] = useState<{ model: string; plate: string; status: 'available' | 'rented' | 'maintenance' }>({ model: '', plate: '', status: 'available' }); + const [batteryForm, setBatteryForm] = useState<{ brand: string; model: string; serialNumber: string; chargeLevel: number; status: 'available' | 'charging' | 'deployed' | 'maintenance' }>({ brand: '', model: '', serialNumber: '', chargeLevel: 100, status: 'available' }); + const [deleteModal, setDeleteModal] = useState<{ type: 'bike' | 'battery'; item: BikeInfo | BatteryInfo } | null>(null); const handleSaveEdit = () => { setHub(editForm); @@ -155,8 +185,8 @@ export default function HubDetailPage() { + @@ -351,8 +390,8 @@ export default function HubDetailPage() {
{bike.status} @@ -360,6 +399,73 @@ export default function HubDetailPage() {

{bike.model}

{bike.plate}

ID: {bike.id}

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

Hub Batteries ({batteries.length})

+ +
+
+ {batteries.map(battery => ( +
+
+ + + {battery.status} + +
+

{battery.brand} {battery.model}

+

SN: {battery.serialNumber}

+
+
+ Charge + 50 ? 'text-green-600' : battery.chargeLevel > 20 ? 'text-amber-600' : 'text-red-600'}`}> + {battery.chargeLevel}% + +
+
+
50 ? 'bg-green-500' : battery.chargeLevel > 20 ? 'bg-amber-500' : 'bg-red-500'}`} style={{ width: `${battery.chargeLevel}%` }} /> +
+
+ {battery.assignedBike && ( +

Assigned to: {battery.assignedBike}

+ )} +
+ + {battery.assignedBike && ( + + )} +
+
+ + +
))}
@@ -370,9 +476,9 @@ export default function HubDetailPage() {

Hub Rentals ({rentals.length})

- + */}
@@ -416,11 +522,10 @@ export default function HubDetailPage() { ৳{rental.totalPaid.toLocaleString()} @@ -433,6 +538,213 @@ export default function HubDetailPage() { )} + + {assignModal && ( +
+
+
+

Assign Battery to Bike

+ +
+
+
+

Battery

+

{assignModal.brand} {assignModal.model}

+

SN: {assignModal.serialNumber}

+
+
+ + +
+ {selectedBike && ( +
+

Battery will be assigned to:

+

+ {bikes.find(b => b.id === selectedBike)?.model} ({bikes.find(b => b.id === selectedBike)?.plate}) +

+
+ )} +
+
+ + +
+
+
+ )} + + {addBikeModal && ( +
+
+
+

{editingBike ? 'Edit Bike' : 'Add New Bike'}

+ +
+
+
+ + setBikeForm(f => ({ ...f, model: e.target.value }))} placeholder="e.g. AIMA Lightning" disabled={!!editingBike} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm disabled:bg-slate-100 disabled:text-slate-500" /> +
+
+ + setBikeForm(f => ({ ...f, plate: e.target.value }))} placeholder="e.g. Dhaka Metro Cha-1234" disabled={!!editingBike} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm disabled:bg-slate-100 disabled:text-slate-500" /> +
+
+ + +
+
+
+ + +
+
+
+ )} + + {addBatteryModal && ( +
+
+
+

{editingBattery ? 'Edit Battery' : 'Add New Battery'}

+ +
+
+
+ + setBatteryForm(f => ({ ...f, brand: e.target.value }))} placeholder="e.g. Lithium" disabled={!!editingBattery} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm disabled:bg-slate-100 disabled:text-slate-500" /> +
+
+ + setBatteryForm(f => ({ ...f, model: e.target.value }))} placeholder="e.g. 60V/30Ah" disabled={!!editingBattery} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm disabled:bg-slate-100 disabled:text-slate-500" /> +
+
+ + setBatteryForm(f => ({ ...f, serialNumber: e.target.value }))} placeholder="e.g. LTH-2024-001" disabled={!!editingBattery} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm disabled:bg-slate-100 disabled:text-slate-500" /> +
+
+ + setBatteryForm(f => ({ ...f, chargeLevel: parseInt(e.target.value) || 0 }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + +
+
+
+ + +
+
+
+ )} + + {deleteModal && ( +
+
+
+
+ +
+

Confirm Delete

+

+ Are you sure you want to delete this {deleteModal.type === 'bike' ? 'bike' : 'battery'}? + {deleteModal.type === 'bike' && ( + {(deleteModal.item as BikeInfo).model} - {(deleteModal.item as BikeInfo).plate} + )} + {deleteModal.type === 'battery' && ( + {(deleteModal.item as BatteryInfo).brand} {(deleteModal.item as BatteryInfo).model} + )} +

+
+
+ + +
+
+
+ )} ); } \ No newline at end of file
- + {rental.status}