Merge pull request #19010 from BerriAI/litellm_ui_filter_components

[Refactor] UI - User and Team Table Filters to Reusable Component
This commit is contained in:
yuneng-jiang
2026-01-13 16:26:07 -08:00
committed by GitHub
2 changed files with 50 additions and 158 deletions
@@ -5,6 +5,10 @@ import TeamSSOSettings from "@/components/TeamSSOSettings";
import { isProxyAdminRole } from "@/utils/roles";
import { InfoCircleOutlined } from "@ant-design/icons";
import { ChevronDownIcon, ChevronRightIcon, RefreshIcon } from "@heroicons/react/outline";
import { FilterInput } from "@/components/common_components/Filters/FilterInput";
import { FiltersButton } from "@/components/common_components/Filters/FiltersButton";
import { ResetFiltersButton } from "@/components/common_components/Filters/ResetFiltersButton";
import { Search, User } from "lucide-react";
import {
Accordion,
AccordionBody,
@@ -681,91 +685,34 @@ const Teams: React.FC<TeamProps> = ({
{/* Search and Filter Controls */}
<div className="flex flex-wrap items-center gap-3">
{/* Team Alias Search */}
<div className="relative w-64">
<input
type="text"
placeholder="Search by Team Name..."
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={filters.team_alias}
onChange={(e) => handleFilterChange("team_alias", e.target.value)}
/>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
<FilterInput
placeholder="Search by Team Name..."
value={filters.team_alias}
onChange={(value) => handleFilterChange("team_alias", value)}
icon={Search}
/>
{/* Filter Button */}
<button
className={`px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2 ${showFilters ? "bg-gray-100" : ""}`}
<FiltersButton
onClick={() => setShowFilters(!showFilters)}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
Filters
{(filters.team_id || filters.team_alias || filters.organization_id) && (
<span className="w-2 h-2 rounded-full bg-blue-500"></span>
)}
</button>
active={showFilters}
hasActiveFilters={!!(filters.team_id || filters.team_alias || filters.organization_id)}
/>
{/* Reset Filters Button */}
<button
className="px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2"
onClick={handleFilterReset}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Reset Filters
</button>
<ResetFiltersButton onClick={handleFilterReset} />
</div>
{/* Additional Filters */}
{showFilters && (
<div className="flex flex-wrap items-center gap-3 mt-3">
{/* Team ID Search */}
<div className="relative w-64">
<input
type="text"
placeholder="Enter Team ID"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={filters.team_id}
onChange={(e) => handleFilterChange("team_id", e.target.value)}
/>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<FilterInput
placeholder="Enter Team ID"
value={filters.team_id}
onChange={(value) => handleFilterChange("team_id", value)}
icon={User}
/>
{/* Organization Dropdown */}
<div className="w-64">
@@ -6,6 +6,10 @@ import { Skeleton } from "antd";
import { UserInfo } from "./types";
import UserInfoView from "./user_info_view";
import { columns as createColumns } from "./columns";
import { FilterInput } from "@/components/common_components/Filters/FilterInput";
import { FiltersButton } from "@/components/common_components/Filters/FiltersButton";
import { ResetFiltersButton } from "@/components/common_components/Filters/ResetFiltersButton";
import { Search, User, CircleUserRound } from "lucide-react";
interface FilterState {
email: string;
@@ -215,93 +219,45 @@ export function UserDataTable({
{/* Search and Filter Controls */}
<div className="flex flex-wrap items-center gap-3">
{/* Email Search */}
<div className="relative w-64">
<input
type="text"
placeholder="Search by email..."
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={filters.email}
onChange={(e) => updateFilters({ email: e.target.value })}
/>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
<FilterInput
placeholder="Search by email..."
value={filters.email}
onChange={(value) => updateFilters({ email: value })}
icon={Search}
/>
{/* Filter Button */}
<button
className={`px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2 ${showFilters ? "bg-gray-100" : ""}`}
<FiltersButton
onClick={() => setShowFilters(!showFilters)}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
Filters
{(filters.user_id || filters.user_role || filters.team) && (
<span className="w-2 h-2 rounded-full bg-blue-500"></span>
)}
</button>
active={showFilters}
hasActiveFilters={!!(filters.user_id || filters.user_role || filters.team)}
/>
{/* Reset Filters Button */}
<button
className="px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2"
<ResetFiltersButton
onClick={() => {
updateFilters(initialFilters);
}}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Reset Filters
</button>
/>
</div>
{/* Additional Filters */}
{showFilters && (
<div className="flex flex-wrap items-center gap-3 mt-3">
{/* User ID Search */}
<div className="relative w-64">
<input
type="text"
placeholder="Filter by User ID"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={filters.user_id}
onChange={(e) => updateFilters({ user_id: e.target.value })}
/>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<FilterInput
placeholder="Filter by User ID"
value={filters.user_id}
onChange={(value) => updateFilters({ user_id: value })}
icon={User}
/>
<FilterInput
placeholder="Filter by SSO ID"
value={filters.sso_user_id}
onChange={(value) => updateFilters({ sso_user_id: value })}
icon={CircleUserRound}
/>
{/* Role Dropdown */}
<div className="w-64">
@@ -333,17 +289,6 @@ export function UserDataTable({
))}
</Select>
</div>
{/* SSO ID Search */}
<div className="relative w-64">
<input
type="text"
placeholder="Filter by SSO ID"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={filters.sso_user_id}
onChange={(e) => updateFilters({ sso_user_id: e.target.value })}
/>
</div>
</div>
)}