Skip to content

feat: implement Export Invoice to JSON/CSV#138

Merged
kumawatkaran523 merged 1 commit intoStabilityNexus:mainfrom
Atharva0506:feature/export-invoices-json-csv-v2
Mar 24, 2026
Merged

feat: implement Export Invoice to JSON/CSV#138
kumawatkaran523 merged 1 commit intoStabilityNexus:mainfrom
Atharva0506:feature/export-invoices-json-csv-v2

Conversation

@Atharva0506
Copy link
Copy Markdown
Contributor

@Atharva0506 Atharva0506 commented Mar 10, 2026

Addressed Issues

Fixes #136

Screenshots/Recordings

A demo video has been recorded demonstrating the Export Invoice to JSON and CSV functionality.

The video shows:

  • Exporting an invoice as a JSON file
  • Exporting an invoice as a CSV file
  • Verifying that exported files contain correct structured invoice data
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:

  • Added dedicated utilities:
    • frontend/src/utils/generateInvoiceJSON.js
    • frontend/src/utils/generateInvoiceCSV.js
  • Added shared export helpers:
    • frontend/src/utils/invoiceExportHelpers.js
  • Implemented secure client-side file generation with the Blob API for instant downloads.
  • Replaced the previous single Download action with an Export Invoice menu in:
    • frontend/src/page/SentInvoice.jsx
    • frontend/src/page/ReceivedInvoice.jsx
  • Unified export UX for PDF, CSV, and JSON.
  • Refactored duplicated total-calculation logic into a shared helper for consistency.
  • Applied CodeRabbit-requested fixes (currency/fee handling, CSV safety, metadata accuracy, and export behavior improvements).

Local 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

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

AI tools used: GitHub Copilot (AI code editor assistance)

Checklist

  • My PR addresses a single issue, fixes a single bug, or makes a single improvement.
  • My code follows the project's code style and conventions.
  • If applicable, I have made corresponding changes or additions to documentation.
  • If applicable, I have made corresponding changes or additions to tests.
  • My changes generate no new warnings or errors.
  • I have joined the Discord server and I will share a link to this PR with maintainers there.
  • I have read the Contribution Guidelines.
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit comments.
  • I have filled this PR template completely and carefully.

Summary by CodeRabbit

  • New Features

    • Export invoices from the UI via an Export menu (PDF, CSV, JSON) in lists and detail drawer; batch "Export Selected" for CSV/JSON.
    • Download helpers for CSV and JSON exports with safe value handling, clear filenames, and download initiation.
  • Improvements

    • Consistent invoice total and date formatting across the app.
    • UI tweaks: export icons, menus, and improved spacing; success/error toasts for export actions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 10, 2026

Walkthrough

Adds 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

Cohort / File(s) Summary
Invoice Page Components
frontend/src/page/ReceivedInvoice.jsx, frontend/src/page/SentInvoice.jsx
Added Export UI (Export button + anchor menus) with PDF/CSV/JSON options, wired export handlers, integrated useInvoiceExport, replaced inline total calc with formatInvoiceTotal(...), and adjusted token-key grouping logic.
CSV Export Utility
frontend/src/utils/generateInvoiceCSV.js
New downloadInvoiceCSV(invoiceOrInvoices, fee) that builds safe CSV (headers, itemized rows or summary), prevents formula injection, supports single/multi exports, adds UTF-8 BOM, and triggers browser download.
JSON Export Utility
frontend/src/utils/generateInvoiceJSON.js
New downloadInvoiceJSON(invoiceOrInvoices, fee) that normalizes invoices (ids, parties, items, totals, chain/token meta), pretty-prints JSON, and triggers browser download for single/multi exports.
Export Helpers
frontend/src/utils/invoiceExportHelpers.js
New helpers: resolveChainMeta, formatNetworkFee, formatInvoiceId, resolveStatus, buildParty, normalizeItem, toDisplayDate, toISODate, formatInvoiceTotal for metadata extraction and formatting (handles native vs token totals).
Export Hook
frontend/src/hooks/useInvoiceExport.js
New useInvoiceExport(selectedInvoice, fee, onExportDone) hook providing handleExportCSV and handleExportJSON that call download utilities and surface success/error toasts.
Shared imports/adjustments
frontend/src/page/...
Added imports for export utilities and helpers, introduced export icons and menu state, and small UI/formatting adjustments to accommodate new menus and batch export flow.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Typescript Lang

Suggested reviewers

  • adityabhattad2021

Poem

🐰 I nibble bytes and stitch a row,

CSV and JSON watch me go.
Menus pop and files take flight,
Invoices tidy, neat, and bright.
Hoppity hop — export delight!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR successfully implements all core coding requirements from Issue #136: client-side CSV/JSON export using Blob API, export UI on both Sent and Received pages, shared helper utilities, and decrypted-data-only approach.
Out of Scope Changes check ✅ Passed All changes are directly aligned with Issue #136 objectives. The token-key construction update and minor formatting adjustments are necessary for the export functionality implementation.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing invoice export functionality in JSON and CSV formats.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Atharva0506 Atharva0506 marked this pull request as ready for review March 10, 2026 11:53
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b83780d and 4ca9692.

📒 Files selected for processing (5)
  • frontend/src/page/ReceivedInvoice.jsx
  • frontend/src/page/SentInvoice.jsx
  • frontend/src/utils/generateInvoiceCSV.js
  • frontend/src/utils/generateInvoiceJSON.js
  • frontend/src/utils/invoiceExportHelpers.js

@Atharva0506
Copy link
Copy Markdown
Contributor Author

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.
Would you like me to update this PR to move these strings into the existing i18n resource files and reuse them in both this component and frontend/src/page/ReceivedInvoice.jsx?

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca9692 and 12d39a9.

📒 Files selected for processing (3)
  • frontend/src/utils/generateInvoiceCSV.js
  • frontend/src/utils/generateInvoiceJSON.js
  • frontend/src/utils/invoiceExportHelpers.js

@Atharva0506 Atharva0506 force-pushed the feature/export-invoices-json-csv-v2 branch from 12d39a9 to 0adffeb Compare March 12, 2026 07:55
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 12d39a9 and 0adffeb.

📒 Files selected for processing (6)
  • frontend/src/hooks/useInvoiceExport.js
  • frontend/src/page/ReceivedInvoice.jsx
  • frontend/src/page/SentInvoice.jsx
  • frontend/src/utils/generateInvoiceCSV.js
  • frontend/src/utils/generateInvoiceJSON.js
  • frontend/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
@Atharva0506 Atharva0506 force-pushed the feature/export-invoices-json-csv-v2 branch from 0adffeb to 0dbf1f6 Compare March 12, 2026 08:14
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (6)
frontend/src/utils/generateInvoiceCSV.js (1)

165-169: ⚠️ Potential issue | 🟡 Minor

Filter falsy invoices before generating the CSV.

The current guard only skips the all-empty case. A mixed payload like [invoice, null] still reaches generateCSVContent(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 | 🟠 Major

Batch export is still bound to the drawer invoice.

useInvoiceExport is initialized with drawerState.selectedInvoice, but the new Export Selected menu 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 | 🟠 Major

Externalize 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 | 🟠 Major

Externalize the new export menu labels.

Export Invoice and the PDF/CSV/JSON menu labels are hard-coded here. Reuse shared translation keys so this menu stays aligned with frontend/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 | 🟡 Minor

Filter falsy invoices before building the JSON payload.

This only handles the all-empty case. A mixed payload like [invoice, undefined] still reaches generateJSONContent(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 | 🟠 Major

Move 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0adffeb and 0dbf1f6.

📒 Files selected for processing (6)
  • frontend/src/hooks/useInvoiceExport.js
  • frontend/src/page/ReceivedInvoice.jsx
  • frontend/src/page/SentInvoice.jsx
  • frontend/src/utils/generateInvoiceCSV.js
  • frontend/src/utils/generateInvoiceJSON.js
  • frontend/src/utils/invoiceExportHelpers.js

@Atharva0506
Copy link
Copy Markdown
Contributor Author

@kumawatkaran523 @Zahnentferner
I’ve addressed the feedback and updated the PR. Could you please review it when you get a chance? Thank you!

@Atharva0506 Atharva0506 changed the title feat: implement Export Invoice to JSON/CSV (Issue #136) feat: implement Export Invoice to JSON/CSV Mar 13, 2026
@kumawatkaran523
Copy link
Copy Markdown
Contributor

kumawatkaran523 commented Mar 23, 2026

@Atharva0506,
What is the purpose of json extraction ?
Why would someone need json ?
Think from the customer point of view...and answer.

@Atharva0506
Copy link
Copy Markdown
Contributor Author

@kumawatkaran523

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.
While CSV covers most non-technical users' needs, JSON adds value for developer integrations and nested data structures. Since implementing both is purely frontend with no smart contract changes or backend needed, the added effort is minimal while the value for technical users is significant making it a low-risk, high-reward addition.

@kumawatkaran523 kumawatkaran523 merged commit c0e5721 into StabilityNexus:main Mar 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Add CSV/JSON Export for Invoice Data

2 participants