From 3c4de4dfbd83a309d446486f0f1fdc0e8f11892f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Tue, 11 Aug 2026 15:07:59 +0200 Subject: [PATCH] refactor(settings): put the remaining two settings tables on the shared pieces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #770 extracted SettingsTable for the categories and labels pages; accounts and automation-rules still carried their own copy of the same 30-line table markup, so they move onto it too. All four also repeated the identical useReactTable setup — four pieces of state and the same six options — which is now useSettingsTable(data, columns). SettingsTable's container becomes overflow-x-auto, which is what the accounts table (the widest of the four) already used. On the other three it only matters when the table outgrows its container, where scrolling beats clipping. --- .../js/components/shared/settings-table.tsx | 3 +- resources/js/hooks/use-settings-table.ts | 44 +++++++ resources/js/pages/settings/accounts.tsx | 113 +++-------------- .../js/pages/settings/automation-rules.tsx | 114 +++--------------- resources/js/pages/settings/categories.tsx | 35 +----- resources/js/pages/settings/labels.tsx | 39 +----- 6 files changed, 87 insertions(+), 261 deletions(-) create mode 100644 resources/js/hooks/use-settings-table.ts diff --git a/resources/js/components/shared/settings-table.tsx b/resources/js/components/shared/settings-table.tsx index b8b12038..ad034452 100644 --- a/resources/js/components/shared/settings-table.tsx +++ b/resources/js/components/shared/settings-table.tsx @@ -30,7 +30,8 @@ export function SettingsTable({ const rows = table.getRowModel().rows; return ( -
+ // Scrolls rather than clips: the accounts table is wider than a phone. +
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/resources/js/hooks/use-settings-table.ts b/resources/js/hooks/use-settings-table.ts new file mode 100644 index 00000000..9115df02 --- /dev/null +++ b/resources/js/hooks/use-settings-table.ts @@ -0,0 +1,44 @@ +import { + type ColumnDef, + type ColumnFiltersState, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + type SortingState, + useReactTable, + type VisibilityState, +} from '@tanstack/react-table'; +import { useState } from 'react'; + +/** + * A sortable, filterable table over a list the server already sent whole — the + * shape every settings page needs. Sorting, filtering and column visibility are + * client-side, so no page has to wire the same four pieces of state again. + */ +export function useSettingsTable( + data: TData[], + columns: ColumnDef[], + initialSorting: SortingState = [], +) { + const [sorting, setSorting] = useState(initialSorting); + const [columnFilters, setColumnFilters] = useState([]); + const [columnVisibility, setColumnVisibility] = useState( + {}, + ); + + return useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + state: { + sorting, + columnFilters, + columnVisibility, + }, + }); +} diff --git a/resources/js/pages/settings/accounts.tsx b/resources/js/pages/settings/accounts.tsx index 80504987..918e3667 100644 --- a/resources/js/pages/settings/accounts.tsx +++ b/resources/js/pages/settings/accounts.tsx @@ -1,18 +1,6 @@ import { __ } from '@/utils/i18n'; import { Head, router } from '@inertiajs/react'; -import { - Cell, - ColumnDef, - ColumnFiltersState, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getSortedRowModel, - Row, - SortingState, - useReactTable, - VisibilityState, -} from '@tanstack/react-table'; +import { Cell, ColumnDef, flexRender, Row } from '@tanstack/react-table'; import { ArrowUpDown, Link2, MoreHorizontal } from 'lucide-react'; import { useState } from 'react'; @@ -23,6 +11,7 @@ import { DeleteAccountDialog } from '@/components/accounts/delete-account-dialog import { EditAccountDialog } from '@/components/accounts/edit-account-dialog'; import { BankLogo } from '@/components/bank-logo'; import HeadingSmall from '@/components/heading-small'; +import { SettingsTable } from '@/components/shared/settings-table'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -40,14 +29,8 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; +import { TableCell, TableRow } from '@/components/ui/table'; +import { useSettingsTable } from '@/hooks/use-settings-table'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { type BreadcrumbItem } from '@/types'; @@ -177,12 +160,6 @@ interface AccountsPageProps { } export default function Accounts({ accounts }: AccountsPageProps) { - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState( - {}, - ); - const breadcrumbs: BreadcrumbItem[] = [ { title: __('Bank accounts'), @@ -291,21 +268,7 @@ export default function Accounts({ accounts }: AccountsPageProps) { }, ]; - const table = useReactTable({ - data: accounts, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - state: { - sorting, - columnFilters, - columnVisibility, - }, - }); + const table = useSettingsTable(accounts, columns); return ( @@ -340,61 +303,17 @@ export default function Accounts({ accounts }: AccountsPageProps) { /> -
-
- - {table - .getHeaderGroups() - .map((headerGroup) => ( - - {headerGroup.headers.map( - (header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header - .column - .columnDef - .header, - header.getContext(), - )} - - ); - }, - )} - - ))} - - - {table.getRowModel().rows?.length ? ( - table - .getRowModel() - .rows.map((row) => ( - - )) - ) : ( - - - {__('No accounts found.')} - - - )} - -
-
+ ( + + )} + />
diff --git a/resources/js/pages/settings/automation-rules.tsx b/resources/js/pages/settings/automation-rules.tsx index 6a387dd7..1eefcfea 100644 --- a/resources/js/pages/settings/automation-rules.tsx +++ b/resources/js/pages/settings/automation-rules.tsx @@ -1,17 +1,5 @@ import { Head, usePage } from '@inertiajs/react'; -import { - Cell, - ColumnDef, - ColumnFiltersState, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getSortedRowModel, - Row, - SortingState, - useReactTable, - VisibilityState, -} from '@tanstack/react-table'; +import { Cell, ColumnDef, flexRender, Row } from '@tanstack/react-table'; import { MoreHorizontal } from 'lucide-react'; import { useMemo, useState } from 'react'; @@ -24,6 +12,7 @@ import { DeleteAutomationRuleDialog } from '@/components/automation-rules/delete import { EditAutomationRuleDialog } from '@/components/automation-rules/edit-automation-rule-dialog'; import { PostSaveApplyRulePrompt } from '@/components/automation-rules/post-save-apply-rule-prompt'; import HeadingSmall from '@/components/heading-small'; +import { SettingsTable } from '@/components/shared/settings-table'; import { Button } from '@/components/ui/button'; import { ContextMenu, @@ -40,14 +29,8 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; +import { TableCell, TableRow } from '@/components/ui/table'; +import { useSettingsTable } from '@/hooks/use-settings-table'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { type BreadcrumbItem } from '@/types'; @@ -223,11 +206,6 @@ export default function AutomationRules() { })), [rawRules], ); - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState( - {}, - ); const columns: ColumnDef[] = [ { @@ -257,21 +235,7 @@ export default function AutomationRules() { }, ]; - const table = useReactTable({ - data: rules, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - state: { - sorting, - columnFilters, - columnVisibility, - }, - }); + const table = useSettingsTable(rules, columns); return ( @@ -308,62 +272,18 @@ export default function AutomationRules() { /> -
- - - {table - .getHeaderGroups() - .map((headerGroup) => ( - - {headerGroup.headers.map( - (header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header - .column - .columnDef - .header, - header.getContext(), - )} - - ); - }, - )} - - ))} - - - {table.getRowModel().rows?.length ? ( - table - .getRowModel() - .rows.map((row) => ( - - )) - ) : ( - - - {__( - 'No automation rules found.', - )} - - - )} - -
-
+ ( + + )} + />
diff --git a/resources/js/pages/settings/categories.tsx b/resources/js/pages/settings/categories.tsx index 32470759..93989248 100644 --- a/resources/js/pages/settings/categories.tsx +++ b/resources/js/pages/settings/categories.tsx @@ -1,16 +1,6 @@ import { __ } from '@/utils/i18n'; import { Head, usePage } from '@inertiajs/react'; -import { - ColumnDef, - ColumnFiltersState, - getCoreRowModel, - getFilteredRowModel, - getSortedRowModel, - Row, - SortingState, - useReactTable, - VisibilityState, -} from '@tanstack/react-table'; +import { ColumnDef, Row } from '@tanstack/react-table'; import * as Icons from 'lucide-react'; import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; import { useMemo, useState } from 'react'; @@ -34,6 +24,7 @@ import { TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; +import { useSettingsTable } from '@/hooks/use-settings-table'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { @@ -148,12 +139,6 @@ export default function Categories() { ); - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState( - {}, - ); - const columns: ColumnDef[] = [ { accessorKey: 'name', @@ -318,21 +303,7 @@ export default function Categories() { }, ]; - const table = useReactTable({ - data: orderedCategories, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - state: { - sorting, - columnFilters, - columnVisibility, - }, - }); + const table = useSettingsTable(orderedCategories, columns); return ( diff --git a/resources/js/pages/settings/labels.tsx b/resources/js/pages/settings/labels.tsx index 862902c5..a090b7e7 100644 --- a/resources/js/pages/settings/labels.tsx +++ b/resources/js/pages/settings/labels.tsx @@ -1,18 +1,7 @@ import { __ } from '@/utils/i18n'; import { Head, usePage } from '@inertiajs/react'; -import { - ColumnDef, - ColumnFiltersState, - getCoreRowModel, - getFilteredRowModel, - getSortedRowModel, - Row, - SortingState, - useReactTable, - VisibilityState, -} from '@tanstack/react-table'; +import { ColumnDef, Row } from '@tanstack/react-table'; import { ArrowUpDown, Tag } from 'lucide-react'; -import { useState } from 'react'; import { index as labelsIndex } from '@/actions/App/Http/Controllers/Settings/LabelController'; import HeadingSmall from '@/components/heading-small'; @@ -28,6 +17,7 @@ import { SettingsTable } from '@/components/shared/settings-table'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { useSettingsTable } from '@/hooks/use-settings-table'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { type BreadcrumbItem } from '@/types'; @@ -67,13 +57,6 @@ function LabelRow({ row }: { row: Row