sp/src/components/PrintInvoice.tsx

273 lines
13 KiB
TypeScript

import React, { useEffect, useState } from "react";
interface Invoice {
invoice_id: number;
invoice_number: string;
customer_id: string;
invoice_date: string;
due_date: string;
subtotal: number;
tax_amount: number;
total_amount: number;
status: string;
payment_terms: string | null;
notes: string;
created_at: string;
updated_at: string;
}
export default function PrintInvoice() {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [selectedInvoice, setSelectedInvoice] = useState<Invoice | null>(null);
useEffect(() => {
const getInvoiceListData = async () => {
try {
const response = await fetch('http://localhost:2058/host-api/v1/invoice/invoice-info/', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Session fetch failed');
}
setInvoiceList(data.data);
} catch (error: any) {
console.error('Fetch error:', error);
setError(error.message);
} finally {
setLoading(false);
}
};
getInvoiceListData();
}, []);
const handlePrint = () => {
window.print();
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
const viewInvoiceDetails = (invoice: Invoice) => {
setSelectedInvoice(invoice);
}
const closeInvoiceDetails = () => {
setSelectedInvoice(null);
}
if (error) {
return <div className="p-4 text-red-600">Error: {error}</div>;
}
if (loading) {
return <div className="p-4">Loading invoice data...</div>;
}
return (
<div className="p-4 max-w-6xl mx-auto">
{/* Invoice List */}
<div className="mb-8">
<h1 className="text-2xl font-bold mb-4">Invoices</h1>
<div className="overflow-x-auto">
<table className="min-w-full bg-white border border-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice #</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{invoiceList.map((invoice) => (
<tr key={invoice.invoice_id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">{invoice.invoice_number}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatDate(invoice.invoice_date)}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatDate(invoice.due_date)}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatCurrency(invoice.total_amount)}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full
${invoice.status === 'paid' ? 'bg-green-100 text-green-800' :
invoice.status === 'draft' ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'}`}>
{invoice.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
onClick={() => viewInvoiceDetails(invoice)}
className="text-blue-600 hover:text-blue-900 mr-2"
>
View
</button>
<button
onClick={handlePrint}
className="text-gray-600 hover:text-gray-900"
>
Print
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Invoice Detail Modal */}
{selectedInvoice && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
{/* Printable Invoice */}
<div id="printable-invoice" className="p-8">
<div className="flex justify-between items-start mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-800">INVOICE</h1>
<p className="text-gray-600">{selectedInvoice.invoice_number}</p>
</div>
<div className="text-right">
<p className="text-gray-600">Status: <span className={`font-semibold
${selectedInvoice.status === 'paid' ? 'text-green-600' :
selectedInvoice.status === 'draft' ? 'text-yellow-600' :
'text-red-600'}`}>
{selectedInvoice.status.toUpperCase()}
</span></p>
<p className="text-gray-600">Date: {formatDate(selectedInvoice.invoice_date)}</p>
<p className="text-gray-600">Due: {formatDate(selectedInvoice.due_date)}</p>
</div>
</div>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">From</h2>
<p className="text-gray-800">Your Company Name</p>
<p className="text-gray-600">123 Business Street</p>
<p className="text-gray-600">City, State 12345</p>
<p className="text-gray-600">contact@yourcompany.com</p>
</div>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">Bill To</h2>
<p className="text-gray-800">Customer ID: {selectedInvoice.customer_id}</p>
<p className="text-gray-600">[Customer Name]</p>
<p className="text-gray-600">[Customer Address]</p>
<p className="text-gray-600">[Customer Email]</p>
</div>
<div className="border-t border-b border-gray-200 py-4 mb-6">
<div className="grid grid-cols-12 gap-4 font-semibold text-gray-700">
<div className="col-span-6">Description</div>
<div className="col-span-2 text-right">Subtotal</div>
<div className="col-span-2 text-right">Tax</div>
<div className="col-span-2 text-right">Total</div>
</div>
</div>
<div className="mb-6">
<div className="grid grid-cols-12 gap-4 mb-2">
<div className="col-span-6 text-gray-800">[Service/Product Description]</div>
<div className="col-span-2 text-right">{formatCurrency(selectedInvoice.subtotal)}</div>
<div className="col-span-2 text-right">{formatCurrency(selectedInvoice.tax_amount)}</div>
<div className="col-span-2 text-right font-semibold">{formatCurrency(selectedInvoice.total_amount)}</div>
</div>
</div>
<div className="flex justify-end mb-8">
<div className="w-64">
<div className="flex justify-between py-2 border-b border-gray-200">
<span className="font-semibold">Subtotal:</span>
<span>{formatCurrency(selectedInvoice.subtotal)}</span>
</div>
<div className="flex justify-between py-2 border-b border-gray-200">
<span className="font-semibold">Tax:</span>
<span>{formatCurrency(selectedInvoice.tax_amount)}</span>
</div>
<div className="flex justify-between py-2 font-bold text-lg">
<span>Total:</span>
<span>{formatCurrency(selectedInvoice.total_amount)}</span>
</div>
</div>
</div>
{selectedInvoice.notes && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">Notes</h2>
<p className="text-gray-600">{selectedInvoice.notes}</p>
</div>
)}
<div className="pt-8 border-t border-gray-200">
<p className="text-gray-600 text-center">Thank you for your business!</p>
</div>
</div>
<div className="p-4 border-t border-gray-200 flex justify-end">
<button
onClick={closeInvoiceDetails}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300 mr-2"
>
Close
</button>
<button
onClick={handlePrint}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Print Invoice
</button>
</div>
</div>
</div>
)}
{/* Print Styles */}
<style>
{`
@media print {
body * {
visibility: hidden;
}
#printable-invoice, #printable-invoice * {
visibility: visible;
}
#printable-invoice {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 20mm;
}
.no-print {
display: none !important;
}
}
`}
</style>
</div>
);
}