mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
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:
parent
5cd7a4189e
commit
5afaf46f83
3 changed files with 103 additions and 81 deletions
|
|
@ -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 “Team ID” 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 “Team ID” 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
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue