Add budget tabs and sidebar budget indicators
This commit is contained in:
@@ -185,10 +185,11 @@ function scrollToContainerBottom(container: ScrollContainer, behavior: ScrollBeh
|
||||
container.scrollTo({ top: container.scrollHeight, behavior });
|
||||
}
|
||||
|
||||
type AgentDetailView = "dashboard" | "configuration" | "runs";
|
||||
type AgentDetailView = "dashboard" | "configuration" | "runs" | "budget";
|
||||
|
||||
function parseAgentDetailView(value: string | null): AgentDetailView {
|
||||
if (value === "configure" || value === "configuration") return "configuration";
|
||||
if (value === "budget") return "budget";
|
||||
if (value === "runs") return value;
|
||||
return "dashboard";
|
||||
}
|
||||
@@ -638,6 +639,7 @@ export function AgentDetail() {
|
||||
{ value: "dashboard", label: "Dashboard" },
|
||||
{ value: "configuration", label: "Configuration" },
|
||||
{ value: "runs", label: "Runs" },
|
||||
{ value: "budget", label: "Budget" },
|
||||
]}
|
||||
value={activeView}
|
||||
onValueChange={(value) => navigate(`/agents/${canonicalAgentRef}/${value}`)}
|
||||
@@ -645,15 +647,6 @@ export function AgentDetail() {
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
{!urlRunId && resolvedCompanyId ? (
|
||||
<BudgetPolicyCard
|
||||
summary={agentBudgetSummary}
|
||||
isSaving={budgetMutation.isPending}
|
||||
compact
|
||||
onSave={(amount) => budgetMutation.mutate(amount)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{actionError && <p className="text-sm text-destructive">{actionError}</p>}
|
||||
{isPendingApproval && (
|
||||
<p className="text-sm text-amber-500">
|
||||
@@ -752,6 +745,17 @@ export function AgentDetail() {
|
||||
adapterType={agent.adapterType}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeView === "budget" && resolvedCompanyId ? (
|
||||
<div className="max-w-3xl">
|
||||
<BudgetPolicyCard
|
||||
summary={agentBudgetSummary}
|
||||
isSaving={budgetMutation.isPending}
|
||||
onSave={(amount) => budgetMutation.mutate(amount)}
|
||||
variant="plain"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { PluginSlotMount, PluginSlotOutlet, usePluginSlots } from "@/plugins/slo
|
||||
|
||||
/* ── Top-level tab types ── */
|
||||
|
||||
type ProjectBaseTab = "overview" | "list" | "configuration";
|
||||
type ProjectBaseTab = "overview" | "list" | "configuration" | "budget";
|
||||
type ProjectPluginTab = `plugin:${string}`;
|
||||
type ProjectTab = ProjectBaseTab | ProjectPluginTab;
|
||||
|
||||
@@ -41,6 +41,7 @@ function resolveProjectTab(pathname: string, projectId: string): ProjectTab | nu
|
||||
const tab = segments[projectsIdx + 2];
|
||||
if (tab === "overview") return "overview";
|
||||
if (tab === "configuration") return "configuration";
|
||||
if (tab === "budget") return "budget";
|
||||
if (tab === "issues") return "list";
|
||||
return null;
|
||||
}
|
||||
@@ -328,6 +329,10 @@ export function ProjectDetail() {
|
||||
navigate(`/projects/${canonicalProjectRef}/configuration`, { replace: true });
|
||||
return;
|
||||
}
|
||||
if (activeTab === "budget") {
|
||||
navigate(`/projects/${canonicalProjectRef}/budget`, { replace: true });
|
||||
return;
|
||||
}
|
||||
if (activeTab === "list") {
|
||||
if (filter) {
|
||||
navigate(`/projects/${canonicalProjectRef}/issues/${filter}`, { replace: true });
|
||||
@@ -454,6 +459,8 @@ export function ProjectDetail() {
|
||||
}
|
||||
if (tab === "overview") {
|
||||
navigate(`/projects/${canonicalProjectRef}/overview`);
|
||||
} else if (tab === "budget") {
|
||||
navigate(`/projects/${canonicalProjectRef}/budget`);
|
||||
} else if (tab === "configuration") {
|
||||
navigate(`/projects/${canonicalProjectRef}/configuration`);
|
||||
} else {
|
||||
@@ -470,12 +477,20 @@ export function ProjectDetail() {
|
||||
onSelect={(color) => updateProject.mutate({ color })}
|
||||
/>
|
||||
</div>
|
||||
<InlineEditor
|
||||
value={project.name}
|
||||
onSave={(name) => updateProject.mutate({ name })}
|
||||
as="h2"
|
||||
className="text-xl font-bold"
|
||||
/>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<InlineEditor
|
||||
value={project.name}
|
||||
onSave={(name) => updateProject.mutate({ name })}
|
||||
as="h2"
|
||||
className="text-xl font-bold"
|
||||
/>
|
||||
{project.pauseReason === "budget" ? (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-red-500/30 bg-red-500/10 px-3 py-1 text-[11px] font-medium uppercase tracking-[0.18em] text-red-200">
|
||||
<span className="h-2 w-2 rounded-full bg-red-400" />
|
||||
Paused by budget hard stop
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PluginSlotOutlet
|
||||
@@ -515,6 +530,7 @@ export function ProjectDetail() {
|
||||
{ value: "overview", label: "Overview" },
|
||||
{ value: "list", label: "List" },
|
||||
{ value: "configuration", label: "Configuration" },
|
||||
{ value: "budget", label: "Budget" },
|
||||
...pluginTabItems.map((item) => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
@@ -526,15 +542,6 @@ export function ProjectDetail() {
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
{resolvedCompanyId ? (
|
||||
<BudgetPolicyCard
|
||||
summary={projectBudgetSummary}
|
||||
compact
|
||||
isSaving={budgetMutation.isPending}
|
||||
onSave={(amount) => budgetMutation.mutate(amount)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{activeTab === "overview" && (
|
||||
<OverviewContent
|
||||
project={project}
|
||||
@@ -563,6 +570,17 @@ export function ProjectDetail() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "budget" && resolvedCompanyId ? (
|
||||
<div className="max-w-3xl">
|
||||
<BudgetPolicyCard
|
||||
summary={projectBudgetSummary}
|
||||
variant="plain"
|
||||
isSaving={budgetMutation.isPending}
|
||||
onSave={(amount) => budgetMutation.mutate(amount)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activePluginTab && (
|
||||
<PluginSlotMount
|
||||
slot={activePluginTab.slot}
|
||||
|
||||
Reference in New Issue
Block a user