diff --git a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx index 611f414a64e..5ba53b79755 100644 --- a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx +++ b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx @@ -25,6 +25,8 @@ export interface MemberTableProps { /** Controlled current page (optional). When provided, pair with onPageChange. */ currentPage?: number; onPageChange?: (page: number) => void; + /** Total unfiltered member count. When provided and differs from members.length, shows "X of Y Members". */ + totalMembers?: number; } export default function MemberTable({ @@ -43,6 +45,7 @@ export default function MemberTable({ defaultPageSize = 50, currentPage: controlledPage, onPageChange, + totalMembers, }: MemberTableProps) { const [internalPage, setInternalPage] = useState(1); const [pageSize, setPageSize] = useState(defaultPageSize); @@ -54,6 +57,9 @@ export default function MemberTable({ const totalPages = Math.max(1, Math.ceil(total / pageSize)); const safePage = Math.min(page, totalPages); const pagedMembers = withPagination ? members.slice((safePage - 1) * pageSize, safePage * pageSize) : members; + const countDisplay = totalMembers !== undefined && total !== totalMembers + ? `${total} of ${totalMembers} Members` + : `${total} Member${total !== 1 ? "s" : ""}`; const baseColumns: ColumnsType = [ { title: "User Email", @@ -128,7 +134,7 @@ export default function MemberTable({
- {total} Member{total !== 1 ? "s" : ""} + {countDisplay} {withPagination && ( { setPage(p); if (ps !== pageSize) { setPageSize(ps); setPage(1); } }} + onChange={(p, ps) => { + if (ps !== pageSize) { + setPageSize(ps); + setPage(1); + } else { + setPage(p); + } + }} /> )}
diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx index 2a67cac1203..3e798682c31 100644 --- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx @@ -453,6 +453,25 @@ describe("TeamMembersComponent", () => { expect(screen.getAllByText("user2@test.com").length).toBeGreaterThanOrEqual(1); }); + it("should show filtered count when search is active", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const searchInput = screen.getByPlaceholderText(/search by email or user id/i); + await user.type(searchInput, "user1"); + + expect(screen.getByText("1 of 2 Members")).toBeInTheDocument(); + }); + it("should hide action buttons when canEditTeam is false", () => { renderWithProviders( (null); const [memberTablePage, setMemberTablePage] = useState(1); - // Reset to page 1 when filter/search changes — without remounting MemberTable - useEffect(() => { setMemberTablePage(1); }, [searchText, roleFilter]); - // O(1) lookup instead of O(n) find() per member per column const membershipsMap = useMemo( () => @@ -226,8 +223,8 @@ export default function TeamMemberTab({ placeholder="Search by email or user ID" allowClear style={{ width: 280 }} - onChange={(e) => setSearchText(e.target.value)} - onSearch={(v) => setSearchText(v)} + onChange={(e) => { setSearchText(e.target.value); setMemberTablePage(1); }} + onSearch={(v) => { setSearchText(v); setMemberTablePage(1); }} />