From 2c801febffdea18299790e3db182d19e80f1a69a Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Tue, 5 May 2026 15:35:14 -0700 Subject: [PATCH] fix(ui): split paginated team table from shared global teams state Greptile reviewed the previous commit and pointed out that the page-level v2TeamListCall I removed was also the only writer that populated the shared `teams` state for non-Teams pages (Models, Users, Agents). Removing it would have left those pages with `teams === null` until the user visited Teams or API Keys, which in turn broke `isUserTeamAdmin` checks on Models and similar lookups elsewhere. Split the two roles instead of conflating them: - Restore the page-level v2TeamListCall(size=100) in app/page.tsx as the sole writer of the shared `teams` state. Models, Users, Agents and OldTeams' own cross-team lookups (is_team_admin, in-place edit propagation) all read from this list. - Give OldTeams its own local `paginatedTeams` state for the table. fetchTeamsV2 and the filter-change handler now write to local state only; displayTeams reads from it. This stops OldTeams from clobbering the global list with a 10-row paginated subset. Net effect: race between two writers is gone (different state slots), non-Teams pages keep working as before, and OldTeams' table renders its paginated view consistently across reloads. Verified live in the browser. --- ui/litellm-dashboard/src/app/page.tsx | 6 ++++++ ui/litellm-dashboard/src/components/OldTeams.tsx | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index ea8c505b540..06bf3b68d05 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -8,6 +8,7 @@ import AgentsPanel from "@/components/agents"; import BudgetPanel from "@/components/budgets/budget_panel"; import CacheDashboard from "@/components/cache_dashboard"; import ClaudeCodePluginsPanel from "@/components/claude_code_plugins"; +import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams"; import LoadingScreen from "@/components/common_components/LoadingScreen"; import { CostTrackingSettings } from "@/components/CostTrackingSettings"; import GeneralSettings from "@/components/general_settings"; @@ -352,6 +353,11 @@ function CreateKeyPageContent() { if (accessToken && userID && userRole) { fetchUserModels(userID, userRole, accessToken, setUserModels); } + if (accessToken && userID && userRole) { + v2TeamListCall(accessToken, 1, 100, { + userID: userRole !== "Admin" && userRole !== "Admin Viewer" ? userID : null, + }).then((response) => setTeams(response.teams ?? [])).catch(console.error); + } if (accessToken) { fetchOrganizations(accessToken, setOrganizations); } diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index abc26c4cd44..80c141d60f2 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -197,6 +197,7 @@ const Teams: React.FC = ({ const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [totalTeams, setTotalTeams] = useState(0); + const [paginatedTeams, setPaginatedTeams] = useState(null); const [currentOrg, setCurrentOrg] = useState(null); const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); const [filters, setFilters] = useState({ @@ -240,7 +241,7 @@ const Teams: React.FC = ({ sortOrder: sortOrder || null, }, ); - setTeams(response.teams ?? []); + setPaginatedTeams(response.teams ?? []); setTotalTeams(response.total ?? 0); } catch (err: any) { setFetchError(err?.message || "Failed to fetch teams"); @@ -659,7 +660,7 @@ const Teams: React.FC = ({ sortOrder: newFilters.sort_order || null, }, ); - setTeams(response.teams ?? []); + setPaginatedTeams(response.teams ?? []); setTotalTeams(response.total ?? 0); } catch (error) { console.error("Error fetching teams:", error); @@ -863,7 +864,7 @@ const Teams: React.FC = ({ }, ], [userRole, perTeamInfo, organizationsData, organizations]); - const displayTeams = useMemo(() => teams ?? [], [teams]); + const displayTeams = useMemo(() => paginatedTeams ?? [], [paginatedTeams]); const renderTeamsContent = () => { if (isLoading) {