initial commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# v2 PDF Generation — Known Issues and Fixes
|
||||
|
||||
## jspdf 4.x Array-Based `doc.text()` Causes Text Overlap
|
||||
|
||||
**Symptom:** Text in the generated PDF overlaps heavily — job titles, descriptions, prices, and status flags render on top of each other.
|
||||
|
||||
**Root cause:** When you pass a string array to `doc.text(array, x, y)`, jspdf 4.x uses internal matrix-based line spacing that does NOT match manual `yPos += array.length * lineHeight` calculations. This causes yPos to desync from where jspdf actually rendered the text, cascading into overlap throughout the entire document.
|
||||
|
||||
**Fix:** NEVER pass arrays to `doc.text()`. Instead, iterate through each line and render individually:
|
||||
|
||||
```typescript
|
||||
// BROKEN (jspdf 4.x):
|
||||
const lines = doc.splitTextToSize(message, maxWidth);
|
||||
doc.text(lines, x, yPos); // jspdf uses its own spacing
|
||||
yPos += lines.length * 6 + 15; // does NOT match actual render position
|
||||
|
||||
// FIXED:
|
||||
const lines = doc.splitTextToSize(message, maxWidth);
|
||||
lines.forEach((line) => {
|
||||
doc.text(line, x, yPos);
|
||||
yPos += 6; // explicit control per line
|
||||
});
|
||||
yPos += 15;
|
||||
```
|
||||
|
||||
## Service Height Calculation Must Match Actual yPos Increments
|
||||
|
||||
The page-break estimator (`serviceHeight`) must use the EXACT same multipliers as the actual yPos advances. Mismatched values cause services to overflow the page edge without triggering a page break.
|
||||
|
||||
| Element | Actual yPos advance | Required in estimate |
|
||||
|---------|-------------------|---------------------|
|
||||
| Customer decision badge | `yPos += 5` | `serviceHeight += 5` |
|
||||
| Service name + price | `yPos += 6` | `serviceHeight += 6` |
|
||||
| Priority (label + reason) | `yPos += 6; yPos += 10` | `serviceHeight += 16` |
|
||||
| Recommendation | `yPos += 8` | `serviceHeight += 8` |
|
||||
| Explanation (per line) | `yPos += 5` then `yPos += 2` | `lines.length * 5 + 2` |
|
||||
| Parts flags (each) | `yPos += 8` (or 16 with extra line) | `8` or `16` |
|
||||
| Separator | `yPos += 6` | `serviceHeight += 6` |
|
||||
|
||||
## `boxBottomY` Stale After Summary Page Break
|
||||
|
||||
When the pricing summary box triggers a page break, `boxBottomY` must be updated to the new page's coordinates. Declare it with `let` (not `const`) and reassign in the page-break branch:
|
||||
|
||||
```typescript
|
||||
let boxBottomY = boxTopY + boxPadding + boxContentHeight + boxPadding;
|
||||
if (boxBottomY + 50 > PAGE_HEIGHT - 20) {
|
||||
doc.addPage();
|
||||
// ... re-render box ...
|
||||
boxBottomY = bby; // update for new page position
|
||||
}
|
||||
// ...
|
||||
yPos = boxBottomY + 10; // consistent regardless of page break
|
||||
```
|
||||
|
||||
## Customer Info Not Populating When Loading Saved Quote
|
||||
|
||||
**Symptom:** Services load from PocketBase but customer info fields remain empty.
|
||||
|
||||
**Causes and fixes:**
|
||||
|
||||
1. **`reset()` ordering:** `reset()` wipes all state including `customerInfo`. Must call `reset()` BEFORE `setCustomerInfo()`:
|
||||
```typescript
|
||||
reset();
|
||||
if (data.customerInfo) setCustomerInfo(data.customerInfo); // after reset
|
||||
```
|
||||
|
||||
2. **PocketBase JSON field type:** If the PocketBase collection stores `customerInfo` as a text field (not json), the retrieved value is a JSON string. Handle both cases:
|
||||
```typescript
|
||||
const ci = typeof data.customerInfo === 'string'
|
||||
? JSON.parse(data.customerInfo)
|
||||
: data.customerInfo;
|
||||
if (ci && typeof ci === 'object') setCustomerInfo(ci);
|
||||
```
|
||||
Apply the same defensive parsing to `services` and `discount` fields.
|
||||
Reference in New Issue
Block a user