diff --git a/ui/litellm-dashboard/src/components/Teams.test.tsx b/ui/litellm-dashboard/src/components/Teams.test.tsx
index bee35ac5c5d..1a317c6aa91 100644
--- a/ui/litellm-dashboard/src/components/Teams.test.tsx
+++ b/ui/litellm-dashboard/src/components/Teams.test.tsx
@@ -1488,3 +1488,71 @@ describe("Teams - the exact bytes the create call sends", () => {
expect(teamCreateCall).not.toHaveBeenCalled();
});
});
+
+describe("Teams - create team modal keeps user selections", () => {
+ const orgs = () => [
+ { organization_id: "org-1", organization_alias: "Org One", models: [], members: [] },
+ { organization_id: "org-2", organization_alias: "Org Two", models: [], members: [] },
+ ];
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4"]);
+ vi.mocked(fetchMCPAccessGroups).mockResolvedValue([]);
+ vi.mocked(getGuardrailsList).mockResolvedValue({ guardrails: [] });
+ mockUseOrganizations.mockReturnValue({ data: orgs() });
+ });
+
+ const openCreateModal = async () => {
+ const queryClient = createQueryClient();
+ const wrap = (children: React.ReactElement) => (
+
+ {children}
+
+ );
+ const view = render(wrap());
+ act(() => {
+ fireEvent.click(screen.getAllByRole("button", { name: /create team/i })[0]);
+ });
+ await waitFor(() => {
+ expect(screen.getByLabelText(/team name/i)).toBeInTheDocument();
+ });
+ return {
+ rerenderTeams: () => view.rerender(wrap()),
+ };
+ };
+
+ const selectOrganization = async (label: string) => {
+ const user = userEvent.setup();
+ const orgCombobox = screen.getByRole("combobox", { name: /organization/i });
+ await user.click(orgCombobox);
+ await user.click(await screen.findByText(label));
+ return orgCombobox;
+ };
+
+ it("keeps the selected organization and models when the organizations list refetches", async () => {
+ const { rerenderTeams } = await openCreateModal();
+
+ const orgCombobox = await selectOrganization("Org One");
+ fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
+
+ mockUseOrganizations.mockReturnValue({ data: orgs() });
+ act(() => {
+ rerenderTeams();
+ });
+
+ expect(orgCombobox).toHaveValue("Org One");
+ expect(screen.getByTestId("create-team-models-select")).toHaveValue("gpt-4");
+ });
+
+ it("clears the selected models when the user picks a different organization", async () => {
+ await openCreateModal();
+
+ await selectOrganization("Org One");
+ fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
+ expect(screen.getByTestId("create-team-models-select")).toHaveValue("gpt-4");
+
+ await selectOrganization("Org Two");
+ expect(screen.getByTestId("create-team-models-select")).toHaveValue("");
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/Teams.tsx b/ui/litellm-dashboard/src/components/Teams.tsx
index e2eda4adb23..acc55d2c371 100644
--- a/ui/litellm-dashboard/src/components/Teams.tsx
+++ b/ui/litellm-dashboard/src/components/Teams.tsx
@@ -17,7 +17,7 @@ import { useZodForm } from "@/lib/forms/useZodForm";
import { TagsInput } from "@/app/(dashboard)/guardrails/_components/content_filter/TagsInput";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ChevronDown, Plus, Users } from "lucide-react";
-import React, { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod/v4";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { PageHeader } from "@/components/shared/PageHeader";
@@ -264,13 +264,10 @@ const Teams: React.FC = ({ accessToken, userID, userRole, premiumUser
? `Default: ${getBudgetDurationLabel(defaultBudgetDuration)} (${defaultBudgetDuration})`
: "n/a";
- useEffect(() => {
- form.setValue("models", []);
- }, [currentOrgForCreateTeam, userModels]);
-
// Handle organization preselection when modal opens
+ const wasTeamModalVisible = useRef(false);
useEffect(() => {
- if (isTeamModalVisible) {
+ if (isTeamModalVisible && !wasTeamModalVisible.current) {
const adminOrgs = getAdminOrganizations(userRole, userID, organizations);
// Org admins must scope a team to an org, so with exactly one we preselect it.
@@ -284,6 +281,7 @@ const Teams: React.FC = ({ accessToken, userID, userRole, premiumUser
setCurrentOrgForCreateTeam(currentOrg);
}
}
+ wasTeamModalVisible.current = isTeamModalVisible;
}, [isTeamModalVisible, isOrgAdmin, userRole, userID, organizations, currentOrg]);
// Add this useEffect to fetch guardrails
@@ -686,6 +684,11 @@ const Teams: React.FC = ({ accessToken, userID, userRole, premiumUser
const adminOrgs = getAdminOrganizations(userRole, userID, organizations);
const isSingleOrg = adminOrgs.length === 1;
const hasNoOrgs = adminOrgs.length === 0;
+ const handleCreateTeamOrgChange = (next: string, onChange: (value: string | null) => void) => {
+ onChange(next === "" ? null : next);
+ setCurrentOrgForCreateTeam(adminOrgs.find((org) => org.organization_id === next) ?? null);
+ form.setValue("models", []);
+ };
return (
<>
@@ -721,12 +724,7 @@ const Teams: React.FC = ({ accessToken, userID, userRole, premiumUser
hasNoOrgs ? "No organizations available" : "Search or select an Organization"
}
emptyText="No organizations available"
- onValueChange={(next) => {
- onChange(next === "" ? null : next);
- setCurrentOrgForCreateTeam(
- adminOrgs.find((org) => org.organization_id === next) ?? null,
- );
- }}
+ onValueChange={(next) => handleCreateTeamOrgChange(next, onChange)}
/>
)}