add sitcky table cols for left and right cols

This commit is contained in:
miloschwartz
2025-11-07 18:03:33 -08:00
parent 47bcadb329
commit 3993e5b705
29 changed files with 689 additions and 620 deletions

View File

@@ -2156,5 +2156,6 @@
"checkSelectedStatus": "Check Status of Selected", "checkSelectedStatus": "Check Status of Selected",
"clients": "Clients", "clients": "Clients",
"accessClientSelect": "Select machine clients", "accessClientSelect": "Select machine clients",
"resourceClientDescription": "Machine clients that can access this resource" "resourceClientDescription": "Machine clients that can access this resource",
"regenerate": "Regenerate"
} }

View File

@@ -35,6 +35,9 @@ export function IdpDataTable<TData, TValue>({
}} }}
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -96,6 +96,7 @@ export default function IdpTable({ idps }: Props) {
}, },
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -133,19 +134,12 @@ export default function IdpTable({ idps }: Props) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const siteRow = row.original; const siteRow = row.original;
return ( return (
<div className="flex items-center"> <div className="flex items-center gap-2 justify-end">
<Link href={`/admin/idp/${siteRow.idpId}/general`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -176,6 +170,14 @@ export default function IdpTable({ idps }: Props) {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link href={`/admin/idp/${siteRow.idpId}/general`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }

View File

@@ -32,6 +32,9 @@ export function UsersDataTable<TData, TValue>({
searchColumn="email" searchColumn="email"
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="username"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -103,6 +103,7 @@ export default function UsersTable({ users }: Props) {
}, },
{ {
accessorKey: "username", accessorKey: "username",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -201,46 +202,45 @@ export default function UsersTable({ users }: Props) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const r = row.original; const r = row.original;
return ( return (
<> <div className="flex items-center gap-2 justify-end">
<div className="flex items-center gap-2"> <DropdownMenu>
<Button <DropdownMenuTrigger asChild>
variant={"outline"} <Button
onClick={() => { variant="ghost"
router.push(`/admin/users/${r.id}`); className="h-8 w-8 p-0"
}} >
> <span className="sr-only">
{t("edit")} Open menu
<ArrowRight className="ml-2 w-4 h-4" /> </span>
</Button> <MoreHorizontal className="h-4 w-4" />
<DropdownMenu> </Button>
<DropdownMenuTrigger asChild> </DropdownMenuTrigger>
<Button <DropdownMenuContent align="end">
variant="ghost" <DropdownMenuItem
className="h-8 w-8 p-0" onClick={() => {
> setSelected(r);
<span className="sr-only"> setIsDeleteModalOpen(true);
Open menu }}
</span> >
<MoreHorizontal className="h-4 w-4" /> {t("delete")}
</Button> </DropdownMenuItem>
</DropdownMenuTrigger> </DropdownMenuContent>
<DropdownMenuContent align="end"> </DropdownMenu>
<DropdownMenuItem <Button
onClick={() => { variant={"outline"}
setSelected(r); onClick={() => {
setIsDeleteModalOpen(true); router.push(`/admin/users/${r.id}`);
}} }}
> >
{t("delete")} {t("edit")}
</DropdownMenuItem> <ArrowRight className="ml-2 w-4 h-4" />
</DropdownMenuContent> </Button>
</DropdownMenu> </div>
</div>
</>
); );
} }
} }

View File

@@ -59,6 +59,9 @@ export function ApiKeysDataTable<TData, TValue>({
addButtonText={t('apiKeysAdd')} addButtonText={t('apiKeysAdd')}
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -88,6 +88,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
const columns: ColumnDef<ApiKeyRow>[] = [ const columns: ColumnDef<ApiKeyRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -120,19 +121,12 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const r = row.original; const r = row.original;
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 justify-end">
<Link href={`/admin/api-keys/${r.id}`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -162,6 +156,14 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link href={`/admin/api-keys/${r.id}`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }

View File

@@ -61,6 +61,7 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
}, },
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -157,10 +158,11 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<div className="flex"> <div className="flex justify-end">
<Button <Button
variant="outline" variant="outline"
className="items-center" className="items-center"
@@ -187,6 +189,9 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
title={t("blueprints")} title={t("blueprints")}
searchPlaceholder={t("searchBlueprintProgress")} searchPlaceholder={t("searchBlueprintProgress")}
searchColumn="name" searchColumn="name"
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
onAdd={() => { onAdd={() => {
router.push(`/${orgId}/settings/blueprints/create`); router.push(`/${orgId}/settings/blueprints/create`);
}} }}

View File

@@ -334,6 +334,7 @@ export default function ClientsTable({
const baseColumns: ColumnDef<ClientRow>[] = [ const baseColumns: ColumnDef<ClientRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -531,19 +532,59 @@ export default function ClientsTable({
if (hasRowsWithoutUserId) { if (hasRowsWithoutUserId) {
baseColumns.push({ baseColumns.push({
id: "actions", id: "actions",
header: () => <span className="p-3">{t("actions")}</span>, enableHiding: false,
header: ({ table }) => {
const hasHideableColumns = table
.getAllColumns()
.some((column) => column.getCanHide());
if (!hasHideableColumns) {
return <span className="p-3"></span>;
}
return (
<div className="flex flex-col items-end gap-1 p-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-7 w-7 p-0">
<Columns className="h-4 w-4" />
<span className="sr-only">
{t("columns") || "Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>
{t("toggleColumns") || "Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{typeof column.columnDef.header ===
"string"
? column.columnDef.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
},
cell: ({ row }) => { cell: ({ row }) => {
const clientRow = row.original; const clientRow = row.original;
return !clientRow.userId ? ( return !clientRow.userId ? (
<div className="flex items-center"> <div className="flex items-center gap-2 justify-end">
<Link
href={`/${clientRow.orgId}/settings/clients/${clientRow.id}`}
>
<Button variant={"outline"}>
Edit
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -570,6 +611,14 @@ export default function ClientsTable({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link
href={`/${clientRow.orgId}/settings/clients/${clientRow.id}`}
>
<Button variant={"outline"}>
Edit
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
) : null; ) : null;
} }
@@ -693,122 +742,6 @@ export default function ClientsTable({
</TabsList> </TabsList>
</div> </div>
<div className="flex items-center gap-2 sm:justify-end"> <div className="flex items-center gap-2 sm:justify-end">
{currentView === "user" &&
userTable
.getAllColumns()
.some((column) =>
column.getCanHide()
) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Columns className="mr-0 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">
{t("columns") ||
"Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48"
>
<DropdownMenuLabel>
{t("toggleColumns") ||
"Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{userTable
.getAllColumns()
.filter((column) =>
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(
value
) =>
column.toggleVisibility(
!!value
)
}
>
{typeof column
.columnDef
.header ===
"string"
? column
.columnDef
.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
{currentView === "machine" &&
machineTable
.getAllColumns()
.some((column) =>
column.getCanHide()
) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Columns className="mr-0 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">
{t("columns") ||
"Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48"
>
<DropdownMenuLabel>
{t("toggleColumns") ||
"Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{machineTable
.getAllColumns()
.filter((column) =>
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(
value
) =>
column.toggleVisibility(
!!value
)
}
>
{typeof column
.columnDef
.header ===
"string"
? column
.columnDef
.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
<div> <div>
<Button <Button
variant="outline" variant="outline"
@@ -828,7 +761,8 @@ export default function ClientsTable({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<TabsContent value="user"> <TabsContent value="user">
<Table> <div className="overflow-x-auto">
<Table>
<TableHeader> <TableHeader>
{userTable {userTable
.getHeaderGroups() .getHeaderGroups()
@@ -841,6 +775,15 @@ export default function ClientsTable({
.map((header) => ( .map((header) => (
<TableHead <TableHead
key={header.id} key={header.id}
className={`whitespace-nowrap ${
header.column.id ===
"actions"
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
: header.column.id ===
"name"
? "md:sticky md:left-0 z-10 bg-card"
: ""
}`}
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@@ -876,6 +819,15 @@ export default function ClientsTable({
key={ key={
cell.id 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( {flexRender(
cell cell
@@ -900,6 +852,7 @@ export default function ClientsTable({
)} )}
</TableBody> </TableBody>
</Table> </Table>
</div>
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={userTable} table={userTable}
@@ -910,7 +863,8 @@ export default function ClientsTable({
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="machine"> <TabsContent value="machine">
<Table> <div className="overflow-x-auto">
<Table>
<TableHeader> <TableHeader>
{machineTable {machineTable
.getHeaderGroups() .getHeaderGroups()
@@ -923,6 +877,15 @@ export default function ClientsTable({
.map((header) => ( .map((header) => (
<TableHead <TableHead
key={header.id} key={header.id}
className={`whitespace-nowrap ${
header.column.id ===
"actions"
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
: header.column.id ===
"name"
? "md:sticky md:left-0 z-10 bg-card"
: ""
}`}
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@@ -958,6 +921,15 @@ export default function ClientsTable({
key={ key={
cell.id 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( {flexRender(
cell cell
@@ -982,6 +954,7 @@ export default function ClientsTable({
)} )}
</TableBody> </TableBody>
</Table> </Table>
</div>
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={machineTable} table={machineTable}

View File

@@ -33,6 +33,9 @@ export function DomainsDataTable<TData, TValue>({
onAdd={onAdd} onAdd={onAdd}
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="baseDomain"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -137,6 +137,7 @@ export default function DomainsTable({ domains, orgId }: Props) {
const columns: ColumnDef<DomainRow>[] = [ const columns: ColumnDef<DomainRow>[] = [
{ {
accessorKey: "baseDomain", accessorKey: "baseDomain",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -209,13 +210,47 @@ export default function DomainsTable({ domains, orgId }: Props) {
}, },
{ {
id: "actions", id: "actions",
header: () => <span className="p-3">{t("actions")}</span>, enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const domain = row.original; const domain = row.original;
const isRestarting = restartingDomains.has(domain.domainId); const isRestarting = restartingDomains.has(domain.domainId);
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
Open menu
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
className="block w-full"
href={`/${orgId}/settings/domains/${domain.domainId}`}
>
<DropdownMenuItem>
{t("viewSettings")}
</DropdownMenuItem>
</Link>
<DropdownMenuItem
onClick={() => {
setSelectedDomain(domain);
setIsDeleteModalOpen(true);
}}
>
<span className="text-red-500">
{t("delete")}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{domain.failed && ( {domain.failed && (
<Button <Button
variant="outline" variant="outline"
@@ -240,41 +275,6 @@ export default function DomainsTable({ domains, orgId }: Props) {
<ArrowRight className="ml-2 w-4 h-4" /> <ArrowRight className="ml-2 w-4 h-4" />
</Button> </Button>
</Link> </Link>
<div className="flex items-center justify-end gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
Open menu
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
className="block w-full"
href={`/${orgId}/settings/domains/${domain.domainId}`}
>
<DropdownMenuItem>
{t("viewSettings")}
</DropdownMenuItem>
</Link>
<DropdownMenuItem
onClick={() => {
setSelectedDomain(domain);
setIsDeleteModalOpen(true);
}}
>
<span className="text-red-500">
{t("delete")}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* <Button {/* <Button
variant="secondary" variant="secondary"
size="sm" size="sm"

View File

@@ -32,6 +32,9 @@ export function InvitationsDataTable<TData, TValue>({
searchColumn="email" searchColumn="email"
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="email"
stickyRightColumn="dots"
/> />
); );
} }

View File

@@ -69,6 +69,7 @@ export default function InvitationsTable({
const columns: ColumnDef<InvitationRow>[] = [ const columns: ColumnDef<InvitationRow>[] = [
{ {
accessorKey: "email", accessorKey: "email",
enableHiding: false,
header: () => (<span className="p-3">{t("email")}</span>) header: () => (<span className="p-3">{t("email")}</span>)
}, },
{ {
@@ -91,6 +92,8 @@ export default function InvitationsTable({
}, },
{ {
id: "dots", id: "dots",
enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const invitation = row.original; const invitation = row.original;
return ( return (
@@ -119,13 +122,13 @@ export default function InvitationsTable({
</DropdownMenu> </DropdownMenu>
<Button <Button
variant={"secondary"} variant={"outline"}
onClick={() => { onClick={() => {
setIsRegenerateModalOpen(true); setIsRegenerateModalOpen(true);
setSelectedInvitation(invitation); setSelectedInvitation(invitation);
}} }}
> >
<span>{t("inviteRegenerate")}</span> {t("regenerate", { fallback: "Regenerate" })}
</Button> </Button>
</div> </div>
); );

View File

@@ -33,6 +33,7 @@ export function LicenseKeysDataTable({
const columns: ColumnDef<LicenseKeyCache>[] = [ const columns: ColumnDef<LicenseKeyCache>[] = [
{ {
accessorKey: "licenseKey", accessorKey: "licenseKey",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -123,9 +124,10 @@ export function LicenseKeysDataTable({
}, },
{ {
id: "delete", id: "delete",
header: () => <span className="p-3">{t("actions")}</span>, enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center space-x-2"> <div className="flex items-center gap-2 justify-end">
<Button variant={"outline"} <Button variant={"outline"}
onClick={() => onDelete(row.original)} onClick={() => onDelete(row.original)}
> >
@@ -146,6 +148,9 @@ export function LicenseKeysDataTable({
searchColumn="licenseKey" searchColumn="licenseKey"
onAdd={onCreate} onAdd={onCreate}
addButtonText={t("licenseKeyAdd")} addButtonText={t("licenseKeyAdd")}
enableColumnVisibility={true}
stickyLeftColumn="licenseKey"
stickyRightColumn="delete"
/> />
); );
} }

View File

@@ -34,6 +34,9 @@ export function OrgApiKeysDataTable<TData, TValue>({
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
addButtonText={t('apiKeysAdd')} addButtonText={t('apiKeysAdd')}
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -91,6 +91,7 @@ export default function OrgApiKeysTable({
const columns: ColumnDef<OrgApiKeyRow>[] = [ const columns: ColumnDef<OrgApiKeyRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -123,19 +124,12 @@ export default function OrgApiKeysTable({
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const r = row.original; const r = row.original;
return ( return (
<div className="flex items-center"> <div className="flex items-center gap-2 justify-end">
<Link href={`/${orgId}/settings/api-keys/${r.id}`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -167,6 +161,14 @@ export default function OrgApiKeysTable({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link href={`/${orgId}/settings/api-keys/${r.id}`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }

View File

@@ -28,6 +28,9 @@ export function PolicyDataTable<TData, TValue>({
searchColumn="orgId" searchColumn="orgId"
addButtonText={t('orgPoliciesAdd')} addButtonText={t('orgPoliciesAdd')}
onAdd={onAdd} onAdd={onAdd}
enableColumnVisibility={true}
stickyLeftColumn="orgId"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -37,34 +37,9 @@ interface Props {
export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props) { export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props) {
const t = useTranslations(); const t = useTranslations();
const columns: ColumnDef<PolicyRow>[] = [ const columns: ColumnDef<PolicyRow>[] = [
{
id: "dots",
cell: ({ row }) => {
const r = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">{t('openMenu')}</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
onDelete(r.orgId);
}}
>
<span className="text-red-500">{t('delete')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
},
{ {
accessorKey: "orgId", accessorKey: "orgId",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -135,14 +110,31 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t('actions')}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const policy = row.original; const policy = row.original;
return ( return (
<div className="flex items-center"> <div className="flex items-center gap-2 justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">{t('openMenu')}</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
onDelete(policy.orgId);
}}
>
<span className="text-red-500">{t('delete')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button <Button
variant={"outline"} variant={"outline"}
className="ml-2"
onClick={() => onEdit(policy)} onClick={() => onEdit(policy)}
> >
{t('edit')} {t('edit')}

View File

@@ -586,6 +586,7 @@ export default function ResourcesTable({
const proxyColumns: ColumnDef<ResourceRow>[] = [ const proxyColumns: ColumnDef<ResourceRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -748,19 +749,59 @@ export default function ResourcesTable({
}, },
{ {
id: "actions", id: "actions",
header: () => <span className="p-3">{t("actions")}</span>, enableHiding: false,
header: ({ table }) => {
const hasHideableColumns = table
.getAllColumns()
.some((column) => column.getCanHide());
if (!hasHideableColumns) {
return <span className="p-3"></span>;
}
return (
<div className="flex flex-col items-end gap-1 p-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-7 w-7 p-0">
<Columns className="h-4 w-4" />
<span className="sr-only">
{t("columns") || "Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>
{t("toggleColumns") || "Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{typeof column.columnDef.header ===
"string"
? column.columnDef.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
},
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 justify-end">
<Link
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -791,6 +832,14 @@ export default function ResourcesTable({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }
@@ -800,6 +849,7 @@ export default function ResourcesTable({
const internalColumns: ColumnDef<InternalResourceRow>[] = [ const internalColumns: ColumnDef<InternalResourceRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -902,20 +952,59 @@ export default function ResourcesTable({
{ {
id: "actions", id: "actions",
header: () => <span className="p-3">{t("actions")}</span>, enableHiding: false,
header: ({ table }) => {
const hasHideableColumns = table
.getAllColumns()
.some((column) => column.getCanHide());
if (!hasHideableColumns) {
return <span className="p-3"></span>;
}
return (
<div className="flex flex-col items-end gap-1 p-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-7 w-7 p-0">
<Columns className="h-4 w-4" />
<span className="sr-only">
{t("columns") || "Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>
{t("toggleColumns") || "Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{typeof column.columnDef.header ===
"string"
? column.columnDef.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
},
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 justify-end">
<Button
variant={"outline"}
onClick={() => {
setEditingResource(resourceRow);
setIsEditDialogOpen(true);
}}
>
{t("edit")}
</Button>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -940,6 +1029,15 @@ export default function ResourcesTable({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Button
variant={"outline"}
onClick={() => {
setEditingResource(resourceRow);
setIsEditDialogOpen(true);
}}
>
{t("edit")}
</Button>
</div> </div>
); );
} }
@@ -1090,122 +1188,6 @@ export default function ResourcesTable({
)} )}
</div> </div>
<div className="flex items-center gap-2 sm:justify-end"> <div className="flex items-center gap-2 sm:justify-end">
{currentView === "proxy" &&
proxyTable
.getAllColumns()
.some((column) =>
column.getCanHide()
) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Columns className="mr-0 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">
{t("columns") ||
"Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48"
>
<DropdownMenuLabel>
{t("toggleColumns") ||
"Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{proxyTable
.getAllColumns()
.filter((column) =>
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(
value
) =>
column.toggleVisibility(
!!value
)
}
>
{typeof column
.columnDef
.header ===
"string"
? column
.columnDef
.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
{currentView === "internal" &&
internalTable
.getAllColumns()
.some((column) =>
column.getCanHide()
) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Columns className="mr-0 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">
{t("columns") ||
"Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48"
>
<DropdownMenuLabel>
{t("toggleColumns") ||
"Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{internalTable
.getAllColumns()
.filter((column) =>
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(
value
) =>
column.toggleVisibility(
!!value
)
}
>
{typeof column
.columnDef
.header ===
"string"
? column
.columnDef
.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
<div> <div>
<Button <Button
variant="outline" variant="outline"
@@ -1225,7 +1207,8 @@ export default function ResourcesTable({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<TabsContent value="proxy"> <TabsContent value="proxy">
<Table> <div className="overflow-x-auto">
<Table>
<TableHeader> <TableHeader>
{proxyTable {proxyTable
.getHeaderGroups() .getHeaderGroups()
@@ -1238,6 +1221,15 @@ export default function ResourcesTable({
.map((header) => ( .map((header) => (
<TableHead <TableHead
key={header.id} key={header.id}
className={`whitespace-nowrap ${
header.column.id ===
"actions"
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
: header.column.id ===
"name"
? "md:sticky md:left-0 z-10 bg-card"
: ""
}`}
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@@ -1273,6 +1265,15 @@ export default function ResourcesTable({
key={ key={
cell.id 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( {flexRender(
cell cell
@@ -1301,6 +1302,7 @@ export default function ResourcesTable({
)} )}
</TableBody> </TableBody>
</Table> </Table>
</div>
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={proxyTable} table={proxyTable}
@@ -1311,7 +1313,8 @@ export default function ResourcesTable({
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="internal"> <TabsContent value="internal">
<Table> <div className="overflow-x-auto">
<Table>
<TableHeader> <TableHeader>
{internalTable {internalTable
.getHeaderGroups() .getHeaderGroups()
@@ -1324,6 +1327,15 @@ export default function ResourcesTable({
.map((header) => ( .map((header) => (
<TableHead <TableHead
key={header.id} key={header.id}
className={`whitespace-nowrap ${
header.column.id ===
"actions"
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
: header.column.id ===
"name"
? "md:sticky md:left-0 z-10 bg-card"
: ""
}`}
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@@ -1359,6 +1371,15 @@ export default function ResourcesTable({
key={ key={
cell.id 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( {flexRender(
cell cell
@@ -1387,6 +1408,7 @@ export default function ResourcesTable({
)} )}
</TableBody> </TableBody>
</Table> </Table>
</div>
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={internalTable} table={internalTable}

View File

@@ -36,6 +36,9 @@ export function RolesDataTable<TData, TValue>({
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
addButtonText={t('accessRolesAdd')} addButtonText={t('accessRolesAdd')}
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -64,6 +64,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
const columns: ColumnDef<RoleRow>[] = [ const columns: ColumnDef<RoleRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -84,12 +85,13 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const roleRow = row.original; const roleRow = row.original;
return ( return (
<div className="flex items-center"> <div className="flex items-center gap-2 justify-end">
<Button <Button
variant={"outline"} variant={"outline"}
disabled={roleRow.isAdmin || false} disabled={roleRow.isAdmin || false}

View File

@@ -36,6 +36,9 @@ export function ShareLinksDataTable<TData, TValue>({
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
addButtonText={t('shareCreate')} addButtonText={t('shareCreate')}
enableColumnVisibility={true}
stickyLeftColumn="resourceName"
stickyRightColumn="delete"
/> />
); );
} }

View File

@@ -105,6 +105,7 @@ export default function ShareLinksTable({
const columns: ColumnDef<ShareLinkRow>[] = [ const columns: ColumnDef<ShareLinkRow>[] = [
{ {
accessorKey: "resourceName", accessorKey: "resourceName",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -254,11 +255,12 @@ export default function ShareLinksTable({
}, },
{ {
id: "delete", id: "delete",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
return ( return (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2 justify-end">
{/* <DropdownMenu> */} {/* <DropdownMenu> */}
{/* <DropdownMenuTrigger asChild> */} {/* <DropdownMenuTrigger asChild> */}
{/* <Button variant="ghost" className="h-8 w-8 p-0"> */} {/* <Button variant="ghost" className="h-8 w-8 p-0"> */}

View File

@@ -44,6 +44,8 @@ export function SitesDataTable<TData, TValue>({
}} }}
columnVisibility={columnVisibility} columnVisibility={columnVisibility}
enableColumnVisibility={enableColumnVisibility} enableColumnVisibility={enableColumnVisibility}
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -109,6 +109,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
const columns: ColumnDef<SiteRow>[] = [ const columns: ColumnDef<SiteRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -361,19 +362,12 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
: []), : []),
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const siteRow = row.original; const siteRow = row.original;
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 justify-end">
<Link
href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -402,6 +396,14 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link
href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }

View File

@@ -36,6 +36,9 @@ export function UsersDataTable<TData, TValue>({
onRefresh={onRefresh} onRefresh={onRefresh}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
addButtonText={t('accessUserCreate')} addButtonText={t('accessUserCreate')}
enableColumnVisibility={true}
stickyLeftColumn="displayUsername"
stickyRightColumn="actions"
/> />
); );
} }

View File

@@ -73,6 +73,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
const columns: ColumnDef<UserRow>[] = [ const columns: ColumnDef<UserRow>[] = [
{ {
accessorKey: "displayUsername", accessorKey: "displayUsername",
enableHiding: false,
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -133,9 +134,6 @@ export default function UsersTable({ users: u }: UsersTableProps) {
return ( return (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{userRow.isOwner && (
<Crown className="w-4 h-4 text-yellow-600" />
)}
<span>{userRow.role}</span> <span>{userRow.role}</span>
</div> </div>
); );
@@ -143,21 +141,58 @@ export default function UsersTable({ users: u }: UsersTableProps) {
}, },
{ {
id: "actions", id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>), enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const userRow = row.original; const userRow = row.original;
return ( return (
<div className="flex items-center"> <div className="flex items-center justify-end">
{userRow.isOwner && ( <div>
<Button {!userRow.isOwner && (
variant={"outline"} <>
className="ml-2" <DropdownMenu>
disabled={true} <DropdownMenuTrigger asChild>
> <Button
{t("manage")} variant="ghost"
<ArrowRight className="ml-2 w-4 h-4" /> className="h-8 w-8 p-0"
</Button> >
)} <span className="sr-only">
{t("openMenu")}
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`}
className="block w-full"
>
<DropdownMenuItem>
{t("accessUsersManage")}
</DropdownMenuItem>
</Link>
{`${userRow.username}-${userRow.idpId}` !==
`${user?.username}-${user?.idpId}` && (
<DropdownMenuItem
onClick={() => {
setIsDeleteModalOpen(
true
);
setSelectedUser(
userRow
);
}}
>
<span className="text-red-500">
{t("accessUserRemove")}
</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
)}
</div>
{!userRow.isOwner && ( {!userRow.isOwner && (
<Link <Link
href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`} href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`}
@@ -172,59 +207,6 @@ export default function UsersTable({ users: u }: UsersTableProps) {
</Button> </Button>
</Link> </Link>
)} )}
<>
<div>
{userRow.isOwner && (
<MoreHorizontal className="h-4 w-4 opacity-0" />
)}
{!userRow.isOwner && (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
{t("openMenu")}
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`}
className="block w-full"
>
<DropdownMenuItem>
{t("accessUsersManage")}
</DropdownMenuItem>
</Link>
{`${userRow.username}-${userRow.idpId}` !==
`${user?.username}-${user?.idpId}` && (
<DropdownMenuItem
onClick={() => {
setIsDeleteModalOpen(
true
);
setSelectedUser(
userRow
);
}}
>
<span className="text-red-500">
{t(
"accessUserRemove"
)}
</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
)}
</div>
</>
</div> </div>
); );
} }
@@ -273,9 +255,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
}} }}
dialog={ dialog={
<div> <div>
<p> <p>{t("userQuestionOrgRemove")}</p>
{t("userQuestionOrgRemove")}
</p>
<p>{t("userMessageOrgRemove")}</p> <p>{t("userMessageOrgRemove")}</p>
</div> </div>
} }

View File

@@ -121,16 +121,7 @@ export default function IdpTable({ idps, orgId }: Props) {
cell: ({ row }) => { cell: ({ row }) => {
const siteRow = row.original; const siteRow = row.original;
return ( return (
<div className="flex items-center justify-end"> <div className="flex items-center gap-2 justify-end">
<Link href={`/${orgId}/settings/idp/${siteRow.idpId}/general`}>
<Button
variant={"outline"}
className="ml-2"
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -161,6 +152,14 @@ export default function IdpTable({ idps, orgId }: Props) {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Link href={`/${orgId}/settings/idp/${siteRow.idpId}/general`}>
<Button
variant={"outline"}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div> </div>
); );
} }

View File

@@ -152,6 +152,8 @@ type DataTableProps<TData, TValue> = {
columnVisibility?: Record<string, boolean>; columnVisibility?: Record<string, boolean>;
enableColumnVisibility?: boolean; enableColumnVisibility?: boolean;
persistColumnVisibility?: boolean | string; 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<TData, TValue>({ export function DataTable<TData, TValue>({
@@ -171,7 +173,9 @@ export function DataTable<TData, TValue>({
defaultPageSize = 20, defaultPageSize = 20,
columnVisibility: defaultColumnVisibility, columnVisibility: defaultColumnVisibility,
enableColumnVisibility = false, enableColumnVisibility = false,
persistColumnVisibility = false persistColumnVisibility = false,
stickyLeftColumn,
stickyRightColumn
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const t = useTranslations(); const t = useTranslations();
@@ -290,6 +294,28 @@ export function DataTable<TData, TValue>({
} }
}; };
// 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 ( return (
<div className="container mx-auto max-w-12xl"> <div className="container mx-auto max-w-12xl">
<Card> <Card>
@@ -329,131 +355,150 @@ export function DataTable<TData, TValue>({
)} )}
</div> </div>
<div className="flex items-center gap-2 sm:justify-end"> <div className="flex items-center gap-2 sm:justify-end">
{enableColumnVisibility &&
table
.getAllColumns()
.some((column) => column.getCanHide()) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Columns className="mr-0 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">
{t("columns") || "Columns"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48"
>
<DropdownMenuLabel>
{t("toggleColumns") || "Toggle columns"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(
!!value
)
}
>
{typeof column.columnDef
.header === "string"
? column.columnDef
.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
{onRefresh && ( {onRefresh && (
<Button <div>
variant="outline" <Button
onClick={onRefresh} variant="outline"
disabled={isRefreshing} onClick={onRefresh}
> disabled={isRefreshing}
<RefreshCw >
className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} <RefreshCw
/> className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
<span className="hidden sm:inline"> />
{t("refresh")} <span className="hidden sm:inline">
</span> {t("refresh")}
</Button> </span>
</Button>
</div>
)} )}
{onAdd && addButtonText && ( {onAdd && addButtonText && (
<Button onClick={onAdd}> <div>
<Plus className="mr-2 h-4 w-4" /> <Button onClick={onAdd}>
{addButtonText} <Plus className="mr-2 h-4 w-4" />
</Button> {addButtonText}
</Button>
</div>
)} )}
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Table> <div className="overflow-x-auto">
<TableHeader> <Table>
{table.getHeaderGroups().map((headerGroup) => ( <TableHeader>
<TableRow key={headerGroup.id}> {table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => ( <TableRow key={headerGroup.id}>
<TableHead {headerGroup.headers.map((header) => {
key={header.id} const columnId = header.column.id;
className="whitespace-nowrap" const accessorKey = (header.column.columnDef as any).accessorKey as string | undefined;
> const stickyClasses = getStickyClasses(columnId, accessorKey);
{header.isPlaceholder const isRightSticky = isStickyColumn(columnId, accessorKey, "right");
? null const hasHideableColumns = enableColumnVisibility &&
: flexRender( table.getAllColumns().some((col) => col.getCanHide());
header.column.columnDef
.header, return (
header.getContext() <TableHead
)} key={header.id}
</TableHead> className={`whitespace-nowrap ${stickyClasses}`}
))} >
</TableRow> {header.isPlaceholder ? null : (
))} isRightSticky && hasHideableColumns ? (
</TableHeader> <div className="flex flex-col items-end pr-3">
<TableBody> <DropdownMenu>
{table.getRowModel().rows?.length ? ( <DropdownMenuTrigger asChild>
table.getRowModel().rows.map((row) => ( <Button variant="outline" size="sm" className="h-7 w-7 p-0 mb-1">
<TableRow <Columns className="h-4 w-4" />
key={row.id} <span className="sr-only">
data-state={ {t("columns") || "Columns"}
row.getIsSelected() && "selected" </span>
} </Button>
> </DropdownMenuTrigger>
{row.getVisibleCells().map((cell) => ( <DropdownMenuContent align="end" className="w-48">
<TableCell <DropdownMenuLabel>
key={cell.id} {t("toggleColumns") || "Toggle columns"}
className="whitespace-nowrap" </DropdownMenuLabel>
> <DropdownMenuSeparator />
{flexRender( {table
cell.column.columnDef.cell, .getAllColumns()
cell.getContext() .filter((column) => column.getCanHide())
)} .map((column) => {
</TableCell> return (
))} <DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{typeof column.columnDef.header === "string"
? column.columnDef.header
: column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<div className="h-0 opacity-0 pointer-events-none overflow-hidden">
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
</div>
) : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)
)}
</TableHead>
);
})}
</TableRow> </TableRow>
)) ))}
) : ( </TableHeader>
<TableRow> <TableBody>
<TableCell {table.getRowModel().rows?.length ? (
colSpan={columns.length} table.getRowModel().rows.map((row) => (
className="h-24 text-center" <TableRow
> key={row.id}
No results found. data-state={
</TableCell> row.getIsSelected() && "selected"
</TableRow> }
)} >
</TableBody> {row.getVisibleCells().map((cell) => {
</Table> 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 (
<TableCell
key={cell.id}
className={`whitespace-nowrap ${stickyClasses} ${isRightSticky ? "text-right" : ""}`}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results found.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={table} table={table}