fix(ui): prevent white screen on usage page cross-filtering

Recharts Brush component crashes when data length changes after
cross-filtering because its internal indices go out of bounds.
Add key={chartData.length} to force re-mount on data change.
Add ErrorBoundary wrapper for resilience against future chart crashes.
This commit is contained in:
viettranx committed 2026-03-12 18:13:18 +07:00
1 parent 7a4a20b2e8
commit 23e34eec80
3 files changed
+50 -2

No files matched your search

@@ -0,0 +1,45 @@
import { Component, type ErrorInfo, type ReactNode } from "react";
import { AlertTriangle, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("[ErrorBoundary]", error, info.componentStack);
}
private handleRetry = () => {
this.setState({ hasError: false });
};
render() {
if (!this.state.hasError) return this.props.children;
if (this.props.fallback) return this.props.fallback;
return (
<div className="flex flex-col items-center justify-center gap-3 rounded-lg border bg-card p-8 text-center">
<AlertTriangle className="h-8 w-8 text-muted-foreground" />
<p className="text-sm text-muted-foreground">Something went wrong rendering this section.</p>
<Button variant="outline" size="sm" className="gap-1.5" onClick={this.handleRetry}>
<RefreshCw className="h-3.5 w-3.5" />
Retry
</Button>
</div>
);
}
}
@@ -38,7 +38,7 @@ export function TokenAreaChart({ data, loading, granularity }: TokenAreaChartPro
emptyText={t("analytics.noData")}
>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={chartData} margin={{ top: 4, right: 16, left: 0, bottom: 0 }}>
<AreaChart key={chartData.length} data={chartData} margin={{ top: 4, right: 16, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="inputGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
+4 -1
View File
@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/shared/page-header";
import { EmptyState } from "@/components/shared/empty-state";
import { Pagination } from "@/components/shared/pagination";
import { ErrorBoundary } from "@/components/shared/error-boundary";
import { formatTokens, formatCost } from "@/lib/format";
import { useAgents } from "@/pages/agents/hooks/use-agents";
import { useUsage } from "./hooks/use-usage";
@@ -173,7 +174,9 @@ function AnalyticsDashboard() {
export function UsagePage() {
return (
<UsageFilterProvider>
<AnalyticsDashboard />
<ErrorBoundary>
<AnalyticsDashboard />
</ErrorBoundary>
</UsageFilterProvider>
);
}