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:
+1
-15
@@ -70,7 +70,6 @@ function renderTable() {
|
|||||||
|
|
||||||
tbody.innerHTML = checks.map(renderRow).join('');
|
tbody.innerHTML = checks.map(renderRow).join('');
|
||||||
updateSortIndicators();
|
updateSortIndicators();
|
||||||
updateCheckboxStates();
|
|
||||||
|
|
||||||
// Attach row-level event listeners
|
// Attach row-level event listeners
|
||||||
tbody.querySelectorAll('input[type="checkbox"]').forEach(cb => {
|
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() {
|
function refreshPdfButton() {
|
||||||
const n = state.selected.size;
|
const n = state.selected.size;
|
||||||
@@ -178,16 +169,11 @@ function refreshPdfButton() {
|
|||||||
function onCheckboxChange(cb) {
|
function onCheckboxChange(cb) {
|
||||||
const id = parseInt(cb.dataset.id, 10);
|
const id = parseInt(cb.dataset.id, 10);
|
||||||
if (cb.checked) {
|
if (cb.checked) {
|
||||||
if (state.selected.size >= 3) {
|
|
||||||
cb.checked = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.selected.add(id);
|
state.selected.add(id);
|
||||||
} else {
|
} else {
|
||||||
state.selected.delete(id);
|
state.selected.delete(id);
|
||||||
}
|
}
|
||||||
refreshPdfButton();
|
refreshPdfButton();
|
||||||
updateCheckboxStates();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Slide-in panel ───────────────────────────────────────────────────────────
|
// ── Slide-in panel ───────────────────────────────────────────────────────────
|
||||||
@@ -298,7 +284,7 @@ async function deleteCheck(id) {
|
|||||||
|
|
||||||
async function generatePdf() {
|
async function generatePdf() {
|
||||||
const ids = [...state.selected];
|
const ids = [...state.selected];
|
||||||
if (ids.length === 0 || ids.length > 3) return;
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
const btn = document.getElementById('btn-generate-pdf');
|
const btn = document.getElementById('btn-generate-pdf');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|||||||
+2
-2
@@ -17,8 +17,8 @@ const { generateCheckPdf } = require('../services/pdfService');
|
|||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
const { checkIds } = req.body;
|
const { checkIds } = req.body;
|
||||||
|
|
||||||
if (!Array.isArray(checkIds) || checkIds.length === 0 || checkIds.length > 3) {
|
if (!Array.isArray(checkIds) || checkIds.length === 0) {
|
||||||
return res.status(400).json({ error: 'checkIds must be an array of 1–3 IDs' });
|
return res.status(400).json({ error: 'checkIds must be a non-empty array' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch account
|
// Fetch account
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ function formatMicrLine(routingNo, accountNo, checkNo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main export: generates a PDF buffer for 1–3 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 {Object} account - Account row from database
|
||||||
* @param {Array} checks - Array of 1–3 check rows from database
|
* @param {Array} checks - Array of 1–3 check rows from database
|
||||||
@@ -128,15 +128,18 @@ function generateCheckPdf(account, checks, fields) {
|
|||||||
const hasUsableLogo = !!(logoField && account.logo_data);
|
const hasUsableLogo = !!(logoField && account.logo_data);
|
||||||
const COMPANY_FIELDS = new Set(['Company Name', 'Company Name2', 'Company Name3', 'Company Name4']);
|
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;
|
|
||||||
|
|
||||||
// Offset adjustments from account calibration
|
|
||||||
const offX = (account.offset_right - account.offset_left);
|
const offX = (account.offset_right - account.offset_left);
|
||||||
const offY = (account.offset_down - account.offset_up);
|
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)
|
// Helper: convert inches (relative to slot) to PDF points (absolute page)
|
||||||
const pt = (xIn, yIn) => ({
|
const pt = (xIn, yIn) => ({
|
||||||
x: (xIn + offX) * POINTS_PER_INCH,
|
x: (xIn + offX) * POINTS_PER_INCH,
|
||||||
@@ -220,7 +223,8 @@ function generateCheckPdf(account, checks, fields) {
|
|||||||
.text(micrLine, micrPos.x, micrPos.y, { lineBreak: false });
|
.text(micrLine, micrPos.x, micrPos.y, { lineBreak: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // end slot loop
|
||||||
|
} // end page loop
|
||||||
|
|
||||||
doc.end();
|
doc.end();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user