From bd18c265ca4a6e4410768ec938c9b7448b116547 Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Sat, 16 May 2026 19:33:28 +0600 Subject: [PATCH] feat: add damage and maintenance history tracking with CRUD functionality to battery detail page --- src/app/admin/batteries/[id]/page.tsx | 516 +++++++++++++++++++++++++- 1 file changed, 508 insertions(+), 8 deletions(-) diff --git a/src/app/admin/batteries/[id]/page.tsx b/src/app/admin/batteries/[id]/page.tsx index 0ec033d..f4e46c4 100644 --- a/src/app/admin/batteries/[id]/page.tsx +++ b/src/app/admin/batteries/[id]/page.tsx @@ -5,7 +5,7 @@ import Link from 'next/link'; import { Battery, ArrowLeft, X, BatteryCharging, Activity, Gauge, MapPin, Bike, User, History, Calendar, DollarSign, CheckCircle, Clock, ArrowRightLeft, Handshake, TrendingUp, Edit, - RefreshCw + RefreshCw, AlertTriangle, Wrench, Plus, Trash2 } from 'lucide-react'; interface BMSData { @@ -43,6 +43,28 @@ interface OwnershipLog { timestamp: string; } +interface DamageRecord { + id: string; + date: string; + type: string; + description: string; + reportedBy: string; + estimatedCost: number; + actualCost?: number; + status: 'reported' | 'in-progress' | 'resolved'; +} + +interface MaintenanceRecord { + id: string; + date: string; + type: 'routine' | 'repair' | 'replacement' | 'inspection'; + description: string; + cost: number; + performedBy: string; + nextDueDate?: string; + status: 'completed' | 'pending' | 'overdue'; +} + interface Battery { id: string; serialNumber: string; @@ -73,6 +95,8 @@ interface Battery { bmsData?: BMSData; monthlyRent?: number; ownershipLogs: OwnershipLog[]; + damageHistory?: DamageRecord[]; + maintenanceHistory?: MaintenanceRecord[]; } const mockBattery: Battery = { @@ -106,7 +130,16 @@ const mockBattery: Battery = { { id: 'OL-001', batteryId: 'BAT-001', fromOwner: 'JAIBEN Hub', fromOwnerType: 'hub', toOwner: 'Rahim Ahmed', toOwnerType: 'biker', toBikeId: 'EV001', toBikeModel: 'Etron ET50', toHubId: 'HUB-001', toHubName: 'JAIBEN Head Office', action: 'rented', rentAmount: 1500, timestamp: '2024-03-15 08:30:00' }, { id: 'OL-002', batteryId: 'BAT-001', fromOwner: 'Rahim Ahmed', fromOwnerType: 'biker', toOwner: 'JAIBEN Hub', toOwnerType: 'hub', fromBikeId: 'EV001', fromBikeModel: 'Etron ET50', fromHubId: 'HUB-001', fromHubName: 'JAIBEN Head Office', action: 'returned', timestamp: '2024-03-28 18:00:00', notes: 'Returned for charging' }, { id: 'OL-003', batteryId: 'BAT-001', fromOwner: 'JAIBEN Hub', fromOwnerType: 'hub', toOwner: 'Rahim Ahmed', toOwnerType: 'biker', toBikeId: 'EV001', toBikeModel: 'Etron ET50', toHubId: 'HUB-001', toHubName: 'JAIBEN Head Office', action: 'rented', rentAmount: 1500, timestamp: '2024-03-28 18:30:00' }, - ] + ], + damageHistory: [ + { id: 'DMG-001', date: '2024-02-15', type: 'Physical Damage', description: 'Battery casing cracked due to drop', reportedBy: 'Rahim Ahmed', estimatedCost: 5000, actualCost: 4500, status: 'resolved' }, + { id: 'DMG-002', date: '2024-03-20', type: 'Connector Damage', description: 'Charging connector bent', reportedBy: 'Tech Staff', estimatedCost: 1500, status: 'in-progress' }, + ], + maintenanceHistory: [ + { id: 'MNT-001', date: '2024-01-20', type: 'routine', description: 'General inspection and cleaning', cost: 500, performedBy: 'Tech Team', nextDueDate: '2024-04-20', status: 'completed' }, + { id: 'MNT-002', date: '2024-03-01', type: 'inspection', description: 'BMS calibration and cell balancing', cost: 800, performedBy: 'Engineer Team', nextDueDate: '2024-06-01', status: 'completed' }, + { id: 'MNT-003', date: '2024-04-15', type: 'repair', description: 'Replaced faulty connector', cost: 1500, performedBy: 'Tech Team', status: 'pending' }, + ], }; const statusColors: Record = { @@ -126,10 +159,50 @@ const typeLabels: Record = { export default function BatteryDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params); const [battery, setBattery] = useState(mockBattery); - const [activeTab, setActiveTab] = useState<'info' | 'bms' | 'history' | 'rent'>('info'); + const [activeTab, setActiveTab] = useState<'info' | 'bms' | 'history' | 'rent' | 'damage' | 'maintenance'>('info'); const [showEditModal, setShowEditModal] = useState(false); const [refreshing, setRefreshing] = useState(false); + // Damage History State + const [showDamageModal, setShowDamageModal] = useState(false); + const [editingDamage, setEditingDamage] = useState(null); + const [damageForm, setDamageForm] = useState({ + date: '', + type: '', + description: '', + reportedBy: '', + estimatedCost: 0, + actualCost: 0, + status: 'reported' as 'reported' | 'in-progress' | 'resolved', + hubId: '', + hubName: '', + }); + + // Maintenance State + const [showMaintenanceModal, setShowMaintenanceModal] = useState(false); + const [editingMaintenance, setEditingMaintenance] = useState(null); + const [maintenanceForm, setMaintenanceForm] = useState({ + date: '', + type: 'routine' as 'routine' | 'repair' | 'replacement' | 'inspection', + description: '', + cost: 0, + performedBy: '', + nextDueDate: '', + status: 'pending' as 'completed' | 'pending' | 'overdue', + hubId: '', + hubName: '', + }); + + const mockHubs = [ + { id: 'HUB-001', name: 'Gulshan Hub' }, + { id: 'HUB-002', name: 'Banani Hub' }, + { id: 'HUB-003', name: 'Uttara Hub' }, + { id: 'HUB-004', name: 'Mirpur Hub' }, + ]; + + const damageTypes = ['Physical Damage', 'Connector Damage', 'Battery Cell Damage', 'Charging Port Damage', 'Water Damage', 'Software Issue', 'Other']; + const maintenanceTypes = ['routine', 'repair', 'replacement', 'inspection'] as const; + const handleSaveEdit = (updatedBattery: Battery) => { setBattery(updatedBattery); setShowEditModal(false); @@ -159,6 +232,160 @@ export default function BatteryDetailPage({ params }: { params: Promise<{ id: st }, 1000); }; + // Damage CRUD Handlers + const handleAddDamage = () => { + const newDamage: DamageRecord = { + id: `DMG-${Date.now()}`, + ...damageForm, + estimatedCost: Number(damageForm.estimatedCost), + actualCost: damageForm.actualCost ? Number(damageForm.actualCost) : undefined, + }; + setBattery(prev => ({ + ...prev, + damageHistory: [...(prev.damageHistory || []), newDamage] + })); + setShowDamageModal(false); + resetDamageForm(); + }; + + const handleUpdateDamage = () => { + if (!editingDamage) return; + const updatedDamage: DamageRecord = { + ...editingDamage, + ...damageForm, + estimatedCost: Number(damageForm.estimatedCost), + actualCost: damageForm.actualCost ? Number(damageForm.actualCost) : undefined, + }; + setBattery(prev => ({ + ...prev, + damageHistory: prev.damageHistory?.map(d => d.id === editingDamage.id ? updatedDamage : d) || [] + })); + setShowDamageModal(false); + setEditingDamage(null); + resetDamageForm(); + }; + + const handleDeleteDamage = (damageId: string) => { + setBattery(prev => ({ + ...prev, + damageHistory: prev.damageHistory?.filter(d => d.id !== damageId) || [] + })); + }; + + const resetDamageForm = () => { + setDamageForm({ + date: '', + type: '', + description: '', + reportedBy: '', + estimatedCost: 0, + actualCost: 0, + status: 'reported', + hubId: '', + hubName: '', + }); + }; + + const openAddDamageModal = () => { + resetDamageForm(); + setDamageForm(prev => ({ ...prev, date: new Date().toISOString().split('T')[0] })); + setEditingDamage(null); + setShowDamageModal(true); + }; + + const openEditDamageModal = (damage: DamageRecord) => { + setDamageForm({ + date: damage.date, + type: damage.type, + description: damage.description, + reportedBy: damage.reportedBy, + estimatedCost: damage.estimatedCost, + actualCost: damage.actualCost || 0, + status: damage.status, + hubId: '', + hubName: '', + }); + setEditingDamage(damage); + setShowDamageModal(true); + }; + + // Maintenance CRUD Handlers + const handleAddMaintenance = () => { + const newMaintenance: MaintenanceRecord = { + id: `MNT-${Date.now()}`, + ...maintenanceForm, + cost: Number(maintenanceForm.cost), + nextDueDate: maintenanceForm.nextDueDate || undefined, + }; + setBattery(prev => ({ + ...prev, + maintenanceHistory: [...(prev.maintenanceHistory || []), newMaintenance] + })); + setShowMaintenanceModal(false); + resetMaintenanceForm(); + }; + + const handleUpdateMaintenance = () => { + if (!editingMaintenance) return; + const updatedMaintenance: MaintenanceRecord = { + ...editingMaintenance, + ...maintenanceForm, + cost: Number(maintenanceForm.cost), + nextDueDate: maintenanceForm.nextDueDate || undefined, + }; + setBattery(prev => ({ + ...prev, + maintenanceHistory: prev.maintenanceHistory?.map(m => m.id === editingMaintenance.id ? updatedMaintenance : m) || [] + })); + setShowMaintenanceModal(false); + setEditingMaintenance(null); + resetMaintenanceForm(); + }; + + const handleDeleteMaintenance = (maintenanceId: string) => { + setBattery(prev => ({ + ...prev, + maintenanceHistory: prev.maintenanceHistory?.filter(m => m.id !== maintenanceId) || [] + })); + }; + + const resetMaintenanceForm = () => { + setMaintenanceForm({ + date: '', + type: 'routine', + description: '', + cost: 0, + performedBy: '', + nextDueDate: '', + status: 'pending', + hubId: '', + hubName: '', + }); + }; + + const openAddMaintenanceModal = () => { + resetMaintenanceForm(); + setMaintenanceForm(prev => ({ ...prev, date: new Date().toISOString().split('T')[0] })); + setEditingMaintenance(null); + setShowMaintenanceModal(true); + }; + + const openEditMaintenanceModal = (maintenance: MaintenanceRecord) => { + setMaintenanceForm({ + date: maintenance.date, + type: maintenance.type, + description: maintenance.description, + cost: maintenance.cost, + performedBy: maintenance.performedBy, + nextDueDate: maintenance.nextDueDate || '', + status: maintenance.status, + hubId: '', + hubName: '', + }); + setEditingMaintenance(maintenance); + setShowMaintenanceModal(true); + }; + return (
@@ -261,11 +488,13 @@ export default function BatteryDetailPage({ params }: { params: Promise<{ id: st )}
-
- - - - +
+ + + + + +
@@ -435,9 +664,280 @@ export default function BatteryDetailPage({ params }: { params: Promise<{ id: st )}
)} + + {activeTab === 'damage' && ( +
+
+
+ +

Damage History

+
+ +
+ {(battery.damageHistory || []).length > 0 ? ( +
+ + + + + + + + + + + + + + + {battery.damageHistory?.map(damage => ( + + + + + + + + + + + ))} + +
DateTypeDescriptionHubEst. CostActual CostStatusActions
{damage.date}{damage.type}{damage.description}{damageForm.hubName || '-'}৳{damage.estimatedCost.toLocaleString()}{damage.actualCost ? `৳${damage.actualCost.toLocaleString()}` : '-'} + + {damage.status === 'resolved' ? 'Resolved' : damage.status === 'in-progress' ? 'In Progress' : 'Reported'} + + +
+ + +
+
+
+ ) : ( +
+ No damage history found. +
+ )} +
+ )} + + {activeTab === 'maintenance' && ( +
+
+
+ +

Maintenance History

+
+ +
+ {(battery.maintenanceHistory || []).length > 0 ? ( +
+ + + + + + + + + + + + + + + + {battery.maintenanceHistory?.map(maint => ( + + + + + + + + + + + + ))} + +
DateTypeDescriptionHubCostPerformed ByNext DueStatusActions
{maint.date}{maint.type}{maint.description}{maintenanceForm.hubName || '-'}৳{maint.cost.toLocaleString()}{maint.performedBy}{maint.nextDueDate || '-'} + + {maint.status === 'completed' ? 'Completed' : maint.status === 'pending' ? 'Pending' : 'Overdue'} + + +
+ + +
+
+
+ ) : ( +
+ No maintenance history found. +
+ )} +
+ )}
+ {/* Damage Modal */} + {showDamageModal && ( +
+
+
+

{editingDamage ? 'Edit Damage' : 'Add Damage'}

+ +
+
+
+
+ + setDamageForm({ ...damageForm, date: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> +
+
+ + +
+
+
+ +