"use client"; import { keepPreviousData } from "@tanstack/react-query"; import { getCoreRowModel, getSortedRowModel, useReactTable, type ColumnDef } from "@tanstack/react-table"; import { useMemo, useReducer, useRef, useState } from "react"; import { DataTableProvider, DataTable, DataTableToolbar, DataTableFilters, DataTableSelectionBar, DataTablePagination, useColumnFilters, useFetchMoreOnBottomReached, } from "@calcom/features/data-table"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; import { Avatar, Badge, Checkbox, SkeletonText } from "@calcom/ui"; import { DeleteBulkUsers } from "./BulkActions/DeleteBulkUsers"; import { DeleteMemberModal } from "./DeleteMemberModal"; import type { UserTableState, UserTableAction, PlatformManagedUserTableUser } from "./types"; const initialState: UserTableState = { changeMemberRole: { showModal: false, }, deleteMember: { showModal: false, }, impersonateMember: { showModal: false, }, inviteMember: { showModal: false, }, editSheet: { showModal: false, }, }; const initalColumnVisibility = { select: true, member: true, role: true, teams: true, actions: true, }; type PlatformManagedUsersTableProps = { oAuthClientId: string; }; export function PlatformManagedUsersTable(props: PlatformManagedUsersTableProps) { return ( ); } function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps) { const { t } = useLocale(); const tableContainerRef = useRef(null); const [state, dispatch] = useReducer(reducer, initialState); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); const [rowSelection, setRowSelection] = useState({}); const columnFilters = useColumnFilters(); const { data, isPending, hasNextPage, fetchNextPage, isFetching } = trpc.viewer.organizations.listMembers.useInfiniteQuery( { limit: 30, searchTerm: debouncedSearchTerm, filters: columnFilters, oAuthClientId, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, placeholderData: keepPreviousData, enabled: !!oAuthClientId, } ); const totalDBRowCount = data?.pages?.[0]?.meta?.totalRowCount ?? 0; //we must flatten the array of arrays from the useInfiniteQuery hook const flatData = useMemo( () => data?.pages?.flatMap((page) => page.rows) ?? [], [data] ) as PlatformManagedUserTableUser[]; const columns = useMemo(() => { const cols: ColumnDef[] = [ // Disabling select for this PR: Will work on actions etc in a follow up { id: "select", enableHiding: false, enableSorting: false, enableResizing: false, size: 30, meta: { sticky: { position: "left", }, }, header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" className="translate-y-[2px]" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" className="translate-y-[2px]" /> ), }, { id: "member", accessorFn: (data) => data.email, enableHiding: false, size: 200, header: () => { return `Managed Users`; }, meta: { sticky: { position: "left", gap: 24 }, }, cell: ({ row }) => { if (isPending) { return ; } const { username, email, avatarUrl } = row.original; return ( {username || "No username"} {email} ); }, filterFn: (rows, id, filterValue) => { const userEmail = rows.original.email; return filterValue.includes(userEmail); }, }, { id: "role", accessorFn: (data) => data.role, header: "Role", size: 100, cell: ({ row, table }) => { if (isPending) { return ; } const { role, username } = row.original; return ( { table.getColumn("role")?.setFilterValue([role]); }}> {role} ); }, filterFn: (rows, id, filterValue) => { if (filterValue.includes("PENDING")) { if (filterValue.length === 1) return !rows.original.accepted; else return !rows.original.accepted || filterValue.includes(rows.getValue(id)); } // Show only the selected roles return filterValue.includes(rows.getValue(id)); }, }, { id: "teams", accessorFn: (data) => data.teams.map((team) => team.name), header: "Teams", size: 140, cell: ({ row, table }) => { if (isPending) { return ; } const { teams, accepted, email, username } = row.original; // TODO: Implement click to filter return ( {accepted ? null : ( { table.getColumn("role")?.setFilterValue(["PENDING"]); }}> Pending )} {teams.map((team) => ( { table.getColumn("teams")?.setFilterValue([team.name]); }}> {team.name} ))} ); }, filterFn: (rows, _, filterValue: string[]) => { const teamNames = rows.original.teams.map((team) => team.name); return filterValue.some((value: string) => teamNames.includes(value)); }, }, ]; return cols; }, [isPending]); const table = useReactTable({ data: flatData, columns, enableRowSelection: true, columnResizeMode: "onChange", debugTable: true, manualPagination: true, initialState: { columnVisibility: initalColumnVisibility, }, defaultColumn: { size: 150, }, state: { columnFilters, rowSelection, }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onRowSelectionChange: setRowSelection, getRowId: (row) => `${row.id}`, }); function reducer(state: UserTableState, action: UserTableAction): UserTableState { switch (action.type) { case "SET_CHANGE_MEMBER_ROLE_ID": return { ...state, changeMemberRole: action.payload }; case "SET_DELETE_ID": return { ...state, deleteMember: action.payload }; case "SET_IMPERSONATE_ID": return { ...state, impersonateMember: action.payload }; case "INVITE_MEMBER": return { ...state, inviteMember: action.payload }; case "EDIT_USER_SHEET": return { ...state, editSheet: action.payload }; case "CLOSE_MODAL": return { ...state, changeMemberRole: { showModal: false }, deleteMember: { showModal: false }, impersonateMember: { showModal: false }, inviteMember: { showModal: false }, editSheet: { showModal: false }, }; default: return state; } } const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({ tableContainerRef, hasNextPage, fetchNextPage, isFetching, }); const numberOfSelectedRows = useMemo(() => table.getSelectedRowModel().rows.length, [table]); return ( <> fetchMoreOnBottomReached(e.target as HTMLDivElement)}> setDebouncedSearchTerm(value)} className="sm:max-w-64 max-w-full" /> {/* We have to omit member because we don't want the filter to show but we can't disable filtering as we need that for the search bar */} {numberOfSelectedRows > 0 && ( {t("number_selected", { count: numberOfSelectedRows })} row.original)} onRemove={() => table.toggleAllPageRowsSelected(false)} /> )} {state.deleteMember.showModal && } > ); }
{t("number_selected", { count: numberOfSelectedRows })}