row.toggleSelected(!!value)}
aria-label="Select row"
className="translate-y-[2px]"
/>
),
},
{
id: "member",
accessorFn: (data) => data.email,
enableHiding: false,
enableColumnFilter: false,
size: 200,
header: t("members"),
cell: ({ row }) => {
const { username, email, avatarUrl } = row.original;
return (
{username || "No username"}
{email}
);
},
},
{
id: "role",
accessorFn: (data) => data.role,
header: t("role"),
size: 100,
meta: {
filter: { type: ColumnFilterType.MULTI_SELECT },
},
cell: ({ row, table }) => {
const { role, username, customRole } = row.original;
const roleName = customRole?.name || role;
return (
{
table.getColumn("role")?.setFilterValue([role]);
}}>
{roleName}
);
},
},
{
id: "teams",
accessorFn: (data) => data.teams.map((team) => team.name),
header: t("teams"),
size: 140,
meta: {
filter: { type: ColumnFilterType.MULTI_SELECT },
},
cell: ({ row, table }) => {
const { teams, accepted, email, username } = row.original;
// TODO: Implement click to filter
return (
{accepted ? null : (
{
table.getColumn("role")?.setFilterValue(["PENDING"]);
}}>
{t("pending")}
)}
{teams.map((team) => (
{
table.getColumn("teams")?.setFilterValue([team.name]);
}}>
{team.name}
))}
);
},
},
...generateAttributeColumns(),
{
id: "lastActiveAt",
accessorKey: "lastActiveAt",
header: t("last_active"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
},
},
cell: ({ row }) => {row.original.lastActiveAt}
,
},
{
id: "createdAt",
accessorKey: "createdAt",
header: t("member_since"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
},
},
cell: ({ row }) => {row.original.createdAt || ""}
,
},
{
id: "updatedAt",
accessorKey: "updatedAt",
header: t("last_updated"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
},
},
cell: ({ row }) => {row.original.updatedAt || ""}
,
},
{
id: "twoFactorEnabled",
accessorKey: "twoFactorEnabled",
header: t("2fa"),
enableHiding: adminOrOwner,
enableSorting: false,
enableColumnFilter: false,
size: 80,
cell: ({ row }) => {
const { twoFactorEnabled } = row.original;
if (!adminOrOwner || twoFactorEnabled === undefined) {
return null;
}
return (
{twoFactorEnabled ? t("enabled") : t("disabled")}
);
},
},
{
id: "actions",
enableHiding: false,
enableSorting: false,
enableResizing: false,
size: 80,
cell: ({ row }) => {
const user = row.original;
const permissionsRaw = tablePermissions;
const isSelf = user.id === session?.user.id;
const permissionsForUser = {
canEdit: permissionsRaw.canEdit && user.accepted && !isSelf,
canRemove: permissionsRaw.canRemove && !isSelf,
canImpersonate:
user.accepted &&
!user.disableImpersonation &&
!isSelf &&
!!org?.canAdminImpersonate &&
permissionsRaw.canImpersonate,
canLeave: user.accepted && isSelf,
canResendInvitation: permissionsRaw.canResendInvitation && !user.accepted,
};
return (
);
},
},
];
return cols;
}, [session?.user.id, adminOrOwner, dispatch, domain, attributes, org?.canAdminImpersonate, permissions]);
const table = useReactTable({
data: flatData,
columns: memorisedColumns,
enableRowSelection: true,
manualPagination: true,
state: {
rowSelection,
},
initialState: {
columnVisibility: initalColumnVisibility,
columnPinning: {
left: ["select", "member"],
right: ["actions"],
},
},
defaultColumn: {
size: 150,
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
onRowSelectionChange: setRowSelection,
getRowId: (row) => `${row.id}`,
getFacetedUniqueValues: (_, columnId) => () => {
if (facetedTeamValues) {
switch (columnId) {
case "role":
return convertFacetedValuesToMap(
facetedTeamValues.roles.map((role) => ({
label: role.name,
value: role.id,
}))
);
case "teams":
return convertFacetedValuesToMap(
facetedTeamValues.teams.map((team) => ({
label: team.name,
value: team.name,
}))
);
default:
const attribute = facetedTeamValues.attributes.find((attr) => attr.id === columnId);
if (attribute) {
return convertFacetedValuesToMap(
attribute?.options.map(({ value }) => ({
label: value,
value,
})) ?? []
);
}
return new Map();
}
}
return new Map();
},
});
const utils = trpc.useUtils();
const numberOfSelectedRows = table.getSelectedRowModel().rows.length;
const handleDownload = async () => {
try {
if (!org?.slug || !org?.name) {
throw new Error("Org slug or name is missing.");
}
setIsDownloading(true);
const headers = generateHeaderFromReactTable(table);
if (!headers || !headers.length) {
throw new Error("Header is missing.");
}
// Fetch all pages
let allRows: UserTableUser[] = [];
let offset: number | undefined = 0;
const limit = 100;
while (offset !== undefined) {
const result = await utils.viewer.organizations.listMembers.fetch({
limit,
offset,
searchTerm,
expand: ["attributes"],
filters: columnFilters,
});
if (!result.rows?.length) {
offset = undefined;
continue;
}
allRows = [...allRows, ...result.rows];
offset = offset + limit;
}
if (!allRows.length) {
throw new Error("There are no members found.");
}
const ATTRIBUTE_IDS = attributes?.map((attr) => attr.id) ?? [];
const csvRaw = generateCsvRawForMembersTable(headers, allRows, ATTRIBUTE_IDS, domain);
if (!csvRaw) {
throw new Error("Generating CSV file failed.");
}
const filename = `${org.name}_${new Date().toISOString().split("T")[0]}.csv`;
downloadAsCsv(csvRaw, filename);
} catch (error) {
showToast(`Error: ${error}`, "error");
} finally {
setIsDownloading(false);
}
};
return (
<>
testId="user-list-data-table"
table={table}
isPending={isPending}
totalRowCount={data?.meta?.totalRowCount}
paginationMode="standard"
ToolbarLeft={
<>
>
}
ToolbarRight={
<>
>
}>
{numberOfSelectedRows >= 2 && dynamicLinkVisible && (
)}
{numberOfSelectedRows > 0 && (
{t("number_selected", { count: numberOfSelectedRows })}
{!isPlatformUser ? (
<>
{permissions?.canChangeMemberRole && }
{numberOfSelectedRows >= 2 && (
setDynamicLinkVisible(!dynamicLinkVisible)}
icon="handshake">
{t("group_meeting")}
)}
{(permissions?.canChangeMemberRole ?? adminOrOwner) && (
)}
{(permissions?.canChangeMemberRole ?? adminOrOwner) && (
)}
>
) : null}
{(permissions?.canRemove ?? adminOrOwner) && (
row.original)}
onRemove={() => table.toggleAllPageRowsSelected(false)}
/>
)}
)}
{state.deleteMember.showModal && }
{state.inviteMember.showModal && }
{state.impersonateMember.showModal && }
{state.changeMemberRole.showModal && }
{state.editSheet.showModal && }
{ctaContainerRef.current &&
createPortal(
handleDownload()}
data-testid="export-members-button">
{t("download")}
{(permissions?.canInvite ?? adminOrOwner) && (
dispatch({
type: "INVITE_MEMBER",
payload: {
showModal: true,
},
})
}
data-testid="new-organization-member-button">
{t("add")}
)}
,
ctaContainerRef.current
)}
>
);
}