mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 04:18:44 +00:00
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:
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} />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user