whisper-money/resources/js/components/shared
Víctor Falcón 8d58e0bf81
refactor(settings): dedupe the categories and labels pages (#770)
## Why

`settings/categories.tsx` and `settings/labels.tsx` were the last big
duplication cluster after the landing page (#768): 6 clone pairs between
and within them, ~100 duplicated lines.

They are the same page with a different record type:

- the trailing `...` dropdown with Edit / Delete
- the right-click context menu on each row, with the same two items
- the pair of dialog mounts wired to two `useState` flags — written
**twice per page**, once for the dropdown and once for the context menu
- 39 lines of table markup (header groups, body, empty row) differing
only in the row component and the empty message

## What changed

Three shared pieces:

| component | replaces |
|---|---|
| `RowActionsDropdown` | the `...` cell + its two dialogs, in both pages
|
| `RowWithActionsContextMenu` | the row + context menu + its two
dialogs, in both pages |
| `SettingsTable` | the table markup, in both pages |

The dialogs come in as render props (`renderEditDialog` /
`renderDeleteDialog`), so each page keeps its own dialog components and
their extra props — `categories` needs the full category list, labels
needs nothing — while the menus own the open state.

`categories.tsx` −86 lines, `labels.tsx` −100 lines.

### Why not the existing DataTable

`components/ui/data-table.tsx` already renders a table, but it is
**virtualized**: its `renderRow` hands back a `VirtualItem` and the
virtualizer, and rows are expected to attach `measureElement`. These
settings lists are short and their rows are wrapped in a
`ContextMenuTrigger asChild`, so adopting it would mean threading
measurement through the context menu to gain nothing. `SettingsTable` is
the plain version, and `accounts.tsx` / `automation-rules.tsx` (which
repeat the same markup) can move onto it next.

## Metrics

Cumulative with the other refactor PRs in flight; measured against
`main`:

| | before | after |
|---|---|---|
| duplicated lines (tsx) | 5.82% | 5.40% |
| duplicated lines (total) | 5.34% | 5.11% |
| clones | 322 | 314 |

## Testing

ESLint and `tsc --noEmit` clean. No behaviour change: same markup, same
handlers, same dialogs — only their location moved.

The browser suite is what actually exercises these pages
(`CategoriesTest` covers viewing, creating, filtering, the empty state
and cell alignment). I could not run it locally without a production
asset build, so it is verified here by CI's `browser-tests-matrix`.
2026-08-11 14:37:23 +02:00
..
category-breakdown-list.tsx fix(analysis): truncate long breakdown names so amounts stay in widget (#518) 2026-06-10 21:58:36 +02:00
category-combobox.tsx feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
label-combobox-state.test.ts fix: prevent account label combobox crash (#230) 2026-03-16 13:03:11 +01:00
label-combobox-state.ts fix: prevent account label combobox crash (#230) 2026-03-16 13:03:11 +01:00
label-combobox.tsx refactor(js): extract shared getCsrfToken util (#475) 2026-06-03 17:26:09 +02:00
row-edit-delete-actions.tsx refactor(settings): dedupe the categories and labels pages (#770) 2026-08-11 14:37:23 +02:00
settings-table.tsx refactor(settings): dedupe the categories and labels pages (#770) 2026-08-11 14:37:23 +02:00