diff --git a/src/app/admin/maintenance/page.tsx b/src/app/admin/maintenance/page.tsx index bef82ed..0c8f108 100644 --- a/src/app/admin/maintenance/page.tsx +++ b/src/app/admin/maintenance/page.tsx @@ -3,8 +3,8 @@ import Link from 'next/link'; import { useState } from 'react'; -import { - AlertTriangle, Search, Plus, X, Check, Clock, Bike, User, Phone, +import { + AlertTriangle, Search, Plus, X, Check, Clock, Bike, User, Phone, MapPin, FileText, Image, DollarSign, Wrench, Battery, Key, CheckCircle, XCircle, ChevronDown, ChevronUp, Download, Eye, Edit, MessageSquare, Filter, Calendar, Save, Printer, Send @@ -24,31 +24,31 @@ interface MaintenanceRecord { severity: DamageSeverity; status: MaintenanceStatus; paymentStatus: PaymentStatus; - + bikeId: string; bikeModel: string; bikePlate: string; batteryId?: string; - + reporterId: string; reporterName: string; reporterPhone: string; reporterRole: 'biker' | 'staff' | 'hub'; - + description: string; location: string; - + estimatedCost: number; actualCost?: number; partsUsed?: string[]; - + images: { id: string; name: string; url: string; uploadedAt: string }[]; bills?: { id: string; name: string; url: string }[]; - + assignedTo?: string; notes: string[]; resolvedAt?: string; - + createdAt: string; createdBy: string; } @@ -245,7 +245,8 @@ const typeIcons: Record = { }; export default function MaintenancePage() { - const [activeTab, setActiveTab] = useState<'all' | MaintenanceType>('all'); + const [mainCategory, setMainCategory] = useState<'damage' | 'maintenance'>('damage'); + const [targetType, setTargetType] = useState<'all' | 'battery' | 'fleet'>('all'); const [records, setRecords] = useState(mockMaintenance); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); @@ -259,20 +260,36 @@ export default function MaintenancePage() { const [expandedNotes, setExpandedNotes] = useState([]); const [newNoteText, setNewNoteText] = useState(''); const [editForm, setEditForm] = useState>({}); + const [reportType, setReportType] = useState<'damage' | 'maintenance'>('damage'); const filteredRecords = records.filter(r => { - const matchesTab = activeTab === 'all' || r.type === activeTab; - const matchesSearch = !searchQuery || + const isDamage = r.type === 'damage'; + const matchesCategory = mainCategory === 'damage' ? isDamage : !isDamage; + const matchesTarget = targetType === 'all' || + (targetType === 'battery' && r.batteryId) || + (targetType === 'fleet' && r.bikeId && !r.batteryId); + const matchesSearch = !searchQuery || r.bikeId.toLowerCase().includes(searchQuery.toLowerCase()) || r.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()) || r.bikePlate.toLowerCase().includes(searchQuery.toLowerCase()) || r.reporterName.toLowerCase().includes(searchQuery.toLowerCase()) || - r.id.toLowerCase().includes(searchQuery.toLowerCase()); + r.id.toLowerCase().includes(searchQuery.toLowerCase()) || + (r.batteryId && r.batteryId.toLowerCase().includes(searchQuery.toLowerCase())); const matchesStatus = statusFilter === 'all' || r.status === statusFilter; - return matchesTab && matchesSearch && matchesStatus; + return matchesCategory && matchesTarget && matchesSearch && matchesStatus; }); + const damageRecords = records.filter(r => r.type === 'damage'); + const maintenanceRecords = records.filter(r => r.type !== 'damage'); const stats = { + damageCount: damageRecords.length, + maintenanceCount: maintenanceRecords.length, + batteryDamage: damageRecords.filter(r => r.batteryId).length, + fleetDamage: damageRecords.filter(r => r.bikeId && !r.batteryId).length, + batteryMaintenance: maintenanceRecords.filter(r => r.batteryId).length, + fleetMaintenance: maintenanceRecords.filter(r => r.bikeId && !r.batteryId).length, + pendingMaintenance: maintenanceRecords.filter(r => r.status === 'reported' || r.status === 'in_progress').length, + completedMaintenance: maintenanceRecords.filter(r => r.status === 'completed').length, critical: records.filter(r => r.severity === 'critical' && r.status !== 'completed').length, inProgress: records.filter(r => r.status === 'in_progress' || r.status === 'parts_ordered').length, completed: records.filter(r => r.status === 'completed').length, @@ -281,15 +298,15 @@ export default function MaintenancePage() { }; const toggleNotes = (id: string) => { - setExpandedNotes(prev => + setExpandedNotes(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] ); }; const handleAddNote = () => { if (!selectedRecord || !newNoteText.trim()) return; - setRecords(prev => prev.map(r => - r.id === selectedRecord.id + setRecords(prev => prev.map(r => + r.id === selectedRecord.id ? { ...r, notes: [...r.notes, newNoteText] } : r )); @@ -299,8 +316,8 @@ export default function MaintenancePage() { const handleCompleteRecord = () => { if (!selectedRecord) return; - setRecords(prev => prev.map(r => - r.id === selectedRecord.id + setRecords(prev => prev.map(r => + r.id === selectedRecord.id ? { ...r, status: 'completed', resolvedAt: new Date().toISOString().split('T')[0] } : r )); @@ -311,9 +328,9 @@ export default function MaintenancePage() { const handlePayment = (source: 'biker' | 'company') => { if (!selectedRecord) return; const cost = selectedRecord.actualCost || selectedRecord.estimatedCost; - - setRecords(prev => prev.map(r => - r.id === selectedRecord.id + + setRecords(prev => prev.map(r => + r.id === selectedRecord.id ? { ...r, paymentStatus: 'paid' } : r )); @@ -347,22 +364,22 @@ export default function MaintenancePage() { import('jspdf').then(jsPDF => { const doc = new jsPDF.default(); const cost = selectedRecord.actualCost || selectedRecord.estimatedCost; - + doc.setFontSize(18); doc.setTextColor(6, 95, 70); doc.text('JAIBEN Mobility Ltd', 20, 20); - + doc.setFontSize(14); doc.setTextColor(0); doc.text('Maintenance Invoice', 20, 32); - + doc.setFontSize(10); doc.setTextColor(100); doc.text(`Invoice No: INV-${selectedRecord.id}`, 20, 42); doc.text(`Date: ${selectedRecord.date}`, 20, 48); doc.text(`Issue Type: ${selectedRecord.type}`, 20, 54); doc.text(`Severity: ${selectedRecord.severity}`, 20, 60); - + doc.setFontSize(11); doc.setTextColor(0); doc.text('Bike Details', 20, 72); @@ -371,27 +388,27 @@ export default function MaintenancePage() { doc.text(`Model: ${selectedRecord.bikeModel}`, 20, 84); doc.text(`License Plate: ${selectedRecord.bikePlate}`, 20, 90); if (selectedRecord.batteryId) doc.text(`Battery ID: ${selectedRecord.batteryId}`, 20, 96); - + doc.setFontSize(11); doc.text('Description', 20, 108); doc.setFontSize(10); const descLines = doc.splitTextToSize(selectedRecord.description, 170); doc.text(descLines, 20, 114); - + doc.setFontSize(11); doc.text('Cost Breakdown', 20, 130); doc.setFontSize(10); doc.text(`Estimated Cost: ৳${selectedRecord.estimatedCost}`, 20, 136); if (selectedRecord.actualCost) doc.text(`Actual Cost: ৳${selectedRecord.actualCost}`, 20, 142); - + doc.setFontSize(12); doc.setTextColor(6, 95, 70); doc.text(`Total: ৳${cost}`, 20, 152); - + doc.setFontSize(9); doc.setTextColor(150); doc.text('Generated from JAIBEN Maintenance System', 20, 280); - + doc.save(`maintenance-invoice-${selectedRecord.id}.pdf`); }); }; @@ -404,7 +421,7 @@ export default function MaintenancePage() {

Track bike damage, repairs, and service records

-
-
-
+
+
-

{stats.critical}

-

Critical

+

{stats.damageCount}

+

Total Damage

-
+
-

{stats.inProgress}

-

In Progress

+

{stats.maintenanceCount}

+

Total Maintenance

@@ -458,7 +475,7 @@ export default function MaintenancePage() {
-
+ {/*
@@ -468,43 +485,145 @@ export default function MaintenancePage() {

Total Cost

-
+
*/}
+ {mainCategory === 'damage' && ( +
+
+
+
+ +
+
+

{stats.damageCount}

+

Total Damage

+
+
+
+
+
+
+ +
+
+

{stats.batteryDamage}

+

Battery Damage

+
+
+
+
+
+
+ +
+
+

{stats.fleetDamage}

+

Fleet Damage

+
+
+
+
+
+
+ +
+
+

{stats.critical}

+

Critical Damage

+
+
+
+
+ )} + + {mainCategory === 'maintenance' && ( +
+
+
+
+ +
+
+

{stats.maintenanceCount}

+

Total Maintenance

+
+
+
+
+
+
+ +
+
+

{stats.batteryMaintenance}

+

Battery Maintenance

+
+
+
+
+
+
+ +
+
+

{stats.fleetMaintenance}

+

Fleet Maintenance

+
+
+
+
+
+
+ +
+
+

{stats.pendingMaintenance}

+

Pending Maintenance

+
+
+
+
+ )} +
-
- - - - - +
+
+ + +
+
+ + + +
@@ -546,6 +665,16 @@ export default function MaintenancePage() {

{record.id}

+ {record.batteryId && ( + + Battery + + )} + {!record.batteryId && record.bikeId && ( + + Fleet + + )} {record.severity} @@ -557,7 +686,7 @@ export default function MaintenancePage() {

- +

{record.description}

@@ -573,7 +702,7 @@ export default function MaintenancePage() {

)} {record.notes.length > 0 && ( - )}
- + {expandedNotes.includes(record.id) && record.notes.length > 0 && (
{record.notes.map((note, idx) => ( @@ -591,7 +720,7 @@ export default function MaintenancePage() {
)}
- +

৳{record.actualCost || record.estimatedCost}

@@ -605,9 +734,9 @@ export default function MaintenancePage() { {record.status === 'cancelled' && } {record.status.replace('_', ' ')} - +
- -
{selectedRecord.status !== 'completed' && ( - )} {selectedRecord.status === 'completed' && selectedRecord.paymentStatus !== 'paid' && ( - )} {selectedRecord.paymentStatus === 'paid' && ( -
+
+ +
+ + +
+
- +
-
- - -
+
+ + +
+
+
+
+ + +
+
+ + +
-