feat: enhance maintenance details page with issue history navigation and responsive layout improvements

This commit is contained in:
sazzadulalambd
2026-05-17 19:06:24 +06:00
parent 440a87f0b5
commit 8f445857a9
3 changed files with 349 additions and 209 deletions

View File

@@ -862,7 +862,7 @@ export default function BikerDetailPage() {
<ArrowLeft className="w-4 h-4" /> Back to Bikers <ArrowLeft className="w-4 h-4" /> Back to Bikers
</button> </button>
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-4"> <div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-8">
<div className="p-4 lg:p-6 flex flex-col lg:flex-row lg:items-center gap-4"> <div className="p-4 lg:p-6 flex flex-col lg:flex-row lg:items-center gap-4">
<div className="relative group"> <div className="relative group">
{biker.profileImage ? ( {biker.profileImage ? (

View File

@@ -7,7 +7,7 @@ import {
MapPin, FileText, Image, DollarSign, Wrench, Battery, Key, MapPin, FileText, Image, DollarSign, Wrench, Battery, Key,
CheckCircle, XCircle, ChevronLeft, Save, Printer, Send, QrCode, CheckCircle, XCircle, ChevronLeft, Save, Printer, Send, QrCode,
Wallet, Building, Edit, MessageSquare, Calendar, ArrowLeft, Trash2, Wallet, Building, Edit, MessageSquare, Calendar, ArrowLeft, Trash2,
Package, Settings Package, Settings, History, ArrowRight
} from 'lucide-react'; } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
@@ -284,7 +284,6 @@ export default function MaintenanceDetailPage() {
const [showInvoiceModal, setShowInvoiceModal] = useState(false); const [showInvoiceModal, setShowInvoiceModal] = useState(false);
const [showAddNoteModal, setShowAddNoteModal] = useState(false); const [showAddNoteModal, setShowAddNoteModal] = useState(false);
const [showAddPartModal, setShowAddPartModal] = useState(false); const [showAddPartModal, setShowAddPartModal] = useState(false);
const [showAddServiceCostModal, setShowAddServiceCostModal] = useState(false);
const [partSearch, setPartSearch] = useState(''); const [partSearch, setPartSearch] = useState('');
const [invoiceData, setInvoiceData] = useState({ tips: 0, discount: 0 }); const [invoiceData, setInvoiceData] = useState({ tips: 0, discount: 0 });
const [invoiceCreated, setInvoiceCreated] = useState(false); const [invoiceCreated, setInvoiceCreated] = useState(false);
@@ -295,7 +294,6 @@ export default function MaintenanceDetailPage() {
const [actualCost, setActualCost] = useState(''); const [actualCost, setActualCost] = useState('');
const [selectedPart, setSelectedPart] = useState<EVPart | null>(null); const [selectedPart, setSelectedPart] = useState<EVPart | null>(null);
const [partQuantity, setPartQuantity] = useState(1); const [partQuantity, setPartQuantity] = useState(1);
const [serviceCostInput, setServiceCostInput] = useState('');
useEffect(() => { useEffect(() => {
const found = mockMaintenance.find(r => r.id === id); const found = mockMaintenance.find(r => r.id === id);
@@ -457,12 +455,12 @@ export default function MaintenanceDetailPage() {
<ArrowLeft className="w-4 h-4" /> Back to Maintenance <ArrowLeft className="w-4 h-4" /> Back to Maintenance
</button> </button>
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden"> <div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-12 lg:mb-0">
<div className="p-6 border-b border-slate-100"> <div className="p-6 border-b border-slate-100">
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4"> <div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
<div> <div className="min-w-0">
<div className="flex items-center gap-3"> <div className="flex flex-wrap items-center gap-2">
<h1 className="text-2xl font-extrabold text-slate-800">{record.id}</h1> <h1 className="text-xl lg:text-2xl font-extrabold text-slate-800">{record.id}</h1>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${severityColors[record.severity]}`}> <span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${severityColors[record.severity]}`}>
{record.severity} {record.severity}
</span> </span>
@@ -475,7 +473,7 @@ export default function MaintenanceDetailPage() {
</div> </div>
<p className="text-slate-500 mt-1">{typeLabels[record.type]} {record.date}</p> <p className="text-slate-500 mt-1">{typeLabels[record.type]} {record.date}</p>
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2">
{editMode ? ( {editMode ? (
<> <>
<button onClick={handleSaveEdit} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"> <button onClick={handleSaveEdit} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2">
@@ -619,6 +617,48 @@ export default function MaintenanceDetailPage() {
)} )}
</div> </div>
<div className="bg-orange-50 p-4 rounded-xl border border-orange-100">
<h3 className="font-semibold text-orange-800 mb-3 flex items-center gap-2">
<History className="w-5 h-5" /> Issue History
</h3>
<div className="space-y-2">
{record.batteryId && (
<Link
href={`/admin/batteries/${record.batteryId}`}
className="flex items-center justify-between p-2 bg-white rounded-lg hover:bg-orange-50 transition-colors"
>
<div className="flex items-center gap-2">
<Battery className="w-4 h-4 text-green-600" />
<span className="text-sm text-slate-700">Battery History</span>
</div>
<ArrowRight className="w-4 h-4 text-orange-400" />
</Link>
)}
{record.bikeId && (
<Link
href={`/admin/fleet/${record.bikeId}`}
className="flex items-center justify-between p-2 bg-white rounded-lg hover:bg-orange-50 transition-colors"
>
<div className="flex items-center gap-2">
<Bike className="w-4 h-4 text-purple-600" />
<span className="text-sm text-slate-700">Fleet History</span>
</div>
<ArrowRight className="w-4 h-4 text-orange-400" />
</Link>
)}
<Link
href="/admin/maintenance"
className="flex items-center justify-between p-2 bg-white rounded-lg hover:bg-orange-50 transition-colors"
>
<div className="flex items-center gap-2">
<Wrench className="w-4 h-4 text-blue-600" />
<span className="text-sm text-slate-700">All Maintenance</span>
</div>
<ArrowRight className="w-4 h-4 text-orange-400" />
</Link>
</div>
</div>
<div className="bg-green-50 p-4 rounded-xl border border-green-100"> <div className="bg-green-50 p-4 rounded-xl border border-green-100">
<h3 className="font-semibold text-green-800 mb-3 flex items-center gap-2"> <h3 className="font-semibold text-green-800 mb-3 flex items-center gap-2">
<User className="w-5 h-5" /> Reporter <User className="w-5 h-5" /> Reporter
@@ -790,8 +830,10 @@ export default function MaintenanceDetailPage() {
</button> </button>
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-3">
{(record.partsUsed || []).length > 0 ? ( {record.partsUsed && record.partsUsed.length > 0 ? (
<>
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead className="bg-orange-100"> <thead className="bg-orange-100">
<tr> <tr>
@@ -843,19 +885,67 @@ export default function MaintenanceDetailPage() {
</tr> </tr>
))} ))}
</tbody> </tbody>
<tfoot className="bg-orange-50">
<tr>
<td colSpan={3} className="px-3 py-2 text-right font-semibold text-orange-800">Parts Total:</td>
<td className="px-3 py-2 text-right font-bold text-orange-700">
{record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0).toLocaleString()}
</td>
<td></td>
</tr>
</tfoot>
</table> </table>
</div>
<div className="lg:hidden grid grid-cols-1 sm:grid-cols-2 gap-3">
{record.partsUsed.map((part) => (
<div key={part.id} className="bg-white rounded-lg border border-orange-200 p-3">
<div className="flex items-start justify-between mb-2">
<span className="font-medium text-slate-800">{part.partName}</span>
<button
onClick={() => {
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.filter(p => p.id !== part.id)
} : null);
}}
className="text-red-400 hover:text-red-600 p-1"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between text-sm mb-2">
<div className="flex items-center gap-2">
<span className="text-slate-500">Qty:</span>
<input
type="number"
min="1"
value={part.quantity}
onChange={(e) => {
const newQty = Math.max(1, parseInt(e.target.value) || 1);
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.map(p =>
p.id === part.id
? { ...p, quantity: newQty, totalPrice: newQty * p.unitPrice }
: p
)
} : null);
}}
className="w-16 px-2 py-1 border border-orange-200 rounded text-center text-sm"
/>
</div>
<span className="text-slate-600">{part.unitPrice.toLocaleString()}/unit</span>
</div>
<div className="pt-2 border-t border-orange-100 flex justify-between">
<span className="text-xs text-slate-500">Total</span>
<span className="font-bold text-orange-700">{part.totalPrice.toLocaleString()}</span>
</div>
</div>
))}
</div>
</>
) : ( ) : (
<p className="text-sm text-orange-400">No parts added</p> <p className="text-sm text-orange-400">No parts added</p>
)} )}
{record.partsUsed && record.partsUsed.length > 0 && (
<div className="bg-orange-50 rounded-lg p-3 flex justify-between items-center">
<span className="font-semibold text-orange-800">Parts Total:</span>
<span className="text-lg font-bold text-orange-700">
{record.partsUsed.reduce((sum, p) => sum + p.totalPrice, 0).toLocaleString()}
</span>
</div>
)}
</div> </div>
@@ -922,17 +1012,22 @@ export default function MaintenanceDetailPage() {
</div> </div>
<div className="p-4 space-y-4"> <div className="p-4 space-y-4">
<div className="bg-slate-50 p-3 rounded-lg"> <div className="bg-slate-50 p-3 rounded-lg">
<div className="flex justify-between text-sm mb-2">
<span className="text-slate-500">Estimated Cost:</span>
<span className="font-medium text-slate-600">{record.estimatedCost.toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm mb-2"> <div className="flex justify-between text-sm mb-2">
<span className="text-slate-500">Parts Total:</span> <span className="text-slate-500">Parts Total:</span>
<span className="font-medium text-orange-600">{record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0).toLocaleString()}</span> <span className="font-medium text-orange-600">{record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0).toLocaleString()}</span>
</div> </div>
<div className="flex justify-between text-sm mb-2"> <div>
<span className="text-slate-500">Service Cost (Labor):</span> <label className="text-xs font-medium text-blue-600 mb-1 block">Service Cost (Labor)</label>
<span className="font-medium text-blue-600">{(record.serviceCost || 0).toLocaleString()}</span> <div className="flex gap-2">
<input
type="number"
min="0"
value={record.serviceCost || ''}
onChange={(e) => setRecord(prev => prev ? { ...prev, serviceCost: parseInt(e.target.value) || 0 } : null)}
className="flex-1 px-3 py-2 border border-blue-200 rounded-lg text-sm"
placeholder="Enter labor cost"
/>
</div>
</div> </div>
</div> </div>
@@ -1256,59 +1351,6 @@ export default function MaintenanceDetailPage() {
</div> </div>
)} )}
<div className="fixed bottom-4 right-4 z-40">
<button
onClick={() => setShowAddServiceCostModal(true)}
className="px-4 py-3 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 flex items-center gap-2"
>
<DollarSign className="w-5 h-5" /> Add Service Cost
</button>
</div>
{showAddServiceCostModal && (
<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-4 border-b border-slate-100 flex justify-between items-center">
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
<Wrench className="w-5 h-5 text-blue-600" /> Add Service Cost
</h3>
<button onClick={() => setShowAddServiceCostModal(false)} 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">Service Cost (Labor charge)</label>
<input
type="number"
value={serviceCostInput}
onChange={(e) => setServiceCostInput(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-lg"
placeholder="Enter service cost"
/>
</div>
<div className="bg-slate-50 p-3 rounded-lg">
<p className="text-sm text-slate-600">Current Service Cost: <span className="font-bold text-blue-600">{record?.serviceCost || 0}</span></p>
</div>
</div>
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
<button onClick={() => setShowAddServiceCostModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
<button
onClick={() => {
const cost = parseFloat(serviceCostInput) || 0;
setRecord(prev => prev ? { ...prev, serviceCost: cost } : null);
setShowAddServiceCostModal(false);
setServiceCostInput('');
}}
className="px-4 py-2 bg-blue-600 text-white rounded-lg flex items-center gap-2"
>
<Plus className="w-4 h-4" /> Add Cost
</button>
</div>
</div>
</div>
)}
{showPaymentSuccess && ( {showPaymentSuccess && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <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="bg-white rounded-xl shadow-xl w-full max-w-md">

View File

@@ -1,13 +1,14 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { import {
AlertTriangle, Search, Plus, X, Check, Clock, Bike, User, Phone, AlertTriangle, Search, Plus, X, Check, Clock, Bike, User, Phone,
MapPin, FileText, Image, DollarSign, Wrench, Battery, Key, MapPin, FileText, Image as ImageIcon, DollarSign, Wrench, Battery, Key,
CheckCircle, XCircle, ChevronDown, ChevronUp, Download, Eye, Edit, CheckCircle, XCircle, ChevronDown, ChevronUp, Download, Eye, Edit,
MessageSquare, Filter, Calendar, Save, Printer, Send MessageSquare, Filter, Calendar, Save, Printer, Send, Activity
} from 'lucide-react'; } from 'lucide-react';
type TransactionType = 'deposit' | 'rent_income' | 'investor_funding' | 'investor_withdraw' | 'salary' | 'rent_expense' | 'utility' | 'maintenance' | 'bike_purchase' | 'bike_sale' | 'other_income' | 'other_expense'; type TransactionType = 'deposit' | 'rent_income' | 'investor_funding' | 'investor_withdraw' | 'salary' | 'rent_expense' | 'utility' | 'maintenance' | 'bike_purchase' | 'bike_sale' | 'other_income' | 'other_expense';
@@ -245,6 +246,7 @@ const typeIcons: Record<string, any> = {
}; };
export default function MaintenancePage() { export default function MaintenancePage() {
const router = useRouter();
const [mainCategory, setMainCategory] = useState<'damage' | 'maintenance'>('damage'); const [mainCategory, setMainCategory] = useState<'damage' | 'maintenance'>('damage');
const [targetType, setTargetType] = useState<'all' | 'battery' | 'fleet'>('all'); const [targetType, setTargetType] = useState<'all' | 'battery' | 'fleet'>('all');
const [records, setRecords] = useState<MaintenanceRecord[]>(mockMaintenance); const [records, setRecords] = useState<MaintenanceRecord[]>(mockMaintenance);
@@ -261,6 +263,7 @@ export default function MaintenancePage() {
const [newNoteText, setNewNoteText] = useState(''); const [newNoteText, setNewNoteText] = useState('');
const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({}); const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({});
const [reportType, setReportType] = useState<'damage' | 'maintenance'>('damage'); const [reportType, setReportType] = useState<'damage' | 'maintenance'>('damage');
const [showSuccessModal, setShowSuccessModal] = useState(false);
const filteredRecords = records.filter(r => { const filteredRecords = records.filter(r => {
const isDamage = r.type === 'damage'; const isDamage = r.type === 'damage';
@@ -281,13 +284,21 @@ export default function MaintenancePage() {
const damageRecords = records.filter(r => r.type === 'damage'); const damageRecords = records.filter(r => r.type === 'damage');
const maintenanceRecords = records.filter(r => r.type !== 'damage'); const maintenanceRecords = records.filter(r => r.type !== 'damage');
const currentMonth = new Date().toISOString().slice(0, 7);
const stats = { const stats = {
damageCount: damageRecords.length, damageCount: damageRecords.length,
maintenanceCount: maintenanceRecords.length, maintenanceCount: maintenanceRecords.length,
damageThisMonth: damageRecords.filter(r => r.date?.slice(0, 7) === currentMonth).length,
maintenanceThisMonth: maintenanceRecords.filter(r => r.date?.slice(0, 7) === currentMonth).length,
completedThisMonth: records.filter(r => r.status === 'completed' && r.resolvedAt?.slice(0, 7) === currentMonth).length,
batteryDamage: damageRecords.filter(r => r.batteryId).length, batteryDamage: damageRecords.filter(r => r.batteryId).length,
fleetDamage: damageRecords.filter(r => r.bikeId && !r.batteryId).length, fleetDamage: damageRecords.filter(r => r.bikeId && !r.batteryId).length,
batteryMaintenance: maintenanceRecords.filter(r => r.batteryId).length, batteryMaintenance: maintenanceRecords.filter(r => r.batteryId).length,
fleetMaintenance: maintenanceRecords.filter(r => r.bikeId && !r.batteryId).length, fleetMaintenance: maintenanceRecords.filter(r => r.bikeId && !r.batteryId).length,
upcomingBattery: maintenanceRecords.filter(r => r.batteryId && r.status === 'reported').length,
upcomingFleet: maintenanceRecords.filter(r => r.bikeId && !r.batteryId && r.status === 'reported').length,
ongoingBattery: maintenanceRecords.filter(r => r.batteryId && r.status === 'in_progress').length,
ongoingFleet: maintenanceRecords.filter(r => r.bikeId && !r.batteryId && r.status === 'in_progress').length,
pendingMaintenance: maintenanceRecords.filter(r => r.status === 'reported' || r.status === 'in_progress').length, pendingMaintenance: maintenanceRecords.filter(r => r.status === 'reported' || r.status === 'in_progress').length,
completedMaintenance: maintenanceRecords.filter(r => r.status === 'completed').length, completedMaintenance: maintenanceRecords.filter(r => r.status === 'completed').length,
critical: records.filter(r => r.severity === 'critical' && r.status !== 'completed').length, critical: records.filter(r => r.severity === 'critical' && r.status !== 'completed').length,
@@ -414,7 +425,7 @@ export default function MaintenancePage() {
}; };
return ( return (
<div className="p-4 lg:p-6"> <div className="p-4 lg:p-6 mb-6 lg:mb-0">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6"> <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
<div> <div>
<h1 className="text-2xl lg:text-3xl font-extrabold text-slate-800">Damage & Maintenance</h1> <h1 className="text-2xl lg:text-3xl font-extrabold text-slate-800">Damage & Maintenance</h1>
@@ -436,8 +447,11 @@ export default function MaintenancePage() {
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center">
<AlertTriangle className="w-6 h-6 text-red-600" /> <AlertTriangle className="w-6 h-6 text-red-600" />
</div> </div>
<div> <div className="flex-1">
<div className="flex items-baseline justify-between gap-2">
<p className="text-2xl font-extrabold text-slate-800">{stats.damageCount}</p> <p className="text-2xl font-extrabold text-slate-800">{stats.damageCount}</p>
<span className="text-xs text-red-600 font-medium bg-red-50 px-2 py-0.5 rounded">{stats.damageThisMonth} this month</span>
</div>
<p className="text-sm text-slate-500">Total Damage</p> <p className="text-sm text-slate-500">Total Damage</p>
</div> </div>
</div> </div>
@@ -447,19 +461,25 @@ export default function MaintenancePage() {
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center">
<Wrench className="w-6 h-6 text-blue-600" /> <Wrench className="w-6 h-6 text-blue-600" />
</div> </div>
<div> <div className="flex-1">
<div className="flex items-baseline justify-between gap-2">
<p className="text-2xl font-extrabold text-slate-800">{stats.maintenanceCount}</p> <p className="text-2xl font-extrabold text-slate-800">{stats.maintenanceCount}</p>
<span className="text-xs text-blue-600 font-medium bg-blue-50 px-2 py-0.5 rounded">{stats.maintenanceThisMonth} this month</span>
</div>
<p className="text-sm text-slate-500">Total Maintenance</p> <p className="text-sm text-slate-500">Total Maintenance</p>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-white rounded-xl p-5 shadow-sm border border-slate-100"> <div className="bg-white rounded-xl p-5 shadow-sm border border-green-100">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-green-50 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-green-50 flex items-center justify-center">
<CheckCircle className="w-6 h-6 text-green-600" /> <CheckCircle className="w-6 h-6 text-green-600" />
</div> </div>
<div> <div className="flex-1">
<div className="flex items-baseline justify-between gap-2">
<p className="text-2xl font-extrabold text-slate-800">{stats.completed}</p> <p className="text-2xl font-extrabold text-slate-800">{stats.completed}</p>
<span className="text-xs text-green-600 font-medium bg-green-50 px-2 py-0.5 rounded">{stats.completedThisMonth} this month</span>
</div>
<p className="text-sm text-slate-500">Completed</p> <p className="text-sm text-slate-500">Completed</p>
</div> </div>
</div> </div>
@@ -550,36 +570,36 @@ export default function MaintenancePage() {
</div> </div>
</div> </div>
</div> </div>
<div className="bg-green-50 rounded-xl p-4 border border-green-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Battery className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.batteryMaintenance}</p>
<p className="text-sm text-slate-500">Battery Maintenance</p>
</div>
</div>
</div>
<div className="bg-purple-50 rounded-xl p-4 border border-purple-100 flex items-center justify-between"> <div className="bg-purple-50 rounded-xl p-4 border border-purple-100 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"> <div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<Bike className="w-5 h-5 text-purple-600" /> <Bike className="w-5 h-5 text-purple-600" />
</div> </div>
<div> <div>
<p className="text-lg font-bold text-slate-800">{stats.fleetMaintenance}</p> <p className="text-lg font-bold text-slate-800">{stats.upcomingFleet}</p>
<p className="text-sm text-slate-500">Fleet Maintenance</p> <p className="text-sm text-slate-500">Upcoming Fleet</p>
</div>
</div>
</div>
<div className="bg-green-50 rounded-xl p-4 border border-green-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Battery className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.upcomingBattery}</p>
<p className="text-sm text-slate-500">Upcoming Battery</p>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-orange-50 rounded-xl p-4 border border-orange-100 flex items-center justify-between"> <div className="bg-orange-50 rounded-xl p-4 border border-orange-100 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center"> <div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
<Clock className="w-5 h-5 text-orange-600" /> <Activity className="w-5 h-5 text-orange-600" />
</div> </div>
<div> <div>
<p className="text-lg font-bold text-slate-800">{stats.pendingMaintenance}</p> <p className="text-lg font-bold text-slate-800">{stats.ongoingBattery + stats.ongoingFleet}</p>
<p className="text-sm text-slate-500">Pending Maintenance</p> <p className="text-sm text-slate-500">Ongoing Maintenance</p>
</div> </div>
</div> </div>
</div> </div>
@@ -656,57 +676,59 @@ export default function MaintenancePage() {
{filteredRecords.map(record => { {filteredRecords.map(record => {
const TypeIcon = typeIcons[record.type]; const TypeIcon = typeIcons[record.type];
return ( return (
<Link key={record.id} href={`/admin/maintenance/${record.id}`} className="block p-5 hover:bg-slate-50 transition-colors"> <Link key={record.id} href={`/admin/maintenance/${record.id}`} className="block p-4 lg:p-5 hover:bg-slate-50 transition-colors">
<div className="flex flex-col lg:flex-row lg:items-start gap-4"> <div className="flex flex-col lg:flex-row lg:items-start gap-3 lg:gap-4">
<div className="flex items-center gap-4"> <div className="flex items-start gap-3">
<div className="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center"> <div className="w-10 h-10 lg:w-12 lg:h-12 rounded-lg lg:rounded-xl bg-slate-100 flex items-center justify-center flex-shrink-0">
<TypeIcon className="w-6 h-6 text-slate-600" /> <TypeIcon className="w-5 h-5 lg:w-6 lg:h-6 text-slate-600" />
</div> </div>
<div> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-1.5 lg:gap-2">
<p className="font-semibold text-slate-800">{record.id}</p> <p className="font-semibold text-slate-800 text-sm lg:text-base">{record.id}</p>
{record.batteryId && ( {record.batteryId && (
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700"> <span className="inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded-full bg-green-100 text-green-700">
<Battery className="w-3 h-3" /> Battery <Battery className="w-3 h-3" /> Battery
</span> </span>
)} )}
{!record.batteryId && record.bikeId && ( {!record.batteryId && record.bikeId && (
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-purple-100 text-purple-700"> <span className="inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded-full bg-purple-100 text-purple-700">
<Bike className="w-3 h-3" /> Fleet <Bike className="w-3 h-3" /> Fleet
</span> </span>
)} )}
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${severityColors[record.severity]}`}> <span className={`inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded-full ${severityColors[record.severity]}`}>
{record.severity} {record.severity}
</span> </span>
</div> </div>
<p className="text-sm text-slate-500 flex items-center gap-2"> <p className="text-xs lg:text-sm text-slate-500 flex flex-wrap items-center gap-x-1 lg:gap-x-2">
<Bike className="w-3 h-3" /> {record.bikeModel} ({record.bikePlate}) <span className="flex items-center gap-1"><Bike className="w-3 h-3" /> {record.bikeModel}</span>
<span className="text-slate-300">|</span> <span className="hidden sm:inline text-slate-300">|</span>
<User className="w-3 h-3" /> {record.reporterName} <span className="text-xs">{record.bikePlate}</span>
<span className="hidden lg:inline text-slate-300">|</span>
<span className="flex items-center gap-1"><User className="w-3 h-3" /> {record.reporterName}</span>
</p> </p>
</div> </div>
</div> </div>
<div className="flex-1"> <div className="flex-1 min-w-0">
<p className="text-sm text-slate-700">{record.description}</p> <p className="text-sm text-slate-700 line-clamp-2">{record.description}</p>
<div className="flex flex-wrap gap-4 text-sm text-slate-500 mt-1"> <div className="flex flex-wrap gap-2 lg:gap-4 text-xs lg:text-sm text-slate-500 mt-1.5">
<p className="flex items-center gap-1"> <p className="flex items-center gap-1">
<Clock className="w-3 h-3" /> {record.date} <Clock className="w-3 h-3" /> {record.date}
</p> </p>
<p className="flex items-center gap-1"> <p className="flex items-center gap-1 truncate max-w-[100px] lg:max-w-none">
<MapPin className="w-3 h-3" /> {record.location} <MapPin className="w-3 h-3 flex-shrink-0" /> <span className="truncate">{record.location}</span>
</p> </p>
{record.images.length > 0 && ( {record.images.length > 0 && (
<p className="flex items-center gap-1 text-blue-600"> <p className="flex items-center gap-1 text-blue-600">
<Image className="w-3 h-3" /> {record.images.length} photos <ImageIcon className="w-3 h-3" /> {record.images.length}
</p> </p>
)} )}
{record.notes.length > 0 && ( {record.notes.length > 0 && (
<button <button
onClick={() => toggleNotes(record.id)} onClick={(e) => { e.preventDefault(); toggleNotes(record.id); }}
className="flex items-center gap-1 text-purple-600" className="flex items-center gap-1 text-purple-600"
> >
<MessageSquare className="w-3 h-3" /> {record.notes.length} notes <MessageSquare className="w-3 h-3" /> {record.notes.length}
{expandedNotes.includes(record.id) ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />} {expandedNotes.includes(record.id) ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
</button> </button>
)} )}
@@ -721,24 +743,24 @@ export default function MaintenancePage() {
)} )}
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center justify-between lg:justify-end gap-2 lg:gap-3 mt-2 lg:mt-0">
<div className="text-right"> <div className="text-left lg:text-right">
<p className="text-sm font-medium text-slate-700">{record.actualCost || record.estimatedCost}</p> <p className="text-sm font-medium text-slate-700">{record.actualCost || record.estimatedCost}</p>
<p className="text-xs text-slate-500">{record.paymentStatus === 'paid' ? 'Paid' : record.paymentStatus === 'approved' ? 'Approved' : 'Payment ' + record.paymentStatus}</p> <p className="text-xs text-slate-500">{record.paymentStatus === 'paid' ? 'Paid' : record.paymentStatus === 'approved' ? 'Approved' : 'Payment ' + record.paymentStatus}</p>
</div> </div>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[record.status]}`}> <div className="flex items-center gap-1.5 lg:gap-2">
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full ${statusColors[record.status]}`}>
{record.status === 'reported' && <Clock className="w-3 h-3" />} {record.status === 'reported' && <Clock className="w-3 h-3" />}
{record.status === 'in_progress' && <Wrench className="w-3 h-3" />} {record.status === 'in_progress' && <Wrench className="w-3 h-3" />}
{record.status === 'parts_ordered' && <AlertTriangle className="w-3 h-3" />} {record.status === 'parts_ordered' && <AlertTriangle className="w-3 h-3" />}
{record.status === 'completed' && <CheckCircle className="w-3 h-3" />} {record.status === 'completed' && <CheckCircle className="w-3 h-3" />}
{record.status === 'cancelled' && <XCircle className="w-3 h-3" />} {record.status === 'cancelled' && <XCircle className="w-3 h-3" />}
{record.status.replace('_', ' ')} <span className="hidden sm:inline">{record.status.replace('_', ' ')}</span>
</span> </span>
<div className="flex gap-1">
<button <button
onClick={(e) => { e.preventDefault(); setSelectedRecord(record); setShowDetailsModal(true); }} onClick={(e) => { e.preventDefault(); router.push(`/admin/maintenance/${record.id}`); }}
className="p-2 hover:bg-slate-100 rounded-lg text-slate-500" className="p-1.5 lg:p-2 hover:bg-slate-100 rounded-lg text-slate-500"
title="View Details" title="View Details"
> >
<Eye className="w-4 h-4" /> <Eye className="w-4 h-4" />
@@ -843,7 +865,7 @@ export default function MaintenancePage() {
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{selectedRecord.images.map((img) => ( {selectedRecord.images.map((img) => (
<div key={img.id} className="aspect-square bg-slate-100 rounded-lg flex flex-col items-center justify-center"> <div key={img.id} className="aspect-square bg-slate-100 rounded-lg flex flex-col items-center justify-center">
<Image className="w-8 h-8 text-slate-400" /> <ImageIcon className="w-8 h-8 text-slate-400" />
<span className="text-xs text-slate-500 mt-1">{img.name}</span> <span className="text-xs text-slate-500 mt-1">{img.name}</span>
</div> </div>
))} ))}
@@ -923,6 +945,25 @@ export default function MaintenancePage() {
</button> </button>
</div> </div>
<div className="p-4 overflow-y-auto max-h-[70vh] space-y-4"> <div className="p-4 overflow-y-auto max-h-[70vh] space-y-4">
<div>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Report Type *</label>
<div className="flex gap-2 mb-4">
<button
type="button"
onClick={() => setReportType('damage')}
className={`flex-1 py-3 px-4 rounded-xl border-2 flex items-center justify-center gap-2 font-medium transition-colors ${reportType === 'damage' ? 'border-red-500 bg-red-50 text-red-700' : 'border-slate-200 text-slate-600 hover:border-red-300'}`}
>
<AlertTriangle className="w-5 h-5" /> Damage
</button>
<button
type="button"
onClick={() => setReportType('maintenance')}
className={`flex-1 py-3 px-4 rounded-xl border-2 flex items-center justify-center gap-2 font-medium transition-colors ${reportType === 'maintenance' ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-slate-200 text-slate-600 hover:border-blue-300'}`}
>
<Wrench className="w-5 h-5" /> Maintenance
</button>
</div>
</div>
<div> <div>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Select Target *</label> <label className="text-sm font-semibold text-slate-700 mb-2 block">Select Target *</label>
<div className="grid grid-cols-2 gap-3 mb-4"> <div className="grid grid-cols-2 gap-3 mb-4">
@@ -949,14 +990,26 @@ export default function MaintenancePage() {
<label className="text-xs font-medium text-slate-600 mb-1 block">Category *</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Category *</label>
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"> <select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select Category</option> <option value="">Select Category</option>
{reportType === 'damage' ? (
<>
<option value="damage">Damage</option> <option value="damage">Damage</option>
<option value="repair">Repair</option> <option value="repair">Repair</option>
<option value="accident">Accident</option>
<option value="theft">Theft</option>
<option value="vandalism">Vandalism</option>
</>
) : (
<>
<option value="service">Service</option> <option value="service">Service</option>
<option value="routine_service">Routine Service</option>
<option value="repair">Repair</option>
<option value="battery_swap">Battery Swap</option> <option value="battery_swap">Battery Swap</option>
<option value="inspection">Inspection</option> <option value="inspection">Inspection</option>
<option value="other">Other</option> </>
)}
</select> </select>
</div> </div>
{reportType === 'damage' ? (
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Severity *</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Severity *</label>
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"> <select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
@@ -966,6 +1019,16 @@ export default function MaintenancePage() {
<option value="cosmetic">Cosmetic</option> <option value="cosmetic">Cosmetic</option>
</select> </select>
</div> </div>
) : (
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Status</label>
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="pending">Pending</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
</select>
</div>
)}
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Battery ID</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Battery ID</label>
<input type="text" placeholder="BAT-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="text" placeholder="BAT-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
@@ -976,6 +1039,8 @@ export default function MaintenancePage() {
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
{reportType === 'damage' ? (
<>
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Name *</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Name *</label>
<input type="text" placeholder="Enter name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="text" placeholder="Enter name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
@@ -984,6 +1049,19 @@ export default function MaintenancePage() {
<label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Phone *</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Phone *</label>
<input type="tel" placeholder="01XXXXXXXXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="tel" placeholder="01XXXXXXXXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div> </div>
</>
) : (
<>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Performed By</label>
<input type="text" placeholder="Technician name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Next Due Date</label>
<input type="date" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
</>
)}
</div> </div>
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Description *</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Description *</label>
@@ -995,14 +1073,16 @@ export default function MaintenancePage() {
<input type="text" placeholder="e.g., Gulshan Hub" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="text" placeholder="e.g., Gulshan Hub" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div> </div>
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Estimated Cost ()</label> <label className="text-xs font-medium text-slate-600 mb-1 block">
{reportType === 'damage' ? 'Estimated Cost (৳)' : 'Service Cost (৳)'}
</label>
<input type="number" placeholder="0" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="number" placeholder="0" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div> </div>
</div> </div>
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Upload Images</label> <label className="text-xs font-medium text-slate-600 mb-1 block">Upload Images</label>
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer"> <div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer">
<Image className="w-8 h-8 text-slate-300 mx-auto mb-2" /> <ImageIcon className="w-8 h-8 text-slate-300 mx-auto mb-2" />
<p className="text-sm text-slate-500">Click to upload images</p> <p className="text-sm text-slate-500">Click to upload images</p>
<p className="text-xs text-slate-400">JPG, PNG up to 5MB</p> <p className="text-xs text-slate-400">JPG, PNG up to 5MB</p>
</div> </div>
@@ -1012,7 +1092,7 @@ export default function MaintenancePage() {
<button onClick={() => setShowNewModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50"> <button onClick={() => setShowNewModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
Cancel Cancel
</button> </button>
<button onClick={() => { setShowNewModal(false); alert('Issue reported successfully!'); }} className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark"> <button onClick={() => { setShowNewModal(false); setShowSuccessModal(true); }} className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark">
Submit Report Submit Report
</button> </button>
</div> </div>
@@ -1117,6 +1197,24 @@ export default function MaintenancePage() {
</div> </div>
</div> </div>
)} )}
{showSuccessModal && (
<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 p-6 text-center">
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-bold text-slate-800 mb-2">Issue Reported Successfully!</h3>
<p className="text-slate-500 mb-6">Your issue has been submitted and will be reviewed shortly.</p>
<button
onClick={() => setShowSuccessModal(false)}
className="px-6 py-2 bg-accent text-white rounded-lg hover:bg-accent-dark"
>
OK
</button>
</div>
</div>
)}
</div> </div>
); );
} }