Skip to content

Commit 2063411

Browse files
author
Your Name
committed
Switch PDF export to html2pdf.js for proper ₹ symbol support
1 parent 15e2890 commit 2063411

2 files changed

Lines changed: 54 additions & 65 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.29.6
1+
1.29.7

index.html

Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ <h4>Pending UPI payments <span id="pendingCount" class="pending-badge">0</span><
9292
</main>
9393

9494
<div id="modalRoot"></div>
95-
<footer>New Serchhip North Village Council · v1.29.6</footer>
95+
<footer>New Serchhip North Village Council · v1.29.7</footer>
9696

9797
<script type="module">
9898
/* ============================================================
@@ -1934,22 +1934,22 @@ <h4>Pending UPI payments <span id="pendingCount" class="pending-badge">0</span><
19341934
return window.XLSX;
19351935
}
19361936

1937-
async function ensureJsPDF(){
1938-
if(window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
1937+
async function ensureHtml2Pdf(){
1938+
if(window.html2pdf) return window.html2pdf;
19391939
try {
19401940
await new Promise((res,rej)=>{
19411941
const s=document.createElement("script");
1942-
s.src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js";
1942+
s.src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js";
19431943
s.crossOrigin="anonymous";
19441944
s.onload=res;
1945-
s.onerror=()=>rej(new Error("jsPDF load failed"));
1945+
s.onerror=()=>rej(new Error("html2pdf load failed"));
19461946
document.head.appendChild(s);
19471947
});
19481948
} catch(e) {
1949-
console.warn("jsPDF load failed:", e);
1949+
console.warn("html2pdf load failed:", e);
19501950
throw new Error("Couldn't load PDF generator. Check your connection.");
19511951
}
1952-
return window.jspdf.jsPDF;
1952+
return window.html2pdf;
19531953
}
19541954

19551955
const _num = v => { const n=parseFloat(v); return isNaN(n)?0:n; };
@@ -2796,80 +2796,69 @@ <h4>Pending UPI payments <span id="pendingCount" class="pending-badge">0</span><
27962796

27972797
async function doExportPdf(){
27982798
await withExportUI("exportPdfBtn", "Export owing list (PDF)", async ()=>{
2799-
const jsPDF = await ensureJsPDF();
2799+
const html2pdf = await ensureHtml2Pdf();
28002800
const searchTerm = $("adminSearch").value.trim().toLowerCase();
28012801
const list = filterCitizenList(allCitizens, searchTerm, adminFilter, periodFilter)
28022802
.filter(c => getEffectiveBalance(c) > 0);
28032803
if(!list.length){ toast("No owing households to export"); return; }
28042804

2805-
const fmtRs = n => "Rs. " + (Number(n)||0).toLocaleString("en-IN");
28062805
const ledgers = ledgerCache.size ? Object.fromEntries(ledgerCache) : await store.getAllLedgers();
28072806
const rows = [];
28082807
for(const c of list){
28092808
const bal = getEffectiveBalance(c);
28102809
const entries = ledgers[c.id] || await store.getLedger(c.id);
2811-
const payments = entries.filter(e=>e.type==="payment").map(e=>fmtRs(e.amount));
2810+
const payments = entries.filter(e=>e.type==="payment").map(e=>fmt(e.amount));
28122811
rows.push({
2813-
name: c.name || "—",
2814-
jobCard: c.jobCard || c.id,
2815-
amount: fmtRs(bal),
2812+
name: esc(c.name || "—"),
2813+
jobCard: esc(c.jobCard || c.id),
2814+
amount: fmt(bal),
28162815
payments: payments.length ? "Payment: " + payments.join(", ") : ""
28172816
});
28182817
}
28192818

2820-
const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
2821-
const pageW = doc.internal.pageSize.getWidth();
2822-
const margin = 16;
2823-
const rightX = pageW - margin;
2824-
let y = 22;
2825-
2826-
doc.setFontSize(18);
2827-
doc.setFont(undefined, "bold");
2828-
doc.text("Owing List", margin, y);
2829-
y += 10;
2830-
2831-
doc.setFontSize(10);
2832-
doc.setFont(undefined, "normal");
2833-
doc.text(`${todayISO()} · ${adminFilter.charAt(0).toUpperCase()+adminFilter.slice(1)}${periodFilter?` · ${periodFilter}`:""}`, margin, y);
2834-
y += 14;
2835-
2836-
doc.setDrawColor(230, 230, 230);
2837-
for(const r of rows){
2838-
if(y > doc.internal.pageSize.getHeight() - 28){ doc.addPage(); y = 20; }
2839-
2840-
// Row 1: name (left) and amount (right)
2841-
doc.setFont(undefined, "bold");
2842-
doc.setTextColor(40, 40, 40);
2843-
doc.setFontSize(11);
2844-
const nameLines = doc.splitTextToSize(r.name, pageW - margin*2 - 55);
2845-
doc.text(nameLines, margin, y);
2846-
2847-
const amountW = doc.getTextWidth(r.amount);
2848-
doc.setTextColor(180, 40, 40);
2849-
doc.text(r.amount, rightX - amountW, y);
2850-
2851-
// Row 2: job card (left) and payment history (right)
2852-
y += nameLines.length * 4.5 + 1;
2853-
doc.setFont(undefined, "normal");
2854-
doc.setTextColor(120, 120, 120);
2855-
doc.setFontSize(9);
2856-
doc.text(r.jobCard, margin, y);
2857-
2858-
if(r.payments){
2859-
const payMaxW = 70;
2860-
const payLines = doc.splitTextToSize(r.payments, payMaxW);
2861-
doc.setTextColor(26, 125, 66);
2862-
doc.text(payLines, rightX, y, { align: "right" });
2863-
}
2819+
const rowHtml = rows.map(r => `
2820+
<div style="display:flex;justify-content:space-between;align-items:flex-start;border-bottom:1px solid #e5e5e5;padding:10px 0;">
2821+
<div style="flex:1;min-width:0;padding-right:12px;">
2822+
<div style="font-size:14px;font-weight:600;color:#222;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${r.name}</div>
2823+
<div style="font-size:11px;color:#777;margin-top:2px;">${r.jobCard}</div>
2824+
</div>
2825+
<div style="text-align:right;max-width:45%;">
2826+
<div style="font-size:14px;font-weight:700;color:#b91c1c;">${r.amount}</div>
2827+
${r.payments ? `<div style="font-size:10px;color:#15803d;margin-top:2px;word-break:break-word;">${r.payments}</div>` : ""}
2828+
</div>
2829+
</div>
2830+
`).join("");
2831+
2832+
const el = document.createElement("div");
2833+
el.style.position = "fixed";
2834+
el.style.left = "-9999px";
2835+
el.style.top = "0";
2836+
el.style.width = "210mm";
2837+
el.style.padding = "16mm";
2838+
el.style.background = "#fff";
2839+
el.style.fontFamily = "Arial, sans-serif";
2840+
el.innerHTML = `
2841+
<div style="font-size:22px;font-weight:700;color:#1b4d3e;margin-bottom:4px;">Owing List</div>
2842+
<div style="font-size:11px;color:#666;margin-bottom:16px;">${todayISO()} · ${adminFilter.charAt(0).toUpperCase()+adminFilter.slice(1)}${periodFilter?` · ${periodFilter}`:""}</div>
2843+
${rowHtml}
2844+
`;
2845+
document.body.appendChild(el);
28642846

2865-
doc.setFontSize(11);
2866-
y += 6;
2867-
doc.line(margin, y, pageW - margin, y);
2868-
y += 8;
2847+
try{
2848+
await html2pdf().set({
2849+
margin: 0,
2850+
filename: `owing_${todayISO()}.pdf`,
2851+
image: { type: "jpeg", quality: 0.98 },
2852+
html2canvas: { scale: 2, useCORS: true, logging: false },
2853+
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }
2854+
}).from(el).save();
2855+
toast("PDF exported");
2856+
}catch(err){
2857+
console.error("PDF export failed:", err);
2858+
toast("PDF export failed");
2859+
} finally {
2860+
document.body.removeChild(el);
28692861
}
2870-
2871-
doc.save(`owing_${todayISO()}.pdf`);
2872-
toast("PDF exported");
28732862
});
28742863
}
28752864

0 commit comments

Comments
 (0)