From 16c299ae7f2f945ef16c7b71e7e0c6ce335da1dd Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Tue, 19 May 2026 20:25:01 +0600 Subject: [PATCH] feat: add battery and bike maintenance history pages and update navigation links to include source tracking --- src/app/admin/maintenance/[id]/page.tsx | 15 +- .../maintenance/history/battery/[id]/page.tsx | 541 ++++++++++++++++++ .../maintenance/history/bike/[id]/page.tsx | 537 +++++++++++++++++ 3 files changed, 1081 insertions(+), 12 deletions(-) create mode 100644 src/app/admin/maintenance/history/battery/[id]/page.tsx create mode 100644 src/app/admin/maintenance/history/bike/[id]/page.tsx diff --git a/src/app/admin/maintenance/[id]/page.tsx b/src/app/admin/maintenance/[id]/page.tsx index 31f0045..e47e12e 100644 --- a/src/app/admin/maintenance/[id]/page.tsx +++ b/src/app/admin/maintenance/[id]/page.tsx @@ -624,7 +624,7 @@ export default function MaintenanceDetailPage() {
{record.batteryId && (
@@ -636,7 +636,7 @@ export default function MaintenanceDetailPage() { )} {record.bikeId && (
@@ -646,16 +646,7 @@ export default function MaintenanceDetailPage() { )} - -
- - All Maintenance -
- - +
diff --git a/src/app/admin/maintenance/history/battery/[id]/page.tsx b/src/app/admin/maintenance/history/battery/[id]/page.tsx new file mode 100644 index 0000000..e7a8050 --- /dev/null +++ b/src/app/admin/maintenance/history/battery/[id]/page.tsx @@ -0,0 +1,541 @@ +'use client'; + +import { useState, use } from 'react'; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { + Wrench, ArrowLeft, Battery, AlertTriangle, Calendar, DollarSign, Clock, + CheckCircle, XCircle, Search, RefreshCw, ChevronLeft, ChevronRight, + Eye, User, FileText, ArrowRight, ShieldAlert, Sparkles +} from 'lucide-react'; + +interface HistoryRecord { + id: string; + date: string; + type: 'damage' | 'maintenance' | 'service' | 'repair' | 'inspection' | 'battery_swap'; + severity: 'critical' | 'major' | 'minor' | 'cosmetic'; + status: 'reported' | 'in_progress' | 'parts_ordered' | 'completed' | 'cancelled'; + description: string; + cost: number; + reporter: string; + resolvedAt?: string; + partsUsed?: string[]; +} + +export default function BatteryMaintenanceHistoryPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const batteryId = use(params).id; + const fromRecord = searchParams.get('from'); + + // Realistic mock data for a specific battery's maintenance and damage history + const [historyList] = useState(() => { + return [ + { + id: 'MNT-003', + date: '2024-03-19', + type: 'battery_swap', + severity: 'minor', + status: 'completed', + description: 'Battery not holding charge properly - swapped under warranty.', + cost: 0, + reporter: 'Jamal (Biker)', + resolvedAt: '2024-03-19', + partsUsed: [] + }, + { + id: 'MNT-009', + date: '2024-03-05', + type: 'repair', + severity: 'major', + status: 'completed', + description: 'Battery port connector pin replacement & calibration.', + cost: 1200, + reporter: 'Uttara Hub Staff', + resolvedAt: '2024-03-06', + partsUsed: ['Connector Pins', 'Silicone Seals'] + }, + { + id: 'MNT-015', + date: '2024-02-15', + type: 'service', + severity: 'minor', + status: 'completed', + description: 'Cell rebalancing and firmware upgrade for BMS.', + cost: 800, + reporter: 'Authorized Service Center', + resolvedAt: '2024-02-15', + partsUsed: [] + }, + { + id: 'MNT-020', + date: '2024-01-22', + type: 'damage', + severity: 'critical', + status: 'completed', + description: 'Cell thermal runaway inspection due to temperature alert.', + cost: 1500, + reporter: 'System Alert', + resolvedAt: '2024-01-24', + partsUsed: ['BMS Module'] + }, + { + id: 'MNT-025', + date: '2023-12-01', + type: 'inspection', + severity: 'cosmetic', + status: 'completed', + description: 'Outer plastic protective case scratch audit and hub cleanup.', + cost: 0, + reporter: 'Kamal Ahmed (Biker)', + resolvedAt: '2023-12-01', + partsUsed: [] + } + ]; + }); + + // Client Side Filter & Sorting States + const [searchQuery, setSearchQuery] = useState(''); + const [typeFilter, setTypeFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [sortBy, setSortBy] = useState<'date' | 'cost' | 'severity'>('date'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const [page, setPage] = useState(1); + const pageSize = 5; + + const handleSort = (field: 'date' | 'cost' | 'severity') => { + if (sortBy === field) { + setSortOrder(prev => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setSortBy(field); + setSortOrder('desc'); + } + setPage(1); + }; + + // Logic + const filteredList = historyList.filter(item => { + if (typeFilter !== 'all') { + if (typeFilter === 'damage_all') { + if (item.type !== 'damage') return false; + } else if (typeFilter === 'damage_cosmetic') { + if (item.type !== 'damage' || item.severity !== 'cosmetic') return false; + } else if (typeFilter === 'damage_minor') { + if (item.type !== 'damage' || item.severity !== 'minor') return false; + } else if (typeFilter === 'damage_major') { + if (item.type !== 'damage' || item.severity !== 'major') return false; + } else if (typeFilter === 'damage_critical') { + if (item.type !== 'damage' || item.severity !== 'critical') return false; + } else if (typeFilter === 'maintenance_all') { + if (item.type === 'damage') return false; + } else if (typeFilter === 'maintenance_service') { + if (item.type !== 'service') return false; + } else if (typeFilter === 'maintenance_repair') { + if (item.type !== 'repair') return false; + } else if (typeFilter === 'maintenance_inspection') { + if (item.type !== 'inspection') return false; + } else if (typeFilter === 'maintenance_battery_swap') { + if (item.type !== 'battery_swap') return false; + } + } + if (statusFilter !== 'all' && item.status !== statusFilter) return false; + if (searchQuery && !item.description.toLowerCase().includes(searchQuery.toLowerCase()) && !item.id.toLowerCase().includes(searchQuery.toLowerCase())) return false; + if (dateFrom && new Date(item.date) < new Date(dateFrom)) return false; + if (dateTo && new Date(item.date) > new Date(dateTo)) return false; + return true; + }); + + const sortedList = [...filteredList].sort((a, b) => { + let comp = 0; + if (sortBy === 'date') { + comp = new Date(a.date).getTime() - new Date(b.date).getTime(); + } else if (sortBy === 'cost') { + comp = a.cost - b.cost; + } else if (sortBy === 'severity') { + const ranks = { critical: 4, major: 3, minor: 2, cosmetic: 1 }; + comp = ranks[a.severity] - ranks[b.severity]; + } + return sortOrder === 'desc' ? -comp : comp; + }); + + // Pagination + const totalPages = Math.ceil(sortedList.length / pageSize); + const paginatedList = sortedList.slice((page - 1) * pageSize, page * pageSize); + + // Status/Severity Badge Colors + const severityColors = { + critical: 'bg-red-100 text-red-700', + major: 'bg-orange-100 text-orange-700', + minor: 'bg-amber-100 text-amber-700', + cosmetic: 'bg-slate-100 text-slate-700', + }; + + const statusColors = { + reported: 'bg-amber-50 text-amber-700 border border-amber-200', + in_progress: 'bg-blue-50 text-blue-700 border border-blue-200', + parts_ordered: 'bg-purple-50 text-purple-700 border border-purple-200', + completed: 'bg-green-50 text-green-700 border border-green-200', + cancelled: 'bg-red-50 text-red-700 border border-red-200', + }; + + const totalCost = filteredList.reduce((sum, item) => sum + item.cost, 0); + const criticalCount = filteredList.filter(item => item.severity === 'critical' || item.severity === 'major').length; + + return ( +
+ {/* Back navigation links */} +
+ +
+

+ Battery History Ledger +

+

+ Viewing comprehensive damage & maintenance history for Battery {batteryId} +

+
+
+ + {/* Top Metrics Row */} +
+
+
+
+ +
+
+

Total Events

+

{filteredList.length}

+
+
+
+ +
+
+
+ +
+
+

Major / Critical

+

{criticalCount}

+
+
+
+ +
+
+
+ +
+
+

Accumulated Cost

+

৳{totalCost.toLocaleString()}

+
+
+
+ +
+
+
+ +
+
+

Health Status

+

Excellent

+
+
+
+
+ + {/* Main Ledger Content */} +
+ + {/* Advanced Filters */} +
+
+ +
+
+ + { setSearchQuery(e.target.value); setPage(1); }} + className="pl-9 pr-4 py-2 border border-slate-200 rounded-lg text-sm w-48 sm:w-64 bg-white focus:outline-none focus:border-slate-400 transition-colors" + /> +
+ + + + +
+ +
+
+ { setDateFrom(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> + to + { setDateTo(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> +
+ + {(dateFrom || dateTo || searchQuery || typeFilter !== 'all' || statusFilter !== 'all') && ( + + )} +
+ +
+
+ + {/* Desktop View */} +
+ + + + + + + + + + + + + + + + {paginatedList.length > 0 ? ( + paginatedList.map(item => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
handleSort('date')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors" + > + Date {sortBy === 'date' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Reference ID + + Type + handleSort('severity')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors" + > + Severity {sortBy === 'severity' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Description + handleSort('cost')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors text-right" + > + Cost {sortBy === 'cost' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Status + + Action +
+ {item.date} + + {item.id} + + {item.type.replace('_', ' ')} + + + {item.severity} + + +

{item.description}

+ {item.partsUsed && item.partsUsed.length > 0 && ( +
+ {item.partsUsed.map(p => ( + + {p} + + ))} +
+ )} +
+ ৳{item.cost.toLocaleString()} + + + {item.status.replace('_', ' ')} + + + + + +
+ +

No maintenance logs found

+
+
+ + {/* Mobile View */} +
+ {paginatedList.length > 0 ? ( + paginatedList.map(item => ( +
+
+
+ {item.id} +

+ {item.type.replace('_', ' ')} +

+
+
+ ৳{item.cost.toLocaleString()} + + {item.status.replace('_', ' ')} + +
+
+ +

{item.description}

+ +
+ {item.date} + + Details + +
+
+ )) + ) : ( +
+ +

No maintenance logs found

+
+ )} +
+ + {/* Footer Pagination */} + {sortedList.length > pageSize && ( +
+

+ Showing {((page - 1) * pageSize) + 1} to {Math.min(page * pageSize, sortedList.length)} of {sortedList.length} records +

+ +
+ + + {Array.from({ length: totalPages }, (_, i) => i + 1).map(pageNum => ( + + ))} + + +
+
+ )} + +
+
+ ); +} diff --git a/src/app/admin/maintenance/history/bike/[id]/page.tsx b/src/app/admin/maintenance/history/bike/[id]/page.tsx new file mode 100644 index 0000000..5b9bfac --- /dev/null +++ b/src/app/admin/maintenance/history/bike/[id]/page.tsx @@ -0,0 +1,537 @@ +'use client'; + +import { useState, use } from 'react'; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { + Wrench, ArrowLeft, Bike, AlertTriangle, Calendar, DollarSign, Clock, + CheckCircle, XCircle, Search, RefreshCw, ChevronLeft, ChevronRight, + Eye, User, FileText, ArrowRight, ShieldAlert, Sparkles +} from 'lucide-react'; + +interface HistoryRecord { + id: string; + date: string; + type: 'damage' | 'maintenance' | 'service' | 'repair' | 'inspection'; + severity: 'critical' | 'major' | 'minor' | 'cosmetic'; + status: 'reported' | 'in_progress' | 'parts_ordered' | 'completed' | 'cancelled'; + description: string; + cost: number; + reporter: string; + resolvedAt?: string; + partsUsed?: string[]; +} + +export default function BikeMaintenanceHistoryPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const bikeId = use(params).id; + const fromRecord = searchParams.get('from'); + + // Realistic mock data for a specific bike's maintenance and damage history + const [historyList] = useState(() => { + return [ + { + id: 'MNT-001', + date: '2024-03-21', + type: 'damage', + severity: 'major', + status: 'in_progress', + description: 'Front fender damaged in minor collision at Gulshan signal.', + cost: 3200, + reporter: 'Sofiq Rahman (Biker)', + partsUsed: ['Front fender', 'Mounting brackets'] + }, + { + id: 'MNT-005', + date: '2024-03-17', + type: 'inspection', + severity: 'minor', + status: 'completed', + description: 'Monthly scheduled routine vehicle inspection.', + cost: 250, + reporter: 'Gulshan Hub Staff', + resolvedAt: '2024-03-17', + partsUsed: [] + }, + { + id: 'MNT-012', + date: '2024-02-10', + type: 'repair', + severity: 'critical', + status: 'completed', + description: 'Motor controller overheating check & throttle replacement.', + cost: 7500, + reporter: 'System Alert', + resolvedAt: '2024-02-12', + partsUsed: ['Throttle Assembly', 'Controller Fan'] + }, + { + id: 'MNT-018', + date: '2024-01-15', + type: 'service', + severity: 'minor', + status: 'completed', + description: 'Chain lubrication, brake shoe calibration, and mirror tightening.', + cost: 600, + reporter: 'Kamal Ahmed (Biker)', + resolvedAt: '2024-01-15', + partsUsed: ['Brake Shoe Set'] + }, + { + id: 'MNT-022', + date: '2023-12-05', + type: 'damage', + severity: 'cosmetic', + status: 'completed', + description: 'Side mirror cracked due to parking slip.', + cost: 800, + reporter: 'Kamal Ahmed (Biker)', + resolvedAt: '2023-12-06', + partsUsed: ['Left Side Mirror'] + } + ]; + }); + + // Client Side Filter & Sorting States + const [searchQuery, setSearchQuery] = useState(''); + const [typeFilter, setTypeFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [sortBy, setSortBy] = useState<'date' | 'cost' | 'severity'>('date'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const [page, setPage] = useState(1); + const pageSize = 5; + + const handleSort = (field: 'date' | 'cost' | 'severity') => { + if (sortBy === field) { + setSortOrder(prev => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setSortBy(field); + setSortOrder('desc'); + } + setPage(1); + }; + + // Logic + const filteredList = historyList.filter(item => { + if (typeFilter !== 'all') { + if (typeFilter === 'damage_all') { + if (item.type !== 'damage') return false; + } else if (typeFilter === 'damage_cosmetic') { + if (item.type !== 'damage' || item.severity !== 'cosmetic') return false; + } else if (typeFilter === 'damage_minor') { + if (item.type !== 'damage' || item.severity !== 'minor') return false; + } else if (typeFilter === 'damage_major') { + if (item.type !== 'damage' || item.severity !== 'major') return false; + } else if (typeFilter === 'damage_critical') { + if (item.type !== 'damage' || item.severity !== 'critical') return false; + } else if (typeFilter === 'maintenance_all') { + if (item.type === 'damage') return false; + } else if (typeFilter === 'maintenance_service') { + if (item.type !== 'service') return false; + } else if (typeFilter === 'maintenance_repair') { + if (item.type !== 'repair') return false; + } else if (typeFilter === 'maintenance_inspection') { + if (item.type !== 'inspection') return false; + } + } + if (statusFilter !== 'all' && item.status !== statusFilter) return false; + if (searchQuery && !item.description.toLowerCase().includes(searchQuery.toLowerCase()) && !item.id.toLowerCase().includes(searchQuery.toLowerCase())) return false; + if (dateFrom && new Date(item.date) < new Date(dateFrom)) return false; + if (dateTo && new Date(item.date) > new Date(dateTo)) return false; + return true; + }); + + const sortedList = [...filteredList].sort((a, b) => { + let comp = 0; + if (sortBy === 'date') { + comp = new Date(a.date).getTime() - new Date(b.date).getTime(); + } else if (sortBy === 'cost') { + comp = a.cost - b.cost; + } else if (sortBy === 'severity') { + const ranks = { critical: 4, major: 3, minor: 2, cosmetic: 1 }; + comp = ranks[a.severity] - ranks[b.severity]; + } + return sortOrder === 'desc' ? -comp : comp; + }); + + // Pagination + const totalPages = Math.ceil(sortedList.length / pageSize); + const paginatedList = sortedList.slice((page - 1) * pageSize, page * pageSize); + + // Status/Severity Badge Colors + const severityColors = { + critical: 'bg-red-100 text-red-700', + major: 'bg-orange-100 text-orange-700', + minor: 'bg-amber-100 text-amber-700', + cosmetic: 'bg-slate-100 text-slate-700', + }; + + const statusColors = { + reported: 'bg-amber-50 text-amber-700 border border-amber-200', + in_progress: 'bg-blue-50 text-blue-700 border border-blue-200', + parts_ordered: 'bg-purple-50 text-purple-700 border border-purple-200', + completed: 'bg-green-50 text-green-700 border border-green-200', + cancelled: 'bg-red-50 text-red-700 border border-red-200', + }; + + const totalCost = filteredList.reduce((sum, item) => sum + item.cost, 0); + const criticalCount = filteredList.filter(item => item.severity === 'critical' || item.severity === 'major').length; + + return ( +
+ {/* Back navigation links */} +
+ +
+

+ History Ledger +

+

+ Viewing comprehensive damage & maintenance history for Bike {bikeId} +

+
+
+ + {/* Top Metrics Row */} +
+
+
+
+ +
+
+

Total Events

+

{filteredList.length}

+
+
+
+ +
+
+
+ +
+
+

Major / Critical

+

{criticalCount}

+
+
+
+ +
+
+
+ +
+
+

Accumulated Cost

+

৳{totalCost.toLocaleString()}

+
+
+
+ +
+
+
+ +
+
+

Service Status

+

Healthy

+
+
+
+
+ + {/* Main Ledger Content */} +
+ + {/* Advanced Filters */} +
+
+ +
+
+ + { setSearchQuery(e.target.value); setPage(1); }} + className="pl-9 pr-4 py-2 border border-slate-200 rounded-lg text-sm w-48 sm:w-64 bg-white focus:outline-none focus:border-slate-400 transition-colors" + /> +
+ + + + +
+ +
+
+ { setDateFrom(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> + to + { setDateTo(e.target.value); setPage(1); }} + className="px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white" + /> +
+ + {(dateFrom || dateTo || searchQuery || typeFilter !== 'all' || statusFilter !== 'all') && ( + + )} +
+ +
+
+ + {/* Desktop View */} +
+ + + + + + + + + + + + + + + + {paginatedList.length > 0 ? ( + paginatedList.map(item => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
handleSort('date')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors" + > + Date {sortBy === 'date' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Reference ID + + Type + handleSort('severity')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors" + > + Severity {sortBy === 'severity' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Description + handleSort('cost')} + className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors text-right" + > + Cost {sortBy === 'cost' && {sortOrder === 'asc' ? '↑' : '↓'}} + + Status + + Action +
+ {item.date} + + {item.id} + + {item.type.replace('_', ' ')} + + + {item.severity} + + +

{item.description}

+ {item.partsUsed && item.partsUsed.length > 0 && ( +
+ {item.partsUsed.map(p => ( + + {p} + + ))} +
+ )} +
+ ৳{item.cost.toLocaleString()} + + + {item.status.replace('_', ' ')} + + + + + +
+ +

No maintenance logs found

+
+
+ + {/* Mobile View */} +
+ {paginatedList.length > 0 ? ( + paginatedList.map(item => ( +
+
+
+ {item.id} +

+ {item.type.replace('_', ' ')} +

+
+
+ ৳{item.cost.toLocaleString()} + + {item.status.replace('_', ' ')} + +
+
+ +

{item.description}

+ +
+ {item.date} + + Details + +
+
+ )) + ) : ( +
+ +

No maintenance logs found

+
+ )} +
+ + {/* Footer Pagination */} + {sortedList.length > pageSize && ( +
+

+ Showing {((page - 1) * pageSize) + 1} to {Math.min(page * pageSize, sortedList.length)} of {sortedList.length} records +

+ +
+ + + {Array.from({ length: totalPages }, (_, i) => i + 1).map(pageNum => ( + + ))} + + +
+
+ )} + +
+
+ ); +}