add download pdf functionality - MVP V1
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useBill } from "../hooks";
|
import { useBill } from "../hooks";
|
||||||
|
import { generatePDF } from "../utils";
|
||||||
import { BillHeader } from "./BillHeader";
|
import { BillHeader } from "./BillHeader";
|
||||||
import { BillItemRow } from "./BillItemRow";
|
import { BillItemRow } from "./BillItemRow";
|
||||||
import { BillSummary } from "./BillSummary";
|
import { BillSummary } from "./BillSummary";
|
||||||
@@ -45,6 +46,15 @@ export default function App() {
|
|||||||
|
|
||||||
<BillSummary bill={computedBill} />
|
<BillSummary bill={computedBill} />
|
||||||
|
|
||||||
|
{computedBill.items.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={() => generatePDF(computedBill)}
|
||||||
|
className="w-full bg-indigo-500 hover:bg-indigo-600 active:scale-95 text-white font-semibold py-3 rounded-xl text-sm transition-all"
|
||||||
|
>
|
||||||
|
Download PDF
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{computedBill.items.length > 0 && (
|
{computedBill.items.length > 0 && (
|
||||||
<button
|
<button
|
||||||
className="w-full border border-gray-200 text-gray-400 hover:text-red-400 hover:border-red-200 py-3 rounded-xl text-sm transition-all cursor-pointer"
|
className="w-full border border-gray-200 text-gray-400 hover:text-red-400 hover:border-red-200 py-3 rounded-xl text-sm transition-all cursor-pointer"
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import jsPDF from "jspdf";
|
||||||
|
import type { ComputedBill } from "../types";
|
||||||
|
import { formatCurrency } from "./calculator";
|
||||||
|
|
||||||
|
export function generatePDF(bill: ComputedBill): void {
|
||||||
|
const doc = new jsPDF("p", "mm", "a4");
|
||||||
|
|
||||||
|
const pageWidth = doc.internal.pageSize.getWidth();
|
||||||
|
const margin = 20;
|
||||||
|
const contentWidth = pageWidth - margin * 2;
|
||||||
|
|
||||||
|
let currentY = 20;
|
||||||
|
|
||||||
|
doc.setFontSize(22);
|
||||||
|
doc.setFont("helvetica", "bold");
|
||||||
|
doc.setTextColor(60, 60, 60);
|
||||||
|
doc.text(bill.title, margin, currentY);
|
||||||
|
currentY += 8;
|
||||||
|
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.setFont("helvetica", "normal");
|
||||||
|
doc.setTextColor(150, 150, 150);
|
||||||
|
doc.text(
|
||||||
|
bill.createdAt.toLocaleDateString("en-IN", {
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
}),
|
||||||
|
margin,
|
||||||
|
currentY,
|
||||||
|
);
|
||||||
|
currentY += 6;
|
||||||
|
|
||||||
|
doc.setDrawColor(220, 220, 220);
|
||||||
|
doc.line(margin, currentY, margin + contentWidth, currentY);
|
||||||
|
currentY += 8;
|
||||||
|
|
||||||
|
doc.setFontSize(9);
|
||||||
|
doc.setFont("helvetica", "bold");
|
||||||
|
doc.setTextColor(120, 120, 120);
|
||||||
|
|
||||||
|
doc.text("ITEM", margin, currentY);
|
||||||
|
doc.text("QTY", margin + contentWidth * 0.6, currentY, { align: "center" });
|
||||||
|
doc.text("AMOUNT", margin + contentWidth, currentY, { align: "right" });
|
||||||
|
currentY += 5;
|
||||||
|
|
||||||
|
doc.setDrawColor(230, 230, 230);
|
||||||
|
doc.line(margin, currentY, margin + contentWidth, currentY);
|
||||||
|
currentY += 5;
|
||||||
|
|
||||||
|
doc.setFont("helvetica", "normal");
|
||||||
|
doc.setTextColor(60, 60, 60);
|
||||||
|
|
||||||
|
bill.items.forEach((item) => {
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.text(item.name, margin, currentY);
|
||||||
|
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.setTextColor(120, 120, 120);
|
||||||
|
doc.text(
|
||||||
|
`${item.quantity} × ${formatCurrency(item.unitPrice, bill.currency)}`,
|
||||||
|
margin,
|
||||||
|
currentY + 4,
|
||||||
|
);
|
||||||
|
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setTextColor(60, 60, 60);
|
||||||
|
doc.text(String(item.quantity), margin + contentWidth * 0.6, currentY, {
|
||||||
|
align: "center",
|
||||||
|
});
|
||||||
|
doc.text(
|
||||||
|
formatCurrency(item.finalPrice, bill.currency),
|
||||||
|
margin + contentWidth,
|
||||||
|
currentY,
|
||||||
|
{ align: "right" },
|
||||||
|
);
|
||||||
|
|
||||||
|
currentY += 12;
|
||||||
|
|
||||||
|
if (currentY > 270) {
|
||||||
|
doc.addPage();
|
||||||
|
currentY = 20;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
currentY += 4;
|
||||||
|
|
||||||
|
doc.setDrawColor(220, 220, 220);
|
||||||
|
doc.line(margin, currentY, margin + contentWidth, currentY);
|
||||||
|
currentY += 8;
|
||||||
|
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont("helvetica", "normal");
|
||||||
|
doc.setTextColor(120, 120, 120);
|
||||||
|
doc.text("Subtotal", margin, currentY);
|
||||||
|
doc.text(
|
||||||
|
formatCurrency(bill.subtotal, bill.currency),
|
||||||
|
margin + contentWidth,
|
||||||
|
currentY,
|
||||||
|
{ align: "right" },
|
||||||
|
);
|
||||||
|
currentY += 7;
|
||||||
|
|
||||||
|
if (bill.taxPercent > 0) {
|
||||||
|
doc.text(`${bill.taxType} (${bill.taxPercent}%)`, margin, currentY);
|
||||||
|
doc.text(
|
||||||
|
formatCurrency(bill.taxAmount, bill.currency),
|
||||||
|
margin + contentWidth,
|
||||||
|
currentY,
|
||||||
|
{ align: "right" },
|
||||||
|
);
|
||||||
|
currentY += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.setDrawColor(220, 220, 220);
|
||||||
|
doc.line(margin, currentY, margin + contentWidth, currentY);
|
||||||
|
currentY += 7;
|
||||||
|
|
||||||
|
doc.setFont("helvetica", "bold");
|
||||||
|
doc.setFontSize(13);
|
||||||
|
doc.setTextColor(60, 60, 60);
|
||||||
|
doc.text("Total", margin, currentY);
|
||||||
|
|
||||||
|
// Total amount in indigo-like colour
|
||||||
|
doc.setTextColor(99, 102, 241);
|
||||||
|
doc.text(
|
||||||
|
formatCurrency(bill.total, bill.currency),
|
||||||
|
margin + contentWidth,
|
||||||
|
currentY,
|
||||||
|
{ align: "right" },
|
||||||
|
);
|
||||||
|
|
||||||
|
const fileName = `${bill.title.replace(/\s+/g, "-").toLowerCase()}-bill.pdf`;
|
||||||
|
doc.save(fileName);
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./calculator";
|
export * from "./calculator";
|
||||||
|
export * from "./generatePDF";
|
||||||
|
|||||||
Reference in New Issue
Block a user