stagingfrontend / components /PdfInvoice.tsx
Antaram's picture
Upload 49 files
6234767 verified
import React, { useState } from 'react';
import { Transaction } from '../types';
import { Download, X, Eye } from 'lucide-react';
import jsPDF from 'jspdf';
import 'jspdf-autotable';
interface PdfInvoiceProps {
transaction: Transaction;
className?: string;
children?: React.ReactNode;
}
const PdfInvoice: React.FC<PdfInvoiceProps> = ({ transaction, className, children }) => {
const [showPreview, setShowPreview] = useState(false);
const [pdfUrl, setPdfUrl] = useState<string>('');
const formatINR = (v: number) => `₹${v.toFixed(2)}`;
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString('en-IN', { day: '2-digit', month: '2-digit', year: 'numeric' });
};
const generatePDF = (download: boolean = false) => {
const doc = new jsPDF();
const subtotal = transaction.subtotal || 0;
const packing = transaction.expenses?.poti_amount || 0;
const cess = transaction.expenses?.cess_amount || 0;
const adat = transaction.expenses?.adat_amount || 0;
const hamali = (transaction.expenses?.hamali_amount || 0) + (transaction.expenses?.packaging_hamali_amount || 0);
const grandTotal = transaction.total_amount || 0;
const paidAmount = transaction.paid_amount || 0;
const balanceAmount = transaction.balance_amount || 0;
// Header - Invoice Date on left, Bill No on right
doc.setFontSize(13);
doc.setFont('helvetica', 'bold');
doc.text(`Invoice Date: ${formatDate(transaction.bill_date)}`, 14, 15);
doc.text(`Bill No: ${transaction.bill_number}`, 196, 15, { align: 'right' });
// Party Details Section - LEFT ALIGNED
doc.setFontSize(11);
doc.setFont('helvetica', 'bold');
doc.text('Party Details', 14, 25);
doc.setLineWidth(0.5);
doc.line(14, 26, 55, 26);
// Party info - explicitly left aligned at x=14
doc.setFont('helvetica', 'normal');
doc.setFontSize(9);
const partyName = transaction.party_name || '-';
const partyCity = transaction.party_city || '-';
const partyPhone = transaction.party_phone || '-';
doc.text(`Name: ${partyName}`, 14, 31, { align: 'left' });
doc.text(`Place: ${partyCity}`, 14, 36, { align: 'left' });
doc.text(`Mobile: ${partyPhone}`, 14, 41, { align: 'left' });
// Items Table
const tableData = transaction.items.map((item, idx) => {
const potiWeights = Array.isArray(item.poti_weights) ? item.poti_weights.join(', ') : (typeof item.poti_weights === 'string' ? item.poti_weights : '-');
const amount = item.net_weight * item.rate_per_kg;
return [idx + 1, `${item.mirchi_name}\n${potiWeights}`, item.poti_count, item.net_weight, item.rate_per_kg, formatINR(amount)];
});
(doc as any).autoTable({
startY: 47,
head: [['#', 'Product Type', 'Bags', 'Net (kg)', 'Rate (₹)', 'Amount (₹)']],
body: tableData,
theme: 'grid',
styles: { fontSize: 8, cellPadding: 2, lineWidth: 0.5, lineColor: [0, 0, 0] },
headStyles: { fillColor: [255, 255, 255], textColor: [0, 0, 0], fontStyle: 'bold' },
bodyStyles: { textColor: [0, 0, 0] },
columnStyles: {
0: { halign: 'center', cellWidth: 10 },
1: { halign: 'left', cellWidth: 60 },
2: { halign: 'center', cellWidth: 20 },
3: { halign: 'center', cellWidth: 25 },
4: { halign: 'center', cellWidth: 25 },
5: { halign: 'right', cellWidth: 35 }
}
});
// Summary Table - Right side
const finalY = (doc as any).lastAutoTable.finalY + 5;
const summaryData = [
['Sub Total', formatINR(subtotal)],
['Packing', formatINR(packing)],
['CESS', formatINR(cess)],
['Adat', formatINR(adat)],
['Hamali', formatINR(hamali)],
['Total Amount', formatINR(grandTotal)]
];
(doc as any).autoTable({
startY: finalY,
body: summaryData,
theme: 'grid',
styles: { fontSize: 8, cellPadding: 2, lineWidth: 0.5, lineColor: [0, 0, 0] },
bodyStyles: { textColor: [0, 0, 0] },
columnStyles: {
0: { halign: 'left', cellWidth: 50 },
1: { halign: 'right', cellWidth: 35 }
},
margin: { left: 111 }
});
// Payment Status Section - LEFT ALIGNED
const paymentY = (doc as any).lastAutoTable.finalY + 10;
doc.setFontSize(11);
doc.setFont('helvetica', 'bold');
doc.text('Payment Status', 14, paymentY, { align: 'left' });
doc.setLineWidth(0.5);
doc.line(14, paymentY + 1, 60, paymentY + 1);
doc.setFont('helvetica', 'normal');
doc.setFontSize(9);
let currentY = paymentY + 6;
if (transaction.payments && transaction.payments.length > 0) {
transaction.payments.forEach((payment) => {
const mode = payment.mode === 'cash' ? 'Cash' : payment.mode === 'online' ? 'Online' : payment.mode;
doc.text(`${mode}: ${formatINR(payment.amount)}`, 14, currentY, { align: 'left' });
currentY += 5;
});
doc.setFont('helvetica', 'bold');
doc.text(`Total Paid: ${formatINR(paidAmount)}`, 14, currentY, { align: 'left' });
currentY += 5;
} else {
doc.text(`Paid Amount: ${formatINR(paidAmount)}`, 14, currentY, { align: 'left' });
currentY += 5;
}
if (balanceAmount > 0) {
doc.setFont('helvetica', 'bold');
doc.setTextColor(211, 47, 47);
doc.text(`Due Amount: ${formatINR(balanceAmount)}`, 14, currentY, { align: 'left' });
doc.setTextColor(0, 0, 0);
}
// Signature - Right side
doc.setFont('helvetica', 'normal');
doc.setFontSize(9);
doc.text('(Authorised Signatory)', 196, paymentY + 20, { align: 'right' });
if (download) {
doc.save(`Invoice_${transaction.bill_number}.pdf`);
} else {
const pdfBlob = doc.output('blob');
const url = URL.createObjectURL(pdfBlob);
setPdfUrl(url);
setShowPreview(true);
}
};
const handleDownload = () => {
generatePDF(true);
setShowPreview(false);
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
}
};
const handleClose = () => {
setShowPreview(false);
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
}
};
return (
<>
<button
onClick={() => generatePDF(false)}
className={className || "p-2 bg-white text-red-600 border border-red-600 rounded-md hover:bg-red-50 transition-colors flex items-center justify-center shadow-sm"}
title="Preview & Download PDF"
>
{children || <Eye size={16} />}
</button>
{showPreview && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-2xl max-w-6xl w-full max-h-[90vh] flex flex-col">
<div className="border-b border-gray-200 px-6 py-4 flex justify-between items-center">
<h2 className="text-xl font-bold text-gray-800">PDF Preview</h2>
<div className="flex gap-2">
<button
onClick={handleDownload}
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors flex items-center gap-2"
>
<Download size={18} />
Download PDF
</button>
<button
onClick={handleClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X size={24} className="text-gray-600" />
</button>
</div>
</div>
<div className="flex-1 overflow-auto p-4 bg-gray-100">
<iframe
src={pdfUrl}
className="w-full h-full min-h-[600px] bg-white rounded shadow-lg"
title="PDF Preview"
/>
</div>
</div>
</div>
)}
</>
);
};
export default PdfInvoice;