feat: enhance maintenance records with structured part management, service cost tracking, and dynamic invoice calculation
This commit is contained in:
@@ -6,8 +6,10 @@ 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, DollarSign, Wrench, Battery, Key,
|
||||||
CheckCircle, XCircle, ChevronLeft, Save, Printer, Send, QrCode,
|
CheckCircle, XCircle, ChevronLeft, Save, Printer, Send, QrCode,
|
||||||
Wallet, Building, Edit, MessageSquare, Calendar, ArrowLeft
|
Wallet, Building, Edit, MessageSquare, Calendar, ArrowLeft, Trash2,
|
||||||
|
Package, Settings
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -16,6 +18,16 @@ type DamageSeverity = 'critical' | 'major' | 'minor' | 'cosmetic';
|
|||||||
type MaintenanceStatus = 'reported' | 'in_progress' | 'parts_ordered' | 'completed' | 'cancelled';
|
type MaintenanceStatus = 'reported' | 'in_progress' | 'parts_ordered' | 'completed' | 'cancelled';
|
||||||
type PaymentStatus = 'pending' | 'approved' | 'paid' | 'rejected';
|
type PaymentStatus = 'pending' | 'approved' | 'paid' | 'rejected';
|
||||||
|
|
||||||
|
interface PartUsed {
|
||||||
|
id: string;
|
||||||
|
partId: string;
|
||||||
|
partName: string;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
totalPrice: number;
|
||||||
|
addedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface MaintenanceRecord {
|
interface MaintenanceRecord {
|
||||||
id: string;
|
id: string;
|
||||||
date: string;
|
date: string;
|
||||||
@@ -35,7 +47,8 @@ interface MaintenanceRecord {
|
|||||||
location: string;
|
location: string;
|
||||||
estimatedCost: number;
|
estimatedCost: number;
|
||||||
actualCost?: number;
|
actualCost?: number;
|
||||||
partsUsed?: string[];
|
serviceCost?: number;
|
||||||
|
partsUsed?: PartUsed[];
|
||||||
images: { id: string; name: string; url: string; uploadedAt: string }[];
|
images: { id: string; name: string; url: string; uploadedAt: string }[];
|
||||||
assignedTo?: string;
|
assignedTo?: string;
|
||||||
notes: string[];
|
notes: string[];
|
||||||
@@ -44,6 +57,30 @@ interface MaintenanceRecord {
|
|||||||
createdBy: string;
|
createdBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EVPart {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
buyingPrice: number;
|
||||||
|
sellingPrice: number;
|
||||||
|
category: string;
|
||||||
|
inStock: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockParts: EVPart[] = [
|
||||||
|
{ id: 'PRT-001', name: 'Front fender', buyingPrice: 800, sellingPrice: 1500, category: 'Body Parts', inStock: true },
|
||||||
|
{ id: 'PRT-002', name: 'Rear fender', buyingPrice: 900, sellingPrice: 1600, category: 'Body Parts', inStock: true },
|
||||||
|
{ id: 'PRT-003', name: 'Mounting brackets', buyingPrice: 400, sellingPrice: 800, category: 'Hardware', inStock: true },
|
||||||
|
{ id: 'PRT-004', name: 'Brake pads', buyingPrice: 350, sellingPrice: 600, category: 'Brakes', inStock: true },
|
||||||
|
{ id: 'PRT-005', name: 'Front wheel', buyingPrice: 2500, sellingPrice: 4000, category: 'Wheels', inStock: true },
|
||||||
|
{ id: 'PRT-006', name: 'Rear wheel', buyingPrice: 2200, sellingPrice: 3500, category: 'Wheels', inStock: true },
|
||||||
|
{ id: 'PRT-007', name: 'Motor controller', buyingPrice: 3500, sellingPrice: 5500, category: 'Electrical', inStock: true },
|
||||||
|
{ id: 'PRT-008', name: 'Display unit', buyingPrice: 1200, sellingPrice: 2000, category: 'Electrical', inStock: true },
|
||||||
|
{ id: 'PRT-009', name: 'Throttle', buyingPrice: 450, sellingPrice: 750, category: 'Controls', inStock: true },
|
||||||
|
{ id: 'PRT-010', name: 'Handlebar', buyingPrice: 800, sellingPrice: 1400, category: 'Controls', inStock: true },
|
||||||
|
{ id: 'PRT-011', name: 'Seat', buyingPrice: 600, sellingPrice: 1000, category: 'Comfort', inStock: true },
|
||||||
|
{ id: 'PRT-012', name: 'Side stand', buyingPrice: 250, sellingPrice: 450, category: 'Hardware', inStock: true },
|
||||||
|
];
|
||||||
|
|
||||||
const mockMaintenance: MaintenanceRecord[] = [
|
const mockMaintenance: MaintenanceRecord[] = [
|
||||||
{
|
{
|
||||||
id: 'MNT-001',
|
id: 'MNT-001',
|
||||||
@@ -64,7 +101,16 @@ const mockMaintenance: MaintenanceRecord[] = [
|
|||||||
location: 'Gulshan, Dhaka',
|
location: 'Gulshan, Dhaka',
|
||||||
estimatedCost: 3500,
|
estimatedCost: 3500,
|
||||||
actualCost: 3200,
|
actualCost: 3200,
|
||||||
partsUsed: ['Front fender', 'Mounting brackets'],
|
partsUsed: [
|
||||||
|
{ id: 'PU-001', partId: 'PRT-001', partName: 'Front fender', quantity: 1, unitPrice: 1500, totalPrice: 1500, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-002', partId: 'PRT-003', partName: 'Mounting brackets', quantity: 2, unitPrice: 800, totalPrice: 1600, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-003', partId: 'PRT-004', partName: 'Brake pads', quantity: 2, unitPrice: 600, totalPrice: 1200, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-004', partId: 'PRT-005', partName: 'Front wheel', quantity: 1, unitPrice: 4000, totalPrice: 4000, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-005', partId: 'PRT-009', partName: 'Throttle', quantity: 1, unitPrice: 750, totalPrice: 750, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-006', partId: 'PRT-011', partName: 'Seat', quantity: 1, unitPrice: 1000, totalPrice: 1000, addedAt: '2024-03-21' },
|
||||||
|
{ id: 'PU-007', partId: 'PRT-010', partName: 'Handlebar', quantity: 1, unitPrice: 1250, totalPrice: 1250, addedAt: '2024-03-21' },
|
||||||
|
],
|
||||||
|
serviceCost: 3200,
|
||||||
images: [
|
images: [
|
||||||
{ id: 'img1', name: 'Damage Front', url: '', uploadedAt: '2024-03-21' },
|
{ id: 'img1', name: 'Damage Front', url: '', uploadedAt: '2024-03-21' },
|
||||||
{ id: 'img2', name: 'Damage Side', url: '', uploadedAt: '2024-03-21' },
|
{ id: 'img2', name: 'Damage Side', url: '', uploadedAt: '2024-03-21' },
|
||||||
@@ -237,9 +283,17 @@ export default function MaintenanceDetailPage() {
|
|||||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||||
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 [showAddServiceCostModal, setShowAddServiceCostModal] = useState(false);
|
||||||
|
const [partSearch, setPartSearch] = useState('');
|
||||||
|
const [invoiceData, setInvoiceData] = useState({ tips: 0, discount: 0 });
|
||||||
|
const [showPaymentSuccess, setShowPaymentSuccess] = useState(false);
|
||||||
const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({});
|
const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({});
|
||||||
const [newNoteText, setNewNoteText] = useState('');
|
const [newNoteText, setNewNoteText] = useState('');
|
||||||
const [actualCost, setActualCost] = useState('');
|
const [actualCost, setActualCost] = useState('');
|
||||||
|
const [selectedPart, setSelectedPart] = useState<EVPart | null>(null);
|
||||||
|
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);
|
||||||
@@ -286,11 +340,11 @@ export default function MaintenanceDetailPage() {
|
|||||||
|
|
||||||
const handlePayment = (source: 'bank' | 'cash' | 'biker') => {
|
const handlePayment = (source: 'bank' | 'cash' | 'biker') => {
|
||||||
if (!record) return;
|
if (!record) return;
|
||||||
const cost = record.actualCost || record.estimatedCost;
|
const cost = ((record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0) || 0) + (record.serviceCost || 0)) + invoiceData.tips - invoiceData.discount;
|
||||||
|
|
||||||
setRecord(prev => prev ? { ...prev, paymentStatus: 'paid' } : null);
|
setRecord(prev => prev ? { ...prev, paymentStatus: 'paid', actualCost: cost, status: 'completed', resolvedAt: new Date().toISOString().split('T')[0] } : null);
|
||||||
setShowPaymentModal(false);
|
setShowPaymentModal(false);
|
||||||
setShowInvoiceModal(true);
|
setShowPaymentSuccess(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenerateInvoice = () => {
|
const handleGenerateInvoice = () => {
|
||||||
@@ -375,6 +429,8 @@ export default function MaintenanceDetailPage() {
|
|||||||
setShowAddNoteModal(false);
|
setShowAddNoteModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const invoiceTotal = record ? ((record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0) || 0) + (record.serviceCost || 0)) + invoiceData.tips - invoiceData.discount : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
||||||
<button
|
<button
|
||||||
@@ -538,142 +594,6 @@ export default function MaintenanceDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<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">
|
<h3 className="font-semibold text-indigo-800 mb-3 flex items-center gap-2">
|
||||||
@@ -709,6 +629,167 @@ export default function MaintenanceDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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-2">
|
||||||
|
{(record.partsUsed || []).length > 0 ? (
|
||||||
|
<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 text-slate-600">{part.quantity}</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 className="text-red-400 hover:text-red-600 p-1">
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
<tfoot className="bg-orange-50">
|
||||||
|
<tr>
|
||||||
|
<td colSpan={3} className="px-3 py-2 text-right font-semibold text-orange-800">Parts Total:</td>
|
||||||
|
<td className="px-3 py-2 text-right font-bold text-orange-700">
|
||||||
|
৳{record.partsUsed?.reduce((sum, p) => sum + p.totalPrice, 0).toLocaleString()}
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-orange-400">No parts added</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
<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">
|
<h3 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
|
||||||
@@ -762,34 +843,90 @@ export default function MaintenanceDetailPage() {
|
|||||||
|
|
||||||
{showCompleteModal && (
|
{showCompleteModal && (
|
||||||
<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-lg">
|
||||||
<div className="p-4 border-b border-slate-100">
|
<div className="p-4 border-b border-slate-100 bg-gradient-to-r from-green-50 to-emerald-50">
|
||||||
<h3 className="font-semibold text-slate-800">Complete Maintenance</h3>
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||||
|
<FileText className="w-5 h-5 text-green-600" /> Maintenance Invoice
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-500">{record.id} • {record.bikeModel}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<div className="bg-green-50 p-4 rounded-lg">
|
<div className="bg-slate-50 p-3 rounded-lg">
|
||||||
<p className="text-sm text-green-700">Enter actual cost to complete this record</p>
|
<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">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm mb-2">
|
||||||
|
<span className="text-slate-500">Service Cost (Labor):</span>
|
||||||
|
<span className="font-medium text-blue-600">৳{(record.serviceCost || 0).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium text-slate-600 mb-2 block">Actual Cost (৳)</label>
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<input
|
<div>
|
||||||
type="number"
|
<label className="text-xs font-medium text-slate-500 mb-1 block">Add Tips (৳)</label>
|
||||||
value={actualCost}
|
<input
|
||||||
onChange={(e) => setActualCost(e.target.value)}
|
type="number"
|
||||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-lg font-bold"
|
min="0"
|
||||||
/>
|
value={invoiceData.tips}
|
||||||
|
onChange={(e) => setInvoiceData({ ...invoiceData, tips: parseInt(e.target.value) || 0 })}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-500 mb-1 block">Discount (৳)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={invoiceData.discount}
|
||||||
|
onChange={(e) => setInvoiceData({ ...invoiceData, discount: parseInt(e.target.value) || 0 })}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-slate-500">
|
|
||||||
Estimated: ৳{record.estimatedCost}
|
<div className="bg-green-50 p-4 rounded-lg border border-green-100">
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<span className="text-slate-600">Subtotal:</span>
|
||||||
|
<span className="font-medium text-slate-700">৳{((record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0) || 0) + (record.serviceCost || 0)).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
{invoiceData.tips > 0 && (
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<span className="text-slate-500">Tips:</span>
|
||||||
|
<span className="text-green-600">+৳{invoiceData.tips.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{invoiceData.discount > 0 && (
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<span className="text-slate-500">Discount:</span>
|
||||||
|
<span className="text-red-500">-৳{invoiceData.discount.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="border-t border-green-200 pt-2 mt-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="font-bold text-green-800">Total Amount:</span>
|
||||||
|
<span className="text-2xl font-bold text-green-700">৳{(((record.partsUsed?.reduce((s, p) => s + p.totalPrice, 0) || 0) + (record.serviceCost || 0)) + invoiceData.tips - invoiceData.discount).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-slate-400 flex items-center gap-1">
|
||||||
|
<AlertTriangle className="w-3 h-3" /> After payment, this maintenance will be marked as completed
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
<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={() => { setShowCompleteModal(false); setInvoiceData({ tips: 0, discount: 0 }); }} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleComplete}
|
onClick={() => { setShowCompleteModal(false); setShowPaymentModal(true); }}
|
||||||
className="px-4 py-2 bg-green-600 text-white rounded-lg flex items-center gap-2"
|
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
|
<FileText className="w-4 h-4" /> Proceed to Payment
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -799,9 +936,14 @@ export default function MaintenanceDetailPage() {
|
|||||||
{showPaymentModal && record && (
|
{showPaymentModal && record && (
|
||||||
<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-lg">
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg">
|
||||||
<div className="p-4 border-b border-slate-100">
|
<div className="p-4 border-b border-slate-100 bg-blue-50">
|
||||||
<h3 className="font-semibold text-slate-800">Process Payment - {record.id}</h3>
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||||
<p className="text-sm text-slate-500">Amount: ৳{record.actualCost || record.estimatedCost}</p>
|
<Wallet className="w-5 h-5 text-blue-600" /> Process Payment
|
||||||
|
</h3>
|
||||||
|
<div className="mt-2 bg-white p-3 rounded-lg border border-blue-100">
|
||||||
|
<p className="text-xs text-slate-500">Invoice Total</p>
|
||||||
|
<p className="text-2xl font-bold text-blue-700">৳{invoiceTotal.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<p className="text-sm text-slate-600 mb-2">Select payment method:</p>
|
<p className="text-sm text-slate-600 mb-2">Select payment method:</p>
|
||||||
@@ -936,6 +1078,202 @@ export default function MaintenanceDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showAddPartModal && (
|
||||||
|
<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 justify-between items-center">
|
||||||
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||||
|
<Package className="w-5 h-5 text-orange-600" /> Add Part
|
||||||
|
</h3>
|
||||||
|
<button onClick={() => setShowAddPartModal(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">Select Part from EV Parts</label>
|
||||||
|
<p className="text-xs text-slate-400 mb-2">Prices are based on selling price from Settings</p>
|
||||||
|
<div className="relative mb-3">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search parts..."
|
||||||
|
value={partSearch}
|
||||||
|
onChange={(e) => setPartSearch(e.target.value)}
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-48 overflow-y-auto border border-slate-200 rounded-lg">
|
||||||
|
{mockParts.filter(p => p.name.toLowerCase().includes(partSearch.toLowerCase())).map(part => (
|
||||||
|
<div
|
||||||
|
key={part.id}
|
||||||
|
onClick={() => { setSelectedPart(part); setPartQuantity(1); }}
|
||||||
|
className={`p-3 border-b border-slate-100 cursor-pointer hover:bg-orange-50 ${selectedPart?.id === part.id ? 'bg-orange-50 border-orange-300' : ''}`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-700">{part.name}</p>
|
||||||
|
<p className="text-xs text-slate-500">{part.category}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="font-bold text-orange-600">৳{part.sellingPrice.toLocaleString()}</p>
|
||||||
|
<p className="text-xs text-slate-400">per unit</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedPart && (
|
||||||
|
<div className="bg-orange-50 p-4 rounded-lg border border-orange-200">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-xs font-medium text-slate-500 mb-1 block">Quantity</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={partQuantity}
|
||||||
|
onChange={(e) => setPartQuantity(Math.max(1, parseInt(e.target.value) || 1))}
|
||||||
|
className="w-full px-3 py-2 border border-orange-200 rounded-lg text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-xs font-medium text-slate-500 mb-1 block">Unit Price</label>
|
||||||
|
<p className="text-lg font-bold text-orange-700">৳{selectedPart.sellingPrice}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-xs font-medium text-slate-500 mb-1 block">Total</label>
|
||||||
|
<p className="text-lg font-bold text-orange-700">৳{(selectedPart.sellingPrice * partQuantity).toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||||
|
<button onClick={() => setShowAddPartModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg">Cancel</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedPart) {
|
||||||
|
const newPart: PartUsed = {
|
||||||
|
id: 'PU-' + Date.now(),
|
||||||
|
partId: selectedPart.id,
|
||||||
|
partName: selectedPart.name,
|
||||||
|
quantity: partQuantity,
|
||||||
|
unitPrice: selectedPart.sellingPrice,
|
||||||
|
totalPrice: selectedPart.sellingPrice * partQuantity,
|
||||||
|
addedAt: new Date().toISOString().split('T')[0]
|
||||||
|
};
|
||||||
|
setRecord(prev => prev ? { ...prev, partsUsed: [...(prev.partsUsed || []), newPart] } : null);
|
||||||
|
setShowAddPartModal(false);
|
||||||
|
setSelectedPart(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!selectedPart}
|
||||||
|
className="px-4 py-2 bg-orange-600 text-white rounded-lg disabled:opacity-50 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" /> Add Part
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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 && (
|
||||||
|
<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-8 text-center">
|
||||||
|
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<CheckCircle className="w-12 h-12 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-green-600 mb-2">Payment Successful!</h3>
|
||||||
|
<p className="text-slate-600 mb-4">Maintenance has been completed</p>
|
||||||
|
|
||||||
|
<div className="bg-slate-50 p-4 rounded-lg text-left mb-6">
|
||||||
|
<div className="flex justify-between mb-2">
|
||||||
|
<span className="text-sm text-slate-500">Maintenance ID</span>
|
||||||
|
<span className="text-sm font-bold text-slate-700">{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 text-slate-700">{record?.bikeModel}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between mb-2">
|
||||||
|
<span className="text-sm text-slate-500">Status</span>
|
||||||
|
<span className="text-sm font-bold text-green-600">Completed</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between pt-2 border-t border-slate-200">
|
||||||
|
<span className="text-sm font-semibold text-slate-700">Total Paid</span>
|
||||||
|
<span className="text-lg font-bold text-green-600">৳{((record?.partsUsed?.reduce((s, p) => s + p.totalPrice, 0) || 0) + (record?.serviceCost || 0) + invoiceData.tips - invoiceData.discount).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => { setShowPaymentSuccess(false); setInvoiceData({ tips: 0, discount: 0 }); }}
|
||||||
|
className="px-6 py-3 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user