Surface budget_reset_at on team info and members tab

Adds a formatBudgetReset helper (dayjs-based, with validity guard) that
renders the next reset as "today" / "in N days" / "on MMM D, YYYY". The
team budget card now shows the team's reset timestamp and the member-
default reset (when a shared team_member_budget is configured), and the
Members tab gains a Budget Reset column per member.
This commit is contained in:
Ryan Crabbe 2026-04-22 17:28:05 -07:00
parent 331e3f2250
commit 5e5a94ac8d
No known key found for this signature in database
3 changed files with 48 additions and 4 deletions

View file

@ -15,6 +15,7 @@ import {
teamUpdateCall,
} from "@/components/networking";
import { useGuardrails } from "@/app/(dashboard)/hooks/guardrails/useGuardrails";
import { formatBudgetReset } from "@/utils/budgetUtils";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils";
import { isProxyAdminRole } from "@/utils/roles";
@ -70,6 +71,7 @@ export interface TeamMembership {
rpm_limit: number | null;
model_max_budget: Record<string, number> | null;
budget_duration: string | null;
budget_reset_at: string | null;
allowed_models?: string[] | null;
};
}
@ -120,6 +122,7 @@ export interface TeamData {
team_member_budget_table: {
max_budget: number;
budget_duration: string;
budget_reset_at: string | null;
tpm_limit: number | null;
rpm_limit: number | null;
} | null;
@ -732,12 +735,21 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<Text>
of {info.max_budget === null ? "Unlimited" : `$${formatNumberWithCommas(info.max_budget, 4)}`}
</Text>
{info.budget_duration && <Text className="text-gray-500">Reset: {info.budget_duration}</Text>}
{formatBudgetReset(info.budget_reset_at) && (
<Text className="text-gray-500">Resets {formatBudgetReset(info.budget_reset_at)}</Text>
)}
<br />
{info.team_member_budget_table && (
<Text className="text-gray-500">
Team Member Budget: ${formatNumberWithCommas(info.team_member_budget_table.max_budget, 4)}
</Text>
<>
<Text className="text-gray-500">
Team Member Budget: ${formatNumberWithCommas(info.team_member_budget_table.max_budget, 4)}
</Text>
{formatBudgetReset(info.team_member_budget_table.budget_reset_at) && (
<Text className="text-gray-500">
Member budgets reset {formatBudgetReset(info.team_member_budget_table.budget_reset_at)}
</Text>
)}
</>
)}
</div>
</Card>

View file

@ -1,6 +1,7 @@
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { Member } from "@/components/networking";
import { formatBudgetReset } from "@/utils/budgetUtils";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { isProxyAdminRole, isUserTeamAdminForSingleTeam } from "@/utils/roles";
import { InfoCircleOutlined } from "@ant-design/icons";
@ -88,6 +89,12 @@ export default function TeamMemberTab({
return models && models.length > 0 ? models : null;
};
const getUserBudgetReset = (userId: string | null): string | null => {
if (!userId) return null;
const membership = teamData.team_memberships.find((tm) => tm.user_id === userId);
return formatBudgetReset(membership?.litellm_budget_table?.budget_reset_at);
};
const extraColumns: ColumnsType<Member> = [
{
title: (
@ -146,6 +153,18 @@ export default function TeamMemberTab({
);
},
},
{
title: "Budget Reset",
key: "budget_reset",
render: (_: unknown, record: Member) => {
const reset = getUserBudgetReset(record.user_id);
return reset ? (
<Typography.Text>{reset}</Typography.Text>
) : (
<Typography.Text type="secondary">—</Typography.Text>
);
},
},
{
title: (
<Space direction="horizontal">

View file

@ -0,0 +1,13 @@
import dayjs from "dayjs";
export function formatBudgetReset(iso: string | null | undefined): string | null {
if (!iso) return null;
const resetDate = dayjs(iso);
if (!resetDate.isValid()) return null;
const days = resetDate.diff(dayjs(), "day");
if (days < 0) return `on ${resetDate.format("MMM D, YYYY")}`;
if (days === 0) return "today";
if (days < 7) return `in ${days} day${days === 1 ? "" : "s"}`;
return `on ${resetDate.format("MMM D, YYYY")}`;
}