diff --git a/frontend/src/components/Table/TableHeader.tsx b/frontend/src/components/Table/TableHeader.tsx index bf627ce992..dd409d59d8 100644 --- a/frontend/src/components/Table/TableHeader.tsx +++ b/frontend/src/components/Table/TableHeader.tsx @@ -1,3 +1,5 @@ +import { IconArrowsSort, IconChevronDown, IconChevronUp } from "@tabler/icons-react"; +import { flexRender } from "@tanstack/react-table"; import type { TableLayoutProps } from "src/components"; function TableHeader(props: TableLayoutProps) { @@ -11,9 +13,34 @@ function TableHeader(props: TableLayoutProps) { {headerGroup.headers.map((header: any) => { const { column } = header; const { className } = (column.columnDef.meta as any) ?? {}; + const canSort = column.getCanSort(); + const sortDir = column.getIsSorted(); + + const headerContent = header.isPlaceholder + ? null + : typeof column.columnDef.header === "string" + ? column.columnDef.header + : flexRender(column.columnDef.header, header.getContext()); + + const sortIcon = canSort ? ( + sortDir === "asc" ? ( + + ) : sortDir === "desc" ? ( + + ) : ( + + ) + ) : null; + return ( - - {typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null} + + {headerContent} + {sortIcon} ); })} diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx index f8a1a27542..17143e7504 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Table.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { DeadHost } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -47,6 +59,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -128,10 +141,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onDelete, onEdit, onDisableToggle], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index 9d58b26acd..5af58081ad 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { ProxyHost } from "src/api/backend"; import { AccessListFormatter, @@ -30,6 +36,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -41,6 +48,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -49,6 +61,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "forwardHost", header: intl.formatMessage({ id: "column.destination" }), + sortingFn: (a, b) => { + const aVal = `${a.original.forwardHost}:${a.original.forwardPort}`; + const bVal = `${b.original.forwardHost}:${b.original.forwardPort}`; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return `${value.forwardScheme}://${value.forwardHost}:${value.forwardPort}`; @@ -56,6 +73,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -63,6 +81,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.accessList, { id: "accessList", + enableSorting: false, header: intl.formatMessage({ id: "column.access" }), cell: (info: any) => { return ; @@ -144,10 +163,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onEdit, onDisableToggle, onDelete], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx index 6ac4152348..8e2d90f555 100644 --- a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx +++ b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { RedirectionHost } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -68,6 +80,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -149,10 +162,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onEdit, onDisableToggle, onDelete], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/Streams/Table.tsx b/frontend/src/pages/Nginx/Streams/Table.tsx index 4b9ff7d600..9ccddf73dc 100644 --- a/frontend/src/pages/Nginx/Streams/Table.tsx +++ b/frontend/src/pages/Nginx/Streams/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { Stream } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, columnHelper.accessor((row: any) => row, { id: "incomingPort", header: intl.formatMessage({ id: "column.incoming-port" }), + sortingFn: (a, b) => (a.original.incomingPort ?? 0) - (b.original.incomingPort ?? 0), cell: (info: any) => { const value = info.getValue(); return ; @@ -48,6 +56,11 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, columnHelper.accessor((row: any) => row, { id: "forwardHttpCode", header: intl.formatMessage({ id: "column.destination" }), + sortingFn: (a, b) => { + const aVal = `${a.original.forwardingHost}:${a.original.forwardingPort}`; + const bVal = `${b.original.forwardingHost}:${b.original.forwardingPort}`; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return `${value.forwardingHost}:${value.forwardingPort}`; @@ -55,6 +68,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, }), columnHelper.accessor((row: any) => row, { id: "tcpForwarding", + enableSorting: false, header: intl.formatMessage({ id: "column.protocol" }), cell: (info: any) => { const value = info.getValue(); @@ -76,6 +90,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -157,10 +172,15 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, [columnHelper, onEdit, onDisableToggle, onDelete], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching,