Spaces:
Sleeping
Sleeping
File size: 5,582 Bytes
f359e2d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { useState, useEffect } from 'react';
import { Download, Eye, Loader2 } from 'lucide-react';
import api from '../api/api';
import { downloadInvoiceAsPDF, downloadInvoiceAsJSON } from '../utils/invoiceGenerator';
const InvoiceView = ({ invoiceId, photographer, customer }) => {
const [invoice, setInvoice] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchInvoice();
}, [invoiceId]);
const fetchInvoice = async () => {
try {
const res = await api.get(`/invoices/${invoiceId}`);
setInvoice(res.data);
} catch (err) {
console.error('Failed to load invoice:', err);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center py-8">
<Loader2 className="animate-spin text-blue-600" size={28} />
</div>
);
}
if (!invoice) {
return <div className="text-center text-gray-600">Invoice not found</div>;
}
const statusColor = {
pending: 'amber',
paid: 'green',
overdue: 'red',
};
const color = statusColor[invoice.paymentStatus];
return (
<div className="space-y-6">
{/* Header */}
<div className="bg-white border border-gray-200 rounded-lg p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-2xl font-bold text-gray-900">Invoice</h2>
<p className="text-lg text-gray-600 font-semibold">{invoice.invoiceNumber}</p>
</div>
<div className={`px-4 py-2 rounded-lg bg-${color}-100 text-${color}-700 font-bold capitalize`}>
{invoice.paymentStatus}
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<p className="text-gray-500">Issue Date</p>
<p className="font-semibold">{new Date(invoice.issueDate).toLocaleDateString()}</p>
</div>
<div>
<p className="text-gray-500">Due Date</p>
<p className="font-semibold">{new Date(invoice.dueDate).toLocaleDateString()}</p>
</div>
<div>
<p className="text-gray-500">From</p>
<p className="font-semibold">{photographer?.firstName} {photographer?.lastName}</p>
</div>
<div>
<p className="text-gray-500">To</p>
<p className="font-semibold">{customer?.firstName} {customer?.lastName}</p>
</div>
</div>
</div>
{/* Items Table */}
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50 border-b">
<tr>
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-700">Description</th>
<th className="px-6 py-3 text-right text-sm font-semibold text-gray-700">Quantity</th>
<th className="px-6 py-3 text-right text-sm font-semibold text-gray-700">Unit Price</th>
<th className="px-6 py-3 text-right text-sm font-semibold text-gray-700">Total</th>
</tr>
</thead>
<tbody>
{invoice.items?.map((item, idx) => (
<tr key={idx} className="border-b hover:bg-gray-50">
<td className="px-6 py-3 text-gray-900">{item.description}</td>
<td className="px-6 py-3 text-right text-gray-900">{item.quantity}</td>
<td className="px-6 py-3 text-right text-gray-900">₹{item.unitPrice.toFixed(2)}</td>
<td className="px-6 py-3 text-right text-gray-900">₹{item.total.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
{/* Totals */}
<div className="bg-gray-50 px-6 py-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-700">Subtotal</span>
<span className="font-semibold text-gray-900">₹{invoice.subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-700">Tax ({invoice.taxRate}%)</span>
<span className="font-semibold text-gray-900">₹{invoice.tax.toFixed(2)}</span>
</div>
{invoice.discount > 0 && (
<div className="flex justify-between">
<span className="text-gray-700">Discount</span>
<span className="font-semibold text-gray-900">-₹{invoice.discount.toFixed(2)}</span>
</div>
)}
<div className="border-t pt-2 flex justify-between text-base font-bold">
<span className="text-gray-900">Total Amount</span>
<span className="text-blue-600">₹{invoice.totalAmount.toFixed(2)}</span>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-3">
<button
onClick={() => downloadInvoiceAsPDF(invoice, photographer, customer)}
className="flex-1 flex items-center justify-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 font-medium"
>
<Download size={18} /> Download PDF
</button>
<button
onClick={() => downloadInvoiceAsJSON(invoice)}
className="flex-1 flex items-center justify-center gap-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 font-medium"
>
<Eye size={18} /> Export JSON
</button>
</div>
</div>
);
};
export default InvoiceView;
|