Compare commits

..

2 Commits

3 changed files with 586 additions and 282 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,44 +830,68 @@ 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 ? (
<table className="w-full text-sm"> <>
<thead className="bg-orange-100"> <div className="hidden lg:block overflow-x-auto">
<tr> <table className="w-full text-sm">
<th className="px-3 py-2 text-left text-orange-800 font-medium">Part</th> <thead className="bg-orange-100">
<th className="px-3 py-2 text-center text-orange-800 font-medium">Qty</th> <tr>
<th className="px-3 py-2 text-right text-orange-800 font-medium">Unit Price</th> <th className="px-3 py-2 text-left text-orange-800 font-medium">Part</th>
<th className="px-3 py-2 text-right text-orange-800 font-medium">Total</th> <th className="px-3 py-2 text-center text-orange-800 font-medium">Qty</th>
<th className="px-3 py-2 text-center text-orange-800 font-medium">Action</th> <th className="px-3 py-2 text-right text-orange-800 font-medium">Unit Price</th>
</tr> <th className="px-3 py-2 text-right text-orange-800 font-medium">Total</th>
</thead> <th className="px-3 py-2 text-center text-orange-800 font-medium">Action</th>
<tbody> </tr>
{record.partsUsed?.map((part) => ( </thead>
<tr key={part.id} className="bg-white border-b border-orange-100"> <tbody>
<td className="px-3 py-2 text-slate-700">{part.partName}</td> {record.partsUsed?.map((part) => (
<td className="px-3 py-2 text-center"> <tr key={part.id} className="bg-white border-b border-orange-100">
<input <td className="px-3 py-2 text-slate-700">{part.partName}</td>
type="number" <td className="px-3 py-2 text-center">
min="1" <input
value={part.quantity} type="number"
onChange={(e) => { min="1"
const newQty = Math.max(1, parseInt(e.target.value) || 1); value={part.quantity}
setRecord(prev => prev ? { onChange={(e) => {
...prev, const newQty = Math.max(1, parseInt(e.target.value) || 1);
partsUsed: prev.partsUsed?.map(p => setRecord(prev => prev ? {
p.id === part.id ...prev,
? { ...p, quantity: newQty, totalPrice: newQty * p.unitPrice } partsUsed: prev.partsUsed?.map(p =>
: p p.id === part.id
) ? { ...p, quantity: newQty, totalPrice: newQty * p.unitPrice }
} : null); : p
}} )
className="w-16 px-2 py-1 border border-orange-200 rounded text-center text-sm" } : null);
/> }}
</td> className="w-16 px-2 py-1 border border-orange-200 rounded text-center text-sm"
<td className="px-3 py-2 text-right text-slate-600">{part.unitPrice.toLocaleString()}</td> />
<td className="px-3 py-2 text-right font-medium text-orange-700">{part.totalPrice.toLocaleString()}</td> </td>
<td className="px-3 py-2 text-center"> <td className="px-3 py-2 text-right text-slate-600">{part.unitPrice.toLocaleString()}</td>
<td className="px-3 py-2 text-right font-medium text-orange-700">{part.totalPrice.toLocaleString()}</td>
<td className="px-3 py-2 text-center">
<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>
</td>
</tr>
))}
</tbody>
</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 <button
onClick={() => { onClick={() => {
setRecord(prev => prev ? { setRecord(prev => prev ? {
@@ -839,23 +903,49 @@ export default function MaintenanceDetailPage() {
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
</button> </button>
</td> </div>
</tr> <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>
))} ))}
</tbody> </div>
<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>
) : ( ) : (
<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';
@@ -24,31 +25,31 @@ interface MaintenanceRecord {
severity: DamageSeverity; severity: DamageSeverity;
status: MaintenanceStatus; status: MaintenanceStatus;
paymentStatus: PaymentStatus; paymentStatus: PaymentStatus;
bikeId: string; bikeId: string;
bikeModel: string; bikeModel: string;
bikePlate: string; bikePlate: string;
batteryId?: string; batteryId?: string;
reporterId: string; reporterId: string;
reporterName: string; reporterName: string;
reporterPhone: string; reporterPhone: string;
reporterRole: 'biker' | 'staff' | 'hub'; reporterRole: 'biker' | 'staff' | 'hub';
description: string; description: string;
location: string; location: string;
estimatedCost: number; estimatedCost: number;
actualCost?: number; actualCost?: number;
partsUsed?: string[]; partsUsed?: string[];
images: { id: string; name: string; url: string; uploadedAt: string }[]; images: { id: string; name: string; url: string; uploadedAt: string }[];
bills?: { id: string; name: string; url: string }[]; bills?: { id: string; name: string; url: string }[];
assignedTo?: string; assignedTo?: string;
notes: string[]; notes: string[];
resolvedAt?: string; resolvedAt?: string;
createdAt: string; createdAt: string;
createdBy: string; createdBy: string;
} }
@@ -245,7 +246,9 @@ const typeIcons: Record<string, any> = {
}; };
export default function MaintenancePage() { export default function MaintenancePage() {
const [activeTab, setActiveTab] = useState<'all' | MaintenanceType>('all'); const router = useRouter();
const [mainCategory, setMainCategory] = useState<'damage' | 'maintenance'>('damage');
const [targetType, setTargetType] = useState<'all' | 'battery' | 'fleet'>('all');
const [records, setRecords] = useState<MaintenanceRecord[]>(mockMaintenance); const [records, setRecords] = useState<MaintenanceRecord[]>(mockMaintenance);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all');
@@ -259,20 +262,45 @@ export default function MaintenancePage() {
const [expandedNotes, setExpandedNotes] = useState<string[]>([]); const [expandedNotes, setExpandedNotes] = useState<string[]>([]);
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 [showSuccessModal, setShowSuccessModal] = useState(false);
const filteredRecords = records.filter(r => { const filteredRecords = records.filter(r => {
const matchesTab = activeTab === 'all' || r.type === activeTab; const isDamage = r.type === 'damage';
const matchesSearch = !searchQuery || 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.bikeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()) || r.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.bikePlate.toLowerCase().includes(searchQuery.toLowerCase()) || r.bikePlate.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.reporterName.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; 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 currentMonth = new Date().toISOString().slice(0, 7);
const stats = { const stats = {
damageCount: damageRecords.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,
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,
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,
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,
inProgress: records.filter(r => r.status === 'in_progress' || r.status === 'parts_ordered').length, inProgress: records.filter(r => r.status === 'in_progress' || r.status === 'parts_ordered').length,
completed: records.filter(r => r.status === 'completed').length, completed: records.filter(r => r.status === 'completed').length,
@@ -281,15 +309,15 @@ export default function MaintenancePage() {
}; };
const toggleNotes = (id: string) => { const toggleNotes = (id: string) => {
setExpandedNotes(prev => setExpandedNotes(prev =>
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
); );
}; };
const handleAddNote = () => { const handleAddNote = () => {
if (!selectedRecord || !newNoteText.trim()) return; if (!selectedRecord || !newNoteText.trim()) return;
setRecords(prev => prev.map(r => setRecords(prev => prev.map(r =>
r.id === selectedRecord.id r.id === selectedRecord.id
? { ...r, notes: [...r.notes, newNoteText] } ? { ...r, notes: [...r.notes, newNoteText] }
: r : r
)); ));
@@ -299,8 +327,8 @@ export default function MaintenancePage() {
const handleCompleteRecord = () => { const handleCompleteRecord = () => {
if (!selectedRecord) return; if (!selectedRecord) return;
setRecords(prev => prev.map(r => setRecords(prev => prev.map(r =>
r.id === selectedRecord.id r.id === selectedRecord.id
? { ...r, status: 'completed', resolvedAt: new Date().toISOString().split('T')[0] } ? { ...r, status: 'completed', resolvedAt: new Date().toISOString().split('T')[0] }
: r : r
)); ));
@@ -311,9 +339,9 @@ export default function MaintenancePage() {
const handlePayment = (source: 'biker' | 'company') => { const handlePayment = (source: 'biker' | 'company') => {
if (!selectedRecord) return; if (!selectedRecord) return;
const cost = selectedRecord.actualCost || selectedRecord.estimatedCost; const cost = selectedRecord.actualCost || selectedRecord.estimatedCost;
setRecords(prev => prev.map(r => setRecords(prev => prev.map(r =>
r.id === selectedRecord.id r.id === selectedRecord.id
? { ...r, paymentStatus: 'paid' } ? { ...r, paymentStatus: 'paid' }
: r : r
)); ));
@@ -347,22 +375,22 @@ export default function MaintenancePage() {
import('jspdf').then(jsPDF => { import('jspdf').then(jsPDF => {
const doc = new jsPDF.default(); const doc = new jsPDF.default();
const cost = selectedRecord.actualCost || selectedRecord.estimatedCost; const cost = selectedRecord.actualCost || selectedRecord.estimatedCost;
doc.setFontSize(18); doc.setFontSize(18);
doc.setTextColor(6, 95, 70); doc.setTextColor(6, 95, 70);
doc.text('JAIBEN Mobility Ltd', 20, 20); doc.text('JAIBEN Mobility Ltd', 20, 20);
doc.setFontSize(14); doc.setFontSize(14);
doc.setTextColor(0); doc.setTextColor(0);
doc.text('Maintenance Invoice', 20, 32); doc.text('Maintenance Invoice', 20, 32);
doc.setFontSize(10); doc.setFontSize(10);
doc.setTextColor(100); doc.setTextColor(100);
doc.text(`Invoice No: INV-${selectedRecord.id}`, 20, 42); doc.text(`Invoice No: INV-${selectedRecord.id}`, 20, 42);
doc.text(`Date: ${selectedRecord.date}`, 20, 48); doc.text(`Date: ${selectedRecord.date}`, 20, 48);
doc.text(`Issue Type: ${selectedRecord.type}`, 20, 54); doc.text(`Issue Type: ${selectedRecord.type}`, 20, 54);
doc.text(`Severity: ${selectedRecord.severity}`, 20, 60); doc.text(`Severity: ${selectedRecord.severity}`, 20, 60);
doc.setFontSize(11); doc.setFontSize(11);
doc.setTextColor(0); doc.setTextColor(0);
doc.text('Bike Details', 20, 72); doc.text('Bike Details', 20, 72);
@@ -371,40 +399,40 @@ export default function MaintenancePage() {
doc.text(`Model: ${selectedRecord.bikeModel}`, 20, 84); doc.text(`Model: ${selectedRecord.bikeModel}`, 20, 84);
doc.text(`License Plate: ${selectedRecord.bikePlate}`, 20, 90); doc.text(`License Plate: ${selectedRecord.bikePlate}`, 20, 90);
if (selectedRecord.batteryId) doc.text(`Battery ID: ${selectedRecord.batteryId}`, 20, 96); if (selectedRecord.batteryId) doc.text(`Battery ID: ${selectedRecord.batteryId}`, 20, 96);
doc.setFontSize(11); doc.setFontSize(11);
doc.text('Description', 20, 108); doc.text('Description', 20, 108);
doc.setFontSize(10); doc.setFontSize(10);
const descLines = doc.splitTextToSize(selectedRecord.description, 170); const descLines = doc.splitTextToSize(selectedRecord.description, 170);
doc.text(descLines, 20, 114); doc.text(descLines, 20, 114);
doc.setFontSize(11); doc.setFontSize(11);
doc.text('Cost Breakdown', 20, 130); doc.text('Cost Breakdown', 20, 130);
doc.setFontSize(10); doc.setFontSize(10);
doc.text(`Estimated Cost: ৳${selectedRecord.estimatedCost}`, 20, 136); doc.text(`Estimated Cost: ৳${selectedRecord.estimatedCost}`, 20, 136);
if (selectedRecord.actualCost) doc.text(`Actual Cost: ৳${selectedRecord.actualCost}`, 20, 142); if (selectedRecord.actualCost) doc.text(`Actual Cost: ৳${selectedRecord.actualCost}`, 20, 142);
doc.setFontSize(12); doc.setFontSize(12);
doc.setTextColor(6, 95, 70); doc.setTextColor(6, 95, 70);
doc.text(`Total: ৳${cost}`, 20, 152); doc.text(`Total: ৳${cost}`, 20, 152);
doc.setFontSize(9); doc.setFontSize(9);
doc.setTextColor(150); doc.setTextColor(150);
doc.text('Generated from JAIBEN Maintenance System', 20, 280); doc.text('Generated from JAIBEN Maintenance System', 20, 280);
doc.save(`maintenance-invoice-${selectedRecord.id}.pdf`); doc.save(`maintenance-invoice-${selectedRecord.id}.pdf`);
}); });
}; };
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>
<p className="text-sm text-slate-500 mt-1">Track bike damage, repairs, and service records</p> <p className="text-sm text-slate-500 mt-1">Track bike damage, repairs, and service records</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => setShowNewModal(true)} onClick={() => setShowNewModal(true)}
className="py-2 px-4 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark flex items-center gap-2" className="py-2 px-4 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark flex items-center gap-2"
> >
@@ -413,36 +441,45 @@ export default function MaintenancePage() {
</div> </div>
</div> </div>
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4 mb-6"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<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-red-100">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<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">
<p className="text-2xl font-extrabold text-slate-800">{stats.critical}</p> <div className="flex items-baseline justify-between gap-2">
<p className="text-sm text-slate-500">Critical</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>
</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-blue-100">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<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">
<p className="text-2xl font-extrabold text-slate-800">{stats.inProgress}</p> <div className="flex items-baseline justify-between gap-2">
<p className="text-sm text-slate-500">In Progress</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>
</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">
<p className="text-2xl font-extrabold text-slate-800">{stats.completed}</p> <div className="flex items-baseline justify-between gap-2">
<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>
@@ -458,7 +495,7 @@ export default function MaintenancePage() {
</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-slate-100">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center">
<DollarSign className="w-6 h-6 text-purple-600" /> <DollarSign className="w-6 h-6 text-purple-600" />
@@ -468,43 +505,145 @@ export default function MaintenancePage() {
<p className="text-sm text-slate-500">Total Cost</p> <p className="text-sm text-slate-500">Total Cost</p>
</div> </div>
</div> </div>
</div> </div> */}
</div> </div>
{mainCategory === 'damage' && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-red-50 rounded-xl p-4 border border-red-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<AlertTriangle className="w-5 h-5 text-red-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.damageCount}</p>
<p className="text-sm text-slate-500">Total Damage</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.batteryDamage}</p>
<p className="text-sm text-slate-500">Battery Damage</p>
</div>
</div>
</div>
<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="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<Bike className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.fleetDamage}</p>
<p className="text-sm text-slate-500">Fleet Damage</p>
</div>
</div>
</div>
<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="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
<Clock className="w-5 h-5 text-orange-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.critical}</p>
<p className="text-sm text-slate-500">Critical Damage</p>
</div>
</div>
</div>
</div>
)}
{mainCategory === 'maintenance' && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-blue-50 rounded-xl p-4 border border-blue-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<Wrench className="w-5 h-5 text-blue-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.maintenanceCount}</p>
<p className="text-sm text-slate-500">Total 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="flex items-center gap-3">
<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" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.upcomingFleet}</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 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="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
<Activity className="w-5 h-5 text-orange-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.ongoingBattery + stats.ongoingFleet}</p>
<p className="text-sm text-slate-500">Ongoing Maintenance</p>
</div>
</div>
</div>
</div>
)}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 mb-6"> <div className="bg-white rounded-xl shadow-sm border border-slate-100 mb-6">
<div className="p-4 border-b border-slate-100"> <div className="p-4 border-b border-slate-100">
<div className="flex flex-col lg:flex-row lg:items-center gap-4"> <div className="flex flex-col lg:flex-row lg:items-center gap-4">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-3">
<button <div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg">
onClick={() => setActiveTab('all')} <button
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${activeTab === 'all' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`} onClick={() => setMainCategory('damage')}
> className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 ${mainCategory === 'damage' ? 'bg-red-600 text-white shadow-sm' : 'text-slate-600 hover:bg-white hover:shadow-sm'}`}
All >
</button> <AlertTriangle className="w-4 h-4" /> Damage
<button </button>
onClick={() => setActiveTab('damage')} <button
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'damage' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`} onClick={() => setMainCategory('maintenance')}
> className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 ${mainCategory === 'maintenance' ? 'bg-blue-600 text-white shadow-sm' : 'text-slate-600 hover:bg-white hover:shadow-sm'}`}
<AlertTriangle className="w-4 h-4" /> Damage >
</button> <Wrench className="w-4 h-4" /> Maintenance
<button </button>
onClick={() => setActiveTab('repair')} </div>
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'repair' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`} <div className="flex items-center gap-1">
> <button
<Wrench className="w-4 h-4" /> Repair onClick={() => setTargetType('all')}
</button> className={`px-3 py-1.5 rounded-lg text-sm font-medium ${targetType === 'all' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
<button >
onClick={() => setActiveTab('service')} All
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'service' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`} </button>
> <button
<Wrench className="w-4 h-4" /> Service onClick={() => setTargetType('battery')}
</button> className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${targetType === 'battery' ? 'bg-green-600 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
<button >
onClick={() => setActiveTab('battery_swap')} <Battery className="w-4 h-4" /> Battery
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'battery_swap' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`} </button>
> <button
<Battery className="w-4 h-4" /> Battery onClick={() => setTargetType('fleet')}
</button> className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${targetType === 'fleet' ? 'bg-purple-600 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
<Bike className="w-4 h-4" /> Fleet
</button>
</div>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="relative"> <div className="relative">
@@ -537,52 +676,64 @@ 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>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${severityColors[record.severity]}`}> {record.batteryId && (
<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
</span>
)}
{!record.batteryId && record.bikeId && (
<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
</span>
)}
<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>
)} )}
</div> </div>
{expandedNotes.includes(record.id) && record.notes.length > 0 && ( {expandedNotes.includes(record.id) && record.notes.length > 0 && (
<div className="mt-2 p-3 bg-purple-50 rounded-lg space-y-1"> <div className="mt-2 p-3 bg-purple-50 rounded-lg space-y-1">
{record.notes.map((note, idx) => ( {record.notes.map((note, idx) => (
@@ -591,25 +742,25 @@ export default function MaintenancePage() {
</div> </div>
)} )}
</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">
{record.status === 'reported' && <Clock className="w-3 h-3" />} <span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full ${statusColors[record.status]}`}>
{record.status === 'in_progress' && <Wrench className="w-3 h-3" />} {record.status === 'reported' && <Clock className="w-3 h-3" />}
{record.status === 'parts_ordered' && <AlertTriangle className="w-3 h-3" />} {record.status === 'in_progress' && <Wrench className="w-3 h-3" />}
{record.status === 'completed' && <CheckCircle className="w-3 h-3" />} {record.status === 'parts_ordered' && <AlertTriangle className="w-3 h-3" />}
{record.status === 'cancelled' && <XCircle className="w-3 h-3" />} {record.status === 'completed' && <CheckCircle className="w-3 h-3" />}
{record.status.replace('_', ' ')} {record.status === 'cancelled' && <XCircle className="w-3 h-3" />}
</span> <span className="hidden sm:inline">{record.status.replace('_', ' ')}</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" />
@@ -620,7 +771,7 @@ export default function MaintenancePage() {
</Link> </Link>
); );
})} })}
{filteredRecords.length === 0 && ( {filteredRecords.length === 0 && (
<div className="p-12 text-center"> <div className="p-12 text-center">
<Wrench className="w-12 h-12 text-slate-300 mx-auto mb-4" /> <Wrench className="w-12 h-12 text-slate-300 mx-auto mb-4" />
@@ -714,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>
))} ))}
@@ -737,13 +888,13 @@ export default function MaintenancePage() {
</div> </div>
<div className="p-4 border-t border-slate-100 flex justify-between"> <div className="p-4 border-t border-slate-100 flex justify-between">
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => { setShowDetailsModal(false); setShowEditModal(true); }} onClick={() => { setShowDetailsModal(false); setShowEditModal(true); }}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2" className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2"
> >
<Edit className="w-4 h-4" /> Edit Record <Edit className="w-4 h-4" /> Edit Record
</button> </button>
<button <button
onClick={() => { setShowDetailsModal(false); setShowAddNoteModal(true); }} onClick={() => { setShowDetailsModal(false); setShowAddNoteModal(true); }}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2" className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50 flex items-center gap-2"
> >
@@ -752,7 +903,7 @@ export default function MaintenancePage() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
{selectedRecord.status !== 'completed' && ( {selectedRecord.status !== 'completed' && (
<button <button
onClick={handleCompleteRecord} onClick={handleCompleteRecord}
className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2" className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"
> >
@@ -760,7 +911,7 @@ export default function MaintenancePage() {
</button> </button>
)} )}
{selectedRecord.status === 'completed' && selectedRecord.paymentStatus !== 'paid' && ( {selectedRecord.status === 'completed' && selectedRecord.paymentStatus !== 'paid' && (
<button <button
onClick={() => { setShowDetailsModal(false); setShowPaymentModal(true); }} onClick={() => { setShowDetailsModal(false); setShowPaymentModal(true); }}
className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 flex items-center gap-2" className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 flex items-center gap-2"
> >
@@ -768,7 +919,7 @@ export default function MaintenancePage() {
</button> </button>
)} )}
{selectedRecord.paymentStatus === 'paid' && ( {selectedRecord.paymentStatus === 'paid' && (
<button <button
onClick={handleGenerateInvoice} onClick={handleGenerateInvoice}
className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 flex items-center gap-2" className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 flex items-center gap-2"
> >
@@ -794,53 +945,146 @@ 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>
<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">
<button
type="button"
className="p-4 border-2 rounded-xl flex flex-col items-center gap-2 hover:border-green-500 hover:bg-green-50 transition-colors"
>
<Battery className="w-8 h-8 text-green-600" />
<span className="font-medium text-slate-700">Battery</span>
<span className="text-xs text-slate-500">For battery issues</span>
</button>
<button
type="button"
className="p-4 border-2 rounded-xl flex flex-col items-center gap-2 hover:border-purple-500 hover:bg-purple-50 transition-colors"
>
<Bike className="w-8 h-8 text-purple-600" />
<span className="font-medium text-slate-700">Fleet (Bike)</span>
<span className="text-xs text-slate-500">For bike issues</span>
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Issue Type *</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="damage">Damage</option> <option value="">Select Category</option>
<option value="repair">Repair</option> {reportType === 'damage' ? (
<option value="service">Service</option> <>
<option value="battery_swap">Battery Swap</option> <option value="damage">Damage</option>
<option value="inspection">Inspection</option> <option value="repair">Repair</option>
<option value="other">Other</option> <option value="accident">Accident</option>
<option value="theft">Theft</option>
<option value="vandalism">Vandalism</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="inspection">Inspection</option>
</>
)}
</select> </select>
</div> </div>
<div> {reportType === 'damage' ? (
<label className="text-xs font-medium text-slate-600 mb-1 block">Severity *</label> <div>
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"> <label className="text-xs font-medium text-slate-600 mb-1 block">Severity *</label>
<option value="critical">Critical</option> <select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="major">Major</option> <option value="critical">Critical</option>
<option value="minor">Minor</option> <option value="major">Major</option>
<option value="cosmetic">Cosmetic</option> <option value="minor">Minor</option>
</select> <option value="cosmetic">Cosmetic</option>
</div> </select>
<div> </div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Bike ID *</label> ) : (
<input type="text" placeholder="EV-XXX" 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">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" />
</div> </div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Bike ID</label>
<input type="text" placeholder="EV-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
{reportType === 'damage' ? (
<>
<div>
<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" />
</div>
<div>
<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" />
</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>
<textarea rows={3} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Describe the issue..." /> <textarea rows={3} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Describe the issue in detail..." />
</div> </div>
<div> <div className="grid grid-cols-2 gap-4">
<label className="text-xs font-medium text-slate-600 mb-1 block">Location *</label> <div>
<input type="text" placeholder="Where did the issue occur?" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <label className="text-xs font-medium text-slate-600 mb-1 block">Location / Hub *</label>
</div> <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>
<label className="text-xs font-medium text-slate-600 mb-1 block">Estimated Cost</label> <div>
<input type="number" placeholder="0" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <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" />
</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"> <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">Drag and drop or click to upload</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>
</div> </div>
</div> </div>
</div> </div>
@@ -848,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>
@@ -881,7 +1125,7 @@ export default function MaintenancePage() {
<button onClick={() => setShowAddNoteModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50"> <button onClick={() => setShowAddNoteModal(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 <button
onClick={handleAddNote} onClick={handleAddNote}
disabled={!newNoteText.trim()} disabled={!newNoteText.trim()}
className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark disabled:opacity-50" className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent-dark disabled:opacity-50"
@@ -914,7 +1158,7 @@ export default function MaintenancePage() {
<div> <div>
<label className="text-sm font-medium text-slate-600 mb-2 block">Payment Source</label> <label className="text-sm font-medium text-slate-600 mb-2 block">Payment Source</label>
<div className="space-y-2"> <div className="space-y-2">
<button <button
onClick={() => handlePayment('company')} onClick={() => handlePayment('company')}
className="w-full p-4 border-2 border-slate-200 rounded-lg text-left hover:border-accent hover:bg-accent-light/30 transition-colors" className="w-full p-4 border-2 border-slate-200 rounded-lg text-left hover:border-accent hover:bg-accent-light/30 transition-colors"
> >
@@ -928,7 +1172,7 @@ export default function MaintenancePage() {
</div> </div>
</div> </div>
</button> </button>
<button <button
onClick={() => handlePayment('biker')} onClick={() => handlePayment('biker')}
className="w-full p-4 border-2 border-slate-200 rounded-lg text-left hover:border-accent hover:bg-accent-light/30 transition-colors" className="w-full p-4 border-2 border-slate-200 rounded-lg text-left hover:border-accent hover:bg-accent-light/30 transition-colors"
> >
@@ -953,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>
); );
} }