feat(ui): migrate teams TeamsHeaderTabs+TeamsView tremor tabs to shadcn

Refactors TeamsHeaderTabs to accept explicit panel props instead of
tremor-positional children, migrates the parent TeamsView to pass those
panels, and updates the TeamsHeaderTabs test to use the new API and
query by role.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-24 13:38:34 +00:00
parent 5cd7a4189e
commit 5afaf46f83
No known key found for this signature in database
3 changed files with 103 additions and 81 deletions

View file

@ -6,8 +6,6 @@ import { fetchTeams } from "@/components/common_components/fetch_teams";
import TeamInfoView from "@/components/team/TeamInfo";
import TeamSSOSettings from "@/components/TeamSSOSettings";
import { isAdminRole } from "@/utils/roles";
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
import { TabPanel } from "@tremor/react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import AvailableTeamsPanel from "@/components/team/available_teams";
@ -273,57 +271,60 @@ const TeamsView: React.FC<TeamProps> = ({
premiumUser={premiumUser}
/>
) : (
<TeamsHeaderTabs lastRefreshed={lastRefreshed} onRefresh={handleRefreshClick} userRole={userRole}>
<TabPanel>
<p className="text-sm">
Click on &ldquo;Team ID&rdquo; to view team details <b>and</b> manage team members.
</p>
<div className="grid grid-cols-1 gap-2 pt-2 pb-2 h-[75vh] w-full mt-2">
<div>
<Card className="w-full mx-auto flex-auto overflow-hidden overflow-y-auto max-h-[50vh]">
<div className="border-b px-6 py-4">
<div className="flex flex-col space-y-4">
<TeamsFilters
filters={filters}
organizations={organizations}
showFilters={showFilters}
onToggleFilters={setShowFilters}
onChange={handleFilterChange}
onReset={handleFilterReset}
/>
<TeamsHeaderTabs
lastRefreshed={lastRefreshed}
onRefresh={handleRefreshClick}
userRole={userRole}
yourTeamsPanel={
<>
<p className="text-sm">
Click on &ldquo;Team ID&rdquo; to view team details <b>and</b> manage team members.
</p>
<div className="grid grid-cols-1 gap-2 pt-2 pb-2 h-[75vh] w-full mt-2">
<div>
<Card className="w-full mx-auto flex-auto overflow-hidden overflow-y-auto max-h-[50vh]">
<div className="border-b px-6 py-4">
<div className="flex flex-col space-y-4">
<TeamsFilters
filters={filters}
organizations={organizations}
showFilters={showFilters}
onToggleFilters={setShowFilters}
onChange={handleFilterChange}
onReset={handleFilterReset}
/>
</div>
</div>
</div>
<TeamsTable
teams={teams}
currentOrg={currentOrg}
perTeamInfo={perTeamInfo}
userRole={userRole}
userId={userID}
setSelectedTeamId={setSelectedTeamId}
setEditTeam={setEditTeam}
onDeleteTeam={handleDelete}
/>
{isDeleteModalOpen && (
<DeleteTeamModal
<TeamsTable
teams={teams}
teamToDelete={teamToDelete}
onCancel={cancelDelete}
onConfirm={confirmDelete}
currentOrg={currentOrg}
perTeamInfo={perTeamInfo}
userRole={userRole}
userId={userID}
setSelectedTeamId={setSelectedTeamId}
setEditTeam={setEditTeam}
onDeleteTeam={handleDelete}
/>
)}
</Card>
{isDeleteModalOpen && (
<DeleteTeamModal
teams={teams}
teamToDelete={teamToDelete}
onCancel={cancelDelete}
onConfirm={confirmDelete}
/>
)}
</Card>
</div>
</div>
</div>
</TabPanel>
<TabPanel>
</>
}
availableTeamsPanel={
<AvailableTeamsPanel accessToken={accessToken} userID={userID} />
</TabPanel>
{isAdminRole(userRole || "") && (
<TabPanel>
<TeamSSOSettings accessToken={accessToken} userID={userID || ""} userRole={userRole || ""} />
</TabPanel>
)}
</TeamsHeaderTabs>
}
defaultTeamSettingsPanel={
<TeamSSOSettings accessToken={accessToken} userID={userID || ""} userRole={userRole || ""} />
}
/>
)}
{(userRole == "Admin" || userRole == "Org Admin") && (
<CreateTeamModal

View file

@ -3,21 +3,14 @@ import React from "react";
import { describe, expect, it, vi } from "vitest";
import TeamsHeaderTabs from "./TeamsHeaderTabs";
vi.mock("@tremor/react", () => ({
TabGroup: ({ children, ...props }: any) => <div data-testid="tab-group" {...props}>{children}</div>,
TabList: ({ children, ...props }: any) => <div data-testid="tab-list" {...props}>{children}</div>,
Tab: ({ children, ...props }: any) => <button {...props}>{children}</button>,
TabPanels: ({ children, ...props }: any) => <div data-testid="tab-panels" {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
Icon: ({ onClick, ...props }: any) => <button data-testid="refresh-icon" onClick={onClick} />,
}));
const renderTabs = (props: Partial<Parameters<typeof TeamsHeaderTabs>[0]> = {}) => {
const defaults = {
lastRefreshed: "",
onRefresh: vi.fn(),
userRole: "Internal User",
children: <div data-testid="panel-content">Panel</div>,
yourTeamsPanel: <div data-testid="your-teams-panel">Your</div>,
availableTeamsPanel: <div data-testid="available-teams-panel">Available</div>,
defaultTeamSettingsPanel: <div data-testid="default-settings-panel">Settings</div>,
};
return render(<TeamsHeaderTabs {...defaults} {...props} />);
};
@ -26,20 +19,24 @@ describe("TeamsHeaderTabs", () => {
it("should render 'Your Teams' and 'Available Teams' tabs", () => {
renderTabs();
expect(screen.getByText("Your Teams")).toBeInTheDocument();
expect(screen.getByText("Available Teams")).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Your Teams" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Available Teams" })).toBeInTheDocument();
});
it("should render 'Default Team Settings' tab when user is Admin", () => {
renderTabs({ userRole: "Admin" });
expect(screen.getByText("Default Team Settings")).toBeInTheDocument();
expect(
screen.getByRole("tab", { name: "Default Team Settings" }),
).toBeInTheDocument();
});
it("should not render 'Default Team Settings' tab for non-admin users", () => {
renderTabs({ userRole: "Internal User" });
expect(screen.queryByText("Default Team Settings")).not.toBeInTheDocument();
expect(
screen.queryByRole("tab", { name: "Default Team Settings" }),
).not.toBeInTheDocument();
});
it("should display last refreshed time when provided", () => {

View file

@ -1,5 +1,9 @@
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
import { Icon, Tab, TabGroup, TabList, TabPanels, Text } from "@tremor/react";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import { isAdminRole } from "@/utils/roles";
import { RefreshCw } from "lucide-react";
import React from "react";
@ -8,36 +12,56 @@ type TeamsHeaderTabsProps = {
lastRefreshed: string;
onRefresh: () => void;
userRole: string | null;
children: React.ReactNode;
yourTeamsPanel: React.ReactNode;
availableTeamsPanel: React.ReactNode;
defaultTeamSettingsPanel: React.ReactNode;
};
const TeamsHeaderTabs = ({
lastRefreshed,
onRefresh,
userRole,
children,
yourTeamsPanel,
availableTeamsPanel,
defaultTeamSettingsPanel,
}: TeamsHeaderTabsProps) => {
const showDefaults = isAdminRole(userRole || "");
return (
<TabGroup className="gap-2 h-[75vh] w-full">
<TabList className="flex justify-between mt-2 w-full items-center">
<Tabs defaultValue="your-teams" className="gap-2 h-[75vh] w-full">
<TabsList className="flex justify-between mt-2 w-full items-center bg-transparent p-0 h-auto">
<div className="flex">
<Tab>Your Teams</Tab>
<Tab>Available Teams</Tab>
{isAdminRole(userRole || "") && <Tab>Default Team Settings</Tab>}
<TabsTrigger value="your-teams">Your Teams</TabsTrigger>
<TabsTrigger value="available-teams">Available Teams</TabsTrigger>
{showDefaults && (
<TabsTrigger value="default-settings">
Default Team Settings
</TabsTrigger>
)}
</div>
<div className="flex items-center space-x-2">
{lastRefreshed && <Text>Last Refreshed: {lastRefreshed}</Text>}
<Icon
icon={RefreshCw}
variant="shadow"
size="xs"
className="self-center"
{lastRefreshed && (
<span className="text-muted-foreground text-sm">
Last Refreshed: {lastRefreshed}
</span>
)}
<button
type="button"
onClick={onRefresh}
/>
className="p-1.5 rounded-md border border-border shadow-sm hover:bg-muted transition-colors self-center"
aria-label="Refresh"
>
<RefreshCw className="h-3.5 w-3.5" />
</button>
</div>
</TabList>
<TabPanels>{children}</TabPanels>
</TabGroup>
</TabsList>
<TabsContent value="your-teams">{yourTeamsPanel}</TabsContent>
<TabsContent value="available-teams">{availableTeamsPanel}</TabsContent>
{showDefaults && (
<TabsContent value="default-settings">
{defaultTeamSettingsPanel}
</TabsContent>
)}
</Tabs>
);
};