Support multi-page PDFs for more than 3 selected checks

- pdfService: batch checks into groups of 3, add doc.addPage() between groups
- pdf route: remove 1-3 limit, accept any number of check IDs
- Frontend: remove 3-check selection cap; any number of checks can be selected
This commit is contained in:
2026-03-12 21:16:37 -06:00
parent 667ec146cc
commit 5f9cc16ea5
3 changed files with 16 additions and 26 deletions
+1 -15
View File
@@ -70,7 +70,6 @@ function renderTable() {
tbody.innerHTML = checks.map(renderRow).join('');
updateSortIndicators();
updateCheckboxStates();
// Attach row-level event listeners
tbody.querySelectorAll('input[type="checkbox"]').forEach(cb => {
@@ -157,14 +156,6 @@ function updateSortIndicators() {
});
}
function updateCheckboxStates() {
document.querySelectorAll('#checks-tbody input[type="checkbox"]').forEach(cb => {
const id = parseInt(cb.dataset.id, 10);
if (!state.selected.has(id)) {
cb.disabled = state.selected.size >= 3;
}
});
}
function refreshPdfButton() {
const n = state.selected.size;
@@ -178,16 +169,11 @@ function refreshPdfButton() {
function onCheckboxChange(cb) {
const id = parseInt(cb.dataset.id, 10);
if (cb.checked) {
if (state.selected.size >= 3) {
cb.checked = false;
return;
}
state.selected.add(id);
} else {
state.selected.delete(id);
}
refreshPdfButton();
updateCheckboxStates();
}
// ── Slide-in panel ───────────────────────────────────────────────────────────
@@ -298,7 +284,7 @@ async function deleteCheck(id) {
async function generatePdf() {
const ids = [...state.selected];
if (ids.length === 0 || ids.length > 3) return;
if (ids.length === 0) return;
const btn = document.getElementById('btn-generate-pdf');
btn.disabled = true;
+2 -2
View File
@@ -17,8 +17,8 @@ const { generateCheckPdf } = require('../services/pdfService');
router.post('/', async (req, res) => {
const { checkIds } = req.body;
if (!Array.isArray(checkIds) || checkIds.length === 0 || checkIds.length > 3) {
return res.status(400).json({ error: 'checkIds must be an array of 13 IDs' });
if (!Array.isArray(checkIds) || checkIds.length === 0) {
return res.status(400).json({ error: 'checkIds must be a non-empty array' });
}
// Fetch account
+13 -9
View File
@@ -88,7 +88,7 @@ function formatMicrLine(routingNo, accountNo, checkNo) {
}
/**
* Main export: generates a PDF buffer for 13 checks.
* Main export: generates a multi-page PDF buffer for any number of checks (3 per page).
*
* @param {Object} account - Account row from database
* @param {Array} checks - Array of 13 check rows from database
@@ -128,14 +128,17 @@ function generateCheckPdf(account, checks, fields) {
const hasUsableLogo = !!(logoField && account.logo_data);
const COMPANY_FIELDS = new Set(['Company Name', 'Company Name2', 'Company Name3', 'Company Name4']);
// We always render 3 slots; empty slots get a blank placeholder
for (let slot = 0; slot < 3; slot++) {
const check = checks[slot] || null;
const slotOriginY = slot * SLOT_HEIGHT_IN;
const offX = (account.offset_right - account.offset_left);
const offY = (account.offset_down - account.offset_up);
// Offset adjustments from account calibration
const offX = (account.offset_right - account.offset_left);
const offY = (account.offset_down - account.offset_up);
// Render checks in pages of 3; add a new page for each additional group
const pages = Math.ceil(checks.length / 3);
for (let page = 0; page < pages; page++) {
if (page > 0) doc.addPage();
for (let slot = 0; slot < 3; slot++) {
const check = checks[page * 3 + slot] || null;
const slotOriginY = slot * SLOT_HEIGHT_IN;
// Helper: convert inches (relative to slot) to PDF points (absolute page)
const pt = (xIn, yIn) => ({
@@ -220,7 +223,8 @@ function generateCheckPdf(account, checks, fields) {
.text(micrLine, micrPos.x, micrPos.y, { lineBreak: false });
}
}
} // end slot loop
} // end page loop
doc.end();
});