whisper-money/resources/js
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
..
components refactor(settings): dedupe the categories and labels pages (#770) 2026-08-11 14:37:23 +02:00
contexts Move transaction filtering from client-side to server-side (#126) 2026-02-16 12:49:04 +01:00
hooks refactor(landing): extract the scroll-progress hook every preview duplicated (#768) 2026-08-11 14:34:24 +02:00
layouts feat(budgets): add per-budget email notifications (#731) 2026-07-24 12:52:03 +02:00
lib fix(ui): stop unusable web storage from white-screening the app (#743) 2026-08-09 15:33:22 +00:00
pages refactor(settings): dedupe the categories and labels pages (#770) 2026-08-11 14:37:23 +02:00
providers fix: move community link to user menu (#442) 2026-05-27 17:39:26 +02:00
services feat(transactions): allow editing all fields of manual transactions (#683) 2026-07-16 08:59:10 +02:00
types feat(budgets): add per-budget email notifications (#731) 2026-07-24 12:52:03 +02:00
utils feat(currency): add THB (Thai Baht) (#740) 2026-08-09 14:24:58 +00:00
app.tsx feat(deps): upgrade Inertia.js from v2 to v3 (#769) 2026-08-11 14:33:31 +02:00
ssr.tsx feat(deps): upgrade Inertia.js from v2 to v3 (#769) 2026-08-11 14:33:31 +02:00