refactor(settings): put the remaining two settings tables on the shared pieces
#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.
This commit is contained in:
parent
a912fb2f45
commit
3c4de4dfbd
|
|
@ -30,7 +30,8 @@ export function SettingsTable<TData>({
|
|||
const rows = table.getRowModel().rows;
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
// Scrolls rather than clips: the accounts table is wider than a phone.
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
|
|
|||
|
|
@ -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<TData>(
|
||||
data: TData[],
|
||||
columns: ColumnDef<TData>[],
|
||||
initialSorting: SortingState = [],
|
||||
) {
|
||||
const [sorting, setSorting] = useState<SortingState>(initialSorting);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
{},
|
||||
);
|
||||
|
||||
return useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -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<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
{},
|
||||
);
|
||||
|
||||
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 (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
|
|
@ -340,61 +303,17 @@ export default function Accounts({ accounts }: AccountsPageProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table
|
||||
.getHeaderGroups()
|
||||
.map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map(
|
||||
(header) => {
|
||||
return (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header
|
||||
.column
|
||||
.columnDef
|
||||
.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table
|
||||
.getRowModel()
|
||||
.rows.map((row) => (
|
||||
<AccountRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
onSuccess={
|
||||
handleAccountCreated
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{__('No accounts found.')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<SettingsTable
|
||||
table={table}
|
||||
emptyMessage={__('No accounts found.')}
|
||||
renderRow={(row) => (
|
||||
<AccountRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
onSuccess={handleAccountCreated}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
|
|
|
|||
|
|
@ -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<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
{},
|
||||
);
|
||||
|
||||
const columns: ColumnDef<AutomationRule>[] = [
|
||||
{
|
||||
|
|
@ -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 (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
|
|
@ -308,62 +272,18 @@ export default function AutomationRules() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table
|
||||
.getHeaderGroups()
|
||||
.map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map(
|
||||
(header) => {
|
||||
return (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header
|
||||
.column
|
||||
.columnDef
|
||||
.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table
|
||||
.getRowModel()
|
||||
.rows.map((row) => (
|
||||
<AutomationRuleRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
categories={categories}
|
||||
labels={labels}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{__(
|
||||
'No automation rules found.',
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<SettingsTable
|
||||
table={table}
|
||||
emptyMessage={__('No automation rules found.')}
|
||||
renderRow={(row) => (
|
||||
<AutomationRuleRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
categories={categories}
|
||||
labels={labels}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</Button>
|
||||
);
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
{},
|
||||
);
|
||||
|
||||
const columns: ColumnDef<Category>[] = [
|
||||
{
|
||||
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 (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
|
|
|
|||
|
|
@ -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<Label> }) {
|
|||
|
||||
export default function Labels() {
|
||||
const { labels } = usePage<{ labels: Label[] }>().props;
|
||||
const [sorting, setSorting] = useState<SortingState>([
|
||||
{ id: 'name', desc: false },
|
||||
]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
{},
|
||||
);
|
||||
|
||||
const columns: ColumnDef<Label>[] = [
|
||||
{
|
||||
|
|
@ -128,21 +111,9 @@ export default function Labels() {
|
|||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: labels,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
},
|
||||
});
|
||||
const table = useSettingsTable(labels, columns, [
|
||||
{ id: 'name', desc: false },
|
||||
]);
|
||||
|
||||
return (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue