stagingfrontend / components /PrintInvoice.tsx
Antaram's picture
Upload 49 files
6234767 verified
import React, { useState } from 'react';
import { Transaction } from '../types';
import { Printer, X } from 'lucide-react';
interface PrintInvoiceProps {
transaction: Transaction;
className?: string;
}
const PrintInvoice: React.FC<PrintInvoiceProps> = ({ transaction, className }) => {
const [showPreview, setShowPreview] = useState(false);
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' });
};
// Calculate totals
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 gaadiBharni = transaction.expenses?.gaadi_bharni || 0;
const otherExpenses = transaction.expenses?.other_expenses || 0;
const grandTotal = transaction.total_amount || (subtotal + packing + cess + adat + hamali + gaadiBharni + otherExpenses);
const paidAmount = transaction.paid_amount || 0;
const balanceAmount = transaction.balance_amount || 0;
const handlePrint = () => {
const printContent = document.getElementById('invoice-content');
if (!printContent) return;
const iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const doc = iframe.contentWindow?.document;
if (doc) {
doc.open();
doc.write(`
<html>
<head>
<title>Invoice-${transaction.bill_number}</title>
<style>
/* Reset margins to absolute zero */
@page { size: A4; margin: 0; }
body { margin: 0; padding: 0; background-color: white; font-family: Helvetica, Arial, sans-serif; }
/* Overwrite the container style for printing */
#invoice-container-inner {
width: 210mm !important;
/* Reducing slightly from 297mm to 295mm prevents the 'spillover' blank page */
min-height: 295mm !important;
padding: 15mm !important;
margin: 0 auto !important;
box-sizing: border-box !important;
overflow: hidden !important; /* Cut off any rogue pixels */
}
/* Helper to hide non-print elements if any sneak in */
.print-hidden { display: none !important; }
</style>
</head>
<body>
${printContent.innerHTML}
</body>
</html>
`);
doc.close();
iframe.contentWindow?.focus();
setTimeout(() => {
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
}, 500);
}
};
return (
<>
<button
onClick={() => setShowPreview(true)}
className={className || "p-2 bg-white text-teal-600 border border-teal-600 rounded-md hover:bg-teal-50 transition-colors flex items-center justify-center shadow-sm"}
title="Print Invoice"
>
<Printer size={16} />
</button>
{showPreview && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-2 sm:p-4">
<div className="bg-white rounded-lg shadow-2xl max-w-4xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="sticky top-0 bg-white border-b border-gray-200 px-3 py-3 sm:px-6 sm:py-4 flex justify-between items-center z-10 shrink-0">
<h2 className="text-lg sm:text-xl font-bold text-gray-800 truncate mr-2">Invoice Preview</h2>
<div className="flex gap-2 items-center">
<button
onClick={handlePrint}
className="px-3 py-1.5 sm:px-4 sm:py-2 text-sm sm:text-base bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors flex items-center gap-1 sm:gap-2"
>
<Printer size={16} className="sm:w-[18px] sm:h-[18px]" />
<span>Print</span>
</button>
<button
onClick={() => setShowPreview(false)}
className="p-1.5 sm:p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X size={20} className="text-gray-600 sm:w-6 sm:h-6" />
</button>
</div>
</div>
{/* Scrollable Area */}
<div className="overflow-auto flex-1 p-2 sm:p-8 bg-gray-100">
<div className="mb-4 text-xs text-gray-500 text-center sm:hidden">
Scroll horizontally to view full invoice
</div>
{/* Wrapper for ID targeting */}
<div id="invoice-content">
<div
id="invoice-container-inner"
className="bg-white mx-auto shadow-sm"
style={{
width: '210mm',
minHeight: '297mm', /* This stays 297mm for screen preview, but Print overrides it to 295mm */
padding: '15mm',
margin: 'auto',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: '12px',
color: '#000',
backgroundColor: '#fff',
boxSizing: 'border-box',
position: 'relative'
}}
>
{/* Invoice Header */}
<div style={{ borderBottom: '2px solid #333', paddingBottom: '10px', marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
<div>
<h1 style={{ margin: 0, fontSize: '24px', fontWeight: 'bold', letterSpacing: '1px' }}>INVOICE</h1>
</div>
<div style={{ textAlign: 'right' }}>
{/* <div style={{ fontSize: '14px', fontWeight: 'bold' }}>Bill No: {transaction.bill_number}</div> */}
<div style={{ fontSize: '12px', marginTop: '4px' }}>Date: {formatDate(transaction.bill_date)}</div>
</div>
</div>
{/* Info Section */}
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '20px' }}>
<div style={{ width: '55%', textAlign: 'left' }}>
<div style={{
textTransform: 'uppercase',
fontSize: '10px',
color: '#555',
fontWeight: 'bold',
marginBottom: '4px',
borderBottom: '1px solid #eee',
paddingBottom: '2px',
width: '100%'
}}>Billed To</div>
<div style={{ fontSize: '14px', fontWeight: 'bold', marginTop: '5px' }}>{transaction.party_name}</div>
<div style={{ marginTop: '2px' }}>Phone: {transaction.party_phone || '-'}</div>
</div>
</div>
{/* Table */}
<table style={{ width: '100%', borderCollapse: 'collapse', marginBottom: '20px' }}>
<thead>
<tr style={{ backgroundColor: '#f3f4f6' }}>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'center', width: '5%' }}>#</th>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'left', width: '40%' }}>Item Description</th>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'center', width: '10%' }}>Bags</th>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'center', width: '15%' }}>Net Weight</th>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'right', width: '15%' }}>Rate</th>
<th style={{ border: '1px solid #ccc', padding: '8px 6px', textAlign: 'right', width: '15%' }}>Amount</th>
</tr>
</thead>
<tbody>
{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 (
<tr key={idx}>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'center' }}>{idx + 1}</td>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'left' }}>
<div style={{ fontWeight: 'bold' }}>{item.mirchi_name}</div>
<div style={{ fontSize: '9px', color: '#666', marginTop: '2px', lineHeight: '1.2' }}>
Weights: {potiWeights}
</div>
</td>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'center' }}>{item.poti_count}</td>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'center' }}>{item.net_weight} kg</td>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'right' }}>{formatINR(item.rate_per_kg)}</td>
<td style={{ border: '1px solid #ccc', padding: '6px', textAlign: 'right' }}>{formatINR(amount)}</td>
</tr>
);
})}
</tbody>
</table>
{/* Totals Section */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ width: '50%', paddingRight: '20px', textAlign: 'left' }}>
<div style={{ border: '1px solid #ccc', padding: '10px', borderRadius: '4px' }}>
<div style={{ fontWeight: 'bold', borderBottom: '1px solid #eee', marginBottom: '8px', paddingBottom: '4px' }}>Payment Status</div>
{transaction.payments && transaction.payments.length > 0 ? (
<>
{transaction.payments.map((payment, idx) => (
<div key={idx} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '11px', marginBottom: '2px' }}>
<span>{payment.mode === 'cash' ? 'Cash' : payment.mode === 'online' ? 'Online' : payment.mode}:</span>
<span>{formatINR(payment.amount)}</span>
</div>
))}
<div style={{ borderTop: '1px dashed #ccc', marginTop: '6px', paddingTop: '6px', display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
<span>Total Paid:</span>
<span>{formatINR(paidAmount)}</span>
</div>
</>
) : (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Total Paid:</span>
<span>{formatINR(paidAmount)}</span>
</div>
)}
{balanceAmount > 0 && (
<div style={{ marginTop: '8px', color: '#c62828', fontWeight: 'bold', display: 'flex', justifyContent: 'space-between', fontSize: '13px' }}>
<span>Due Amount:</span>
<span>{formatINR(balanceAmount)}</span>
</div>
)}
</div>
</div>
<div style={{ width: '45%' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
{/* Renamed Sub Total to Total */}
{/* <tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee' }}>Total</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee', fontWeight: 'bold' }}>{formatINR(subtotal)}</td>
</tr> */}
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>Packing Expenses</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(packing)}</td>
</tr>
{/* New Row: Total + Packing Calculation */}
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', fontWeight: 'bold' }}>Sub Total</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee', fontWeight: 'bold' }}>{formatINR(subtotal + packing)}</td>
</tr>
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>CESS</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(cess)}</td>
</tr>
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>Adat</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(adat)}</td>
</tr>
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>Hamali</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(hamali)}</td>
</tr>
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>Gaadi Bharni</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(gaadiBharni)}</td>
</tr>
<tr>
<td style={{ padding: '4px', textAlign: 'left', borderBottom: '1px solid #eee', color: '#555' }}>Other Expenses</td>
<td style={{ padding: '4px', textAlign: 'right', borderBottom: '1px solid #eee' }}>{formatINR(otherExpenses)}</td>
</tr>
<tr style={{ backgroundColor: '#f3f4f6' }}>
<th style={{ padding: '8px 4px', textAlign: 'left', borderTop: '2px solid #000', fontSize: '14px' }}>Grand Total</th>
<th style={{ padding: '8px 4px', textAlign: 'right', borderTop: '2px solid #000', fontSize: '14px' }}>{formatINR(grandTotal)}</th>
</tr>
</tbody>
</table>
</div>
</div>
{/* Signatures */}
<div style={{ marginTop: '50px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
<div style={{ textAlign: 'left', fontSize: '10px', color: '#666' }}>
<p>Thank you for your business.</p>
</div>
<div style={{ textAlign: 'center' }}>
<div style={{ marginBottom: '40px', fontSize: '10px' }}>For Authorised Signatory</div>
<div style={{ borderTop: '1px solid #000', width: '150px', margin: 'auto' }}></div>
<div style={{ fontSize: '10px', marginTop: '4px' }}>(Signature)</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
};
export default PrintInvoice;