From 2274404324d2803f3e2a5afbddb4755f604a952e Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 5 Nov 2025 10:29:29 -0800 Subject: [PATCH] update tables --- server/routers/client/listClients.ts | 12 +- .../remote-exit-nodes/ExitNodesTable.tsx | 3 +- src/app/[orgId]/settings/clients/page.tsx | 3 + .../resources/[niceId]/proxy/page.tsx | 13 +- .../resources/[niceId]/rules/page.tsx | 11 +- .../settings/resources/create/page.tsx | 11 +- src/app/admin/users/AdminUsersTable.tsx | 30 +- src/components/AdminIdpTable.tsx | 21 +- src/components/AdminUsersTable.tsx | 22 +- src/components/ApiKeysTable.tsx | 31 +- src/components/BlueprintsTable.tsx | 6 +- src/components/ClientInfoCard.tsx | 4 +- src/components/ClientsDataTable.tsx | 8 +- src/components/ClientsTable.tsx | 71 +++-- src/components/ContainersSelector.tsx | 16 +- src/components/DNSRecordsDataTable.tsx | 1 - src/components/DomainsTable.tsx | 21 +- src/components/InvitationsTable.tsx | 6 +- src/components/LicenseKeysDataTable.tsx | 6 +- src/components/OrgApiKeysTable.tsx | 26 +- src/components/PolicyTable.tsx | 9 +- src/components/ResourcesTable.tsx | 292 ++++++++++++------ src/components/RolesTable.tsx | 8 +- src/components/ShareLinksTable.tsx | 9 +- src/components/SitesDataTable.tsx | 8 +- src/components/SitesTable.tsx | 26 +- src/components/UsersTable.tsx | 53 ++-- src/components/private/OrgIdpTable.tsx | 20 +- src/components/ui/data-table.tsx | 125 ++++++-- 29 files changed, 540 insertions(+), 332 deletions(-) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 209b54b4..729b59b1 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -1,4 +1,4 @@ -import { db, olms } from "@server/db"; +import { db, olms, users } from "@server/db"; import { clients, orgs, @@ -19,7 +19,7 @@ import { OpenAPITags, registry } from "@server/openApi"; import NodeCache from "node-cache"; import semver from "semver"; -const olmVersionCache = new NodeCache({ stdTTL: 3600 }); +const olmVersionCache = new NodeCache({ stdTTL: 3600 }); async function getLatestOlmVersion(): Promise { try { @@ -29,7 +29,7 @@ async function getLatestOlmVersion(): Promise { } const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 1500); + const timeoutId = setTimeout(() => controller.abort(), 1500); const response = await fetch( "https://api.github.com/repos/fosrl/olm/tags", @@ -112,11 +112,15 @@ function queryClients(orgId: string, accessibleClientIds: number[]) { orgName: orgs.name, type: clients.type, online: clients.online, - olmVersion: olms.version + olmVersion: olms.version, + userId: clients.userId, + username: users.username, + userEmail: users.email }) .from(clients) .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) .leftJoin(olms, eq(clients.clientId, olms.clientId)) + .leftJoin(users, eq(clients.userId, users.userId)) .where( and( inArray(clients.clientId, accessibleClientIds), diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx index 6e9ab237..6cf94328 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx @@ -230,10 +230,11 @@ export default function ExitNodesTable({ }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const nodeRow = row.original; return ( -
+
); @@ -238,13 +238,9 @@ export default function UsersTable({ users }: Props) { }} dialog={
-

- {t("userQuestionRemove")} -

+

{t("userQuestionRemove")}

-

- {t("userMessageRemove")} -

+

{t("userMessageRemove")}

} buttonText={t("userDeleteConfirm")} diff --git a/src/components/AdminIdpTable.tsx b/src/components/AdminIdpTable.tsx index cea32da3..75713845 100644 --- a/src/components/AdminIdpTable.tsx +++ b/src/components/AdminIdpTable.tsx @@ -133,10 +133,19 @@ export default function IdpTable({ idps }: Props) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const siteRow = row.original; return ( -
+
+ + + -
); } diff --git a/src/components/AdminUsersTable.tsx b/src/components/AdminUsersTable.tsx index 5030377a..d48e2b38 100644 --- a/src/components/AdminUsersTable.tsx +++ b/src/components/AdminUsersTable.tsx @@ -201,11 +201,21 @@ export default function UsersTable({ users }: Props) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const r = row.original; return ( <> -
+
+
); diff --git a/src/components/ApiKeysTable.tsx b/src/components/ApiKeysTable.tsx index 1fcae24d..5550b8d8 100644 --- a/src/components/ApiKeysTable.tsx +++ b/src/components/ApiKeysTable.tsx @@ -104,7 +104,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { }, { accessorKey: "key", - header: t("key"), + header: () => ({t("key")}), cell: ({ row }) => { const r = row.original; return {r.key}; @@ -112,7 +112,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { }, { accessorKey: "createdAt", - header: t("createdAt"), + header: () => ({t("createdAt")}), cell: ({ row }) => { const r = row.original; return {moment(r.createdAt).format("lll")} ; @@ -120,10 +120,19 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const r = row.original; return ( -
+
+ + + - -
); } @@ -178,13 +179,9 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { }} dialog={
-

- {t("apiKeysQuestionRemove")} -

+

{t("apiKeysQuestionRemove")}

-

- {t("apiKeysMessageRemove")} -

+

{t("apiKeysMessageRemove")}

} buttonText={t("apiKeysDeleteConfirm")} diff --git a/src/components/BlueprintsTable.tsx b/src/components/BlueprintsTable.tsx index ee1dd095..d35d3729 100644 --- a/src/components/BlueprintsTable.tsx +++ b/src/components/BlueprintsTable.tsx @@ -157,12 +157,10 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) { }, { id: "actions", - header: () => { - return null; - }, + header: () => ({t("actions")}), cell: ({ row }) => { return ( -
+
+ ); + }, + cell: ({ row }) => { + const r = row.original; + return r.userId ? ( + + + + ) : ( + "-" + ); + } + }, // { // accessorKey: "siteName", // header: ({ column }) => { @@ -239,9 +273,7 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) {
{originalRow.olmUpdateAvailable && ( - + )}
); @@ -265,11 +297,19 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const clientRow = row.original; return ( -
- +
+ + + -
); } @@ -321,12 +353,8 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) { }} dialog={
-

- {t("deleteClientQuestion")} -

-

- {t("clientMessageRemove")} -

+

{t("deleteClientQuestion")}

+

{t("clientMessageRemove")}

} buttonText="Confirm Delete Client" @@ -344,6 +372,11 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) { }} onRefresh={refreshData} isRefreshing={isRefreshing} + columnVisibility={{ + client: false, + subnet: false + }} + enableColumnVisibility={true} /> ); diff --git a/src/components/ContainersSelector.tsx b/src/components/ContainersSelector.tsx index b97e0eeb..0f1f9ddd 100644 --- a/src/components/ContainersSelector.tsx +++ b/src/components/ContainersSelector.tsx @@ -184,14 +184,14 @@ const DockerContainersTable: FC<{ const columns: ColumnDef[] = [ { accessorKey: "name", - header: t("containerName"), + header: () => ({t("containerName")}), cell: ({ row }) => (
{row.original.name}
) }, { accessorKey: "image", - header: t("containerImage"), + header: () => ({t("containerImage")}), cell: ({ row }) => (
{row.original.image} @@ -200,7 +200,7 @@ const DockerContainersTable: FC<{ }, { accessorKey: "state", - header: t("containerState"), + header: () => ({t("containerState")}), cell: ({ row }) => ( ({t("containerNetworks")}), cell: ({ row }) => { const networks = Object.keys(row.original.networks); return ( @@ -233,7 +233,7 @@ const DockerContainersTable: FC<{ }, { accessorKey: "hostname", - header: t("containerHostnameIp"), + header: () => ({t("containerHostnameIp")}), enableHiding: false, cell: ({ row }) => (
@@ -243,7 +243,7 @@ const DockerContainersTable: FC<{ }, { accessorKey: "labels", - header: t("containerLabels"), + header: () => ({t("containerLabels")}), cell: ({ row }) => { const labels = row.original.labels || {}; const labelEntries = Object.entries(labels); @@ -295,7 +295,7 @@ const DockerContainersTable: FC<{ }, { accessorKey: "ports", - header: t("containerPorts"), + header: () => ({t("containerPorts")}), enableHiding: false, cell: ({ row }) => { const ports = getExposedPorts(row.original); @@ -353,7 +353,7 @@ const DockerContainersTable: FC<{ }, { id: "actions", - header: t("containerActions"), + header: () => ({t("containerActions")}), cell: ({ row }) => { const ports = getExposedPorts(row.original); return ( diff --git a/src/components/DNSRecordsDataTable.tsx b/src/components/DNSRecordsDataTable.tsx index 418ec9f2..2a6b75fd 100644 --- a/src/components/DNSRecordsDataTable.tsx +++ b/src/components/DNSRecordsDataTable.tsx @@ -124,7 +124,6 @@ export function DNSRecordsDataTable({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( diff --git a/src/components/DomainsTable.tsx b/src/components/DomainsTable.tsx index 3e12bab5..a4c22954 100644 --- a/src/components/DomainsTable.tsx +++ b/src/components/DomainsTable.tsx @@ -209,16 +209,16 @@ export default function DomainsTable({ domains, orgId }: Props) { }, { id: "actions", + header: () => {t("actions")}, cell: ({ row }) => { const domain = row.original; const isRestarting = restartingDomains.has(domain.domainId); return ( -
+
{domain.failed && ( )} + + +
@@ -266,15 +274,6 @@ export default function DomainsTable({ domains, orgId }: Props) { - - - -
{/* + -
); } diff --git a/src/components/PolicyTable.tsx b/src/components/PolicyTable.tsx index 81fed28f..988b8324 100644 --- a/src/components/PolicyTable.tsx +++ b/src/components/PolicyTable.tsx @@ -102,7 +102,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props info={mapping} /> ) : ( - "--" + "-" ); } }, @@ -129,18 +129,19 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props info={mapping} /> ) : ( - "--" + "-" ); } }, { id: "actions", + header: () => ({t('actions')}), cell: ({ row }) => { const policy = row.original; return ( -
+
+ -
); } @@ -545,14 +555,14 @@ export default function ResourcesTable({ }, { accessorKey: "siteName", - header: t("siteName"), + header: () => ({t("siteName")}), cell: ({ row }) => { const resourceRow = row.original; return ( - @@ -562,7 +572,7 @@ export default function ResourcesTable({ }, { accessorKey: "protocol", - header: t("protocol"), + header: () => ({t("protocol")}), cell: ({ row }) => { const resourceRow = row.original; return {resourceRow.protocol.toUpperCase()}; @@ -570,7 +580,7 @@ export default function ResourcesTable({ }, { accessorKey: "proxyPort", - header: t("proxyPort"), + header: () => ({t("proxyPort")}), cell: ({ row }) => { const resourceRow = row.original; return ( @@ -583,7 +593,7 @@ export default function ResourcesTable({ }, { accessorKey: "destination", - header: t("resourcesTableDestination"), + header: () => ({t("resourcesTableDestination")}), cell: ({ row }) => { const resourceRow = row.original; const destination = `${resourceRow.destinationIp}:${resourceRow.destinationPort}`; @@ -593,10 +603,20 @@ export default function ResourcesTable({ { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const resourceRow = row.original; return ( -
+
+
); } @@ -647,6 +657,7 @@ export default function ResourcesTable({ onColumnFiltersChange: setProxyColumnFilters, getFilteredRowModel: getFilteredRowModel(), onGlobalFilterChange: setProxyGlobalFilter, + onColumnVisibilityChange: setProxyColumnVisibility, initialState: { pagination: { pageSize: proxyPageSize, @@ -656,7 +667,8 @@ export default function ResourcesTable({ state: { sorting: proxySorting, columnFilters: proxyColumnFilters, - globalFilter: proxyGlobalFilter + globalFilter: proxyGlobalFilter, + columnVisibility: proxyColumnVisibility } }); @@ -670,6 +682,7 @@ export default function ResourcesTable({ onColumnFiltersChange: setInternalColumnFilters, getFilteredRowModel: getFilteredRowModel(), onGlobalFilterChange: setInternalGlobalFilter, + onColumnVisibilityChange: setInternalColumnVisibility, initialState: { pagination: { pageSize: internalPageSize, @@ -679,18 +692,19 @@ export default function ResourcesTable({ state: { sorting: internalSorting, columnFilters: internalColumnFilters, - globalFilter: internalGlobalFilter + globalFilter: internalGlobalFilter, + columnVisibility: internalColumnVisibility } }); const handleProxyPageSizeChange = (newPageSize: number) => { setProxyPageSize(newPageSize); - setStoredPageSize(newPageSize, 'proxy-resources'); + setStoredPageSize(newPageSize, "proxy-resources"); }; const handleInternalPageSizeChange = (newPageSize: number) => { setInternalPageSize(newPageSize); - setStoredPageSize(newPageSize, 'internal-resources'); + setStoredPageSize(newPageSize, "internal-resources"); }; return ( @@ -704,12 +718,8 @@ export default function ResourcesTable({ }} dialog={
-

- {t("resourceQuestionRemove")} -

-

- {t("resourceMessageRemove")} -

+

{t("resourceQuestionRemove")}

+

{t("resourceMessageRemove")}

} buttonText={t("resourceDeleteConfirm")} @@ -728,12 +738,8 @@ export default function ResourcesTable({ }} dialog={
-

- {t("resourceQuestionRemove")} -

-

- {t("resourceMessageRemove")} -

+

{t("resourceQuestionRemove")}

+

{t("resourceMessageRemove")}

} buttonText={t("resourceDeleteConfirm")} @@ -771,6 +777,80 @@ export default function ResourcesTable({ )}
+ {currentView === "proxy" && proxyTable.getAllColumns().some((column) => column.getCanHide()) && ( + + + + + + + {t("toggleColumns") || "Toggle columns"} + + + {proxyTable + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {typeof column.columnDef.header === "string" + ? column.columnDef.header + : column.id} + + ); + })} + + + )} + {currentView === "internal" && internalTable.getAllColumns().some((column) => column.getCanHide()) && ( + + + + + + + {t("toggleColumns") || "Toggle columns"} + + + {internalTable + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {typeof column.columnDef.header === "string" + ? column.columnDef.header + : column.id} + + ); + })} + + + )}
-
- {getActionButton()} -
+
{getActionButton()}
@@ -796,23 +876,25 @@ export default function ResourcesTable({ .getHeaderGroups() .map((headerGroup) => ( - {headerGroup.headers.map( - (header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} - - ) - )} + {headerGroup.headers + .filter((header) => header.column.getIsVisible()) + .map( + (header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header + .column + .columnDef + .header, + header.getContext() + )} + + ) + )} ))} @@ -867,7 +949,9 @@ export default function ResourcesTable({
@@ -897,23 +981,25 @@ export default function ResourcesTable({ .getHeaderGroups() .map((headerGroup) => ( - {headerGroup.headers.map( - (header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} - - ) - )} + {headerGroup.headers + .filter((header) => header.column.getIsVisible()) + .map( + (header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header + .column + .columnDef + .header, + header.getContext() + )} + + ) + )} ))} @@ -968,7 +1054,9 @@ export default function ResourcesTable({
diff --git a/src/components/RolesTable.tsx b/src/components/RolesTable.tsx index 292384a8..805062c1 100644 --- a/src/components/RolesTable.tsx +++ b/src/components/RolesTable.tsx @@ -80,18 +80,18 @@ export default function UsersTable({ roles: r }: RolesTableProps) { }, { accessorKey: "description", - header: t("description") + header: () => ({t("description")}) }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const roleRow = row.original; return ( -
+
@@ -254,10 +254,11 @@ export default function ShareLinksTable({ }, { id: "delete", + header: () => ({t("actions")}), cell: ({ row }) => { const resourceRow = row.original; return ( -
+
{/* */} {/* */} {/* + -
); } @@ -440,6 +440,12 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { } onRefresh={refreshData} isRefreshing={isRefreshing} + columnVisibility={{ + nice: false, + exitNode: false, + address: false + }} + enableColumnVisibility={true} /> ); diff --git a/src/components/UsersTable.tsx b/src/components/UsersTable.tsx index b6827dff..36ca88c3 100644 --- a/src/components/UsersTable.tsx +++ b/src/components/UsersTable.tsx @@ -143,10 +143,35 @@ export default function UsersTable({ users: u }: UsersTableProps) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const userRow = row.original; return ( -
+
+ {userRow.isOwner && ( + + )} + {!userRow.isOwner && ( + + + + )} <>
{userRow.isOwner && ( @@ -200,32 +225,6 @@ export default function UsersTable({ users: u }: UsersTableProps) { )}
- {userRow.isOwner && ( - - )} - {!userRow.isOwner && ( - - - - )}
); } diff --git a/src/components/private/OrgIdpTable.tsx b/src/components/private/OrgIdpTable.tsx index 059d175f..5e3d25be 100644 --- a/src/components/private/OrgIdpTable.tsx +++ b/src/components/private/OrgIdpTable.tsx @@ -117,10 +117,20 @@ export default function IdpTable({ idps, orgId }: Props) { }, { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => { const siteRow = row.original; return (
+ + + -
); } diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index ae94b12e..0a09c679 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -9,7 +9,8 @@ import { SortingState, getSortedRowModel, ColumnFiltersState, - getFilteredRowModel + getFilteredRowModel, + VisibilityState } from "@tanstack/react-table"; import { Table, @@ -23,7 +24,7 @@ import { Button } from "@app/components/ui/button"; import { useEffect, useMemo, useState } from "react"; import { Input } from "@app/components/ui/input"; import { DataTablePagination } from "@app/components/DataTablePagination"; -import { Plus, Search, RefreshCw } from "lucide-react"; +import { Plus, Search, RefreshCw, Columns } from "lucide-react"; import { Card, CardContent, @@ -32,16 +33,24 @@ import { } from "@app/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@app/components/ui/tabs"; import { useTranslations } from "next-intl"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger +} from "@app/components/ui/dropdown-menu"; const STORAGE_KEYS = { - PAGE_SIZE: 'datatable-page-size', - getTablePageSize: (tableId?: string) => + PAGE_SIZE: "datatable-page-size", + getTablePageSize: (tableId?: string) => tableId ? `${tableId}-size` : STORAGE_KEYS.PAGE_SIZE }; const getStoredPageSize = (tableId?: string, defaultSize = 20): number => { - if (typeof window === 'undefined') return defaultSize; - + if (typeof window === "undefined") return defaultSize; + try { const key = STORAGE_KEYS.getTablePageSize(tableId); const stored = localStorage.getItem(key); @@ -53,19 +62,19 @@ const getStoredPageSize = (tableId?: string, defaultSize = 20): number => { } } } catch (error) { - console.warn('Failed to read page size from localStorage:', error); + console.warn("Failed to read page size from localStorage:", error); } return defaultSize; }; const setStoredPageSize = (pageSize: number, tableId?: string): void => { - if (typeof window === 'undefined') return; - + if (typeof window === "undefined") return; + try { const key = STORAGE_KEYS.getTablePageSize(tableId); localStorage.setItem(key, pageSize.toString()); } catch (error) { - console.warn('Failed to save page size to localStorage:', error); + console.warn("Failed to save page size to localStorage:", error); } }; @@ -93,6 +102,8 @@ type DataTableProps = { defaultTab?: string; persistPageSize?: boolean | string; defaultPageSize?: number; + columnVisibility?: Record; + enableColumnVisibility?: boolean; }; export function DataTable({ @@ -109,13 +120,16 @@ export function DataTable({ tabs, defaultTab, persistPageSize = false, - defaultPageSize = 20 + defaultPageSize = 20, + columnVisibility: defaultColumnVisibility, + enableColumnVisibility = false }: DataTableProps) { const t = useTranslations(); - + // Determine table identifier for storage - const tableId = typeof persistPageSize === 'string' ? persistPageSize : undefined; - + const tableId = + typeof persistPageSize === "string" ? persistPageSize : undefined; + // Initialize page size from storage or default const [pageSize, setPageSize] = useState(() => { if (persistPageSize) { @@ -123,12 +137,15 @@ export function DataTable({ } return defaultPageSize; }); - + const [sorting, setSorting] = useState( defaultSort ? [defaultSort] : [] ); const [columnFilters, setColumnFilters] = useState([]); const [globalFilter, setGlobalFilter] = useState([]); + const [columnVisibility, setColumnVisibility] = useState( + defaultColumnVisibility || {} + ); const [activeTab, setActiveTab] = useState( defaultTab || tabs?.[0]?.id || "" ); @@ -157,16 +174,19 @@ export function DataTable({ onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onGlobalFilterChange: setGlobalFilter, + onColumnVisibilityChange: setColumnVisibility, initialState: { pagination: { pageSize: pageSize, pageIndex: 0 - } + }, + columnVisibility: defaultColumnVisibility || {} }, state: { sorting, columnFilters, - globalFilter + globalFilter, + columnVisibility } }); @@ -174,7 +194,7 @@ export function DataTable({ const currentPageSize = table.getState().pagination.pageSize; if (currentPageSize !== pageSize) { table.setPageSize(pageSize); - + // Persist to localStorage if enabled if (persistPageSize) { setStoredPageSize(pageSize, tableId); @@ -192,7 +212,7 @@ export function DataTable({ const handlePageSizeChange = (newPageSize: number) => { setPageSize(newPageSize); table.setPageSize(newPageSize); - + // Persist immediately when changed if (persistPageSize) { setStoredPageSize(newPageSize, tableId); @@ -238,6 +258,53 @@ export function DataTable({ )}
+ {enableColumnVisibility && + table + .getAllColumns() + .some((column) => column.getCanHide()) && ( + + + + + + + {t("toggleColumns") || "Toggle columns"} + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility( + !!value + ) + } + > + {typeof column.columnDef + .header === "string" + ? column.columnDef + .header + : column.id} + + ); + })} + + + )} {onRefresh && ( )} {onAdd && addButtonText && ( @@ -264,7 +333,10 @@ export function DataTable({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( - + {header.isPlaceholder ? null : flexRender( @@ -287,7 +359,10 @@ export function DataTable({ } > {row.getVisibleCells().map((cell) => ( - + {flexRender( cell.column.columnDef.cell, cell.getContext() @@ -309,8 +384,8 @@ export function DataTable({
-