feat: add support for battery swapping status and bike image gallery management in fleet details
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
GaugeCircle, CheckCircle, AlertTriangle, Activity, Award, TrendingUp, Wallet,
|
||||
MoreHorizontal, Map, Navigation2, Satellite, FileCheck, FileX, Clock3,
|
||||
History, CreditCard, User2, Phone, Mail, MapPinned, ExternalLink, Plus,
|
||||
AlertCircle, Image as ImageIcon
|
||||
AlertCircle, Image as ImageIcon, Camera
|
||||
} from 'lucide-react';
|
||||
|
||||
interface GPSDevice {
|
||||
@@ -104,7 +104,8 @@ interface BatteryHistory {
|
||||
serialNumber: string;
|
||||
assignedDate: string;
|
||||
returnedDate?: string;
|
||||
status: 'active' | 'returned';
|
||||
swappedToBatteryId?: string;
|
||||
status: 'active' | 'returned' | 'swapped';
|
||||
socStart: number;
|
||||
socEnd?: number;
|
||||
monthlyRent?: number;
|
||||
@@ -152,6 +153,12 @@ interface Bike {
|
||||
damageHistory?: DamageRecord[];
|
||||
maintenanceHistory?: MaintenanceRecord[];
|
||||
batteryHistory?: BatteryHistory[];
|
||||
bikeImages?: {
|
||||
front?: string;
|
||||
back?: string;
|
||||
left?: string;
|
||||
right?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const mockBikes: Bike[] = [
|
||||
@@ -188,8 +195,8 @@ const mockBikes: Bike[] = [
|
||||
batteryHistory: [
|
||||
{ id: 'BH001', batteryId: 'BAT-001', brand: 'EVE Energy', model: 'Li-Ion 60V50Ah', serialNumber: 'SN-2024-00001', assignedDate: '2024-03-15', status: 'active', socStart: 85, monthlyRent: 1500 },
|
||||
{ id: 'BH002', batteryId: 'BAT-005', brand: 'Samsung SDI', model: 'Li-Ion 60V45Ah', serialNumber: 'SN-2023-00045', assignedDate: '2024-01-10', returnedDate: '2024-03-14', status: 'returned', socStart: 80, socEnd: 45, monthlyRent: 1200 },
|
||||
{ id: 'BH003', batteryId: 'BAT-008', brand: 'LG Chem', model: 'Li-Ion 48V40Ah', serialNumber: 'SN-2023-00012', assignedDate: '2023-11-05', returnedDate: '2024-01-09', status: 'returned', socStart: 90, socEnd: 60, monthlyRent: 1000 },
|
||||
{ id: 'BH004', batteryId: 'BAT-003', brand: 'Panasonic', model: 'Li-Ion 60V50Ah', serialNumber: 'SN-2023-00078', assignedDate: '2023-08-20', returnedDate: '2023-11-04', status: 'returned', socStart: 88, socEnd: 55, monthlyRent: 1500 },
|
||||
{ id: 'BH003', batteryId: 'BAT-008', brand: 'LG Chem', model: 'Li-Ion 48V40Ah', serialNumber: 'SN-2023-00012', assignedDate: '2023-11-05', returnedDate: '2024-01-09', status: 'swapped', socStart: 90, socEnd: 60, monthlyRent: 1000 },
|
||||
{ id: 'BH004', batteryId: 'BAT-003', brand: 'Panasonic', model: 'Li-Ion 60V50Ah', serialNumber: 'SN-2023-00078', assignedDate: '2023-08-20', returnedDate: '2023-11-04', status: 'swapped', socStart: 88, socEnd: 55, monthlyRent: 1500 },
|
||||
{ id: 'BH005', batteryId: 'BAT-002', brand: 'Sony', model: 'Li-Ion 48V35Ah', serialNumber: 'SN-2023-00034', assignedDate: '2023-05-15', returnedDate: '2023-08-19', status: 'returned', socStart: 75, socEnd: 40, monthlyRent: 900 },
|
||||
{ id: 'BH006', batteryId: 'BAT-012', brand: 'BYD', model: 'LiFePO4 60V40Ah', serialNumber: 'SN-2023-00056', assignedDate: '2024-04-20', returnedDate: '2024-06-15', status: 'returned', socStart: 92, socEnd: 35, monthlyRent: 1300 },
|
||||
{ id: 'BH007', batteryId: 'BAT-015', brand: 'CATL', model: 'Li-Ion 72V50Ah', serialNumber: 'SN-2024-00089', assignedDate: '2024-06-20', status: 'active', socStart: 88, monthlyRent: 1800 },
|
||||
@@ -198,7 +205,15 @@ const mockBikes: Bike[] = [
|
||||
{ id: 'BH010', batteryId: 'BAT-007', brand: 'Kexin', model: 'Li-Ion 48V36Ah', serialNumber: 'SN-2022-00045', assignedDate: '2022-12-05', returnedDate: '2023-02-08', status: 'returned', socStart: 85, socEnd: 55, monthlyRent: 850 },
|
||||
{ id: 'BH011', batteryId: 'BAT-004', brand: 'Faraday', model: 'LiFePO4 48V42Ah', serialNumber: 'SN-2022-00089', assignedDate: '2022-09-15', returnedDate: '2022-12-04', status: 'returned', socStart: 90, socEnd: 48, monthlyRent: 1200 },
|
||||
{ id: 'BH012', batteryId: 'BAT-006', brand: 'Reliance', model: 'Lead Acid 48V32Ah', serialNumber: 'SN-2022-00034', assignedDate: '2022-06-20', returnedDate: '2022-09-14', status: 'returned', socStart: 95, socEnd: 30, monthlyRent: 600 },
|
||||
]
|
||||
{ id: 'BH013', batteryId: 'BAT-020', brand: 'Maxell', model: 'Li-Ion 60V45Ah', serialNumber: 'SN-2024-00123', assignedDate: '2024-07-10', swappedToBatteryId: 'BAT-025', status: 'swapped', socStart: 75, socEnd: 65, monthlyRent: 1400 },
|
||||
{ id: 'BH014', batteryId: 'BAT-018', brand: 'Nikola', model: 'LiFePO4 48V40Ah', serialNumber: 'SN-2023-00078', assignedDate: '2024-05-05', swappedToBatteryId: 'BAT-022', status: 'swapped', socStart: 82, socEnd: 55, monthlyRent: 1150 },
|
||||
],
|
||||
bikeImages: {
|
||||
front: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
|
||||
back: 'https://images.unsplash.com/photo-1591637333184-19aa84de3fbd?w=400&h=300&fit=crop',
|
||||
left: 'https://images.unsplash.com/photo-1622185135505-2d795043906a?w=400&h=300&fit=crop',
|
||||
right: 'https://images.unsplash.com/photo-1609630875171-b1321377ee53?w=400&h=300&fit=crop',
|
||||
}
|
||||
},
|
||||
{
|
||||
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',
|
||||
@@ -773,7 +788,7 @@ function OverviewTab({ bike }: { bike: Bike }) {
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Battery ID</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Brand/Model</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Assigned Date</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Returned Date</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Returned/Swapped</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Start SOC</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">End SOC</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-semibold text-slate-500">Monthly Rent</th>
|
||||
@@ -786,13 +801,20 @@ function OverviewTab({ bike }: { bike: Bike }) {
|
||||
<td className="px-3 py-2 text-sm text-slate-700 font-medium">{bh.batteryId}</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">{bh.brand} {bh.model}</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">{bh.assignedDate}</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">{bh.returnedDate || '-'}</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">
|
||||
{bh.status === 'swapped' ? (
|
||||
<>
|
||||
<span className="text-blue-600 block">Swapped to {bh.swappedToBatteryId}</span>
|
||||
<span className="text-blue-600 text-[10px] block opacity-75">{bh.returnedDate}</span>
|
||||
</>
|
||||
) : bh.returnedDate || '-'}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">{bh.socStart}%</td>
|
||||
<td className="px-3 py-2 text-sm text-slate-600">{bh.socEnd ? `${bh.socEnd}%` : '-'}</td>
|
||||
<td className="px-3 py-2 text-sm text-green-600">৳{bh.monthlyRent}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`text-xs font-medium px-2 py-1 rounded-full ${bh.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{bh.status === 'active' ? 'Active' : 'Returned'}
|
||||
<span className={`text-xs font-medium px-2 py-1 rounded-full ${bh.status === 'active' ? 'bg-green-100 text-green-700' : bh.status === 'swapped' ? 'bg-blue-100 text-blue-700' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{bh.status === 'active' ? 'Active' : bh.status === 'swapped' ? 'Swapped' : 'Returned'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -926,9 +948,50 @@ function GPSTab({ bike }: { bike: Bike }) {
|
||||
|
||||
function DocumentsTab({ bike }: { bike: Bike }) {
|
||||
const docs = bike.documents || [];
|
||||
const [images, setImages] = useState(bike.bikeImages || { front: '', back: '', left: '', right: '' });
|
||||
const [uploading, setUploading] = useState<string | null>(null);
|
||||
|
||||
const handleImageUpload = (view: 'front' | 'back' | 'left' | 'right', e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setUploading(view);
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setImages(prev => ({ ...prev, [view]: reader.result as string }));
|
||||
setUploading(null);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100">
|
||||
<h3 className="font-semibold text-slate-700 mb-4">Bike Images</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{(['front', 'back', 'left', 'right'] as const).map(view => (
|
||||
<div key={view} className="space-y-2">
|
||||
<p className="text-xs font-medium text-slate-500 capitalize">{view} View</p>
|
||||
<div className="relative aspect-video bg-slate-100 rounded-lg overflow-hidden border border-slate-200">
|
||||
{images[view] ? (
|
||||
<img src={images[view]} alt={`${view} view`} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Camera className="w-8 h-8 text-slate-300" />
|
||||
</div>
|
||||
)}
|
||||
<label className="absolute inset-0 cursor-pointer hover:bg-black/20 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity">
|
||||
<span className="bg-white px-3 py-1 rounded-full text-xs font-medium text-slate-700 shadow">
|
||||
{uploading === view ? 'Uploading...' : 'Upload'}
|
||||
</span>
|
||||
<input type="file" accept="image/*" className="hidden" onChange={(e) => handleImageUpload(view, e)} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-slate-100">
|
||||
<h3 className="font-semibold text-slate-700 mb-4">Bike Documents</h3>
|
||||
{docs.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user