Replace PDF preview overlay with direct download on Generate

Clicking Generate now immediately triggers a PDF download instead of
opening a full-screen overlay preview that was inaccessible on smaller
screens. Removes the overlay HTML, its CSS, buildPreviewHTML(), and
closeOverlay().

https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
This commit is contained in:
Claude 2026-05-29 15:13:38 +00:00
parent 0da4956a5a
commit 30fe67fd6e
No known key found for this signature in database

View file

@ -196,106 +196,6 @@
}
#btn-generate:hover { background: var(--accent-hover); }
/* ── Invoice overlay ────────────────────────────────────────────────────── */
#overlay {
display: none;
position: fixed; inset: 0;
background: rgba(15,23,42,.65);
z-index: 900;
overflow-y: auto;
padding: 32px 16px 48px;
align-items: flex-start;
justify-content: center;
flex-direction: column;
}
#overlay.on { display: flex; }
#overlay-actions {
display: flex; gap: 10px;
margin: 0 auto 16px;
width: 794px; max-width: 100%;
}
.btn-dl {
background: var(--success); color: var(--white);
padding: 10px 20px; font-size: 14px; font-weight: 600;
border-radius: var(--radius);
}
.btn-dl:hover { background: #166534; }
.btn-close {
background: var(--white); color: var(--text);
padding: 10px 20px; font-size: 14px;
border: 1px solid var(--border); border-radius: var(--radius);
}
.btn-close:hover { background: var(--bg); }
/* ── Invoice preview (screen only) ──────────────────────────────────────── */
#inv-doc {
background: var(--white);
width: 794px; max-width: 100%; min-height: 1100px;
padding: 52px 60px 60px;
box-shadow: 0 4px 24px rgba(0,0,0,.25);
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
font-size: 11.5px; color: #111827;
}
/* 2×2 invoice header grid */
.d-4hdr {
display: grid; grid-template-columns: 1fr 1fr;
border-bottom: 3px solid var(--navy);
margin-bottom: 22px;
}
.d-4hdr > div { padding: 0; }
.d-4hdr > .d-tl { padding: 0 20px 16px 0; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb; }
.d-4hdr > .d-tr { padding: 0 0 16px 20px; border-bottom: 1px solid #e5e7eb; }
.d-4hdr > .d-bl { padding: 16px 20px 16px 0; border-right: 1px solid #e5e7eb; }
.d-4hdr > .d-br { padding: 16px 0 16px 20px; }
.d-sender .name { font-size: 17px; font-weight: 700; color: var(--navy); margin-bottom: 6px; }
.d-sender p { font-size: 10.5px; color: #4b5563; margin-bottom: 2px; }
.d-pay-hdr .ph-lbl { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.2px; color: #6b7280; margin-bottom: 6px; }
.d-pay-hdr .ph-terms { font-size: 10.5px; color: #4b5563; margin-bottom: 6px; }
.d-pay-hdr .ph-grid { display: grid; grid-template-columns: 130px 1fr; gap: 3px 8px; font-size: 10.5px; margin-top: 6px; }
.d-pay-hdr .ph-grid .pl { color: #6b7280; }
.d-pay-hdr .ph-grid .pv { font-weight: 500; word-break: break-all; }
.d-pay-hdr .ph-ref { margin-top: 6px; font-size: 10.5px; color: #4b5563; }
.d-pay-hdr .ph-ref strong { color: #111827; }
.d-bill .bt-lbl { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.2px; color: #6b7280; margin-bottom: 5px; }
.d-bill .bt-name { font-size: 13px; font-weight: 700; color: var(--navy); margin-bottom: 3px; }
.d-bill p { font-size: 10.5px; color: #4b5563; margin-bottom: 2px; }
.bt-meta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; }
.bt-meta span { font-size: 10px; color: #4b5563; }
.bt-meta strong { color: #111827; }
.d-inv-meta { text-align: right; }
.d-inv-meta h1 { font-size: 28px; font-weight: 800; letter-spacing: 5px; color: var(--navy); margin-bottom: 14px; }
.d-meta { border-collapse: collapse; margin-left: auto; }
.d-meta td { font-size: 10.5px; padding: 2px 0 2px 12px; }
.d-meta .ml { color: #6b7280; text-align: right; }
.d-meta .mv { font-weight: 600; text-align: right; }
.d-lines { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.d-lines thead tr { background: var(--navy); color: white; }
.d-lines thead th { padding: 7px 10px; font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; text-align: left; }
.d-lines thead th.r { text-align: right; }
.d-lines tbody tr { border-bottom: 1px solid #e5e7eb; }
.d-lines tbody tr:nth-child(even) { background: #f9fafb; }
.d-lines tbody td { padding: 7px 10px; font-size: 10.5px; vertical-align: top; }
.d-lines tbody td.r { text-align: right; }
.d-lines tbody td.b { font-weight: 600; }
.d-fx-note { font-size: 9.5px; color: #6b7280; margin-top: 3px; }
.d-tots { width: 100%; border-collapse: collapse; }
.d-tots td { padding: 5px 10px; font-size: 11.5px; }
.d-tots .sp { width: 55%; }
.d-tots .tl { text-align: right; color: #6b7280; padding-right: 20px; }
.d-tots .tv { text-align: right; width: 130px; font-weight: 600; }
.d-tots .sub td { border-top: 1px solid #e5e7eb; }
.d-tots .fin td { background: var(--navy); color: white; font-size: 14px; font-weight: 700; border-top: 2px solid var(--navy); }
.d-tots .fin .tl { color: rgba(255,255,255,.75); }
/* ── Payment card form fields ────────────────────────────────────────────── */
.pay-section-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; color: var(--navy); margin-bottom: 10px; }
@ -348,14 +248,6 @@
</div><!-- /.wrap -->
<!-- ── Invoice preview overlay ───────────────────────────────────────────────── -->
<div id="overlay">
<div id="overlay-actions">
<button class="btn-dl" id="btn-dl" onclick="buildPDF()">&#11015; Download PDF</button>
<button class="btn-close" id="btn-close" onclick="closeOverlay()">&#x2715; Close</button>
</div>
<div id="inv-doc"></div>
</div>
<!-- js-yaml -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"
@ -1183,24 +1075,13 @@ function relabel() {
ctPick.value = cur;
}
document.getElementById("btn-dl").textContent = "⬇ " + t("download-pdf");
document.getElementById("btn-close").textContent = "✕ " + t("close");
}
// ── Generate preview ──────────────────────────────────────────────────────────
// ── Generate invoice ──────────────────────────────────────────────────────────
function generateInvoice() {
saveStorage();
localStorage.setItem(LS_GEN, "true");
document.getElementById("inv-doc").innerHTML = buildPreviewHTML();
const ov = document.getElementById("overlay");
ov.classList.add("on");
ov.scrollTop = 0;
document.getElementById("btn-dl").textContent = "⬇ " + t("download-pdf");
document.getElementById("btn-close").textContent = "✕ " + t("close");
}
function closeOverlay() {
document.getElementById("overlay").classList.remove("on");
buildPDF();
}
// ── Shared: gather invoice data ───────────────────────────────────────────────
@ -1295,99 +1176,6 @@ function gatherData(renderLang) {
}
// ── Build HTML preview ────────────────────────────────────────────────────────
function buildPreviewHTML() {
const { td, sName, sAddr, sCntry, sPh, sEm, sTax, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
pTerm, pPayBy, pAcct, pIban, pBic, pBadr1, pBadr2, pRef, hasBank, hidePaymentOut,
rows, sub, taxes, paid, toPay } = gatherData();
const linesHTML = rows.map(row => {
const fxLine = row.fxNote
? `<div class="d-fx-note">${h(row.fxNote.td("per-item"))}: ${h(row.fxNote.cur)} ${fmt(row.fxNote.per)} `
+ `(${(+row.fxNote.rate).toFixed(5)} ${h(row.fxNote.cur)} = 1 ${h(iCur)})</div>`
: "";
const qStr = row.qty % 1 === 0 ? row.qty : fmt(row.qty);
return `<tr>
<td>${qStr}</td><td>${h(row.uomLbl)}</td>
<td>${h(row.desc)}${fxLine}</td>
<td class="r">${fmt(row.price)}</td>
<td class="r b">${fmt(row.tot)}</td>
</tr>`;
}).join("") || `<tr><td colspan="5" style="text-align:center;color:#9ca3af;padding:20px"></td></tr>`;
const showBank = hasBank && !hidePaymentOut;
return `
<div class="d-4hdr">
<div class="d-tl d-sender">
${sName ? `<div class="name">${h(sName)}</div>` : ""}
${sAddr.map(a=>`<p>${h(a)}</p>`).join("")}
${sCntry ? `<p>${h(sCntry)}</p>` : ""}
<div class="bt-meta">
${sPh ? `<span><strong>${h(td("sender-phone"))}:</strong> ${h(sPh)}</span>` : ""}
${sEm ? `<span><strong>${h(td("sender-email"))}:</strong> ${h(sEm)}</span>` : ""}
${sTax ? `<span><strong>${h(td("vat-id"))}:</strong> ${h(sTax)}</span>` : ""}
</div>
</div>
<div class="d-tr d-inv-meta">
<h1>${h(td("invoice"))}</h1>
<table class="d-meta">
${iNo ? `<tr><td class="ml">${h(td("invoice-no"))}</td><td class="mv">${h(iNo)}</td></tr>` : ""}
${iDate ? `<tr><td class="ml">${h(td("invoice-date"))}</td><td class="mv">${h(fmtDate(iDate))}</td></tr>` : ""}
${pCode ? `<tr><td class="ml">${h(td("project-code"))}</td><td class="mv">${h(pCode)}</td></tr>` : ""}
${iCur ? `<tr><td class="ml">${h(td("invoice-currency"))}</td><td class="mv">${h(iCur)}</td></tr>` : ""}
</table>
</div>
<div class="d-bl d-bill">
<div class="bt-lbl">${h(td("charge-to"))}</div>
${ctName ? `<div class="bt-name">${h(ctName)}</div>` : ""}
${ctAddr.map(a=>`<p>${h(a)}</p>`).join("")}
${ctCntry ? `<p>${h(ctCntry)}</p>` : ""}
<div class="bt-meta">
${ctPh ? `<span><strong>${h(td("charge-to-phone"))}:</strong> ${h(ctPh)}</span>` : ""}
${ctEm ? `<span><strong>${h(td("charge-to-email"))}:</strong> ${h(ctEm)}</span>` : ""}
${ctVat ? `<span><strong>${h(td("vat-id"))}:</strong> ${h(ctVat)}</span>` : ""}
${ctReg ? `<span><strong>${h(td("registration-no"))}:</strong> ${h(ctReg)}</span>` : ""}
</div>
</div>
<div class="d-br d-pay-hdr">
<div class="ph-lbl">${h(td("payment"))}</div>
${pTerm > 0 ? `<div class="ph-terms">${h(td("payment-terms"))}: <strong>${pTerm}</strong> ${h(td("payment-days"))}${pPayBy ? ` &mdash; ${h(td("pay-by"))}: <strong>${h(pPayBy)}</strong>` : ""}</div>` : ""}
${showBank ? `<div class="ph-grid">
${pAcct ? `<span class="pl">${h(td("account-holder"))}</span><span class="pv">${h(pAcct)}</span>` : ""}
${pIban ? `<span class="pl">${h(td("account-no"))}</span><span class="pv">${h(pIban)}</span>` : ""}
${pBic ? `<span class="pl">${h(td("bank-bic"))}</span><span class="pv">${h(pBic)}</span>` : ""}
${pBadr1 || pBadr2 ? `<span class="pl">${h(td("bank-address"))}</span><span class="pv">${[pBadr1,pBadr2].filter(Boolean).map(l=>h(l)).join("<br>")}</span>` : ""}
</div>
${pRef ? `<div class="ph-ref">${h(td("payment-ref"))}: <strong>${h(pRef)}</strong></div>` : ""}` : ""}
</div>
</div>
<table class="d-lines">
<thead><tr>
<th style="width:50px">${h(td("qty"))}</th>
<th style="width:70px">${h(td("uom"))}</th>
<th>${h(td("description"))}</th>
<th class="r" style="width:100px">${h(td("price"))}</th>
<th class="r" style="width:115px">${h(td("line-total"))}</th>
</tr></thead>
<tbody>${linesHTML}</tbody>
</table>
<table class="d-tots">
<tr class="sub">
<td class="sp"></td>
<td class="tl">${h(td("subtotal"))}</td>
<td class="tv">${fmt(sub)}</td>
</tr>
${taxes.map(tx => `<tr><td class="sp"></td><td class="tl">${h(tx.lineLabel)}</td><td class="tv">${fmt(tx.amt)}</td></tr>`).join("")}
${paid > 0 ? `<tr><td class="sp"></td><td class="tl">${h(td("paid"))}</td><td class="tv">&minus;${fmt(paid)}</td></tr>` : ""}
<tr class="fin">
<td class="sp"></td>
<td class="tl">${h(td("to-pay"))}</td>
<td class="tv">${fmt(toPay)}</td>
</tr>
</table>
`;
}
// ── Build PDF with jsPDF + Helvetica ─────────────────────────────────────────
function buildPDF() {
if (!window.jspdf) { alert("PDF library not loaded — check your internet connection."); return; }