feat: implement Export Invoice to JSON/CSV#138
Conversation
WalkthroughAdds client-side CSV and JSON invoice export functionality, helper utilities and a hook, and an Export menu (PDF/CSV/JSON) in Sent and Received invoice pages; invoice total formatting updated to use a shared helper. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as Invoice Page UI
participant Menu as Export Menu
participant Hook as useInvoiceExport
participant Util as download CSV/JSON
participant Browser as Browser Download
User->>UI: Click "Export" or "Export Selected"
UI->>Menu: Show options (PDF / CSV / JSON / CSV Batch / JSON Batch)
User->>Menu: Select CSV or JSON
Menu->>Hook: call handleExportCSV / handleExportJSON
Hook->>Util: downloadInvoiceCSV/JSON(invoice(s), fee)
Util->>Util: resolveChainMeta / normalize items / serialize
Util->>Browser: create Blob -> createObjectURL -> anchor.click()
Browser->>Hook: download starts/completes
Hook->>UI: show success or error toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 the current code and only fix it if needed.
Inline comments:
In `@frontend/src/page/ReceivedInvoice.jsx`:
- Around line 900-928: The CSV/JSON export handlers (handleExportCSV and
handleExportJSON) currently always use drawerState.selectedInvoice so they only
export the open-drawer invoice; update these handlers to derive the export
target(s) from the page-level selection state (selectedInvoices and any current
export-mode like all/selected/filtered) and fall back to
drawerState.selectedInvoice only if selectedInvoices is empty and the user
expects a single-item export; call the exporters
(downloadInvoiceCSV/downloadInvoiceJSON) with the correct array or single-item
payload as they expect, or adapt the exporter invocation to accept multiple
invoices, and ensure the menu action uses that computed selection so
“all/selected/filtered” export behavior is honored.
In `@frontend/src/page/SentInvoice.jsx`:
- Around line 403-431: Replace hard-coded English strings in handleExportCSV and
handleExportJSON (including the "No invoice selected" validation, "CSV
downloaded successfully!", "JSON downloaded successfully!", and the failure
messages) with i18n lookups; add corresponding keys to the app's i18n resource
file and import/use the project's translation helper (e.g., t or useTranslation)
in SentInvoice.jsx, then call downloadInvoiceCSV/downloadInvoiceJSON as before
and pass/consume translated strings for toast.success and toast.error; also
update the duplicated strings in ReceivedInvoice.jsx to use the same i18n keys
so both components share the centralized translations and keep handleExportClose
and drawerState.selectedInvoice logic unchanged.
In `@frontend/src/utils/generateInvoiceCSV.js`:
- Around line 3-10: The CSV generation is using the locale-specific
toDisplayDate(), which can shift date-only values; update generateInvoiceCSV.js
to use toISODate() instead of toDisplayDate() when writing dates into the CSV
(keep toDisplayDate() for UI/PDF only), specifically replace calls to
toDisplayDate(...) in the CSV export flow with toISODate(...) so exported CSV
contains ISO-formatted, timezone-safe dates (also change the other occurrences
noted around the second date usage).
In `@frontend/src/utils/invoiceExportHelpers.js`:
- Around line 103-107: formatInvoiceTotal currently selects chain info using
only invoice.paymentToken.chainId then fallbackChainId, which lets the viewer
wallet chain override the invoice chain; update the chain resolution to mirror
resolveChainMeta order by checking invoice.paymentToken?.chainId first, then
invoice.chainId, then fallbackChainId (i.e.
getWagmiChainInfo(invoice.paymentToken?.chainId || invoice.chainId ||
fallbackChainId)) so the nativeSymbol/fee currency is derived from the invoice
metadata rather than the viewer's chain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 27ad4f54-00da-42ed-bf1b-dbdafb144d82
📒 Files selected for processing (5)
frontend/src/page/ReceivedInvoice.jsxfrontend/src/page/SentInvoice.jsxfrontend/src/utils/generateInvoiceCSV.jsfrontend/src/utils/generateInvoiceJSON.jsfrontend/src/utils/invoiceExportHelpers.js
|
CodeRabbit suggested externalizing the export-related UI strings (button label, menu items, validation text, and toast messages) into the project's i18n resources instead of keeping them hard-coded in the component. |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/utils/generateInvoiceCSV.js`:
- Around line 162-167: The function downloadInvoiceCSV currently converts a
null/undefined invoiceOrInvoices into [null], causing generateCSVContent to
throw; update downloadInvoiceCSV to defensively handle falsy input by returning
early when invoiceOrInvoices is null/undefined or by normalizing into an array
and filtering out null/undefined entries before proceeding; locate the
downloadInvoiceCSV function and ensure the invoices array is built using a
truthy check (and/or Array.isArray) and filtered (e.g., invoices =
invoices.filter(Boolean)) so generateCSVContent only receives valid invoice
objects.
- Around line 189-190: Replace the duplicated padStart logic in
generateInvoiceCSV.js by importing and using the existing helper
formatInvoiceId; specifically add an import for formatInvoiceId and change the
link.download assignment that currently uses
invoices[0].id.toString().padStart(6, "0") to use
formatInvoiceId(invoices[0].id) (so the filename becomes
`invoice-${formatInvoiceId(invoices[0].id)}.csv`), keeping the rest of the logic
unchanged.
In `@frontend/src/utils/generateInvoiceJSON.js`:
- Around line 54-59: downloadInvoiceJSON currently converts a null/undefined
invoiceOrInvoices into [null]/[undefined] which bypasses the empty-length check
and later causes generateJSONContent to throw; add an early guard at the start
of downloadInvoiceJSON to return (or no-op) if invoiceOrInvoices is null or
undefined, and when converting to an array filter out any null/undefined entries
before proceeding so generateJSONContent always receives valid invoice objects.
- Around line 76-77: The download filename is recreating ID padding logic
instead of reusing formatInvoiceId; update the branch that sets link.download
(when invoices.length === 1) to call the existing
formatInvoiceId(invoices[0].id) and use its result in the template string (e.g.,
`invoice-${formatInvoiceId(...)}.json`) so the ID formatting is centralized in
formatInvoiceId and no padStart duplication remains.
In `@frontend/src/utils/invoiceExportHelpers.js`:
- Around line 59-66: normalizeItem currently hardcodes fallbacks ("N/A", "0",
"0%") which may conflict with buildParty's configurable fallback; update
normalizeItem (export const normalizeItem) to accept an optional fallback
parameter (or an options object) and use that for description/discount/tax
values, defaulting to sensible JSON-safe values (e.g., null or 0) when no
fallback is provided, so callers (CSV exporter) can pass "N/A" while JSON paths
get null/0; update all call sites to pass the appropriate fallback where needed.
- Around line 38-39: The function resolveStatus currently returns hard-coded
user-visible strings ("CANCELLED", "PAID", "UNPAID"); change it to return i18n
keys or localized strings from the app's translation resources instead. Replace
direct literals in resolveStatus with lookups to the i18n module used in the
codebase (e.g., t('invoice.status.cancelled'), t('invoice.status.paid'),
t('invoice.status.unpaid')) or return the keys and have the caller translate,
and update any imports to include the i18n helper so all user-facing text is
sourced from resource files rather than hardcoded values.
- Around line 9-16: In resolveChainMeta, replace the use of the || operator for
chainId fallback with the nullish coalescing operator (??) to match
formatInvoiceTotal and avoid treating valid falsy values like 0 as absent;
update the expression in resolveChainMeta (which currently computes const
chainId = invoice.paymentToken?.chainId || invoice.chainId) to use ?? so it
reliably falls back to invoice.chainId only when paymentToken?.chainId is null
or undefined.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: be4807d4-a4d1-46b8-8736-129cc26d3a0e
📒 Files selected for processing (3)
frontend/src/utils/generateInvoiceCSV.jsfrontend/src/utils/generateInvoiceJSON.jsfrontend/src/utils/invoiceExportHelpers.js
12d39a9 to
0adffeb
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/hooks/useInvoiceExport.js`:
- Around line 1-5: This module uses React hooks and browser APIs (useCallback,
toast, downloadInvoiceCSV, downloadInvoiceJSON) but lacks the Next.js client
directive; add the "use client" directive as the very first line of
frontend/src/hooks/useInvoiceExport.js so the file is treated as a client
component and retains usage of useCallback and toast in the browser environment.
- Around line 12-44: Import and use the existing useInvoiceExport hook in
ReceivedInvoice.jsx and SentInvoice.jsx instead of duplicating
handleExportCSV/handleExportJSON logic: remove the local handleExportCSV and
handleExportJSON functions that call downloadInvoiceCSV/downloadInvoiceJSON and
toast, import useInvoiceExport, and call const { handleExportCSV,
handleExportJSON } = useInvoiceExport(drawerState.selectedInvoice, fee,
handleExportClose) (or the equivalent selectedInvoice/fee/onExportDone values)
so onExportDone is passed through; ensure all references to the old local
handlers now use the hook-provided handlers.
In `@frontend/src/page/ReceivedInvoice.jsx`:
- Line 2: Remove the unused import getWagmiChainInfo from this file: it’s not
referenced directly in ReceivedInvoice.jsx (formatInvoiceTotal already uses it
internally), so delete the line importing getWagmiChainInfo to avoid an unused
import warning and keep the file clean.
In `@frontend/src/page/SentInvoice.jsx`:
- Line 50: Remove the unused import getWagmiChainInfo from
frontend/src/page/SentInvoice.jsx—it's not referenced in this file and chain
lookup is already handled by the formatInvoiceTotal helper; update the import
list to eliminate getWagmiChainInfo and ensure there are no remaining references
to that symbol in SentInvoice.jsx.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3a906ea0-cbb2-419c-aa7b-2bf6c4681288
📒 Files selected for processing (6)
frontend/src/hooks/useInvoiceExport.jsfrontend/src/page/ReceivedInvoice.jsxfrontend/src/page/SentInvoice.jsxfrontend/src/utils/generateInvoiceCSV.jsfrontend/src/utils/generateInvoiceJSON.jsfrontend/src/utils/invoiceExportHelpers.js
Add CSV and JSON export functionality for individual invoices alongside the existing PDF download on Sent Invoices and Received Invoices pages. Changes: - Add generateInvoiceCSV.js with CSV formula injection guard and UTF-8 BOM - Add generateInvoiceJSON.js with structured invoice objects and ISO dates - Add invoiceExportHelpers.js with shared utilities (resolveChainMeta, formatNetworkFee, formatInvoiceId, resolveStatus, buildParty, normalizeItem, toISODate, toDisplayDate, formatInvoiceTotal) - Add useInvoiceExport.js hook to deduplicate export handlers across pages - Replace single 'Download Invoice' button with export dropdown (PDF/CSV/JSON) in both SentInvoice and ReceivedInvoice drawers - Detect native payments by token address (ZeroAddress) instead of symbol comparison - Use chain-aware native currency resolution with nullish coalescing (??) for consistent chainId fallback - Add null/undefined input guards in download functions - Reuse formatInvoiceId for download filenames (DRY) - Clear export menu anchorEl when drawer closes to prevent stale DOM references Closes StabilityNexus#136
0adffeb to
0dbf1f6
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (6)
frontend/src/utils/generateInvoiceCSV.js (1)
165-169:⚠️ Potential issue | 🟡 MinorFilter falsy invoices before generating the CSV.
The current guard only skips the all-empty case. A mixed payload like
[invoice, null]still reachesgenerateCSVContent(null, fee)and aborts the whole download.🛡️ Proposed fix
- const invoices = Array.isArray(invoiceOrInvoices) - ? invoiceOrInvoices - : [invoiceOrInvoices]; - - if (invoices.length === 0 || invoices.every((inv) => !inv)) return; + const invoices = ( + Array.isArray(invoiceOrInvoices) + ? invoiceOrInvoices + : [invoiceOrInvoices] + ).filter(Boolean); + + if (invoices.length === 0) return;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/generateInvoiceCSV.js` around lines 165 - 169, The invoices array may contain falsy entries and you must filter them out before proceeding so mixed payloads (e.g., [invoice, null]) don’t call generateCSVContent(null,...). In the block where invoiceOrInvoices is normalized to invoices (in generateInvoiceCSV.js), apply a filter to remove falsy values (e.g., invoices = invoices.filter(Boolean)) and then keep the existing empty-length guard (if invoices.length === 0 return) so subsequent calls to generateCSVContent and any mapping over invoices only see valid invoice objects.frontend/src/page/ReceivedInvoice.jsx (2)
908-912:⚠️ Potential issue | 🟠 MajorBatch export is still bound to the drawer invoice.
useInvoiceExportis initialized withdrawerState.selectedInvoice, but the newExport Selectedmenu reuses those same handlers. With no drawer selection the batch action errors out; with a drawer open it exports the wrong invoice instead of the checked rows.💡 One way to wire the batch menu correctly
+ const selectedInvoicesForExport = receivedInvoices.filter((inv) => + selectedInvoices.has(inv.id) + ); + const { handleExportCSV, handleExportJSON } = useInvoiceExport( drawerState.selectedInvoice, fee, handleExportClose ); + const { + handleExportCSV: handleBatchExportCSV, + handleExportJSON: handleBatchExportJSON, + } = useInvoiceExport( + selectedInvoicesForExport, + fee, + handleBatchExportClose + ); @@ - <MenuItem onClick={() => { handleExportCSV(); handleBatchExportClose(); }}> + <MenuItem onClick={handleBatchExportCSV}> @@ - <MenuItem onClick={() => { handleExportJSON(); handleBatchExportClose(); }}> + <MenuItem onClick={handleBatchExportJSON}>Also applies to: 1112-1152
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/page/ReceivedInvoice.jsx` around lines 908 - 912, The handlers handleExportCSV and handleExportJSON are currently bound to drawerState.selectedInvoice via useInvoiceExport, causing batch "Export Selected" to export the drawer item or fail when no drawer is open; update the wiring so the export handlers use the checked/selected rows instead of drawerState.selectedInvoice — either change useInvoiceExport to accept an invoice or array parameter (and call it with the active selection like checkedRows or selectedInvoiceIds where the export menu is rendered) or make the exported handlers accept the invoices to export at call-time (invoke handleExportCSV(checkedRows) / handleExportJSON(checkedRows)); update all usages (including the other menu block around lines 1112-1152) to pass the current selection rather than relying on drawerState.selectedInvoice.
1112-1152: 🛠️ Refactor suggestion | 🟠 MajorExternalize both new export menus.
Export Selected,Export Invoice, and the PDF/CSV/JSON labels are hard-coded in the new menus. Use shared translation keys here instead of duplicating English copy.As per coding guidelines, "Internationalization: User-visible strings should be externalized to resource files (i18n)".
Also applies to: 1936-1982
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/page/ReceivedInvoice.jsx` around lines 1112 - 1152, Replace hard-coded user-visible strings in the new export menus with i18n keys: update the Button label "Export Selected", the MenuItem texts "Export as CSV"/"Export as JSON", the "Export Invoice" menu and any PDF/CSV/JSON labels used around ReceivedInvoice.jsx to use the project's translation helper (e.g., t('invoices.export_selected'), t('invoices.export_csv'), t('invoices.export_json'), t('invoices.export_invoice_pdf')) instead of literal English; ensure the components and handlers referenced (Button, Menu, MenuItem, ListItemText, handleExportCSV, handleExportJSON, handleBatchExportClick, handleBatchExportClose) call the translation function and add the corresponding keys to the resource files so the same keys can be reused for the other menu block mentioned (lines ~1936-1982).frontend/src/page/SentInvoice.jsx (1)
1032-1077: 🛠️ Refactor suggestion | 🟠 MajorExternalize the new export menu labels.
Export Invoiceand the PDF/CSV/JSON menu labels are hard-coded here. Reuse shared translation keys so this menu stays aligned withfrontend/src/page/ReceivedInvoice.jsx.As per coding guidelines, "Internationalization: User-visible strings should be externalized to resource files (i18n)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/page/SentInvoice.jsx` around lines 1032 - 1077, Replace the hard-coded labels in the export menu with the shared i18n keys used by frontend/src/page/ReceivedInvoice.jsx: import and use the same translation hook (e.g., useTranslation or t) at the top of SentInvoice.jsx, then change the Download button text ("Export Invoice") and the three ListItemText values ("Export as PDF", "Export as CSV", "Export as JSON") to use the same translation keys used in ReceivedInvoice.jsx (keep the existing handlers: handleExportClick, handleExportCSV, handleExportJSON, handlePrint and the Menu/MenuItem/ListItemText components unchanged), and ensure any aria-visible strings are replaced with the translated values as well.frontend/src/utils/generateInvoiceJSON.js (1)
57-61:⚠️ Potential issue | 🟡 MinorFilter falsy invoices before building the JSON payload.
This only handles the all-empty case. A mixed payload like
[invoice, undefined]still reachesgenerateJSONContent(undefined, fee)and aborts the export.🛡️ Proposed fix
- const invoices = Array.isArray(invoiceOrInvoices) - ? invoiceOrInvoices - : [invoiceOrInvoices]; - - if (invoices.length === 0 || invoices.every((inv) => !inv)) return; + const invoices = ( + Array.isArray(invoiceOrInvoices) + ? invoiceOrInvoices + : [invoiceOrInvoices] + ).filter(Boolean); + + if (invoices.length === 0) return;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/generateInvoiceJSON.js` around lines 57 - 61, The code currently only checks for the all-empty case but does not remove falsy entries, causing generateJSONContent(invoice, fee) to be called with undefined; update the invoices normalization to filter out falsy values (e.g., replace the current invoices assignment using invoiceOrInvoices with invoices = (Array.isArray(invoiceOrInvoices) ? invoiceOrInvoices : [invoiceOrInvoices]).filter(Boolean)) and keep the existing empty-check (invoices.length === 0 || invoices.every(inv => !inv)) to early-return; ensure any downstream usage of invoices (such as calls to generateJSONContent) only sees non-falsy invoice objects.frontend/src/hooks/useInvoiceExport.js (1)
13-40: 🛠️ Refactor suggestion | 🟠 MajorMove the export toast and validation copy into i18n.
These strings are centralized now, but they are still hard-coded English and shared by both invoice pages. Pull them from the existing translation resources instead of baking them into the hook.
As per coding guidelines, "Internationalization: User-visible strings should be externalized to resource files (i18n)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useInvoiceExport.js` around lines 13 - 40, handleExportCSV and handleExportJSON currently use hard-coded English strings for validation and toast messages; replace those literals with entries from the app's translation resources (use the existing i18n/translation hook already used elsewhere) so the "No invoice selected", success and failure messages, and any shared copy are pulled via t('...') keys. Update the hook to import and call the translation function, replace all toast.error/toast.success strings in downloadInvoiceCSV/downloadInvoiceJSON flows with the appropriate translation keys, and ensure the same keys are used by both invoice pages for consistency while preserving the existing onExportDone?.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@frontend/src/hooks/useInvoiceExport.js`:
- Around line 13-40: handleExportCSV and handleExportJSON currently use
hard-coded English strings for validation and toast messages; replace those
literals with entries from the app's translation resources (use the existing
i18n/translation hook already used elsewhere) so the "No invoice selected",
success and failure messages, and any shared copy are pulled via t('...') keys.
Update the hook to import and call the translation function, replace all
toast.error/toast.success strings in downloadInvoiceCSV/downloadInvoiceJSON
flows with the appropriate translation keys, and ensure the same keys are used
by both invoice pages for consistency while preserving the existing
onExportDone?.
In `@frontend/src/page/ReceivedInvoice.jsx`:
- Around line 908-912: The handlers handleExportCSV and handleExportJSON are
currently bound to drawerState.selectedInvoice via useInvoiceExport, causing
batch "Export Selected" to export the drawer item or fail when no drawer is
open; update the wiring so the export handlers use the checked/selected rows
instead of drawerState.selectedInvoice — either change useInvoiceExport to
accept an invoice or array parameter (and call it with the active selection like
checkedRows or selectedInvoiceIds where the export menu is rendered) or make the
exported handlers accept the invoices to export at call-time (invoke
handleExportCSV(checkedRows) / handleExportJSON(checkedRows)); update all usages
(including the other menu block around lines 1112-1152) to pass the current
selection rather than relying on drawerState.selectedInvoice.
- Around line 1112-1152: Replace hard-coded user-visible strings in the new
export menus with i18n keys: update the Button label "Export Selected", the
MenuItem texts "Export as CSV"/"Export as JSON", the "Export Invoice" menu and
any PDF/CSV/JSON labels used around ReceivedInvoice.jsx to use the project's
translation helper (e.g., t('invoices.export_selected'),
t('invoices.export_csv'), t('invoices.export_json'),
t('invoices.export_invoice_pdf')) instead of literal English; ensure the
components and handlers referenced (Button, Menu, MenuItem, ListItemText,
handleExportCSV, handleExportJSON, handleBatchExportClick,
handleBatchExportClose) call the translation function and add the corresponding
keys to the resource files so the same keys can be reused for the other menu
block mentioned (lines ~1936-1982).
In `@frontend/src/page/SentInvoice.jsx`:
- Around line 1032-1077: Replace the hard-coded labels in the export menu with
the shared i18n keys used by frontend/src/page/ReceivedInvoice.jsx: import and
use the same translation hook (e.g., useTranslation or t) at the top of
SentInvoice.jsx, then change the Download button text ("Export Invoice") and the
three ListItemText values ("Export as PDF", "Export as CSV", "Export as JSON")
to use the same translation keys used in ReceivedInvoice.jsx (keep the existing
handlers: handleExportClick, handleExportCSV, handleExportJSON, handlePrint and
the Menu/MenuItem/ListItemText components unchanged), and ensure any
aria-visible strings are replaced with the translated values as well.
In `@frontend/src/utils/generateInvoiceCSV.js`:
- Around line 165-169: The invoices array may contain falsy entries and you must
filter them out before proceeding so mixed payloads (e.g., [invoice, null])
don’t call generateCSVContent(null,...). In the block where invoiceOrInvoices is
normalized to invoices (in generateInvoiceCSV.js), apply a filter to remove
falsy values (e.g., invoices = invoices.filter(Boolean)) and then keep the
existing empty-length guard (if invoices.length === 0 return) so subsequent
calls to generateCSVContent and any mapping over invoices only see valid invoice
objects.
In `@frontend/src/utils/generateInvoiceJSON.js`:
- Around line 57-61: The code currently only checks for the all-empty case but
does not remove falsy entries, causing generateJSONContent(invoice, fee) to be
called with undefined; update the invoices normalization to filter out falsy
values (e.g., replace the current invoices assignment using invoiceOrInvoices
with invoices = (Array.isArray(invoiceOrInvoices) ? invoiceOrInvoices :
[invoiceOrInvoices]).filter(Boolean)) and keep the existing empty-check
(invoices.length === 0 || invoices.every(inv => !inv)) to early-return; ensure
any downstream usage of invoices (such as calls to generateJSONContent) only
sees non-falsy invoice objects.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 68a247eb-125e-4260-bb39-6bddb60d0733
📒 Files selected for processing (6)
frontend/src/hooks/useInvoiceExport.jsfrontend/src/page/ReceivedInvoice.jsxfrontend/src/page/SentInvoice.jsxfrontend/src/utils/generateInvoiceCSV.jsfrontend/src/utils/generateInvoiceJSON.jsfrontend/src/utils/invoiceExportHelpers.js
|
@kumawatkaran523 @Zahnentferner |
|
@Atharva0506, |
|
JSON export gives customers machine-readable invoice data to integrate directly into accounting software, automate workflows, and avoid manual re-entry things a PDF cannot enable. |
Addressed Issues
Fixes #136
Screenshots/Recordings
A demo video has been recorded demonstrating the Export Invoice to JSON and CSV functionality.
The video shows:
csv-json-demo.1.mp4
Additional Notes
This PR introduces support for exporting invoices in JSON and CSV formats to improve portability and downstream integrations.
Key implementations:
frontend/src/utils/generateInvoiceJSON.jsfrontend/src/utils/generateInvoiceCSV.jsfrontend/src/utils/invoiceExportHelpers.jsfrontend/src/page/SentInvoice.jsxfrontend/src/page/ReceivedInvoice.jsxLocal Testing Environment Note (
local-demo-env-backup)During development, I prepared a local testing setup (Docker + Anvil + temporary local-only testing helpers) under a separate local backup workflow (
local-demo-env-backup) to migrate and run the project locally for faster verification.To keep this PR focused on Issue #136, none of those local-environment/testing-workaround changes are included here.
If maintainers find it useful, I can open a separate PR that introduces a clean, documented local demo/testing environment.
AI Usage Disclosure
AI tools used: GitHub Copilot (AI code editor assistance)
Checklist
Summary by CodeRabbit
New Features
Improvements