This commit adds full quote PDF generation functionality with per-company letterhead branding: - Server-side PDF rendering using @react-pdf/renderer matching the sample quote layout - In-app modal PDF viewer with print, download, and new-tab controls - Letterhead service to fetch and apply company-specific branding (CNL/ISHERWOOD) - ZAR currency and date formatting utilities for South African quotes - Updated QuoteAttachment type to handle PocketBase's array-based file tokens - Fixed PocketBase attachment URL generation in quote email card - Removed deprecated `included_in_quote` field from QuoteItem and quotes service - Updated quote item creation test to validate draft item logic - Added comprehensive unit tests for PDF rendering and formatting helpers - Added dev scripts for debugging PDF and image rendering issues - Added sample generated PDF files for visual verification - Updated tsconfig to exclude development scripts directory - Added @react-pdf/renderer production dependency - Removed JEI as a valid letterhead option per requirements
104 lines
No EOL
4.9 KiB
TypeScript
104 lines
No EOL
4.9 KiB
TypeScript
/* eslint-disable */
|
|
// Sample PDF generators for visual verification. Logos are loaded as
|
|
// SVG data URIs from `public/letterhead/{cnl,isherwood}.svg` (see
|
|
// `loadLetterheadLogoDataUri` in src/lib/letterhead.ts). We use SVG
|
|
// instead of the original raster (PNG/JPEG) logos because @react-pdf
|
|
// v4.5.1 corrupts any byte ≥ 0x80 in raster image data; SVG is text
|
|
// and sidesteps that code path entirely.
|
|
import { renderQuotePdf } from "@/lib/pdf/render";
|
|
import { loadLetterheadLogoDataUri } from "@/lib/letterhead";
|
|
import type { Letterhead } from "@/lib/letterhead";
|
|
import type { Customer, Quote, QuoteItem } from "@/lib/types";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
async function main() {
|
|
const outDir = path.join("/Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/docs/example-quotes");
|
|
|
|
const baseItem = {
|
|
created: "2026-06-22T13:40:00Z",
|
|
updated: "2026-06-22T13:40:00Z",
|
|
collectionId: "pbc_quote_items",
|
|
collectionName: "quote_items",
|
|
quote_number: "Q009638",
|
|
quote_reference: "",
|
|
quantity: 1,
|
|
supplier: "",
|
|
cost_price: 0,
|
|
notes: "",
|
|
is_processed: true,
|
|
markup: 0,
|
|
quote_pdf_id: "",
|
|
user_edited: true,
|
|
};
|
|
|
|
const cnlLh: Letterhead = {
|
|
id: "lh1", collectionId: "pbc_1010367695", collectionName: "letterhead_settings",
|
|
created: "2026-06-22T20:31:27.165Z", updated: "2026-06-22T20:31:27.165Z",
|
|
display_name: "CNL Mining Supplies",
|
|
address: "47 Station Street\nCarletonville, Gauteng 2499",
|
|
phone: "+27 18 786 2897", email: "quotes@wfsales.co.za",
|
|
vat_number: "4840229449", registration_number: "2014/004024/07",
|
|
bank_name: "Standard Bank - Carletonville",
|
|
bank_account_number: "Current Account\n022196552",
|
|
bank_branch_code: "016141",
|
|
terms: "Terms and Conditions:\n1. This quotation is valid for 30 days from the date of issue.\n2. Prices are quoted excluding VAT unless otherwise stated.\n3. Lead time will be confirmed upon receipt of order.",
|
|
default_markup_percent: 30, footer_text: "", logo: "",
|
|
is_active: true, company: "CNL Mining Supplies (Pty) Ltd", accent_color: "#738224",
|
|
};
|
|
|
|
const isherwoodLh: Letterhead = {
|
|
...cnlLh,
|
|
display_name: "ISHERWOOD MINING SUPPLIES",
|
|
vat_number: "4590136331",
|
|
registration_number: "",
|
|
bank_account_number: "Current Account\n02 290 746 7",
|
|
accent_color: "#7ed957",
|
|
terms: "Terms and Conditions:\n1. This quotation is valid for 30 days from the date of issue.\n2. Prices are quoted excluding VAT unless otherwise stated.\n3. Lead time will be confirmed upon receipt of order.\n4. Isherwood Mining Supplies standard terms apply to all orders.",
|
|
};
|
|
|
|
const cust: Customer = {
|
|
id: "cust1", collectionId: "pbc_customers", collectionName: "customers",
|
|
created: "2026-06-22T00:00:00Z", updated: "2026-06-22T00:00:00Z",
|
|
customer: "Cash Sales", email: "james@microntec.co.za",
|
|
alternative_email: "", buyer_email: "", number: "0615829016",
|
|
company: "", dateadded: "", lastvisiteddate: "", rep: "",
|
|
coordinates: "", description: "",
|
|
};
|
|
|
|
const q: Quote = {
|
|
id: "q1", collectionId: "pbc_quotes", collectionName: "quotes",
|
|
created: "2026-06-22T13:40:00Z", updated: "2026-06-22T13:40:00Z",
|
|
quote_number: "Q009638", quote_reference: "", external_number: "",
|
|
rfq_number: "", markup: 0, customer: "cust1", company: "", claimed_by: "",
|
|
description: "", status: "new", approved_by: "", notes: "",
|
|
email_sender: "", email_subject: "", email_body: "", has_attachments: false,
|
|
company_letterhead: "CNL", email_delivered: false, email_delivered_at: "",
|
|
email_received_at: "", email_opened: false, email_opened_at: "",
|
|
email_tracking_id: "", has_new_reply: false, last_reply_at: "",
|
|
email_failed_at: "", pdf_generated_at: "", email_message_id: "",
|
|
};
|
|
|
|
const items: QuoteItem[] = [
|
|
{ ...baseItem, id: "qi1", quote: "q1", description: "JEICS30 SLUGGER M2-AL HSS", selling_price: 648.7, included_in_quote: true } as unknown as QuoteItem,
|
|
{ ...baseItem, id: "qi2", quote: "q1", description: "JEIPILOT 1", selling_price: 88.36, included_in_quote: true } as unknown as QuoteItem,
|
|
];
|
|
|
|
const cnlLogo = await loadLetterheadLogoDataUri("CNL");
|
|
const isherwoodLogo = await loadLetterheadLogoDataUri("ISHERWOOD");
|
|
if (!cnlLogo) throw new Error("CNL logo missing from public/letterhead/cnl.svg");
|
|
if (!isherwoodLogo) throw new Error("ISHERWOOD logo missing from public/letterhead/isherwood.svg");
|
|
|
|
const cnl = await renderQuotePdf({ quote: q, items, customer: cust, letterhead: cnlLh, logoDataUri: cnlLogo });
|
|
fs.writeFileSync(path.join(outDir, "sample-cnl.pdf"), cnl);
|
|
console.log(`CNL: ${cnl.length} bytes → sample-cnl.pdf`);
|
|
|
|
const ish = await renderQuotePdf({ quote: q, items, customer: cust, letterhead: isherwoodLh, logoDataUri: isherwoodLogo });
|
|
fs.writeFileSync(path.join(outDir, "sample-isherwood.pdf"), ish);
|
|
console.log(`Isherwood: ${ish.length} bytes → sample-isherwood.pdf`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("FAIL:", err);
|
|
process.exit(1);
|
|
}); |