feat: add AI OCR processing state and conditional UI locking for maintenance records

This commit is contained in:
sazzadulalambd
2026-05-20 13:26:25 +06:00
parent 3933141140
commit bb561e493b

View File

@@ -282,6 +282,12 @@ export default function MaintenanceDetailPage() {
const [showCompleteModal, setShowCompleteModal] = useState(false);
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
// AI OCR Simulation States
const [isOcrProcessing, setIsOcrProcessing] = useState(false);
const [ocrComplete, setOcrComplete] = useState(false);
const [ocrFileName, setOcrFileName] = useState('');
const [ocrStep, setOcrStep] = useState('');
const [showAddNoteModal, setShowAddNoteModal] = useState(false);
const [showAddPartModal, setShowAddPartModal] = useState(false);
const [partSearch, setPartSearch] = useState('');
@@ -473,77 +479,84 @@ export default function MaintenanceDetailPage() {
</div>
<p className="text-slate-500 mt-1">{typeLabels[record.type]} {record.date}</p>
</div>
<div className="flex flex-wrap 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>
</>
) : (
<>
{!invoiceCreated && (
<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
<div className="flex flex-wrap items-center gap-2 relative">
{ocrComplete && (
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 text-xs font-bold rounded-lg mr-2 animate-fadeIn">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" /> Invoice Locked (OCR Synced)
</div>
)}
<div className={`flex flex-wrap gap-2 ${ocrComplete ? 'filter blur-[1.5px] opacity-50 pointer-events-none' : ''}`}>
{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>
)}
{!invoiceCreated && (
<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 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>
)}
{record.status !== 'completed' && !invoiceCreated && (
<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"
>
<FileText className="w-4 h-4" /> Create Invoice
</button>
)}
{record.status !== 'completed' && invoiceCreated && (
<>
</>
) : (
<>
{!invoiceCreated && (
<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>
)}
{!invoiceCreated && (
<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' && !invoiceCreated && (
<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"
>
<FileText className="w-4 h-4" /> Create Invoice
</button>
)}
{record.status !== 'completed' && invoiceCreated && (
<>
<button
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"
>
<Printer className="w-4 h-4" /> Print Invoice
</button>
<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" /> Proceed to Payment
</button>
<button
onClick={() => setShowCompleteModal(true)}
className="px-3 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50"
title="Edit Invoice"
>
<Edit className="w-4 h-4" />
</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={handleGenerateInvoice}
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" /> Print Invoice
</button>
<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" /> Proceed to Payment
</button>
<button
onClick={() => setShowCompleteModal(true)}
className="px-3 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50"
title="Edit Invoice"
>
<Edit className="w-4 h-4" />
</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={handleGenerateInvoice}
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" /> Print Invoice
</button>
)}
</>
)}
)}
</>
)}
</div>
</div>
</div>
</div>
@@ -617,36 +630,44 @@ export default function MaintenanceDetailPage() {
)}
</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/maintenance/history/battery/${record.batteryId}?from=${record.id}`}
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/maintenance/history/bike/${record.bikeId}?from=${record.id}`}
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>
)}
<div className="bg-orange-50 p-4 rounded-xl border border-orange-100 relative overflow-hidden transition-all duration-300">
{ocrComplete && (
<div className="absolute inset-0 bg-slate-900/5 backdrop-blur-[1px] flex items-center justify-center z-10 p-2">
<span className="px-3 py-1 bg-slate-900 text-white border border-slate-700 text-[10px] font-bold rounded-lg uppercase tracking-wider flex items-center gap-1.5 shadow-md">
<CheckCircle className="w-3.5 h-3.5 text-emerald-400" /> Populated via AI OCR
</span>
</div>
)}
<div className={ocrComplete ? 'filter blur-[1.5px] opacity-60 pointer-events-none' : ''}>
<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/maintenance/history/battery/${record.batteryId}?from=${record.id}`}
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/maintenance/history/bike/${record.bikeId}?from=${record.id}`}
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>
)}
</div>
</div>
</div>
@@ -724,224 +745,6 @@ export default function MaintenanceDetailPage() {
</>
)}
</div>
</div>
<div className="space-y-4">
<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="bg-gradient-to-r from-purple-50 to-indigo-50 p-4 rounded-xl border border-purple-100">
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-purple-800 flex items-center gap-2">
<DollarSign className="w-5 h-5" /> Cost Breakdown
</h3>
{editMode && (
<input
type="number"
value={editForm.estimatedCost || 0}
onChange={(e) => setEditForm({ ...editForm, estimatedCost: parseInt(e.target.value) })}
className="px-2 py-1 border border-purple-200 rounded text-sm w-24"
/>
)}
</div>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-slate-600">Estimated Cost:</span>
<span className="font-medium text-purple-700">{record.estimatedCost.toLocaleString()}</span>
</div>
<div className="border-t border-purple-100 pt-2">
<div className="flex justify-between items-center text-sm">
<span className="text-slate-600 flex items-center gap-1">
<Package className="w-4 h-4" /> Parts Total:
</span>
<span className="font-medium text-orange-600">{record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0).toLocaleString() || 0}</span>
</div>
<div className="flex justify-between items-center text-sm mt-1">
<span className="text-slate-600 flex items-center gap-1">
<Wrench className="w-4 h-4" /> Service Cost (Labor):
</span>
<span className="font-medium text-blue-600">{(record.serviceCost || 0).toLocaleString()}</span>
</div>
</div>
<div className="border-t-2 border-purple-200 pt-2 mt-2">
<div className="flex justify-between items-center">
<span className="font-semibold text-purple-800">Actual Total Cost:</span>
<span className="text-xl font-bold text-purple-800">
{((record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0) || 0) + (record.serviceCost || 0)).toLocaleString()}
</span>
</div>
</div>
</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 && (
<button
onClick={() => setShowAddPartModal(true)}
className="px-3 py-1 bg-orange-600 text-white text-xs rounded-lg hover:bg-orange-700 flex items-center gap-1"
>
<Plus className="w-3 h-3" /> Add Part
</button>
)}
</div>
<div className="space-y-3">
{record.partsUsed && record.partsUsed.length > 0 ? (
<>
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-orange-100">
<tr>
<th className="px-3 py-2 text-left text-orange-800 font-medium">Part</th>
<th className="px-3 py-2 text-center text-orange-800 font-medium">Qty</th>
<th className="px-3 py-2 text-right text-orange-800 font-medium">Unit Price</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">Action</th>
</tr>
</thead>
<tbody>
{record.partsUsed?.map((part) => (
<tr key={part.id} className="bg-white border-b border-orange-100">
<td className="px-3 py-2 text-slate-700">{part.partName}</td>
<td className="px-3 py-2 text-center">
<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"
/>
</td>
<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
onClick={() => {
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.filter(p => p.id !== part.id)
} : null);
}}
className="text-red-400 hover:text-red-600 p-1"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between text-sm mb-2">
<div className="flex items-center gap-2">
<span className="text-slate-500">Qty:</span>
<input
type="number"
min="1"
value={part.quantity}
onChange={(e) => {
const newQty = Math.max(1, parseInt(e.target.value) || 1);
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.map(p =>
p.id === part.id
? { ...p, quantity: newQty, totalPrice: newQty * p.unitPrice }
: p
)
} : null);
}}
className="w-16 px-2 py-1 border border-orange-200 rounded text-center text-sm"
/>
</div>
<span className="text-slate-600">{part.unitPrice.toLocaleString()}/unit</span>
</div>
<div className="pt-2 border-t border-orange-100 flex justify-between">
<span className="text-xs text-slate-500">Total</span>
<span className="font-bold text-orange-700">{part.totalPrice.toLocaleString()}</span>
</div>
</div>
))}
</div>
</>
) : (
<p className="text-sm text-orange-400">No parts added</p>
)}
{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>
<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})
@@ -988,6 +791,348 @@ export default function MaintenanceDetailPage() {
)}
</div>
</div>
</div>
<div className="space-y-4">
{/* Manual Invoice OCR Card */}
<div className="bg-indigo-50/70 p-4 rounded-xl border border-indigo-100 space-y-3 relative overflow-hidden transition-all duration-300">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-indigo-800 text-sm flex items-center gap-2">
<FileText className="w-5 h-5 text-indigo-600" /> Manual Invoice OCR Upload
</h3>
{ocrComplete && (
<span className="px-2.5 py-0.5 bg-emerald-100 border border-emerald-200 text-emerald-800 text-[10px] font-bold rounded-full uppercase tracking-wider animate-pulse">
OCR Synced
</span>
)}
</div>
{!isOcrProcessing && !ocrComplete && (
<div className="border border-dashed border-indigo-300 hover:border-indigo-500 rounded-xl p-5 text-center cursor-pointer transition-all bg-white relative group hover:bg-indigo-50/50">
<input
type="file"
accept="image/*,.pdf"
className="absolute inset-0 opacity-0 cursor-pointer z-10"
onChange={(e) => {
const file = e.target.files?.[0];
if (!file) return;
setOcrFileName(file.name);
setIsOcrProcessing(true);
setOcrStep('1/3: Parsing document layout & digital signature...');
setTimeout(() => {
setOcrStep('2/3: Running AI layout analysis & line item extraction...');
setTimeout(() => {
setOcrStep('3/3: Auto-populating ledger items and actual costs...');
setTimeout(() => {
setIsOcrProcessing(false);
setOcrComplete(true);
// Populate parts dynamically with mock OCR data!
setRecord(prev => {
if (!prev) return null;
return {
...prev,
partsUsed: [
{ id: 'PU-OCR-1', partId: 'PRT-001', partName: 'Front fender (OCR Extracted)', quantity: 1, unitPrice: 1500, totalPrice: 1500, addedAt: new Date().toISOString().split('T')[0] },
{ id: 'PU-OCR-2', partId: 'PRT-003', partName: 'Mounting brackets (OCR Extracted)', quantity: 2, unitPrice: 800, totalPrice: 1600, addedAt: new Date().toISOString().split('T')[0] },
{ id: 'PU-OCR-3', partId: 'PRT-004', partName: 'Brake pads (OCR Extracted)', quantity: 2, unitPrice: 600, totalPrice: 1200, addedAt: new Date().toISOString().split('T')[0] },
],
serviceCost: 1800,
actualCost: 6100, // 1500 + 1600 + 1200 + 1800
notes: [...prev.notes, `OCR Scan Success: Extracted items from manual invoice "${file.name}".`]
};
});
}, 1000);
}, 1200);
}, 1000);
}}
/>
<div className="flex flex-col items-center justify-center space-y-2">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center text-indigo-600 group-hover:scale-110 transition-transform">
<Plus className="w-5 h-5" />
</div>
<div>
<p className="text-sm font-semibold text-slate-700">Upload manual invoice receipt</p>
<p className="text-xs text-slate-500 mt-0.5">No file chosen</p>
<p className="text-[10px] text-slate-400 mt-2 font-medium">Supports PDF, PNG, JPG (e.g. MNT-001-invoice.pdf)</p>
</div>
</div>
</div>
)}
{isOcrProcessing && (
<div className="p-4 bg-white border border-indigo-100 rounded-xl space-y-3">
<div className="flex justify-between text-xs">
<span className="text-indigo-800 font-semibold animate-pulse">Running JAIBEN AI OCR Engine...</span>
<span className="text-indigo-600 font-bold">{ocrStep.split(':')[0]}</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-1.5 overflow-hidden border border-slate-200">
<div className="bg-gradient-to-r from-indigo-500 to-indigo-700 h-full w-2/3 animate-pulse rounded-full"></div>
</div>
<p className="text-[10px] text-slate-500 italic">{ocrStep}</p>
</div>
)}
{ocrComplete && (
<div className="p-4 bg-emerald-50 border border-emerald-100 rounded-xl space-y-2">
<div className="flex items-center gap-2 text-emerald-800 text-xs font-bold">
<CheckCircle className="w-4 h-4 text-emerald-600" />
<span>OCR PARSING COMPLETE</span>
</div>
<div className="text-xs text-slate-600 space-y-1.5 bg-white p-3 rounded-lg border border-emerald-100">
<p>📄 Document: <strong className="text-slate-800 font-semibold">{ocrFileName}</strong></p>
<p>🔧 Extracted Parts: <strong className="text-slate-800 font-semibold">3 items</strong></p>
<p> Labor Service Costs: <strong className="text-slate-800 font-semibold">1,800</strong></p>
<p>💰 Auto Total Synced: <strong className="text-slate-800 font-semibold">6,100</strong></p>
</div>
<button
onClick={() => {
setOcrComplete(false);
setOcrFileName('');
const found = mockMaintenance.find(r => r.id === id);
if (found) {
setRecord(found);
}
}}
className="w-full py-1.5 mt-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition-colors border border-slate-200 cursor-pointer"
>
Reset OCR Invoice Attachment
</button>
</div>
)}
</div>
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100 relative overflow-hidden transition-all duration-300">
{ocrComplete && (
<div className="absolute inset-0 bg-slate-900/5 backdrop-blur-[1px] flex items-center justify-center z-10 p-2">
<span className="px-3 py-1 bg-slate-900 text-white border border-slate-700 text-[10px] font-bold rounded-lg uppercase tracking-wider flex items-center gap-1.5 shadow-md">
<CheckCircle className="w-3.5 h-3.5 text-emerald-400" /> Populated via AI OCR
</span>
</div>
)}
<div className={ocrComplete ? 'filter blur-[1.5px] opacity-60 pointer-events-none' : ''}>
<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="bg-gradient-to-r from-purple-50 to-indigo-50 p-4 rounded-xl border border-purple-100">
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-purple-800 flex items-center gap-2">
<DollarSign className="w-5 h-5" /> Cost Breakdown
</h3>
{editMode && (
<input
type="number"
value={editForm.estimatedCost || 0}
onChange={(e) => setEditForm({ ...editForm, estimatedCost: parseInt(e.target.value) })}
className="px-2 py-1 border border-purple-200 rounded text-sm w-24"
/>
)}
</div>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-slate-600">Estimated Cost:</span>
<span className="font-medium text-purple-700">{record.estimatedCost.toLocaleString()}</span>
</div>
<div className="border-t border-purple-100 pt-2">
<div className="flex justify-between items-center text-sm">
<span className="text-slate-600 flex items-center gap-1">
<Package className="w-4 h-4" /> Parts Total:
</span>
<span className="font-medium text-orange-600">{record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0).toLocaleString() || 0}</span>
</div>
<div className="flex justify-between items-center text-sm mt-1">
<span className="text-slate-600 flex items-center gap-1">
<Wrench className="w-4 h-4" /> Service Cost (Labor):
</span>
<span className="font-medium text-blue-600">{(record.serviceCost || 0).toLocaleString()}</span>
</div>
</div>
<div className="border-t-2 border-purple-200 pt-2 mt-2">
<div className="flex justify-between items-center">
<span className="font-semibold text-purple-800">Actual Total Cost:</span>
<span className="text-xl font-bold text-purple-800">
{((record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0) || 0) + (record.serviceCost || 0)).toLocaleString()}
</span>
</div>
</div>
</div>
</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 relative overflow-hidden transition-all duration-300">
{ocrComplete && (
<div className="absolute inset-0 bg-slate-900/5 backdrop-blur-[1px] flex items-center justify-center z-10 p-2">
<span className="px-3 py-1 bg-slate-900 text-white border border-slate-700 text-[10px] font-bold rounded-lg uppercase tracking-wider flex items-center gap-1.5 shadow-md">
<CheckCircle className="w-3.5 h-3.5 text-emerald-400" /> Populated via AI OCR
</span>
</div>
)}
<div className={ocrComplete ? 'filter blur-[1.5px] opacity-60 pointer-events-none' : ''}>
<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 && (
<button
onClick={() => setShowAddPartModal(true)}
className="px-3 py-1 bg-orange-600 text-white text-xs rounded-lg hover:bg-orange-700 flex items-center gap-1"
>
<Plus className="w-3 h-3" /> Add Part
</button>
)}
</div>
<div className="space-y-3">
{record.partsUsed && record.partsUsed.length > 0 ? (
<>
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-orange-100">
<tr>
<th className="px-3 py-2 text-left text-orange-800 font-medium">Part</th>
<th className="px-3 py-2 text-center text-orange-800 font-medium">Qty</th>
<th className="px-3 py-2 text-right text-orange-800 font-medium">Unit Price</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">Action</th>
</tr>
</thead>
<tbody>
{record.partsUsed?.map((part) => (
<tr key={part.id} className="bg-white border-b border-orange-100">
<td className="px-3 py-2 text-slate-700">{part.partName}</td>
<td className="px-3 py-2 text-center">
<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"
/>
</td>
<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
onClick={() => {
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.filter(p => p.id !== part.id)
} : null);
}}
className="text-red-400 hover:text-red-600 p-1"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between text-sm mb-2">
<div className="flex items-center gap-2">
<span className="text-slate-500">Qty:</span>
<input
type="number"
min="1"
value={part.quantity}
onChange={(e) => {
const newQty = Math.max(1, parseInt(e.target.value) || 1);
setRecord(prev => prev ? {
...prev,
partsUsed: prev.partsUsed?.map(p =>
p.id === part.id
? { ...p, quantity: newQty, totalPrice: newQty * p.unitPrice }
: p
)
} : null);
}}
className="w-16 px-2 py-1 border border-orange-200 rounded text-center text-sm"
/>
</div>
<span className="text-slate-600">{part.unitPrice.toLocaleString()}/unit</span>
</div>
<div className="pt-2 border-t border-orange-100 flex justify-between">
<span className="text-xs text-slate-500">Total</span>
<span className="font-bold text-orange-700">{part.totalPrice.toLocaleString()}</span>
</div>
</div>
))}
</div>
</>
) : (
<p className="text-sm text-orange-400">No parts added</p>
)}
{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>
</div>
</div>
</div>
</div>