feat: add order invoice component#282
Conversation
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds a new ChangesOrder PDF feature
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant generateInvoicePDF
participant OrderPdf
participant ReactPDF as "`@react-pdf/renderer`"
participant Browser
Caller->>generateInvoicePDF: generateInvoicePDF(order, summit, opts)
generateInvoicePDF->>OrderPdf: render(order, summit, logoSrc, fontFamily)
generateInvoicePDF->>ReactPDF: pdf(OrderPdf).toBlob()
ReactPDF-->>generateInvoicePDF: blob
generateInvoicePDF->>Browser: createObjectURL(blob)
generateInvoicePDF->>Browser: trigger anchor download
generateInvoicePDF->>Browser: revokeObjectURL(url)
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/__tests__/order-pdf-.test.js`:
- Line 16: The test import is wrong in order-pdf-.test.js: it only brings in
buildOrderPdfRows and OrderPdf from the components barrel, but the suite
actually calls buildRows, and importing via the barrel also pulls in react-dnd
through SortableTable. Update the test to import the order-pdf module directly
and use the matching exported helper name (or import the alias used by the
tests) so buildRows is defined and the react-dnd dependency chain is avoided.
In `@src/components/order-pdf/index.js`:
- Around line 82-101: The item filtering in the order PDF is inconsistent with
the displayed default quantity: the current filter in the item iteration drops
rows when quantity is missing, but the row rendering in the same block (`qty` in
the `form.items` processing) treats missing quantity as 1. Update the
`form.items` filter logic so optional/missing quantities are still included when
they should render as default quantity 1, and keep the balance calculation and
PDF rows aligned with the `rows.push` item mapping.
- Around line 804-805: The previewPDF flow is creating a blob URL and opening it
in a new tab without releasing it afterward, which can leak memory on repeated
previews. Update the URL handling around the object URL creation in previewPDF
so the URL is revoked after the new window/tab has been opened, while keeping
the current open-in-new-tab behavior intact.
- Around line 32-33: The formatDate helper currently calls .format()
unconditionally on the result of epochToMomentTimeZone, which can be falsy for
missing or 0 epochs and crash invoice rendering. Update formatDate to guard the
value returned by epochToMomentTimeZone before formatting, and return a safe
fallback when no valid moment-like value exists. Keep the fix localized to
formatDate so callers of the order PDF rendering path continue working without
changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b0cf3870-2447-4bcc-95d2-77eafc9856b4
📒 Files selected for processing (5)
package.jsonsrc/components/__tests__/order-pdf-.test.jssrc/components/index.jssrc/components/order-pdf/index.jswebpack.common.js
| (form.items || []) | ||
| .filter((item) => item.quantity) | ||
| .forEach((item) => { | ||
| // Cancelled is per-item | ||
| const cancelled = !!item.canceled_by_id; | ||
| const cancelledBy = cancelled | ||
| ? `Cancelled by ${item.canceled_by_full_name} on ${formatDate(item.canceled_at, summit.time_zone_id, "YYYY/MM/DD HH:mm")}` | ||
| : ""; | ||
|
|
||
| // Cancelled items still accumulate | ||
| balanceCents += item.amount; | ||
|
|
||
| rows.push({ | ||
| rowKey: `item-${item.line_id ?? item.id}`, | ||
| type: "item", | ||
| // Table shows form.code per item row (columnKey: "formCode", value: form.code) | ||
| code: String(form.code || ""), | ||
| description: String(item.type?.name || item.title || ""), | ||
| addon: String(form.add_on?.name || ""), | ||
| qty: String(item.quantity ?? 1), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep the quantity filter consistent with the default quantity.
Line 83 drops items with missing quantity, but Line 101 renders missing quantity as 1. If quantity is optional, those invoice rows disappear from the balance and PDF.
Proposed fix
- .filter((item) => item.quantity)
+ .filter((item) => (item.quantity ?? 1) > 0)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| (form.items || []) | |
| .filter((item) => item.quantity) | |
| .forEach((item) => { | |
| // Cancelled is per-item | |
| const cancelled = !!item.canceled_by_id; | |
| const cancelledBy = cancelled | |
| ? `Cancelled by ${item.canceled_by_full_name} on ${formatDate(item.canceled_at, summit.time_zone_id, "YYYY/MM/DD HH:mm")}` | |
| : ""; | |
| // Cancelled items still accumulate | |
| balanceCents += item.amount; | |
| rows.push({ | |
| rowKey: `item-${item.line_id ?? item.id}`, | |
| type: "item", | |
| // Table shows form.code per item row (columnKey: "formCode", value: form.code) | |
| code: String(form.code || ""), | |
| description: String(item.type?.name || item.title || ""), | |
| addon: String(form.add_on?.name || ""), | |
| qty: String(item.quantity ?? 1), | |
| (form.items || []) | |
| .filter((item) => (item.quantity ?? 1) > 0) | |
| .forEach((item) => { | |
| // Cancelled is per-item | |
| const cancelled = !!item.canceled_by_id; | |
| const cancelledBy = cancelled | |
| ? `Cancelled by ${item.canceled_by_full_name} on ${formatDate(item.canceled_at, summit.time_zone_id, "YYYY/MM/DD HH:mm")}` | |
| : ""; | |
| // Cancelled items still accumulate | |
| balanceCents += item.amount; | |
| rows.push({ | |
| rowKey: `item-${item.line_id ?? item.id}`, | |
| type: "item", | |
| // Table shows form.code per item row (columnKey: "formCode", value: form.code) | |
| code: String(form.code || ""), | |
| description: String(item.type?.name || item.title || ""), | |
| addon: String(form.add_on?.name || ""), | |
| qty: String(item.quantity ?? 1), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/order-pdf/index.js` around lines 82 - 101, The item filtering
in the order PDF is inconsistent with the displayed default quantity: the
current filter in the item iteration drops rows when quantity is missing, but
the row rendering in the same block (`qty` in the `form.items` processing)
treats missing quantity as 1. Update the `form.items` filter logic so
optional/missing quantities are still included when they should render as
default quantity 1, and keep the balance calculation and PDF rows aligned with
the `rows.push` item mapping.
| const url = URL.createObjectURL(blob); | ||
| window.open(url, "_blank"); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Revoke preview blob URLs after opening.
previewPDF creates an object URL but never releases it, so repeated previews can leak browser memory.
Proposed fix
const url = URL.createObjectURL(blob);
- window.open(url, "_blank");
+ window.open(url, "_blank");
+ setTimeout(() => URL.revokeObjectURL(url), 0);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const url = URL.createObjectURL(blob); | |
| window.open(url, "_blank"); | |
| const url = URL.createObjectURL(blob); | |
| window.open(url, "_blank"); | |
| setTimeout(() => URL.revokeObjectURL(url), 0); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/order-pdf/index.js` around lines 804 - 805, The previewPDF
flow is creating a blob URL and opening it in a new tab without releasing it
afterward, which can leak memory on repeated previews. Update the URL handling
around the object URL creation in previewPDF so the URL is revoked after the new
window/tab has been opened, while keeping the current open-in-new-tab behavior
intact.
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds a reusable “order invoice PDF” component to uicore, enabling apps that consume this library to generate and preview an order invoice PDF (with line items, fees, discounts, payments/refunds, notes, reconciliation, and amount due).
Changes:
- Introduces
OrderPdf+ helpers (buildRows,generateInvoicePDF,previewPDF) undersrc/components/order-invoice-pdf/. - Exposes the new component/helpers via the central
src/components/index.jsexport surface and adds a webpack entrypoint. - Updates
@react-pdf/rendererto v4.x and adds unit tests for row-building and rendering.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
webpack.common.js |
Adds a new webpack entry for the invoice PDF component bundle. |
src/components/order-invoice-pdf/index.js |
Implements PDF layout, row building, and generate/preview helpers. |
src/components/order-invoice-pdf/__tests__/order-invoice-pdf.test.js |
Adds tests for row formatting and basic render behavior. |
src/components/index.js |
Exports the new invoice PDF APIs from the library’s main component index. |
package.json |
Bumps @react-pdf/renderer devDependency and broadens peer dependency to allow v4. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return [ | ||
| address.address_1, | ||
| address.address_2, | ||
| address.city, | ||
| address.state, | ||
| address.zip_code, | ||
| address.country | ||
| ] |
| export const previewPDF = async (order, summit, { logoSrc, fontFamily } = {}) => { | ||
| const blob = await pdf( | ||
| <OrderPdf | ||
| order={order} | ||
| summit={summit} | ||
| logoSrc={logoSrc} | ||
| fontFamily={fontFamily} | ||
| /> | ||
| ).toBlob(); | ||
| const url = URL.createObjectURL(blob); | ||
| window.open(url, "_blank"); | ||
| }; |
| const makeRenderOrder = (overrides = {}) => ({ | ||
| number: "ORD-2026-001", | ||
| total: 0, | ||
| purchased_date: "2026-01-15T10:00:00Z", | ||
| purchased_by_full_name: "Jane Doe", | ||
| client: { contact_name: "Jane Doe", company_name: "Acme Corp" }, |
| expect(text).toContain("Jane Doe"); | ||
| expect(text).toContain("OpenStack Summit 2026"); | ||
| expect(text).toContain("Main Hall"); | ||
| expect(text).toContain("123 Expo Blvd"); | ||
| }); |
| export {default as Panel} from './sections/panel' | ||
| export {default as SimpleLinkList} from './simple-link-list' | ||
| export {default as SummitDropdown} from './summit-dropdown' | ||
| export {OrderPdf, buildRows as buildOrderPdfRows, generateInvoicePDF, previewPDF} from './order-invoice-pdf' |
There was a problem hiding this comment.
this is wrong please do check
this export force to incluide a 3rd party deps even when is not used
| "@mui/material": "^6.4.3", | ||
| "@mui/x-date-pickers": "^7.26.0", | ||
| "@react-pdf/renderer": "^3.1.11", | ||
| "@react-pdf/renderer": "^3.1.11 || ^4.0.0", |
There was a problem hiding this comment.
@tomrndom this is wrong u should use the same version on both places
"@react-pdf/renderer": "^4.4.1",
| } | ||
| }); | ||
|
|
||
| const PdfTableRow = ({ row, styles, rowStyles }) => { |
There was a problem hiding this comment.
can you put each of the components and svgs in this file in its own file ? is very hard to read a 1000 line file
ref: https://app.clickup.com/t/9014802374/86baq9key
Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com
Summary by CodeRabbit
New Features
Tests