diff --git a/public/index.html b/public/index.html
index 9deddd6..cc82062 100644
--- a/public/index.html
+++ b/public/index.html
@@ -886,7 +886,8 @@
-
+
+
diff --git a/public/js/app.js b/public/js/app.js
index 64da08b..5155505 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -2007,6 +2007,7 @@ async function init() {
document.getElementById('nudge-left').addEventListener('click', () => nudgeLayoutField(-1, 0));
document.getElementById('nudge-right').addEventListener('click', () => nudgeLayoutField( 1, 0));
document.getElementById('btn-layout-reset').addEventListener('click', resetLayoutToDefault);
+ document.getElementById('btn-layout-preview').addEventListener('click', previewLayoutPdf);
// Initial auth check → loads app if already signed in
const authed = await checkAuth();
@@ -2456,6 +2457,31 @@ async function saveLayoutField(f) {
}
}
+async function previewLayoutPdf() {
+ const btn = document.getElementById('btn-layout-preview');
+ const orig = btn.textContent;
+ btn.disabled = true;
+ btn.textContent = '…';
+ try {
+ const res = await fetch('/api/pdf/preview', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ account_id: state.activeAccountId }),
+ });
+ if (!res.ok) {
+ const err = await res.json().catch(() => ({ error: res.statusText }));
+ throw new Error(err.error || res.statusText);
+ }
+ const blob = await res.blob();
+ openPdfBlob(blob, 'layout-preview.pdf');
+ } catch (err) {
+ alert('Preview error: ' + err.message);
+ } finally {
+ btn.disabled = false;
+ btn.textContent = orig;
+ }
+}
+
async function resetLayoutToDefault() {
if (!confirm('Reset all layout fields to default positions? This cannot be undone.')) return;
try {
diff --git a/src/routes/pdf.js b/src/routes/pdf.js
index 36940b8..55fa46b 100644
--- a/src/routes/pdf.js
+++ b/src/routes/pdf.js
@@ -70,4 +70,55 @@ router.post('/', async (req, res) => {
}
});
+/**
+ * POST /api/pdf/preview
+ * Body: { account_id: X }
+ *
+ * Generates a layout preview PDF using dummy check data — no real checks touched.
+ * Shows all three slots filled with sample data so every visible field is visible.
+ */
+router.post('/preview', async (req, res) => {
+ const resolvedAccountId = parseInt(req.body.account_id, 10);
+ if (!resolvedAccountId) return res.status(400).json({ error: 'account_id required' });
+
+ const account = db.prepare('SELECT * FROM account WHERE id = ?').get(resolvedAccountId);
+ if (!account) return res.status(404).json({ error: 'Account not found.' });
+
+ const fields = db.prepare('SELECT * FROM layout_fields WHERE account_id = ?').all(resolvedAccountId);
+
+ const DUMMY_CHECK = {
+ id: 0,
+ check_no: 1001,
+ payee: 'Sample Payee Name',
+ amount: 1234.56,
+ check_date: new Date().toISOString().slice(0, 10),
+ memo: 'Sample Memo',
+ payee_address1: '123 Sample Street',
+ payee_address2: 'City, ST 12345',
+ payee_address3: null,
+ payee_address4: null,
+ printed: 0,
+ account_id: resolvedAccountId,
+ };
+
+ const checks = [
+ { ...DUMMY_CHECK, check_no: 1001 },
+ { ...DUMMY_CHECK, check_no: 1002 },
+ { ...DUMMY_CHECK, check_no: 1003 },
+ ];
+
+ try {
+ const pdfBuffer = await generateCheckPdf(account, checks, fields);
+ res.set({
+ 'Content-Type': 'application/pdf',
+ 'Content-Disposition': 'inline; filename="layout-preview.pdf"',
+ 'Content-Length': pdfBuffer.length,
+ });
+ res.send(pdfBuffer);
+ } catch (err) {
+ console.error('Preview PDF error:', err);
+ res.status(500).json({ error: 'Preview generation failed.' });
+ }
+});
+
module.exports = router;