{editingProvider === row.provider ? (
<>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.test.tsx
index cc0169d745c..be6130b0288 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.test.tsx
@@ -174,6 +174,8 @@ describe("AllModelsTable", () => {
const { rerender } = render(
);
expect(screen.getByText("$30")).toBeInTheDocument();
expect(screen.getByText("$60")).toBeInTheDocument();
+ expect(screen.getByRole("cell", { name: /\$30/ })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: /costs/i })).toHaveClass("text-right");
rerender(
);
expect(screen.queryByText(/^\$/)).not.toBeInTheDocument();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx
index cbc31747688..9581d3db198 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx
@@ -437,7 +437,7 @@ export const getModelsTableColumns = ({
{
id: COSTS_COLUMN_ID,
accessorFn: (row) => row.input_cost,
- meta: { title: "Costs" },
+ meta: { title: "Costs", numeric: true },
header: ({ column }) =>
,
enableSorting: true,
size: 130,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx
index 889a17bc88d..15b2a30b50c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx
@@ -19,7 +19,15 @@ import {
} from "@/components/ui/combobox";
import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import {
+ NUMERIC_CELL_CLASS,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { AreaChart, BarChart, DonutChart } from "@/components/shared/charts";
@@ -651,14 +659,14 @@ const UsagePage: React.FC
= ({ accessToken, token, userRole, use
Provider
- Spend
+ Spend
{spendByProvider.map((provider) => (
{provider.provider}
-
+
@@ -840,8 +848,8 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use
Customer
- Spend
- Total Events
+ Spend
+ Total Events
@@ -849,10 +857,10 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use
{topUsers?.map((user: any, index: number) => (
{user.end_user}
-
+
- {user.total_count}
+ {user.total_count}
))}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx
index 9d163fe2c08..839eb406200 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx
@@ -82,6 +82,16 @@ describe("OrganizationsTable", () => {
}
});
+ it("right-aligns the money and count columns only", () => {
+ renderWithProviders();
+ for (const header of ["Spend (USD)", "Budget (USD)", "Members"]) {
+ expect(screen.getByRole("columnheader", { name: header })).toHaveClass("text-right");
+ }
+ for (const header of ["Organization Name", "TPM / RPM Limits"]) {
+ expect(screen.getByRole("columnheader", { name: header })).not.toHaveClass("text-right");
+ }
+ });
+
it("opens the detail view when the organization ID cell is clicked", async () => {
const user = userEvent.setup();
const onOrganizationClick = vi.fn();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx
index 0fea6c6606e..5f170a32941 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx
@@ -129,7 +129,7 @@ export const getOrganizationsTableColumns = ({
{
id: "spend",
accessorKey: "spend",
- meta: { title: "Spend (USD)" },
+ meta: { title: "Spend (USD)", numeric: true },
header: ({ column }) => ,
size: 120,
enableSorting: true,
@@ -137,7 +137,7 @@ export const getOrganizationsTableColumns = ({
},
{
id: "max_budget",
- meta: { title: "Budget (USD)" },
+ meta: { title: "Budget (USD)", numeric: true },
header: "Budget (USD)",
size: 120,
enableSorting: false,
@@ -163,7 +163,7 @@ export const getOrganizationsTableColumns = ({
},
{
id: "members",
- meta: { title: "Members" },
+ meta: { title: "Members", numeric: true },
header: "Members",
size: 100,
enableSorting: false,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/BulkEditUsers.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/BulkEditUsers.tsx
index 459e3fd8c92..67a648dacd1 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/BulkEditUsers.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/BulkEditUsers.tsx
@@ -9,7 +9,16 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import {
+ NUMERIC_CELL_CLASS,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { cn } from "@/lib/cva.config";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
interface BulkEditUserModalProps {
@@ -250,7 +259,7 @@ const BulkEditUserModal: React.FC = ({
User ID
Email
Current Role
- Budget
+ Budget
@@ -263,7 +272,7 @@ const BulkEditUserModal: React.FC = ({
{possibleUIRoles?.[user.user_role]?.ui_label || user.user_role}
-
+
diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.test.tsx
index 3a2fea66b0c..678b811921c 100644
--- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.test.tsx
+++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.test.tsx
@@ -50,6 +50,9 @@ describe("getModelHubTableColumns", () => {
expect(screen.getByText("128.0K / 16.4K")).toBeInTheDocument();
expect(screen.getByText("$2.50")).toBeInTheDocument();
expect(screen.getByText("$10.00")).toBeInTheDocument();
+ expect(screen.getByRole("cell", { name: "128.0K / 16.4K" })).toHaveClass("text-right");
+ expect(screen.getByRole("cell", { name: /\$2\.50/ })).toHaveClass("text-right");
+ expect(screen.getByRole("cell", { name: "gpt-4o" })).not.toHaveClass("text-right");
});
it("shows capability badges only for supported features", () => {
diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx
index 9f74771f3b1..fc71a9340ac 100644
--- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx
+++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx
@@ -143,7 +143,7 @@ export const getModelHubTableColumns = ({ onModelClick }: ModelHubTableColumnsDe
{
id: "max_input_tokens",
accessorKey: "max_input_tokens",
- meta: { title: "Tokens", className: "hidden lg:table-cell" },
+ meta: { title: "Tokens", className: "hidden lg:table-cell", numeric: true },
header: ({ column }) => ,
size: 110,
enableSorting: true,
@@ -165,7 +165,7 @@ export const getModelHubTableColumns = ({ onModelClick }: ModelHubTableColumnsDe
{
id: "input_cost_per_token",
accessorKey: "input_cost_per_token",
- meta: { title: "Cost/1M", skeleton: "twoLine" },
+ meta: { title: "Cost/1M", skeleton: "twoLine", numeric: true },
header: ({ column }) => ,
size: 110,
enableSorting: true,
diff --git a/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx b/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx
index 9699ea2b7d1..ad89b62d611 100644
--- a/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx
@@ -161,6 +161,12 @@ describe("sort contract – only backend-sortable columns are sortable", () => {
});
});
+ it("right-aligns Spend / Budget but not Team", () => {
+ renderTable();
+ expect(screen.getByRole("columnheader", { name: "Spend / Budget" })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: "Team" })).not.toHaveClass("text-right");
+ });
+
it("does not make Spend / Budget sortable (the backend rejects sort_by=spend)", () => {
renderTable();
expect(screen.queryByText("Spend / Budget").closest("button")).toBeNull();
diff --git a/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx b/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx
index 84369a58307..05bfccf6417 100644
--- a/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx
+++ b/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx
@@ -210,7 +210,7 @@ export const getTeamTableColumns = ({
{
id: "spend",
accessorKey: "spend",
- meta: { title: "Spend / Budget", skeleton: "meter" },
+ meta: { title: "Spend / Budget", skeleton: "meter", numeric: true },
header: "Spend / Budget",
size: 200,
enableSorting: false,
@@ -234,7 +234,7 @@ export const getTeamTableColumns = ({
},
{
id: "members",
- meta: { title: "Members" },
+ meta: { title: "Members", numeric: true },
header: "Members",
size: 110,
enableSorting: false,
@@ -242,7 +242,7 @@ export const getTeamTableColumns = ({
},
{
id: "models",
- meta: { title: "Models" },
+ meta: { title: "Models", numeric: true },
header: "Models",
size: 100,
enableSorting: false,
diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
index 7ef7f1fcb09..423723a5938 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
@@ -207,6 +207,12 @@ it("should render VirtualKeysTable component", () => {
expect(screen.getByText("Test Key Alias")).toBeInTheDocument();
});
+it("right-aligns the Spend / Budget column", async () => {
+ renderWithProviders();
+ expect(await screen.findByRole("columnheader", { name: /^Spend/ })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: /^Key$/ })).not.toHaveClass("text-right");
+});
+
it("shows the Budget Reset column by default", async () => {
renderWithProviders();
await waitFor(() => {
diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
index 477d7b0ecb4..d69e90b1882 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
@@ -267,7 +267,7 @@ export const getKeyTableColumns = ({
{
id: "spend",
accessorKey: "spend",
- meta: { title: "Spend / Budget", skeleton: "meter" },
+ meta: { title: "Spend / Budget", skeleton: "meter", numeric: true },
header: ({ table }) => ,
size: 180,
enableSorting: true,
diff --git a/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx b/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx
index 8669f067d6d..b5029e98f9c 100644
--- a/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx
+++ b/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx
@@ -1,7 +1,15 @@
import React, { useState, useEffect } from "react";
import { Button, buttonVariants } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import {
+ NUMERIC_CELL_CLASS,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
import { Download, FileText, FileWarning, Trash2, TriangleAlert, Upload } from "lucide-react";
import { userCreateCall, invitationCreateCall, getProxyUISettings } from "./networking";
import Papa from "papaparse";
@@ -798,7 +806,7 @@ const BulkCreateUsersButton: React.FC = ({
Email
Role
Teams
- Budget
+ Budget
Status
@@ -809,7 +817,7 @@ const BulkCreateUsersButton: React.FC = ({
{record.user_email}
{record.user_role}
{record.teams}
- {record.max_budget}
+ {record.max_budget}
{renderStatusCell(record)}
))}
diff --git a/ui/litellm-dashboard/src/components/chat/KeysPanel.tsx b/ui/litellm-dashboard/src/components/chat/KeysPanel.tsx
index 8b8bd6c8189..fe37102eaba 100644
--- a/ui/litellm-dashboard/src/components/chat/KeysPanel.tsx
+++ b/ui/litellm-dashboard/src/components/chat/KeysPanel.tsx
@@ -10,7 +10,16 @@ import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import {
+ NUMERIC_CELL_CLASS,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { cn } from "@/lib/cva.config";
import { toast } from "@/lib/toast";
import { keyListCall, regenerateKeyCall } from "../networking";
import { KeyResponse } from "../key_team_helpers/key_list";
@@ -176,7 +185,9 @@ const KeysPanel: React.FC = ({ accessToken, userId, premiumUser }) => {
Key
- Spend
+
+ Spend
+
Expires
Created
{premiumUser && (
@@ -220,7 +231,9 @@ const KeysPanel: React.FC = ({ accessToken, userId, premiumUser }) => {
Key
- Spend
+
+ Spend
+
Expires
Created
{premiumUser && (
@@ -237,7 +250,7 @@ const KeysPanel: React.FC = ({ accessToken, userId, premiumUser }) => {
{maskKey(record.key_name)}
{record.key_alias && {record.key_alias}
}
-
+
${record.spend?.toFixed(2) ?? "0.00"}
{record.max_budget != null && record.max_budget > 0 && (
/ ${record.max_budget.toFixed(2)}
diff --git a/ui/litellm-dashboard/src/components/common_components/MemberTable.test.tsx b/ui/litellm-dashboard/src/components/common_components/MemberTable.test.tsx
index a16f11eab60..418f27c8188 100644
--- a/ui/litellm-dashboard/src/components/common_components/MemberTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/common_components/MemberTable.test.tsx
@@ -213,3 +213,20 @@ describe("MemberTable actions", () => {
expect(screen.getByText("No members found")).toBeInTheDocument();
});
});
+
+describe("MemberTable numeric columns", () => {
+ it("right-aligns the header and cells of a numeric extra column only", () => {
+ renderTable({
+ members: [MEMBERS[0]],
+ extraColumns: [
+ { title: "Spend (USD)", key: "spend", numeric: true, render: () => $1.50 },
+ { title: "Joined", key: "joined", render: () => Aug 1 },
+ ],
+ });
+
+ expect(screen.getByRole("columnheader", { name: "Spend (USD)" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("cell", { name: "$1.50" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("columnheader", { name: "Joined" })).not.toHaveClass("text-right");
+ expect(screen.getByRole("cell", { name: "Aug 1" })).not.toHaveClass("text-right");
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx
index 6efc82e2d35..3cbe59bd839 100644
--- a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx
+++ b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx
@@ -24,6 +24,7 @@ export interface MemberTableColumn {
key: string;
render: (member: Member) => React.ReactNode;
sortValue?: (member: Member) => MemberTableSortValue;
+ numeric?: boolean;
}
export interface MemberTableProps {
@@ -87,6 +88,7 @@ const extraColumnDef = (column: MemberTableColumn): ColumnDef => {
header: () => {column.title},
enableSorting: false,
enableGlobalFilter: false,
+ meta: { numeric: column.numeric },
cell: ({ row }) => column.render(row.original),
};
}
@@ -97,6 +99,7 @@ const extraColumnDef = (column: MemberTableColumn): ColumnDef => {
sortDescFirst: false,
sortUndefined: "last",
enableGlobalFilter: false,
+ meta: { numeric: column.numeric },
cell: ({ row }) => column.render(row.original),
};
};
diff --git a/ui/litellm-dashboard/src/components/common_components/simple_table.test.tsx b/ui/litellm-dashboard/src/components/common_components/simple_table.test.tsx
new file mode 100644
index 00000000000..885d0ed51ac
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/simple_table.test.tsx
@@ -0,0 +1,25 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { SimpleTable, type SimpleTableColumn } from "./simple_table";
+
+interface Row {
+ name: string;
+ spend: number;
+}
+
+const columns: SimpleTableColumn[] = [
+ { header: "Name", accessor: "name" },
+ { header: "Spend", accessor: "spend", numeric: true },
+];
+
+describe("SimpleTable numeric columns", () => {
+ it("right-aligns the header and cells of a numeric column only", () => {
+ render();
+
+ expect(screen.getByRole("columnheader", { name: "Spend" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("cell", { name: "42" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("columnheader", { name: "Name" })).not.toHaveClass("text-right");
+ expect(screen.getByRole("cell", { name: "Alice" })).not.toHaveClass("text-right");
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/simple_table.tsx b/ui/litellm-dashboard/src/components/common_components/simple_table.tsx
index 6a30a2e0273..a4b84d28801 100644
--- a/ui/litellm-dashboard/src/components/common_components/simple_table.tsx
+++ b/ui/litellm-dashboard/src/components/common_components/simple_table.tsx
@@ -1,11 +1,20 @@
import React from "react";
-import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
+import {
+ NUMERIC_CELL_CLASS,
+ Table,
+ TableHeader,
+ TableRow,
+ TableHead,
+ TableBody,
+ TableCell,
+} from "@/components/ui/table";
export interface SimpleTableColumn {
header: string;
accessor?: keyof T;
cell?: (row: T) => React.ReactNode;
width?: string;
+ numeric?: boolean;
}
interface SimpleTableProps {
@@ -34,7 +43,11 @@ export function SimpleTable({
{columns.map((column, index) => (
-
+
{column.header}
))}
@@ -51,7 +64,7 @@ export function SimpleTable({
data.map((row, rowIndex) => (
{columns.map((column, colIndex) => (
-
+
{column.cell ? column.cell(row) : String(row[column.accessor as keyof T] ?? "")}
))}
diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx
index c800d12ad62..f325e92d2b5 100644
--- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx
+++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx
@@ -134,6 +134,7 @@ const OrganizationInfoView: React.FC = ({
{
title: "Spend (USD)",
key: "spend",
+ numeric: true,
sortValue: (record: Member) => orgMemberFor(record)?.spend ?? null,
render: (record: Member) => ,
},
diff --git a/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.test.tsx b/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.test.tsx
index a7e4befa6ac..df88287b369 100644
--- a/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.test.tsx
@@ -141,8 +141,33 @@ const expansionColumns: ColumnDef[] = [
},
];
+const numericColumns: ColumnDef[] = [
+ {
+ accessorKey: "name",
+ header: "Name",
+ cell: ({ row }) => {row.original.name},
+ },
+ {
+ id: "spend",
+ header: ({ column }) => ,
+ meta: { numeric: true },
+ cell: () => $1.50,
+ },
+];
+
const CHARLIE_ALICE_BOB: Person[] = [person("c", "Charlie"), person("a", "Alice"), person("b", "Bob")];
+describe("DataTable numeric columns", () => {
+ it("right-aligns the header and cells of a numeric column only", () => {
+ render();
+
+ expect(screen.getByRole("columnheader", { name: "Spend" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("cell", { name: "$1.50" })).toHaveClass("text-right", "tabular-nums");
+ expect(screen.getByRole("columnheader", { name: "Name" })).not.toHaveClass("text-right");
+ expect(screen.getByRole("cell", { name: "Alice" })).not.toHaveClass("text-right");
+ });
+});
+
describe("DataTable sorting", () => {
it("client mode reorders rows when the sort header is clicked", async () => {
const user = userEvent.setup();
diff --git a/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.tsx b/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.tsx
index 340f8d4f44f..feb1615b4e1 100644
--- a/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.tsx
+++ b/ui/litellm-dashboard/src/components/shared/DataTable/DataTable.tsx
@@ -31,6 +31,7 @@ import { Fragment, useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import {
+ NUMERIC_CELL_CLASS,
Table as TableRoot,
TableBody,
TableCell,
@@ -193,7 +194,7 @@ function DataTableHeadCell({ header, size, stickyHeader, enableColumnResi
className={cn(
"relative text-muted-foreground",
size === "compact" ? "h-8 px-2 py-1 text-xs" : "",
- meta?.numeric ? "text-right" : "",
+ meta?.numeric ? NUMERIC_CELL_CLASS : "",
meta?.className,
meta?.headerClassName,
sticky.className,
@@ -238,7 +239,7 @@ function DataTableBodyCell({ cell, size, stickyHeader, enableColumnResizi
className={cn(
"overflow-hidden text-ellipsis",
size === "compact" ? "px-2 py-1 text-xs" : "",
- meta?.numeric ? "text-right tabular-nums" : "",
+ meta?.numeric ? NUMERIC_CELL_CLASS : "",
meta?.className,
sticky.className,
)}
diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx
index bd7398bc6c8..1d50a9a4670 100644
--- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx
@@ -292,6 +292,9 @@ describe("TeamMembersComponent", () => {
expect(screen.getByText("$100.50")).toBeInTheDocument();
expect(screen.getByText("$1,538.26")).toBeInTheDocument();
+ expect(screen.getByRole("cell", { name: "$100.50" })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: /^Team Member Budget \(USD\)/ })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: "User Email" })).not.toHaveClass("text-right");
expect(screen.getByText(/100 RPM/)).toBeInTheDocument();
expect(screen.getByText(/10000 TPM/)).toBeInTheDocument();
});
diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx
index e8576ce6dfe..a24d1b1e7cd 100644
--- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx
@@ -186,6 +186,7 @@ export default function TeamMemberTab({
),
key: "spend",
+ numeric: true,
sortValue: (record: Member) => getUserCurrentCycleSpend(record.user_id),
render: (record: Member) => ,
},
@@ -199,6 +200,7 @@ export default function TeamMemberTab({
),
key: "total_spend",
+ numeric: true,
sortValue: (record: Member) => getUserTotalSpend(record.user_id),
render: (record: Member) => ,
},
@@ -212,11 +214,12 @@ export default function TeamMemberTab({
),
key: "budget",
+ numeric: true,
sortValue: (record: Member) => getUserBudget(record.user_id),
render: (record: Member) => {
const source = getUserBudgetSource(record.user_id);
return (
-
+
{source !== "none" && (
diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
index 08408fb4ff4..7561264ea65 100644
--- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
@@ -131,6 +131,14 @@ describe("TeamVirtualKeysTable", () => {
});
});
+ it("right-aligns the Spend (USD) and Budget (USD) columns", async () => {
+ renderWithProviders();
+
+ expect(await screen.findByRole("columnheader", { name: "Spend (USD)" })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: "Budget (USD)" })).toHaveClass("text-right");
+ expect(screen.getByRole("columnheader", { name: "Key ID" })).not.toHaveClass("text-right");
+ });
+
it("should display keys in table when data is loaded", async () => {
mockUseKeys.mockReturnValue({
data: {
diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
index 5b1b71e060e..4e4fa3f4bb2 100644
--- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
@@ -285,7 +285,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
{
id: "spend",
accessorKey: "spend",
- meta: { title: "Spend (USD)" },
+ meta: { title: "Spend (USD)", numeric: true },
header: ({ column }) => ,
size: 100,
enableSorting: true,
@@ -294,7 +294,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
{
id: "max_budget",
accessorKey: "max_budget",
- meta: { title: "Budget (USD)" },
+ meta: { title: "Budget (USD)", numeric: true },
header: ({ column }) => ,
size: 110,
enableSorting: true,
diff --git a/ui/litellm-dashboard/src/components/ui/table.tsx b/ui/litellm-dashboard/src/components/ui/table.tsx
index 6271a9e89ac..1c4c1a981de 100644
--- a/ui/litellm-dashboard/src/components/ui/table.tsx
+++ b/ui/litellm-dashboard/src/components/ui/table.tsx
@@ -4,6 +4,8 @@ import * as React from "react";
import { cn } from "@/lib/cva.config";
+const NUMERIC_CELL_CLASS = "text-right tabular-nums";
+
const Table = React.forwardRef>(
({ className, ...props }, ref) => (
@@ -96,4 +98,4 @@ const TableCaption = React.forwardRef