Skip to content

NWP-101: let ops choose export columns and scope - #161

Open
yvonne-so wants to merge 3 commits into
JJFromTenex:mainfrom
yvonne-so:NWP-101-export-options
Open

NWP-101: let ops choose export columns and scope#161
yvonne-so wants to merge 3 commits into
JJFromTenex:mainfrom
yvonne-so:NWP-101-export-options

Conversation

@yvonne-so

@yvonne-so yvonne-so commented Sep 10, 2026

Copy link
Copy Markdown

Ticket

Closes NWP-101

What changed

The Export button on /payments now opens an options dialog instead of immediately downloading a fixed file. Ops chooses which columns are included and whether the export covers the current filter or all payments, with the row count for each visible before download. The card last four is excluded by default, so a file bound for a merchant is clean unless someone ticks it on purpose.

Business impact: ops stops hand-editing every export, removing 3–4 hours a month of spreadsheet cleanup and the risk of sending card data to the wrong merchant.

How I verified it

npm test:

 ✓ src/lib/dates.test.ts (7 tests) 21ms
 ✓ src/lib/money.test.ts (12 tests) 22ms
 ✓ src/lib/csv.test.ts (18 tests) 23ms

 Test Files  3 passed (3)
      Tests  37 passed (37)

npm run lint clean, npm run build succeeds. All 9 pre-existing CSV tests still pass unchanged.

Against a running dev server:

Request Result
default columns header omits last4
status=disputed payments-disputed-2026-09-10.csv, 33 rows
scope=all payments-all-2026-09-10.csv, 1,658 rows, filters ignored
columns=amount,id,merchant header in that order
columns= or unknown names only 400, {"message":"Select at least one column to export."}

In the browser: opened the dialog on /payments?status=disputed, confirmed both scope counts (33 and 1,658), confirmed the card last four starts unchecked, and deselected every column to confirm Download becomes a disabled button with a visible error rather than producing an empty file.

  • npm test passes
  • New behavior is covered by a test
  • Checked it in the browser

Acceptance criteria

  • Ops can choose which columns are included. Card last-four is off by default — DEFAULT_EXPORT_COLUMNS in src/lib/csv.ts
  • Ops can choose scope: current filter or all payments, current filter default, row count visible before download — counts passed from the server component, which already has them
  • Filename reflects the scope and the date — exportFilename(date, scope), e.g. payments-disputed-2026-09-10.csv
  • Amounts stay in minor units internally and are formatted once on the way out, with currency in its own column — see the reviewer note below
  • Deselecting every column disables Download rather than producing an empty file — disabled in the dialog, and a 400 from the route

Notes from the team, addressed

  • Validate column names server-side, no SQL interpolation. parseExportColumns checks every name against EXPORT_COLUMNS, drops unknowns, and returns an empty list rather than falling back to everything. A request of columns=password,1=1,cvv yields a 400. The scope is also sanitized before it reaches a filename.
  • The table is paginated; do not build this in the browser. Rows come from exportRows in src/data/queries.ts, which is unpaginated on purpose. Verified: 1,658 rows for all payments versus 20 on screen.
  • Reuse the existing query builder. exportRows calls the same filterPayments and sortPayments the table uses. No second filter path.

Bugs fixed along the way

None fixed. Two found and documented rather than changed, since both sit outside this ticket:

  • sortPayments in src/data/queries.ts sorts by amount with String(...).localeCompare, so 900 orders after 10000. Affects export order when sorting by amount.
  • src/components/ has no Dialog.tsx, though .claude/rules/components.md lists one as available. This dialog reuses Drawer, which is the same Radix dialog primitive.

Notes for the reviewer

On acceptance criterion 4: the amount cell still renders through formatMoney, so it reads $250.00 while the currency column separately reads USD. Amounts remain integer minor units everywhere upstream and are formatted exactly once, at the edge, in src/lib/csv.ts, and currency does have its own column. I left the symbol in place because two pre-existing tests pin that output and csv.test.ts states outright that this ticket changes which columns ship, not how a cell is written. Happy to switch the column to a plain decimal and update those two tests if you would rather the symbol went away.

The first commit on this branch is unrelated onboarding work: root CLAUDE.md additions, an architecture page under public/, and a beige light-mode background. Happy to split it out if you want this PR narrowed to the feature.

🤖 Generated with Claude Code

yvonne-so and others added 2 commits September 10, 2026 10:17
- CLAUDE.md: commands, app architecture map, Release Standards section
- merchant-console/public/architecture.html: architecture page covering the
  payments export flow end to end
- layout.tsx: beige page background in light mode
- package-lock.json: refreshed by npm install

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds an options dialog to the Export button on /payments.

- csv.ts: DEFAULT_EXPORT_COLUMNS (last four excluded), parseExportColumns
  server-side allowlist preserving requested order, scope segment in
  exportFilename
- queries.ts: scope allowlist and exportRows, reusing the one query builder
  unpaginated so the file covers the whole scope, not the current page
- export route: rejects an empty column selection with a 400
- export-dialog.tsx: column checkboxes, scope radios with row counts,
  Download disabled on an empty selection

csv.test.ts: 9 new tests covering column subsets in requested order, the
default exclusion of the card last four, empty and unknown selections, and
scoped filenames. 37 tests pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@yvonne-so yvonne-so changed the title NWP-101: architecture notes, release standards, beige theme NWP-101: let ops choose export columns and scope Sep 10, 2026
The page still described the old fixed export and listed NWP-101 as pending.
Now documents the seven-step flow including the dialog, a before/after table,
and the rough edges left open.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant