feat: add battery management tab with support for viewing, adding, editing, and deleting batteries in hub dashboard
This commit is contained in:
@@ -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<Hub>(mockHub);
|
||||
const [bikes, setBikes] = useState<BikeInfo[]>(mockHubBikes);
|
||||
const [batteries, setBatteries] = useState<BatteryInfo[]>(mockHubBatteries);
|
||||
const [rentals, setRentals] = useState<RentalInfo[]>(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<BatteryInfo | null>(null);
|
||||
const [selectedBike, setSelectedBike] = useState('');
|
||||
const [addBikeModal, setAddBikeModal] = useState(false);
|
||||
const [addBatteryModal, setAddBatteryModal] = useState(false);
|
||||
const [editingBike, setEditingBike] = useState<BikeInfo | null>(null);
|
||||
const [editingBattery, setEditingBattery] = useState<BatteryInfo | null>(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() {
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`py-4 text-sm font-medium border-b-2 transition-colors ${activeTab === 'overview'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
Overview
|
||||
@@ -164,17 +194,26 @@ export default function HubDetailPage() {
|
||||
<button
|
||||
onClick={() => setActiveTab('bikes')}
|
||||
className={`py-4 text-sm font-medium border-b-2 transition-colors ${activeTab === 'bikes'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
Bikes ({bikes.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('batteries')}
|
||||
className={`py-4 text-sm font-medium border-b-2 transition-colors ${activeTab === 'batteries'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
Batteries ({batteries.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('rentals')}
|
||||
className={`py-4 text-sm font-medium border-b-2 transition-colors ${activeTab === 'rentals'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
Rentals ({rentals.length})
|
||||
@@ -341,7 +380,7 @@ export default function HubDetailPage() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Hub Bikes ({bikes.length})</h3>
|
||||
<button className="px-4 py-2 bg-accent text-white rounded-lg text-sm flex items-center gap-2">
|
||||
<button onClick={() => { setBikeForm({ model: '', plate: '', status: 'available' }); setAddBikeModal(true); }} className="px-4 py-2 bg-accent text-white rounded-lg text-sm flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> Add Bike
|
||||
</button>
|
||||
</div>
|
||||
@@ -351,8 +390,8 @@ export default function HubDetailPage() {
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Bike className="w-5 h-5 text-slate-400" />
|
||||
<span className={`text-xs font-medium px-2 py-1 rounded-full ${bike.status === 'available' ? 'bg-green-100 text-green-700' :
|
||||
bike.status === 'rented' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
bike.status === 'rented' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{bike.status}
|
||||
</span>
|
||||
@@ -360,6 +399,73 @@ export default function HubDetailPage() {
|
||||
<p className="font-medium text-slate-800">{bike.model}</p>
|
||||
<p className="text-sm text-slate-500">{bike.plate}</p>
|
||||
<p className="text-xs text-slate-400 mt-2">ID: {bike.id}</p>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<button onClick={() => { setEditingBike(bike); setBikeForm({ model: bike.model, plate: bike.plate, status: bike.status }); setAddBikeModal(true); }} className="flex-1 py-1.5 text-xs font-medium text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-50">Edit</button>
|
||||
<button onClick={() => setDeleteModal({ type: 'bike', item: bike })} className="flex-1 py-1.5 text-xs font-medium text-red-600 border border-red-200 rounded-lg hover:bg-red-50">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'batteries' && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Hub Batteries ({batteries.length})</h3>
|
||||
<button onClick={() => { setBatteryForm({ brand: '', model: '', serialNumber: '', chargeLevel: 100, status: 'available' }); setAddBatteryModal(true); }} className="px-4 py-2 bg-accent text-white rounded-lg text-sm flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> Add Battery
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{batteries.map(battery => (
|
||||
<div key={battery.id} className="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Battery className="w-5 h-5 text-slate-400" />
|
||||
<span className={`text-xs font-medium px-2 py-1 rounded-full ${battery.status === 'available' ? 'bg-green-100 text-green-700' :
|
||||
battery.status === 'charging' ? 'bg-blue-100 text-blue-700' :
|
||||
battery.status === 'deployed' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{battery.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="font-medium text-slate-800">{battery.brand} {battery.model}</p>
|
||||
<p className="text-sm text-slate-500">SN: {battery.serialNumber}</p>
|
||||
<div className="mt-2">
|
||||
<div className="flex items-center justify-between text-xs mb-1">
|
||||
<span className="text-slate-500">Charge</span>
|
||||
<span className={`font-medium ${battery.chargeLevel > 50 ? 'text-green-600' : battery.chargeLevel > 20 ? 'text-amber-600' : 'text-red-600'}`}>
|
||||
{battery.chargeLevel}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div className={`h-full rounded-full ${battery.chargeLevel > 50 ? 'bg-green-500' : battery.chargeLevel > 20 ? 'bg-amber-500' : 'bg-red-500'}`} style={{ width: `${battery.chargeLevel}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
{battery.assignedBike && (
|
||||
<p className="text-xs text-slate-400 mt-2">Assigned to: {battery.assignedBike}</p>
|
||||
)}
|
||||
<div className="mt-2 flex gap-2">
|
||||
<button
|
||||
onClick={() => { setAssignModal(battery); setSelectedBike(battery.assignedBike || ''); }}
|
||||
className="flex-1 py-1.5 text-xs font-medium text-accent border border-accent rounded-lg hover:bg-accent hover:text-white transition-colors"
|
||||
>
|
||||
{battery.assignedBike ? 'Reassign' : 'Assign'}
|
||||
</button>
|
||||
{battery.assignedBike && (
|
||||
<button
|
||||
onClick={() => { setBatteries(prev => prev.map(b => b.id === battery.id ? { ...b, assignedBike: undefined, assignedDate: undefined, status: 'available' as const } : b)); }}
|
||||
className="py-1.5 px-2 text-xs font-medium text-orange-600 border border-orange-200 rounded-lg hover:bg-orange-50"
|
||||
>
|
||||
Unassign
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button onClick={() => { setEditingBattery(battery); setBatteryForm({ brand: battery.brand, model: battery.model, serialNumber: battery.serialNumber, chargeLevel: battery.chargeLevel, status: battery.status }); setAddBatteryModal(true); }} className="flex-1 py-1.5 text-xs font-medium text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-50">Edit</button>
|
||||
<button onClick={() => setDeleteModal({ type: 'battery', item: battery })} className="flex-1 py-1.5 text-xs font-medium text-red-600 border border-red-200 rounded-lg hover:bg-red-50">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -370,9 +476,9 @@ export default function HubDetailPage() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Hub Rentals ({rentals.length})</h3>
|
||||
<button className="px-4 py-2 bg-accent text-white rounded-lg text-sm flex items-center gap-2">
|
||||
{/* <button className="px-4 py-2 bg-accent text-white rounded-lg text-sm flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> New Rental
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
@@ -416,11 +522,10 @@ export default function HubDetailPage() {
|
||||
<span className="text-sm font-medium text-green-600">৳{rental.totalPaid.toLocaleString()}</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${
|
||||
rental.status === 'active' ? 'bg-green-100 text-green-700' :
|
||||
rental.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-blue-100 text-blue-700'
|
||||
}`}>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${rental.status === 'active' ? 'bg-green-100 text-green-700' :
|
||||
rental.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-blue-100 text-blue-700'
|
||||
}`}>
|
||||
{rental.status}
|
||||
</span>
|
||||
</td>
|
||||
@@ -433,6 +538,213 @@ export default function HubDetailPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{assignModal && (
|
||||
<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-md">
|
||||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||
<h3 className="font-semibold text-slate-800">Assign Battery to Bike</h3>
|
||||
<button onClick={() => setAssignModal(null)} className="text-slate-400 hover:text-slate-600">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="bg-slate-50 p-3 rounded-lg">
|
||||
<p className="text-sm text-slate-500">Battery</p>
|
||||
<p className="font-medium text-slate-800">{assignModal.brand} {assignModal.model}</p>
|
||||
<p className="text-xs text-slate-500">SN: {assignModal.serialNumber}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Bike</label>
|
||||
<select
|
||||
value={selectedBike}
|
||||
onChange={(e) => setSelectedBike(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="">-- Select a bike --</option>
|
||||
{bikes.filter(b => b.status !== 'maintenance').map(bike => (
|
||||
<option key={bike.id} value={bike.id}>
|
||||
{bike.model} - {bike.plate}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{selectedBike && (
|
||||
<div className="bg-green-50 p-3 rounded-lg">
|
||||
<p className="text-sm text-green-600">Battery will be assigned to:</p>
|
||||
<p className="font-medium text-green-800">
|
||||
{bikes.find(b => b.id === selectedBike)?.model} ({bikes.find(b => b.id === selectedBike)?.plate})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||
<button onClick={() => setAssignModal(null)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBatteries(prev => prev.map(b => b.id === assignModal.id ? {
|
||||
...b,
|
||||
assignedBike: selectedBike || undefined,
|
||||
assignedDate: selectedBike ? new Date().toISOString().split('T')[0] : undefined,
|
||||
status: selectedBike ? 'deployed' as const : 'available' as const
|
||||
} : b));
|
||||
setAssignModal(null);
|
||||
}}
|
||||
className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark"
|
||||
>
|
||||
{assignModal.assignedBike ? 'Update Assignment' : 'Assign Battery'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{addBikeModal && (
|
||||
<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-md">
|
||||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||
<h3 className="font-semibold text-slate-800">{editingBike ? 'Edit Bike' : 'Add New Bike'}</h3>
|
||||
<button onClick={() => { setAddBikeModal(false); setEditingBike(null); }} className="text-slate-400 hover:text-slate-600">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Model</label>
|
||||
<input type="text" value={bikeForm.model} onChange={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">License Plate</label>
|
||||
<input type="text" value={bikeForm.plate} onChange={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Status</label>
|
||||
<select value={bikeForm.status} onChange={(e) => setBikeForm(f => ({ ...f, status: e.target.value as any }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
||||
<option value="available">Available</option>
|
||||
<option value="rented">Rented</option>
|
||||
<option value="maintenance">Maintenance</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||
<button onClick={() => { setAddBikeModal(false); setEditingBike(null); }} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (editingBike) {
|
||||
setBikes(prev => prev.map(b => b.id === editingBike.id ? { ...b, ...bikeForm } : b));
|
||||
} else {
|
||||
const newBike: BikeInfo = { id: `BIKE-${Date.now()}`, ...bikeForm };
|
||||
setBikes(prev => [...prev, newBike]);
|
||||
}
|
||||
setAddBikeModal(false);
|
||||
setEditingBike(null);
|
||||
}}
|
||||
disabled={!bikeForm.model || !bikeForm.plate}
|
||||
className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark disabled:opacity-50"
|
||||
>
|
||||
{editingBike ? 'Update Bike' : 'Add Bike'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{addBatteryModal && (
|
||||
<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-md">
|
||||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||
<h3 className="font-semibold text-slate-800">{editingBattery ? 'Edit Battery' : 'Add New Battery'}</h3>
|
||||
<button onClick={() => { setAddBatteryModal(false); setEditingBattery(null); }} className="text-slate-400 hover:text-slate-600">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Brand</label>
|
||||
<input type="text" value={batteryForm.brand} onChange={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Model</label>
|
||||
<input type="text" value={batteryForm.model} onChange={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Serial Number</label>
|
||||
<input type="text" value={batteryForm.serialNumber} onChange={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Charge Level (%)</label>
|
||||
<input type="number" min="0" max="100" value={batteryForm.chargeLevel} onChange={(e) => setBatteryForm(f => ({ ...f, chargeLevel: parseInt(e.target.value) || 0 }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Status</label>
|
||||
<select value={batteryForm.status} onChange={(e) => setBatteryForm(f => ({ ...f, status: e.target.value as any }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
||||
<option value="available">Available</option>
|
||||
<option value="charging">Charging</option>
|
||||
<option value="deployed">Deployed</option>
|
||||
<option value="maintenance">Maintenance</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||
<button onClick={() => { setAddBatteryModal(false); setEditingBattery(null); }} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (editingBattery) {
|
||||
setBatteries(prev => prev.map(b => b.id === editingBattery.id ? { ...b, ...batteryForm } : b));
|
||||
} else {
|
||||
const newBattery: BatteryInfo = { id: `BAT-${Date.now()}`, ...batteryForm, assignedBike: undefined, assignedDate: undefined };
|
||||
setBatteries(prev => [...prev, newBattery]);
|
||||
}
|
||||
setAddBatteryModal(false);
|
||||
setEditingBattery(null);
|
||||
}}
|
||||
disabled={!batteryForm.brand || !batteryForm.model || !batteryForm.serialNumber}
|
||||
className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark disabled:opacity-50"
|
||||
>
|
||||
{editingBattery ? 'Update Battery' : 'Add Battery'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{deleteModal && (
|
||||
<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-sm">
|
||||
<div className="p-6 text-center">
|
||||
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<AlertTriangle className="w-6 h-6 text-red-600" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-slate-800 mb-2">Confirm Delete</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
Are you sure you want to delete this {deleteModal.type === 'bike' ? 'bike' : 'battery'}?
|
||||
{deleteModal.type === 'bike' && (
|
||||
<span className="block mt-1 font-medium text-slate-700">{(deleteModal.item as BikeInfo).model} - {(deleteModal.item as BikeInfo).plate}</span>
|
||||
)}
|
||||
{deleteModal.type === 'battery' && (
|
||||
<span className="block mt-1 font-medium text-slate-700">{(deleteModal.item as BatteryInfo).brand} {(deleteModal.item as BatteryInfo).model}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||
<button onClick={() => setDeleteModal(null)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (deleteModal.type === 'bike') {
|
||||
setBikes(prev => prev.filter(b => b.id !== (deleteModal.item as BikeInfo).id));
|
||||
} else {
|
||||
setBatteries(prev => prev.filter(b => b.id !== (deleteModal.item as BatteryInfo).id));
|
||||
}
|
||||
setDeleteModal(null);
|
||||
}}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user