From 3993e5b705e59d40980d81a31d6a445d3a709f47 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 7 Nov 2025 18:03:33 -0800 Subject: [PATCH] add sitcky table cols for left and right cols --- messages/en-US.json | 3 +- src/components/AdminIdpDataTable.tsx | 3 + src/components/AdminIdpTable.tsx | 22 +- src/components/AdminUsersDataTable.tsx | 3 + src/components/AdminUsersTable.tsx | 74 +++--- src/components/ApiKeysDataTable.tsx | 3 + src/components/ApiKeysTable.tsx | 22 +- src/components/BlueprintsTable.tsx | 9 +- src/components/ClientsTable.tsx | 229 ++++++++---------- src/components/DomainsDataTable.tsx | 3 + src/components/DomainsTable.tsx | 74 +++--- src/components/InvitationsDataTable.tsx | 3 + src/components/InvitationsTable.tsx | 7 +- src/components/LicenseKeysDataTable.tsx | 9 +- src/components/OrgApiKeysDataTable.tsx | 3 + src/components/OrgApiKeysTable.tsx | 22 +- src/components/PolicyDataTable.tsx | 3 + src/components/PolicyTable.tsx | 50 ++-- src/components/ResourcesTable.tsx | 300 +++++++++++++----------- src/components/RolesDataTable.tsx | 3 + src/components/RolesTable.tsx | 6 +- src/components/ShareLinksDataTable.tsx | 3 + src/components/ShareLinksTable.tsx | 6 +- src/components/SitesDataTable.tsx | 2 + src/components/SitesTable.tsx | 22 +- src/components/UsersDataTable.tsx | 3 + src/components/UsersTable.tsx | 122 ++++------ src/components/private/OrgIdpTable.tsx | 19 +- src/components/ui/data-table.tsx | 281 ++++++++++++---------- 29 files changed, 689 insertions(+), 620 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index c74719f3..08371288 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2156,5 +2156,6 @@ "checkSelectedStatus": "Check Status of Selected", "clients": "Clients", "accessClientSelect": "Select machine clients", - "resourceClientDescription": "Machine clients that can access this resource" + "resourceClientDescription": "Machine clients that can access this resource", + "regenerate": "Regenerate" } diff --git a/src/components/AdminIdpDataTable.tsx b/src/components/AdminIdpDataTable.tsx index 63a0b4bb..26ec6b31 100644 --- a/src/components/AdminIdpDataTable.tsx +++ b/src/components/AdminIdpDataTable.tsx @@ -35,6 +35,9 @@ export function IdpDataTable({ }} onRefresh={onRefresh} isRefreshing={isRefreshing} + enableColumnVisibility={true} + stickyLeftColumn="name" + stickyRightColumn="actions" /> ); } diff --git a/src/components/AdminIdpTable.tsx b/src/components/AdminIdpTable.tsx index 75713845..d4e278e6 100644 --- a/src/components/AdminIdpTable.tsx +++ b/src/components/AdminIdpTable.tsx @@ -96,6 +96,7 @@ export default function IdpTable({ idps }: Props) { }, { accessorKey: "name", + enableHiding: false, header: ({ column }) => { return ( - +
+
); } diff --git a/src/components/AdminUsersDataTable.tsx b/src/components/AdminUsersDataTable.tsx index b0f38587..2eb3c2e2 100644 --- a/src/components/AdminUsersDataTable.tsx +++ b/src/components/AdminUsersDataTable.tsx @@ -32,6 +32,9 @@ export function UsersDataTable({ searchColumn="email" onRefresh={onRefresh} isRefreshing={isRefreshing} + enableColumnVisibility={true} + stickyLeftColumn="username" + stickyRightColumn="actions" /> ); } diff --git a/src/components/AdminUsersTable.tsx b/src/components/AdminUsersTable.tsx index d48e2b38..7bb2910b 100644 --- a/src/components/AdminUsersTable.tsx +++ b/src/components/AdminUsersTable.tsx @@ -103,6 +103,7 @@ export default function UsersTable({ users }: Props) { }, { accessorKey: "username", + enableHiding: false, header: ({ column }) => { return ( - - - - - - { - setSelected(r); - setIsDeleteModalOpen(true); - }} - > - {t("delete")} - - - - - +
+ + + + + + { + setSelected(r); + setIsDeleteModalOpen(true); + }} + > + {t("delete")} + + + + +
); } } diff --git a/src/components/ApiKeysDataTable.tsx b/src/components/ApiKeysDataTable.tsx index 58ab9252..c4f93136 100644 --- a/src/components/ApiKeysDataTable.tsx +++ b/src/components/ApiKeysDataTable.tsx @@ -59,6 +59,9 @@ export function ApiKeysDataTable({ addButtonText={t('apiKeysAdd')} onRefresh={onRefresh} isRefreshing={isRefreshing} + enableColumnVisibility={true} + stickyLeftColumn="name" + stickyRightColumn="actions" /> ); } diff --git a/src/components/ApiKeysTable.tsx b/src/components/ApiKeysTable.tsx index 5550b8d8..3230debd 100644 --- a/src/components/ApiKeysTable.tsx +++ b/src/components/ApiKeysTable.tsx @@ -88,6 +88,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { const columns: ColumnDef[] = [ { accessorKey: "name", + enableHiding: false, header: ({ column }) => { return ( - +
+
); } diff --git a/src/components/BlueprintsTable.tsx b/src/components/BlueprintsTable.tsx index d35d3729..0289b113 100644 --- a/src/components/BlueprintsTable.tsx +++ b/src/components/BlueprintsTable.tsx @@ -61,6 +61,7 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) { }, { accessorKey: "name", + enableHiding: false, header: ({ column }) => { return ( + + + + {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} + + ); + })} + + + + ); + }, cell: ({ row }) => { const clientRow = row.original; return !clientRow.userId ? ( -
- - - +
+
) : null; } @@ -693,122 +742,6 @@ export default function ClientsTable({
- {currentView === "user" && - userTable - .getAllColumns() - .some((column) => - column.getCanHide() - ) && ( - - - - - - - {t("toggleColumns") || - "Toggle columns"} - - - {userTable - .getAllColumns() - .filter((column) => - column.getCanHide() - ) - .map((column) => { - return ( - - column.toggleVisibility( - !!value - ) - } - > - {typeof column - .columnDef - .header === - "string" - ? column - .columnDef - .header - : column.id} - - ); - })} - - - )} - {currentView === "machine" && - machineTable - .getAllColumns() - .some((column) => - column.getCanHide() - ) && ( - - - - - - - {t("toggleColumns") || - "Toggle columns"} - - - {machineTable - .getAllColumns() - .filter((column) => - column.getCanHide() - ) - .map((column) => { - return ( - - column.toggleVisibility( - !!value - ) - } - > - {typeof column - .columnDef - .header === - "string" - ? column - .columnDef - .header - : column.id} - - ); - })} - - - )}
- +
+
{machineTable .getHeaderGroups() @@ -923,6 +877,15 @@ export default function ClientsTable({ .map((header) => ( {header.isPlaceholder ? null @@ -958,6 +921,15 @@ export default function ClientsTable({ key={ cell.id } + className={`whitespace-nowrap ${ + cell.column.id === + "actions" + ? "sticky right-0 z-10 w-auto min-w-fit bg-card" + : cell.column.id === + "name" + ? "md:sticky md:left-0 z-10 bg-card" + : "" + }`} > {flexRender( cell @@ -982,6 +954,7 @@ export default function ClientsTable({ )}
+
({ onAdd={onAdd} onRefresh={onRefresh} isRefreshing={isRefreshing} + enableColumnVisibility={true} + stickyLeftColumn="baseDomain" + stickyRightColumn="actions" /> ); } diff --git a/src/components/DomainsTable.tsx b/src/components/DomainsTable.tsx index a4c22954..bc300470 100644 --- a/src/components/DomainsTable.tsx +++ b/src/components/DomainsTable.tsx @@ -137,6 +137,7 @@ export default function DomainsTable({ domains, orgId }: Props) { const columns: ColumnDef[] = [ { accessorKey: "baseDomain", + enableHiding: false, header: ({ column }) => { return ( + + + + + {t("viewSettings")} + + + { + setSelectedDomain(domain); + setIsDeleteModalOpen(true); + }} + > + + {t("delete")} + + + + {domain.failed && ( -
- - - - - - - - {t("viewSettings")} - - - { - setSelectedDomain(domain); - setIsDeleteModalOpen(true); - }} - > - - {t("delete")} - - - - -
{/*
); diff --git a/src/components/LicenseKeysDataTable.tsx b/src/components/LicenseKeysDataTable.tsx index 59a663be..980a3b89 100644 --- a/src/components/LicenseKeysDataTable.tsx +++ b/src/components/LicenseKeysDataTable.tsx @@ -33,6 +33,7 @@ export function LicenseKeysDataTable({ const columns: ColumnDef[] = [ { accessorKey: "licenseKey", + enableHiding: false, header: ({ column }) => { return ( - +
+
); } diff --git a/src/components/PolicyDataTable.tsx b/src/components/PolicyDataTable.tsx index 89c1ed19..467e67f9 100644 --- a/src/components/PolicyDataTable.tsx +++ b/src/components/PolicyDataTable.tsx @@ -28,6 +28,9 @@ export function PolicyDataTable({ searchColumn="orgId" addButtonText={t('orgPoliciesAdd')} onAdd={onAdd} + enableColumnVisibility={true} + stickyLeftColumn="orgId" + stickyRightColumn="actions" /> ); } diff --git a/src/components/PolicyTable.tsx b/src/components/PolicyTable.tsx index 988b8324..22cad783 100644 --- a/src/components/PolicyTable.tsx +++ b/src/components/PolicyTable.tsx @@ -37,34 +37,9 @@ interface Props { export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props) { const t = useTranslations(); const columns: ColumnDef[] = [ - { - id: "dots", - cell: ({ row }) => { - const r = row.original; - - return ( - - - - - - { - onDelete(r.orgId); - }} - > - {t('delete')} - - - - ); - } - }, { accessorKey: "orgId", + enableHiding: false, header: ({ column }) => { return ( + + + { + onDelete(policy.orgId); + }} + > + {t('delete')} + + + + + + + {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} + + ); + })} + + +
+ ); + }, cell: ({ row }) => { const resourceRow = row.original; return ( -
- - - +
+
); } @@ -800,6 +849,7 @@ export default function ResourcesTable({ const internalColumns: ColumnDef[] = [ { accessorKey: "name", + enableHiding: false, header: ({ column }) => { return ( + + + + {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} + + ); + })} + + +
+ ); + }, cell: ({ row }) => { const resourceRow = row.original; return ( -
- +
); } @@ -1090,122 +1188,6 @@ 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} - - ); - })} - - - )}
- +
+
{internalTable .getHeaderGroups() @@ -1324,6 +1327,15 @@ export default function ResourcesTable({ .map((header) => ( {header.isPlaceholder ? null @@ -1359,6 +1371,15 @@ export default function ResourcesTable({ key={ cell.id } + className={`whitespace-nowrap ${ + cell.column.id === + "actions" + ? "sticky right-0 z-10 w-auto min-w-fit bg-card" + : cell.column.id === + "name" + ? "md:sticky md:left-0 z-10 bg-card" + : "" + }`} > {flexRender( cell @@ -1387,6 +1408,7 @@ export default function ResourcesTable({ )}
+
({ onRefresh={onRefresh} isRefreshing={isRefreshing} addButtonText={t('accessRolesAdd')} + enableColumnVisibility={true} + stickyLeftColumn="name" + stickyRightColumn="actions" /> ); } diff --git a/src/components/RolesTable.tsx b/src/components/RolesTable.tsx index 805062c1..ee1f498e 100644 --- a/src/components/RolesTable.tsx +++ b/src/components/RolesTable.tsx @@ -64,6 +64,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) { const columns: ColumnDef[] = [ { accessorKey: "name", + enableHiding: false, header: ({ column }) => { return ( - +
+
); } diff --git a/src/components/UsersDataTable.tsx b/src/components/UsersDataTable.tsx index db12b697..7324b8b4 100644 --- a/src/components/UsersDataTable.tsx +++ b/src/components/UsersDataTable.tsx @@ -36,6 +36,9 @@ export function UsersDataTable({ onRefresh={onRefresh} isRefreshing={isRefreshing} addButtonText={t('accessUserCreate')} + enableColumnVisibility={true} + stickyLeftColumn="displayUsername" + stickyRightColumn="actions" /> ); } diff --git a/src/components/UsersTable.tsx b/src/components/UsersTable.tsx index 36ca88c3..5b3903e8 100644 --- a/src/components/UsersTable.tsx +++ b/src/components/UsersTable.tsx @@ -73,6 +73,7 @@ export default function UsersTable({ users: u }: UsersTableProps) { const columns: ColumnDef[] = [ { accessorKey: "displayUsername", + enableHiding: false, header: ({ column }) => { return (
); @@ -143,21 +141,58 @@ export default function UsersTable({ users: u }: UsersTableProps) { }, { id: "actions", - header: () => ({t("actions")}), + enableHiding: false, + header: () => , cell: ({ row }) => { const userRow = row.original; return ( -
- {userRow.isOwner && ( - - )} +
+
+ {!userRow.isOwner && ( + <> + + + + + + + + {t("accessUsersManage")} + + + {`${userRow.username}-${userRow.idpId}` !== + `${user?.username}-${user?.idpId}` && ( + { + setIsDeleteModalOpen( + true + ); + setSelectedUser( + userRow + ); + }} + > + + {t("accessUserRemove")} + + + )} + + + + )} +
{!userRow.isOwner && ( )} - <> -
- {userRow.isOwner && ( - - )} - {!userRow.isOwner && ( - <> - - - - - - - - {t("accessUsersManage")} - - - {`${userRow.username}-${userRow.idpId}` !== - `${user?.username}-${user?.idpId}` && ( - { - setIsDeleteModalOpen( - true - ); - setSelectedUser( - userRow - ); - }} - > - - {t( - "accessUserRemove" - )} - - - )} - - - - )} -
-
); } @@ -273,9 +255,7 @@ export default function UsersTable({ users: u }: UsersTableProps) { }} dialog={
-

- {t("userQuestionOrgRemove")} -

+

{t("userQuestionOrgRemove")}

{t("userMessageOrgRemove")}

} diff --git a/src/components/private/OrgIdpTable.tsx b/src/components/private/OrgIdpTable.tsx index 5e3d25be..5e427b31 100644 --- a/src/components/private/OrgIdpTable.tsx +++ b/src/components/private/OrgIdpTable.tsx @@ -121,16 +121,7 @@ export default function IdpTable({ idps, orgId }: Props) { cell: ({ row }) => { const siteRow = row.original; return ( -
- - - +
+
); } diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index 5faa64b2..fadf26f5 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -152,6 +152,8 @@ type DataTableProps = { columnVisibility?: Record; enableColumnVisibility?: boolean; persistColumnVisibility?: boolean | string; + stickyLeftColumn?: string; // Column ID or accessorKey for left sticky column + stickyRightColumn?: string; // Column ID or accessorKey for right sticky column (typically "actions") }; export function DataTable({ @@ -171,7 +173,9 @@ export function DataTable({ defaultPageSize = 20, columnVisibility: defaultColumnVisibility, enableColumnVisibility = false, - persistColumnVisibility = false + persistColumnVisibility = false, + stickyLeftColumn, + stickyRightColumn }: DataTableProps) { const t = useTranslations(); @@ -290,6 +294,28 @@ export function DataTable({ } }; + // Helper function to check if a column should be sticky + const isStickyColumn = (columnId: string | undefined, accessorKey: string | undefined, position: "left" | "right"): boolean => { + if (position === "left" && stickyLeftColumn) { + return columnId === stickyLeftColumn || accessorKey === stickyLeftColumn; + } + if (position === "right" && stickyRightColumn) { + return columnId === stickyRightColumn || accessorKey === stickyRightColumn; + } + return false; + }; + + // Get sticky column classes + const getStickyClasses = (columnId: string | undefined, accessorKey: string | undefined): string => { + if (isStickyColumn(columnId, accessorKey, "left")) { + return "md:sticky md:left-0 z-10 bg-card"; + } + if (isStickyColumn(columnId, accessorKey, "right")) { + return "sticky right-0 z-10 w-auto min-w-fit bg-card"; + } + return ""; + }; + return (
@@ -329,131 +355,150 @@ 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 && ( - +
+ +
)}
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef - .header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const columnId = header.column.id; + const accessorKey = (header.column.columnDef as any).accessorKey as string | undefined; + const stickyClasses = getStickyClasses(columnId, accessorKey); + const isRightSticky = isStickyColumn(columnId, accessorKey, "right"); + const hasHideableColumns = enableColumnVisibility && + table.getAllColumns().some((col) => col.getCanHide()); + + return ( + + {header.isPlaceholder ? null : ( + isRightSticky && hasHideableColumns ? ( +
+ + + + + + + {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} + + ); + })} + + +
+ {flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+
+ ) : ( + flexRender( + header.column.columnDef.header, + header.getContext() + ) + ) + )} +
+ ); + })}
- )) - ) : ( - - - No results found. - - - )} - -
+ ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + const columnId = cell.column.id; + const accessorKey = (cell.column.columnDef as any).accessorKey as string | undefined; + const stickyClasses = getStickyClasses(columnId, accessorKey); + const isRightSticky = isStickyColumn(columnId, accessorKey, "right"); + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ); + })} + + )) + ) : ( + + + No results found. + + + )} + + +