Snaplocal / client /src /components /InvoiceView.jsx
Kuruva Laxmi
Deployment preparation: Configure Vercel, API URLs, and CORS
f359e2d
Raw
History Blame Contribute Delete
5.58 kB
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;