diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx
new file mode 100644
index 0000000000..814625ff6b
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx
@@ -0,0 +1,125 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import OrganizationFilters, { FilterState } from "./OrganizationFilters";
+
+describe("OrganizationFilters", () => {
+ const defaultFilters: FilterState = {
+ org_id: "",
+ org_alias: "",
+ sort_by: "",
+ sort_order: "asc",
+ };
+
+ it("should render", () => {
+ const onToggleFilters = vi.fn();
+ const onChange = vi.fn();
+ const onReset = vi.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByPlaceholderText("Search by Organization Name")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /^filters$/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /reset filters/i })).toBeInTheDocument();
+ });
+
+ it("should show additional filters when showFilters is true", () => {
+ const onToggleFilters = vi.fn();
+ const onChange = vi.fn();
+ const onReset = vi.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByPlaceholderText("Search by Organization ID")).toBeInTheDocument();
+ });
+
+ it("should call onChange when organization name input changes", async () => {
+ const user = userEvent.setup();
+ const onToggleFilters = vi.fn();
+ const onChange = vi.fn();
+ const onReset = vi.fn();
+
+ render(
+ ,
+ );
+
+ const input = screen.getByPlaceholderText("Search by Organization Name");
+ await user.type(input, "test");
+
+ await waitFor(
+ () => {
+ expect(onChange).toHaveBeenCalledWith("org_alias", expect.any(String));
+ },
+ { timeout: 500 },
+ );
+ });
+
+ it("should call onReset when reset button is clicked", async () => {
+ const user = userEvent.setup();
+ const onToggleFilters = vi.fn();
+ const onChange = vi.fn();
+ const onReset = vi.fn();
+
+ render(
+ ,
+ );
+
+ const resetButton = screen.getByRole("button", { name: /reset filters/i });
+ await user.click(resetButton);
+
+ expect(onReset).toHaveBeenCalledTimes(1);
+ });
+
+ it("should show badge on filters button when filters are active", () => {
+ const onToggleFilters = vi.fn();
+ const onChange = vi.fn();
+ const onReset = vi.fn();
+
+ const filtersWithActive: FilterState = {
+ ...defaultFilters,
+ org_alias: "test org",
+ };
+
+ render(
+ ,
+ );
+
+ const filtersButton = screen.getByRole("button", { name: /^filters$/i });
+ const badgeWrapper = filtersButton.closest(".ant-badge");
+ expect(badgeWrapper).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
index 9b3fa2fc02..5643a4bc51 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
@@ -1,4 +1,7 @@
-import React from "react";
+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";
interface OrganizationFiltersProps {
filters: FilterState;
@@ -22,94 +25,39 @@ const OrganizationFilters = ({
onChange,
onReset,
}: OrganizationFiltersProps) => {
+ const hasActiveFilters = !!(filters.org_id || filters.org_alias);
+
return (
{/* Search and Filter Controls */}
- {/* Organization Alias Search */}
-
-
onChange("org_alias", e.target.value)}
- />
-
-
+
onChange("org_alias", value)}
+ icon={Search}
+ className="w-64"
+ />
- {/* Filter Button */}
-
+ active={showFilters}
+ hasActiveFilters={hasActiveFilters}
+ />
- {/* Reset Filters Button */}
-
+
{/* Additional Filters */}
{showFilters && (
- {/* Organization ID Search */}
-
-
onChange("org_id", e.target.value)}
- />
-
-
+
onChange("org_id", value)}
+ icon={User}
+ className="w-64"
+ />
)}
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.test.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.test.tsx
new file mode 100644
index 0000000000..e96936e179
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.test.tsx
@@ -0,0 +1,59 @@
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { FilterInput } from "./FilterInput";
+
+describe("FilterInput", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.runOnlyPendingTimers();
+ vi.useRealTimers();
+ });
+
+ it("should render", () => {
+ const onChange = vi.fn();
+ render();
+ expect(screen.getByPlaceholderText("Search...")).toBeInTheDocument();
+ });
+
+ it("should call onChange with debounced value", async () => {
+ const onChange = vi.fn();
+ render();
+
+ const input = screen.getByPlaceholderText("Search...");
+
+ act(() => {
+ fireEvent.change(input, { target: { value: "test" } });
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+
+ act(() => {
+ vi.advanceTimersByTime(300);
+ });
+
+ expect(onChange).toHaveBeenCalledWith("test");
+ });
+
+ it("should display the value prop", () => {
+ const onChange = vi.fn();
+ render();
+ const input = screen.getByPlaceholderText("Search...") as HTMLInputElement;
+ expect(input.value).toBe("initial value");
+ });
+
+ it("should update local value immediately when typing", async () => {
+ const onChange = vi.fn();
+ render();
+
+ const input = screen.getByPlaceholderText("Search...") as HTMLInputElement;
+
+ act(() => {
+ fireEvent.change(input, { target: { value: "a" } });
+ });
+
+ expect(input.value).toBe("a");
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.tsx
new file mode 100644
index 0000000000..3590619ba2
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/FilterInput.tsx
@@ -0,0 +1,51 @@
+import { cx } from "@/lib/cva.config";
+import { Input } from "antd";
+import debounce from "lodash/debounce";
+import { LucideIcon } from "lucide-react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+
+interface FilterInputProps {
+ placeholder?: string;
+ value: string;
+ onChange: (value: string) => void;
+ icon?: LucideIcon;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const DEBOUNCE_DELAY = 300;
+
+export const FilterInput: React.FC = ({ placeholder, value, onChange, icon: Icon, className }) => {
+ const [localValue, setLocalValue] = useState(value);
+
+ useEffect(() => {
+ setLocalValue(value);
+ }, [value]);
+
+ const debouncedOnChange = useMemo(() => debounce((val: string) => onChange(val), DEBOUNCE_DELAY), [onChange]);
+
+ useEffect(() => {
+ return () => {
+ debouncedOnChange.cancel();
+ };
+ }, [debouncedOnChange]);
+
+ const handleChange = useCallback(
+ (e: React.ChangeEvent) => {
+ const newValue = e.target.value;
+ setLocalValue(newValue);
+ debouncedOnChange(newValue);
+ },
+ [debouncedOnChange],
+ );
+
+ return (
+ : undefined}
+ className={cx("w-64", className)}
+ />
+ );
+};
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.test.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.test.tsx
new file mode 100644
index 0000000000..ccb2c5d9e5
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { FiltersButton } from "./FiltersButton";
+
+describe("FiltersButton", () => {
+ it("should render", () => {
+ const onClick = vi.fn();
+ render();
+ expect(screen.getByRole("button", { name: /filters/i })).toBeInTheDocument();
+ });
+
+ it("should call onClick when clicked", async () => {
+ const user = userEvent.setup();
+ const onClick = vi.fn();
+ render();
+
+ const button = screen.getByRole("button", { name: /filters/i });
+ await user.click(button);
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
+
+ it("should show badge when hasActiveFilters is true", () => {
+ const onClick = vi.fn();
+ const { container } = render();
+ const button = screen.getByRole("button", { name: /filters/i });
+ const badgeWrapper = button.closest(".ant-badge");
+ expect(badgeWrapper).toBeInTheDocument();
+ });
+
+ it("should render custom label when provided", () => {
+ const onClick = vi.fn();
+ render();
+ expect(screen.getByRole("button", { name: /advanced filters/i })).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.tsx
new file mode 100644
index 0000000000..09782dd446
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/FiltersButton.tsx
@@ -0,0 +1,25 @@
+import { Badge, Button } from "antd";
+import { Filter } from "lucide-react";
+import React from "react";
+
+interface FiltersButtonProps {
+ onClick: () => void;
+ active: boolean;
+ hasActiveFilters: boolean;
+ label?: string;
+}
+
+export const FiltersButton: React.FC = ({
+ onClick,
+ active,
+ hasActiveFilters,
+ label = "Filters",
+}) => {
+ return (
+
+ } className={active ? "bg-gray-100" : ""}>
+ {label}
+
+
+ );
+};
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.test.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.test.tsx
new file mode 100644
index 0000000000..da37ee1b26
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.test.tsx
@@ -0,0 +1,29 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { ResetFiltersButton } from "./ResetFiltersButton";
+
+describe("ResetFiltersButton", () => {
+ it("should render", () => {
+ const onClick = vi.fn();
+ render();
+ expect(screen.getByRole("button", { name: /reset filters/i })).toBeInTheDocument();
+ });
+
+ it("should call onClick when clicked", async () => {
+ const user = userEvent.setup();
+ const onClick = vi.fn();
+ render();
+
+ const button = screen.getByRole("button", { name: /reset filters/i });
+ await user.click(button);
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
+
+ it("should render custom label when provided", () => {
+ const onClick = vi.fn();
+ render();
+ expect(screen.getByRole("button", { name: /clear all/i })).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.tsx b/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.tsx
new file mode 100644
index 0000000000..113d03ddc0
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/Filters/ResetFiltersButton.tsx
@@ -0,0 +1,16 @@
+import { Button } from "antd";
+import { RotateCcw } from "lucide-react";
+import React from "react";
+
+interface ResetFiltersButtonProps {
+ onClick: () => void;
+ label?: string;
+}
+
+export const ResetFiltersButton: React.FC = ({ onClick, label = "Reset Filters" }) => {
+ return (
+ }>
+ {label}
+
+ );
+};