Files
JML/src/app/admin/maintenance/[id]/page.tsx

941 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import {
AlertTriangle, Search, Plus, X, Check, Clock, Bike, User, Phone,
MapPin, FileText, Image, DollarSign, Wrench, Battery, Key,
CheckCircle, XCircle, ChevronLeft, Save, Printer, Send, QrCode,
Wallet, Building, Edit, MessageSquare, Calendar, ArrowLeft
} 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 MaintenanceType = 'damage' | 'repair' | 'service' | 'battery_swap' | 'inspection' | 'other';
type DamageSeverity = 'critical' | 'major' | 'minor' | 'cosmetic';
type MaintenanceStatus = 'reported' | 'in_progress' | 'parts_ordered' | 'completed' | 'cancelled';
type PaymentStatus = 'pending' | 'approved' | 'paid' | 'rejected';
interface MaintenanceRecord {
id: string;
date: string;
type: MaintenanceType;
severity: DamageSeverity;
status: MaintenanceStatus;
paymentStatus: PaymentStatus;
bikeId: string;
bikeModel: string;
bikePlate: string;
batteryId?: string;
reporterId: string;
reporterName: string;
reporterPhone: string;
reporterRole: 'biker' | 'staff' | 'hub';
description: string;
location: string;
estimatedCost: number;
actualCost?: number;
partsUsed?: string[];
images: { id: string; name: string; url: string; uploadedAt: string }[];
assignedTo?: string;
notes: string[];
resolvedAt?: string;
createdAt: string;
createdBy: string;
}
const mockMaintenance: MaintenanceRecord[] = [
{
id: 'MNT-001',
date: '2024-03-21',
type: 'damage',
severity: 'major',
status: 'in_progress',
paymentStatus: 'approved',
bikeId: 'EV-004',
bikeModel: 'AIMA Lightning',
bikePlate: 'Dhaka Metro Cha-5679',
batteryId: 'BAT-044',
reporterId: 'BIKER-004',
reporterName: 'Sofiq Rahman',
reporterPhone: '01712345681',
reporterRole: 'biker',
description: 'Front fender damaged in accident at Gulshan signal',
location: 'Gulshan, Dhaka',
estimatedCost: 3500,
actualCost: 3200,
partsUsed: ['Front fender', 'Mounting brackets'],
images: [
{ id: 'img1', name: 'Damage Front', url: '', uploadedAt: '2024-03-21' },
{ id: 'img2', name: 'Damage Side', url: '', uploadedAt: '2024-03-21' },
],
assignedTo: 'Service Center A',
notes: ['Parts ordered from supplier'],
createdAt: '2024-03-21T10:00:00',
createdBy: 'Admin',
},
{
id: 'MNT-002',
date: '2024-03-20',
type: 'service',
severity: 'minor',
status: 'completed',
paymentStatus: 'paid',
bikeId: 'EV-002',
bikeModel: 'Yadea DT3',
bikePlate: 'Dhaka Metro Ba-1234',
batteryId: 'BAT-021',
reporterId: 'BIKER-002',
reporterName: 'Karim Hasan',
reporterPhone: '01712345679',
reporterRole: 'biker',
description: 'Routine service - brake adjustment and chain lubrication',
location: 'Banani Hub',
estimatedCost: 500,
actualCost: 450,
images: [],
notes: ['Service completed'],
resolvedAt: '2024-03-20T14:00:00',
createdAt: '2024-03-20T08:00:00',
createdBy: 'Hub Staff',
},
{
id: 'MNT-003',
date: '2024-03-19',
type: 'battery_swap',
severity: 'minor',
status: 'completed',
paymentStatus: 'pending',
bikeId: 'EV-007',
bikeModel: 'Etron ET50',
bikePlate: 'Dhaka Metro Ca-8901',
reporterId: 'BIKER-007',
reporterName: 'Jamal',
reporterPhone: '01712345687',
reporterRole: 'biker',
description: 'Battery not holding charge properly - need replacement',
location: 'Dhanmondi, Dhaka',
estimatedCost: 0,
images: [],
notes: ['Battery replaced under warranty'],
resolvedAt: '2024-03-19T16:00:00',
createdAt: '2024-03-19T12:00:00',
createdBy: 'Admin',
},
{
id: 'MNT-004',
date: '2024-03-18',
type: 'repair',
severity: 'critical',
status: 'in_progress',
paymentStatus: 'pending',
bikeId: 'EV-010',
bikeModel: 'TVS iQube',
bikePlate: 'Dhaka Metro Da-4567',
reporterId: 'BIKER-010',
reporterName: 'Ripon',
reporterPhone: '01712345690',
reporterRole: 'biker',
description: 'Motor issue - bike not moving properly',
location: 'Mirpur, Dhaka',
estimatedCost: 8000,
images: [
{ id: 'img3', name: 'Motor Damage', url: '', uploadedAt: '2024-03-18' },
],
assignedTo: 'Authorized Service Center',
notes: ['Motor needs replacement - ordered', 'Waiting for parts'],
createdAt: '2024-03-18T09:00:00',
createdBy: 'Admin',
},
{
id: 'MNT-005',
date: '2024-03-17',
type: 'inspection',
severity: 'minor',
status: 'completed',
paymentStatus: 'paid',
bikeId: 'EV-001',
bikeModel: 'AIMA Lightning',
bikePlate: 'Dhaka Metro Aa-1111',
reporterId: 'Hub-01',
reporterName: 'Gulshan Hub',
reporterPhone: '02-1234567',
reporterRole: 'hub',
description: 'Monthly inspection completed',
location: 'Gulshan Hub',
estimatedCost: 300,
actualCost: 250,
images: [],
notes: ['All checks passed'],
resolvedAt: '2024-03-17T15:00:00',
createdAt: '2024-03-17T10:00:00',
createdBy: 'Hub Staff',
},
{
id: 'MNT-006',
date: '2024-03-15',
type: 'damage',
severity: 'cosmetic',
status: 'completed',
paymentStatus: 'rejected',
bikeId: 'EV-005',
bikeModel: 'Yadea DT3',
bikePlate: 'Dhaka Metro Ba-5678',
reporterId: 'BIKER-005',
reporterName: 'Rahim',
reporterPhone: '01712345685',
reporterRole: 'biker',
description: 'Minor scratch on mirror - customer dropped bike slowly',
location: 'Uttara, Dhaka',
estimatedCost: 500,
images: [],
notes: ['Denied - user responsibility'],
createdAt: '2024-03-15T14:00:00',
createdBy: 'Admin',
},
];
const statusColors: Record<string, string> = {
reported: 'bg-amber-100 text-amber-700',
in_progress: 'bg-blue-100 text-blue-700',
parts_ordered: 'bg-purple-100 text-purple-700',
completed: 'bg-green-100 text-green-700',
cancelled: 'bg-red-100 text-red-700',
};
const severityColors: Record<string, string> = {
critical: 'bg-red-100 text-red-700',
major: 'bg-orange-100 text-orange-700',
minor: 'bg-amber-100 text-amber-700',
cosmetic: 'bg-slate-100 text-slate-700',
};
const paymentColors: Record<string, string> = {
pending: 'bg-amber-100 text-amber-700',
approved: 'bg-blue-100 text-blue-700',
paid: 'bg-green-100 text-green-700',
rejected: 'bg-red-100 text-red-700',
};
const typeLabels: Record<string, string> = {
damage: 'Damage',
repair: 'Repair',
service: 'Service',
battery_swap: 'Battery Swap',
inspection: 'Inspection',
other: 'Other',
};
export default function MaintenanceDetailPage() {
const params = useParams();
const router = useRouter();
const id = params.id as string;
const [record, setRecord] = useState<MaintenanceRecord | null>(null);
const [editMode, setEditMode] = useState(false);
const [showCompleteModal, setShowCompleteModal] = useState(false);
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
const [showAddNoteModal, setShowAddNoteModal] = useState(false);
const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({});
const [newNoteText, setNewNoteText] = useState('');
const [actualCost, setActualCost] = useState('');
useEffect(() => {
const found = mockMaintenance.find(r => r.id === id);
if (found) {
setRecord(found);
setEditForm(found);
setActualCost(found.actualCost?.toString() || found.estimatedCost.toString());
}
}, [id]);
if (!record) {
return (
<div className="p-6 flex items-center justify-center min-h-[50vh]">
<div className="text-center">
<Wrench className="w-16 h-16 text-slate-300 mx-auto mb-4" />
<p className="text-slate-500">Record not found</p>
<button
onClick={() => router.push('/admin/maintenance')}
className="mt-4 px-4 py-2 bg-accent text-white rounded-lg text-sm"
>
Back to Maintenance
</button>
</div>
</div>
);
}
const handleSaveEdit = () => {
setRecord(prev => prev ? { ...prev, ...editForm } : null);
setEditMode(false);
};
const handleComplete = () => {
if (!record) return;
const cost = parseInt(actualCost) || record.estimatedCost;
setRecord(prev => prev ? {
...prev,
status: 'completed',
resolvedAt: new Date().toISOString().split('T')[0],
actualCost: cost
} : null);
setShowCompleteModal(false);
};
const handlePayment = (source: 'bank' | 'cash' | 'biker') => {
if (!record) return;
const cost = record.actualCost || record.estimatedCost;
setRecord(prev => prev ? { ...prev, paymentStatus: 'paid' } : null);
setShowPaymentModal(false);
setShowInvoiceModal(true);
};
const handleGenerateInvoice = () => {
if (!record) return;
import('jspdf').then(jsPDF => {
const doc = new jsPDF.default();
const cost = record.actualCost || record.estimatedCost;
const qrData = `INV-${record.id}|${record.bikePlate}|${record.type}|${cost}|${new Date().toISOString().split('T')[0]}`;
doc.setFontSize(20);
doc.setTextColor(6, 95, 70);
doc.text('JAIBEN Mobility Ltd', 20, 20);
doc.setFontSize(14);
doc.setTextColor(0);
doc.text('Maintenance Invoice', 20, 32);
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(`Invoice No: INV-${record.id}`, 20, 42);
doc.text(`Date: ${record.date}`, 20, 48);
doc.text(`Issue Type: ${typeLabels[record.type]}`, 20, 54);
doc.text(`Severity: ${record.severity}`, 20, 60);
doc.text(`Status: ${record.status}`, 20, 66);
doc.setFontSize(11);
doc.setTextColor(0);
doc.text('Bike Details', 20, 80);
doc.setFontSize(10);
doc.text(`Bike ID: ${record.bikeId}`, 20, 86);
doc.text(`Model: ${record.bikeModel}`, 20, 92);
doc.text(`License Plate: ${record.bikePlate}`, 20, 98);
if (record.batteryId) doc.text(`Battery ID: ${record.batteryId}`, 20, 104);
doc.setFontSize(11);
doc.text('Reporter', 20, 118);
doc.setFontSize(10);
doc.text(`Name: ${record.reporterName}`, 20, 124);
doc.text(`Phone: ${record.reporterPhone}`, 20, 130);
doc.text(`Role: ${record.reporterRole}`, 20, 136);
doc.setFontSize(11);
doc.text('Description', 20, 150);
doc.setFontSize(10);
const descLines = doc.splitTextToSize(record.description, 170);
doc.text(descLines, 20, 156);
doc.setFontSize(11);
doc.text('Service Details', 120, 80);
doc.setFontSize(10);
doc.text(`Location: ${record.location}`, 120, 86);
if (record.assignedTo) doc.text(`Assigned: ${record.assignedTo}`, 120, 92);
if (record.resolvedAt) doc.text(`Resolved: ${record.resolvedAt}`, 120, 98);
doc.setFontSize(11);
doc.text('Cost Breakdown', 20, 175);
doc.setFontSize(10);
doc.text(`Estimated Cost: ৳${record.estimatedCost}`, 20, 181);
if (record.actualCost) doc.text(`Actual Cost: ৳${record.actualCost}`, 20, 187);
if (record.partsUsed && record.partsUsed.length > 0) {
doc.text(`Parts: ${record.partsUsed.join(', ')}`, 20, 193);
}
doc.setFontSize(14);
doc.setTextColor(6, 95, 70);
doc.text(`Total: ৳${cost}`, 20, 205);
doc.setFontSize(9);
doc.setTextColor(150);
doc.text('Generated from JAIBEN Maintenance System', 20, 280);
doc.text(`QR: ${qrData}`, 20, 286);
doc.save(`maintenance-invoice-${record.id}.pdf`);
});
setShowInvoiceModal(false);
};
const handleAddNote = () => {
if (!record || !newNoteText.trim()) return;
setRecord(prev => prev ? { ...prev, notes: [...prev.notes, newNoteText] } : null);
setNewNoteText('');
setShowAddNoteModal(false);
};
return (
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
<button
onClick={() => router.push('/admin/maintenance')}
className="flex items-center gap-2 text-slate-600 hover:text-slate-800 mb-4"
>
<ArrowLeft className="w-4 h-4" /> Back to Maintenance
</button>
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
<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>
<div className="flex items-center gap-3">
<h1 className="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]}`}>
{record.severity}
</span>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[record.status]}`}>
{record.status.replace('_', ' ')}
</span>
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${paymentColors[record.paymentStatus]}`}>
{record.paymentStatus}
</span>
</div>
<p className="text-slate-500 mt-1">{typeLabels[record.type]} {record.date}</p>
</div>
<div className="flex gap-2">
{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">
<Save className="w-4 h-4" /> Save
</button>
<button onClick={() => setEditMode(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
Cancel
</button>
</>
) : (
<>
<button onClick={() => setEditMode(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">
<Edit className="w-4 h-4" /> Edit
</button>
<button onClick={() => 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">
<MessageSquare className="w-4 h-4" /> Note
</button>
{record.status !== 'completed' && (
<button
onClick={() => setShowCompleteModal(true)}
className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"
>
<Check className="w-4 h-4" /> Complete
</button>
)}
{record.status === 'completed' && record.paymentStatus !== 'paid' && (
<button
onClick={() => 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"
>
<DollarSign className="w-4 h-4" /> Payment
</button>
)}
{record.paymentStatus === 'paid' && (
<button
onClick={() => setShowInvoiceModal(true)}
className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 flex items-center gap-2"
>
<Printer className="w-4 h-4" /> Invoice
</button>
)}
</>
)}
</div>
</div>
</div>
<div className="p-6 grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="bg-blue-50 p-4 rounded-xl border border-blue-100">
<h3 className="font-semibold text-blue-800 mb-3 flex items-center gap-2">
<Bike className="w-5 h-5" /> Bike Information
</h3>
{editMode ? (
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs text-blue-700 block mb-1">Bike ID</label>
<input
type="text"
value={editForm.bikeId || ''}
onChange={(e) => setEditForm({ ...editForm, bikeId: e.target.value })}
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-xs text-blue-700 block mb-1">License Plate</label>
<input
type="text"
value={editForm.bikePlate || ''}
onChange={(e) => setEditForm({ ...editForm, bikePlate: e.target.value })}
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-xs text-blue-700 block mb-1">Model</label>
<input
type="text"
value={editForm.bikeModel || ''}
onChange={(e) => setEditForm({ ...editForm, bikeModel: e.target.value })}
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
/>
</div>
<div>
<label className="text-xs text-blue-700 block mb-1">Battery ID</label>
<input
type="text"
value={editForm.batteryId || ''}
onChange={(e) => setEditForm({ ...editForm, batteryId: e.target.value })}
className="w-full px-3 py-2 border border-blue-200 rounded-lg text-sm"
/>
</div>
</div>
) : (
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm text-blue-600">Bike ID</span>
<span className="text-sm font-medium text-blue-800">{record.bikeId}</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-blue-600">Model</span>
<span className="text-sm font-medium text-blue-800">{record.bikeModel}</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-blue-600">License Plate</span>
<span className="text-sm font-medium text-blue-800">{record.bikePlate}</span>
</div>
{record.batteryId && (
<div className="flex justify-between">
<span className="text-sm text-blue-600">Battery ID</span>
<span className="text-sm font-medium text-blue-800">{record.batteryId}</span>
</div>
)}
</div>
)}
</div>
<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">
<User className="w-5 h-5" /> Reporter
</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm text-green-600">Name</span>
<span className="text-sm font-medium text-green-800">{record.reporterName}</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-green-600">Phone</span>
<span className="text-sm font-medium text-green-800">{record.reporterPhone}</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-green-600">Role</span>
<span className="text-sm font-medium text-green-800 capitalize">{record.reporterRole}</span>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div className="bg-amber-50 p-4 rounded-xl border border-amber-100">
<h3 className="font-semibold text-amber-800 mb-3 flex items-center gap-2">
<FileText className="w-5 h-5" /> Description
</h3>
{editMode ? (
<textarea
value={editForm.description || ''}
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm"
rows={3}
/>
) : (
<>
<p className="text-sm text-amber-700 mb-3">{record.description}</p>
<div className="flex items-center gap-2 text-xs text-amber-600">
<MapPin className="w-3 h-3" /> {record.location}
</div>
</>
)}
</div>
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100">
<h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2">
<DollarSign className="w-5 h-5" /> Cost Details
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="bg-white p-3 rounded-lg">
<p className="text-xs text-purple-600">Estimated</p>
{editMode ? (
<input
type="number"
value={editForm.estimatedCost || 0}
onChange={(e) => setEditForm({ ...editForm, estimatedCost: parseInt(e.target.value) })}
className="w-full px-2 py-1 border border-purple-200 rounded text-sm"
/>
) : (
<p className="text-lg font-bold text-purple-800">{record.estimatedCost}</p>
)}
</div>
<div className="bg-white p-3 rounded-lg">
<p className="text-xs text-purple-600">Actual</p>
<p className="text-lg font-bold text-purple-800">{record.actualCost || record.estimatedCost}</p>
</div>
</div>
</div>
<div className="bg-cyan-50 p-4 rounded-xl border border-cyan-100">
<h3 className="font-semibold text-cyan-800 mb-3 flex items-center gap-2">
<User className="w-5 h-5" /> Assigned To
</h3>
{editMode ? (
<select
value={editForm.assignedTo || ''}
onChange={(e) => setEditForm({ ...editForm, assignedTo: e.target.value })}
className="w-full px-3 py-2 border border-cyan-200 rounded-lg text-sm"
>
<option value="">Select Service Center</option>
<option value="Service Center A">Service Center A</option>
<option value="Service Center B">Service Center B</option>
<option value="Authorized Service Center">Authorized Service Center</option>
<option value="Gulshan Hub">Gulshan Hub</option>
<option value="Banani Hub">Banani Hub</option>
<option value="Dhanmondi Hub">Dhanmondi Hub</option>
</select>
) : (
<p className="text-sm text-cyan-700">{record.assignedTo || 'Not assigned'}</p>
)}
</div>
<div className="bg-orange-50 p-4 rounded-xl border border-orange-100">
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-orange-800 flex items-center gap-2">
<Wrench className="w-5 h-5" /> Parts Used
</h3>
{editMode && (
<select
onChange={(e) => {
if (e.target.value) {
const currentParts = editForm.partsUsed || [];
if (!currentParts.includes(e.target.value)) {
setEditForm({ ...editForm, partsUsed: [...currentParts, e.target.value] });
}
e.target.value = '';
}
}}
className="px-2 py-1 text-xs border border-orange-200 rounded"
>
<option value="">+ Add Part</option>
<option value="Front fender">Front fender</option>
<option value="Rear fender">Rear fender</option>
<option value="Mirror">Mirror</option>
<option value="Headlight">Headlight</option>
<option value="Tail light">Tail light</option>
<option value="Brake pad">Brake pad</option>
<option value="Brake shoe">Brake shoe</option>
<option value="Chain">Chain</option>
<option value="Battery">Battery</option>
<option value="Motor">Motor</option>
<option value="Controller">Controller</option>
<option value="Throttle">Throttle</option>
<option value="Lever">Lever</option>
<option value="Stand">Stand</option>
<option value="Seat">Seat</option>
<option value="Tyre">Tyre</option>
<option value="Tube">Tube</option>
<option value="Mounting brackets">Mounting brackets</option>
<option value="Bolt set">Bolt set</option>
</select>
)}
</div>
<div className="flex flex-wrap gap-2">
{(editMode ? editForm.partsUsed : record.partsUsed)?.map((part, idx) => (
<span key={idx} className="px-3 py-1 bg-white rounded-full text-sm text-orange-700 border border-orange-200 flex items-center gap-1">
{part}
{editMode && (
<button
onClick={() => {
const updated = [...(editForm.partsUsed || [])];
updated.splice(idx, 1);
setEditForm({ ...editForm, partsUsed: updated });
}}
className="ml-1 text-orange-400 hover:text-red-500"
>
×
</button>
)}
</span>
))}
{(editMode ? editForm.partsUsed : record.partsUsed)?.length === 0 && (
<p className="text-sm text-orange-400">No parts added</p>
)}
</div>
</div>
<div className="bg-indigo-50 p-4 rounded-xl border border-indigo-100">
<h3 className="font-semibold text-indigo-800 mb-3 flex items-center gap-2">
<Image className="w-5 h-5" /> Images ({(editMode ? editForm.images : record.images)?.length})
</h3>
<div className="grid grid-cols-4 gap-2">
{(editMode ? editForm.images : record.images)?.map((img) => (
<div key={img.id} className="relative aspect-square bg-white rounded-lg flex flex-col items-center justify-center border border-indigo-100">
<Image className="w-8 h-8 text-indigo-400" />
<span className="text-xs text-indigo-500 mt-1 text-center">{img.name}</span>
{editMode && (
<button
onClick={() => {
const updated = (editForm.images || []).filter((i: any) => i.id !== img.id);
setEditForm({ ...editForm, images: updated });
}}
className="absolute top-1 right-1 w-5 h-5 bg-red-100 rounded-full text-red-500 text-xs flex items-center justify-center"
>
×
</button>
)}
</div>
))}
{editMode && (
<label className="aspect-square bg-white rounded-lg flex flex-col items-center justify-center border border-dashed border-indigo-200 cursor-pointer hover:bg-indigo-50">
<Image className="w-8 h-8 text-indigo-400" />
<span className="text-xs text-indigo-500 mt-1">+ Add</span>
<input type="file" className="hidden" accept="image/*" />
</label>
)}
{(editMode ? editForm.images : record.images)?.length === 0 && !editMode && (
<p className="text-sm text-indigo-400 col-span-4 text-center py-4">No images</p>
)}
</div>
</div>
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
<h3 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
<MessageSquare className="w-5 h-5" /> Notes ({(editMode ? editForm.notes : record.notes)?.length})
</h3>
{editMode && (
<div className="flex gap-2 mb-3">
<input
type="text"
id="newNoteInput"
placeholder="Add a note..."
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
onKeyPress={(e) => {
if (e.key === 'Enter') {
const input = e.currentTarget as HTMLInputElement;
if (input.value.trim()) {
setEditForm({ ...editForm, notes: [...(editForm.notes || []), input.value.trim()] });
input.value = '';
}
}
}}
/>
<button
onClick={() => {
const input = document.getElementById('newNoteInput') as HTMLInputElement;
if (input?.value.trim()) {
setEditForm({ ...editForm, notes: [...(editForm.notes || []), input.value.trim()] });
input.value = '';
}
}}
className="px-4 py-2 bg-accent text-white rounded-lg text-sm"
>
Add
</button>
</div>
)}
<div className="space-y-2">
{(editMode ? editForm.notes : record.notes)?.map((note, idx) => (
<div key={idx} className="text-sm text-slate-600 p-2 bg-white rounded-lg">
{note}
</div>
))}
{(editMode ? editForm.notes : record.notes)?.length === 0 && (
<p className="text-sm text-slate-400">No notes yet</p>
)}
</div>
</div>
</div>
</div>
</div>
{showCompleteModal && (
<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="p-4 border-b border-slate-100">
<h3 className="font-semibold text-slate-800">Complete Maintenance</h3>
</div>
<div className="p-4 space-y-4">
<div className="bg-green-50 p-4 rounded-lg">
<p className="text-sm text-green-700">Enter actual cost to complete this record</p>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-2 block">Actual Cost ()</label>
<input
type="number"
value={actualCost}
onChange={(e) => setActualCost(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-lg font-bold"
/>
</div>
<div className="text-sm text-slate-500">
Estimated: {record.estimatedCost}
</div>
</div>
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
<button onClick={() => setShowCompleteModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
<button
onClick={handleComplete}
className="px-4 py-2 bg-green-600 text-white rounded-lg flex items-center gap-2"
>
<Check className="w-4 h-4" /> Mark Complete
</button>
</div>
</div>
</div>
)}
{showPaymentModal && record && (
<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-lg">
<div className="p-4 border-b border-slate-100">
<h3 className="font-semibold text-slate-800">Process Payment - {record.id}</h3>
<p className="text-sm text-slate-500">Amount: {record.actualCost || record.estimatedCost}</p>
</div>
<div className="p-4 space-y-4">
<p className="text-sm text-slate-600 mb-2">Select payment method:</p>
<button
onClick={() => handlePayment('bank')}
className="w-full p-4 border-2 border-slate-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<Building className="w-5 h-5 text-blue-600" />
</div>
<div className="text-left">
<p className="font-medium text-slate-800">Bank Transfer</p>
<p className="text-xs text-slate-500">Debit Bank (1200) Credit Maintenance (5400)</p>
</div>
</div>
</button>
<button
onClick={() => handlePayment('cash')}
className="w-full p-4 border-2 border-slate-200 rounded-lg hover:border-green-500 hover:bg-green-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Wallet className="w-5 h-5 text-green-600" />
</div>
<div className="text-left">
<p className="font-medium text-slate-800">Cash</p>
<p className="text-xs text-slate-500">Debit Cash (1100) Credit Maintenance (5400)</p>
</div>
</div>
</button>
<button
onClick={() => handlePayment('biker')}
className="w-full p-4 border-2 border-slate-200 rounded-lg hover:border-purple-500 hover:bg-purple-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<User className="w-5 h-5 text-purple-600" />
</div>
<div className="text-left">
<p className="font-medium text-slate-800">Biker Wallet</p>
<p className="text-xs text-slate-500">Deduct from rider wallet</p>
</div>
</div>
</button>
</div>
<div className="p-4 border-t border-slate-100 flex justify-end">
<button onClick={() => setShowPaymentModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
</div>
</div>
</div>
)}
{showInvoiceModal && record && (
<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-lg">
<div className="p-4 border-b border-slate-100 flex items-center justify-between">
<h3 className="font-semibold text-slate-800">Invoice Generated!</h3>
</div>
<div className="p-6 text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center">
<CheckCircle className="w-10 h-10 text-green-600" />
</div>
<p className="text-lg font-semibold text-slate-800 mb-2">Payment Complete</p>
<p className="text-slate-600 mb-4">Invoice INV-{record.id} is ready</p>
<div className="bg-slate-50 p-4 rounded-lg text-left mb-4">
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-500">Maintenance ID</span>
<span className="text-sm font-medium">{record.id}</span>
</div>
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-500">Bike</span>
<span className="text-sm font-medium">{record.bikePlate}</span>
</div>
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-500">Amount Paid</span>
<span className="text-sm font-bold text-green-600">{record.actualCost || record.estimatedCost}</span>
</div>
</div>
<div className="flex items-center justify-center mb-4">
<div className="w-24 h-24 bg-white border-2 border-slate-200 rounded-lg flex items-center justify-center">
<QrCode className="w-16 h-16 text-slate-400" />
</div>
</div>
<p className="text-xs text-slate-400">Scan QR for verification</p>
</div>
<div className="p-4 border-t border-slate-100 flex justify-between">
<button
onClick={() => setShowInvoiceModal(false)}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg"
>
Close
</button>
<button
onClick={handleGenerateInvoice}
className="px-4 py-2 bg-purple-600 text-white rounded-lg flex items-center gap-2"
>
<Printer className="w-4 h-4" /> Download PDF
</button>
</div>
</div>
</div>
)}
{showAddNoteModal && (
<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="p-4 border-b border-slate-100">
<h3 className="font-semibold text-slate-800">Add Note</h3>
</div>
<div className="p-4">
<textarea
value={newNoteText}
onChange={(e) => setNewNoteText(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
rows={4}
placeholder="Enter note..."
/>
</div>
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
<button onClick={() => setShowAddNoteModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
<button
onClick={handleAddNote}
disabled={!newNoteText.trim()}
className="px-4 py-2 bg-accent text-white rounded-lg disabled:opacity-50"
>
Save Note
</button>
</div>
</div>
</div>
)}
</div>
);
}