From 55c831e076143f6dc88e70fb3dd6fc9dee79b142 Mon Sep 17 00:00:00 2001
From: Anuj Parihar
Date: Thu, 13 Aug 2026 13:42:01 +0530
Subject: [PATCH] feat: show quarter budget options in budgetting
---
.../sheets/customerDetailSheet.tsx | 27 ++++++++++++++++---
.../governance/views/customerSheet.tsx | 7 ++---
.../views/virtualKeyDetailsSheet.tsx | 5 +++-
ui/components/budgetDisplay.tsx | 12 ++++++---
ui/lib/budgetOutline.ts | 10 ++++++-
ui/lib/constants/governance.ts | 15 +++++++++++
ui/lib/utils/governance.test.ts | 26 ++++++++++++++++++
7 files changed, 90 insertions(+), 12 deletions(-)
diff --git a/ui/app/_fallbacks/enterprise/components/user-groups/sheets/customerDetailSheet.tsx b/ui/app/_fallbacks/enterprise/components/user-groups/sheets/customerDetailSheet.tsx
index 2ee0c195637..858e4d32ef8 100644
--- a/ui/app/_fallbacks/enterprise/components/user-groups/sheets/customerDetailSheet.tsx
+++ b/ui/app/_fallbacks/enterprise/components/user-groups/sheets/customerDetailSheet.tsx
@@ -1,7 +1,7 @@
import { CopyableId } from "@/components/copyableId";
import { Label } from "@/components/ui/label";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
-import { resetDurationLabels } from "@/lib/constants/governance";
+import { fiscalQuarterNote, resetDurationLabels } from "@/lib/constants/governance";
import { Customer } from "@/lib/types/governance";
import { cn } from "@/lib/utils";
import { formatCompactNumber } from "@/lib/utils/numbers";
@@ -32,7 +32,17 @@ function DetailCard({ title, children, contentClassName }: { title: string; chil
);
}
-function BudgetLineBar({ current, max, resetDuration }: { current: number; max: number; resetDuration?: string }) {
+function BudgetLineBar({
+ current,
+ max,
+ resetDuration,
+ resetConfig,
+}: {
+ current: number;
+ max: number;
+ resetDuration?: string;
+ resetConfig?: { quarter_start_month?: number };
+}) {
const pct = max > 0 ? Math.min((current / max) * 100, 100) : 0;
const isOver80 = pct >= 80;
const isOver100 = pct >= 100;
@@ -40,7 +50,10 @@ function BudgetLineBar({ current, max, resetDuration }: { current: number; max:
- {formatResetDuration(resetDuration)}
+
+ {formatResetDuration(resetDuration)}
+ {fiscalQuarterNote(resetDuration, resetConfig)}
+
(b.max_limit || 0) - (a.max_limit || 0))
.map((b) => (
-
+
))}
) : (
diff --git a/ui/app/workspace/governance/views/customerSheet.tsx b/ui/app/workspace/governance/views/customerSheet.tsx
index 7c47e74d127..d7e64a05740 100644
--- a/ui/app/workspace/governance/views/customerSheet.tsx
+++ b/ui/app/workspace/governance/views/customerSheet.tsx
@@ -53,6 +53,7 @@ const createInitialState = (customer?: Customer | null): Omit
{
if (!isEditing || !customer) return false;
- const signature = (rows: { max_limit?: number | null; reset_duration?: string }[]) =>
+ const signature = (rows: { max_limit?: number | null; reset_duration?: string; reset_config?: { quarter_start_month?: number } }[]) =>
[...rows]
- .map((r) => `${r.max_limit ?? ""}:${r.reset_duration ?? ""}`)
+ .map((r) => `${r.max_limit ?? ""}:${r.reset_duration ?? ""}:${r.reset_config?.quarter_start_month ?? ""}`)
.sort()
.join("|");
const next = formData.budgets.filter((b) => b.max_limit !== undefined && b.max_limit !== null);
@@ -223,7 +224,7 @@ export default function CustomerSheet({ open, onOpenChange, customer, onSuccess
const saveCustomer = async (resetBudgetUsage: boolean) => {
const budgetRequests: CreateBudgetRequest[] = formData.budgets
.filter((b) => b.max_limit !== undefined && b.max_limit !== null)
- .map((b) => ({ id: b.id, max_limit: b.max_limit!, reset_duration: b.reset_duration }));
+ .map((b) => ({ id: b.id, max_limit: b.max_limit!, reset_duration: b.reset_duration, reset_config: b.reset_config }));
try {
if (isEditing && customer) {
diff --git a/ui/app/workspace/virtual-keys/views/virtualKeyDetailsSheet.tsx b/ui/app/workspace/virtual-keys/views/virtualKeyDetailsSheet.tsx
index c1bc9117e4f..c823cfeac84 100644
--- a/ui/app/workspace/virtual-keys/views/virtualKeyDetailsSheet.tsx
+++ b/ui/app/workspace/virtual-keys/views/virtualKeyDetailsSheet.tsx
@@ -9,7 +9,7 @@ import { DottedSeparator } from "@/components/ui/separator";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useSheetNavigation } from "@/hooks/useSheetNavigation";
-import { supportsCalendarAlignment } from "@/lib/constants/governance";
+import { fiscalQuarterNote, supportsCalendarAlignment } from "@/lib/constants/governance";
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons";
import { ProviderLabels, ProviderName } from "@/lib/constants/logs";
import { useRemoveVirtualKeyBudgetOverrideMutation, useSetVirtualKeyBudgetOverrideMutation } from "@/lib/store/apis/governanceApi";
@@ -351,6 +351,7 @@ export default function VirtualKeyDetailSheet({
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
+ {fiscalQuarterNote(b.reset_duration, b.reset_config)}
{b.last_reset ? (
@@ -456,6 +457,7 @@ export default function VirtualKeyDetailSheet({
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
+ {fiscalQuarterNote(b.reset_duration, b.reset_config)}
{b.last_reset ? (
Last reset {formatDistanceToNow(new Date(b.last_reset), { addSuffix: true })}
@@ -601,6 +603,7 @@ export default function VirtualKeyDetailSheet({
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
+ {fiscalQuarterNote(b.reset_duration, b.reset_config)}
{b.last_reset ? (
diff --git a/ui/components/budgetDisplay.tsx b/ui/components/budgetDisplay.tsx
index ddb124ea5ed..02a1ad3f3bb 100644
--- a/ui/components/budgetDisplay.tsx
+++ b/ui/components/budgetDisplay.tsx
@@ -1,6 +1,6 @@
import { Progress } from "@/components/ui/progress";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
-import { resetDurationLabels, supportsCalendarAlignment } from "@/lib/constants/governance";
+import { fiscalQuarterNote, resetDurationLabels, supportsCalendarAlignment } from "@/lib/constants/governance";
import { Budget } from "@/lib/types/governance";
import { cn } from "@/lib/utils";
import { formatCurrency, getEffectiveBudgetLimit, hasActiveBudgetOverride } from "@/lib/utils/governance";
@@ -45,7 +45,10 @@ export function BudgetDisplay({ budgets, calendarAligned }: BudgetDisplayProps)
{formatCurrency(effectiveMaxLimit)}
{hasOverride ? override : null}
- {formatResetDuration(b.reset_duration, calendarAligned)}
+
+ {formatResetDuration(b.reset_duration, calendarAligned)}
+ {fiscalQuarterNote(b.reset_duration, b.reset_config)}
+
@@ -60,7 +63,10 @@ export function BudgetDisplay({ budgets, calendarAligned }: BudgetDisplayProps)
) : null}
{b.reset_duration ? (
- Resets {formatResetDuration(b.reset_duration, calendarAligned)}
+
+ Resets {formatResetDuration(b.reset_duration, calendarAligned)}
+ {fiscalQuarterNote(b.reset_duration, b.reset_config)}
+
) : null}
diff --git a/ui/lib/budgetOutline.ts b/ui/lib/budgetOutline.ts
index 7f1d4da3501..448c3e59e10 100644
--- a/ui/lib/budgetOutline.ts
+++ b/ui/lib/budgetOutline.ts
@@ -3,9 +3,13 @@
// compact mono budget labels, allocation-bar math, and the neutral swatch ramp
// consistent across every screen that renders a provider config.
+import { fiscalQuarterNote } from "./constants/governance";
+
export interface BudgetLineLike {
max_limit?: number | null;
reset_duration?: string;
+ // Fiscal quarter definition; only meaningful when reset_duration is "1Q".
+ reset_config?: { quarter_start_month?: number };
}
// Short, mono-friendly period suffixes (e.g. "$20/wk"). Falls back to the raw
@@ -49,10 +53,14 @@ export function money(value: number | null | undefined): string {
// Returns `fallback` when empty.
export function budgetLinesLabel(budgets: BudgetLineLike[] | undefined, fallback = "No budget"): string {
if (!budgets || budgets.length === 0) return fallback;
- return [...budgets]
+ const label = [...budgets]
.sort((a, b) => periodRank(a.reset_duration) - periodRank(b.reset_duration))
.map((b) => `${money(b.max_limit)}/${shortPeriod(b.reset_duration)}`)
.join(" · ");
+ // A group has at most one quarterly line (duplicate periods are blocked), so a
+ // single trailing fiscal-start note is unambiguous. Empty for a January start.
+ const quarterly = budgets.find((b) => b.reset_duration?.endsWith("Q"));
+ return label + fiscalQuarterNote(quarterly?.reset_duration, quarterly?.reset_config);
}
// True when two or more budget lines share the same reset period. A budget
diff --git a/ui/lib/constants/governance.ts b/ui/lib/constants/governance.ts
index e4aeccc9234..a42a8a7f744 100644
--- a/ui/lib/constants/governance.ts
+++ b/ui/lib/constants/governance.ts
@@ -66,6 +66,21 @@ export function formatQuarterPreview(startMonth?: number): string {
.join(" · ");
}
+/**
+ * Compact read-only note naming a quarterly budget's fiscal-year start, e.g.
+ * " · FY starts Apr". Returns "" for non-quarterly budgets and for a January /
+ * unset start (the default), so it only ever appears when it changes behaviour.
+ * Callers append it after the reset-period label (which already reads "Quarterly").
+ */
+export function fiscalQuarterNote(resetDuration?: string, resetConfig?: { quarter_start_month?: number } | null): string {
+ if (!resetDuration || !resetDuration.endsWith("Q")) return "";
+ const start = resetConfig?.quarter_start_month;
+ // Number.isInteger also rejects undefined/NaN; a fractional month like 2.5 would
+ // otherwise pass the range check and index MONTH_ABBREVIATIONS between slots.
+ if (start === undefined || !Number.isInteger(start) || start === 1 || start < 1 || start > 12) return "";
+ return ` · FY starts ${MONTH_ABBREVIATIONS[start - 1]}`;
+}
+
// Month choices for the fiscal quarter start select.
export const quarterStartMonthOptions = MONTH_ABBREVIATIONS.map((_, index) => ({
label: new Date(Date.UTC(2026, index, 1)).toLocaleString("en-US", { month: "long", timeZone: "UTC" }),
diff --git a/ui/lib/utils/governance.test.ts b/ui/lib/utils/governance.test.ts
index 1bc7dc32bda..d800d65bb5e 100644
--- a/ui/lib/utils/governance.test.ts
+++ b/ui/lib/utils/governance.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { periodRank, shortPeriod } from "@/lib/budgetOutline";
import {
budgetResetDurationOptions,
+ fiscalQuarterNote,
formatQuarterPreview,
resetDurationLabels,
resetDurationOptions,
@@ -117,6 +118,31 @@ describe("fiscal quarter preview", () => {
expect(formatQuarterPreview(1.5)).toBe(formatQuarterPreview(1));
});
});
+describe("fiscalQuarterNote", () => {
+ it("names the fiscal start only for a non-January quarterly budget", () => {
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 4 })).toBe(" · FY starts Apr");
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 10 })).toBe(" · FY starts Oct");
+ });
+
+ it("is empty for a January or unset start (the default)", () => {
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 1 })).toBe("");
+ expect(fiscalQuarterNote("1Q", {})).toBe("");
+ expect(fiscalQuarterNote("1Q", undefined)).toBe("");
+ });
+
+ it("is empty for a non-quarterly duration regardless of config", () => {
+ expect(fiscalQuarterNote("1M", { quarter_start_month: 4 })).toBe("");
+ expect(fiscalQuarterNote(undefined, { quarter_start_month: 4 })).toBe("");
+ });
+
+ it("is empty for an out-of-range or non-integer month", () => {
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 0 })).toBe("");
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 13 })).toBe("");
+ // A fractional month sits in range but indexes MONTH_ABBREVIATIONS between
+ // slots, which would render "FY starts undefined" without the integer guard.
+ expect(fiscalQuarterNote("1Q", { quarter_start_month: 2.5 })).toBe("");
+ });
+});
describe("budgetSignature", () => {
const quarterly = (quarterStartMonth?: number) => [
{