mirror of
https://github.com/actualbudget/actual.git
synced 2026-05-06 07:01:45 -05:00
[AI] Add ErrorBoundary around dashboard widgets (#7382)
* [AI] Add ErrorBoundary around dashboard widgets (#7273) Wraps each dashboard widget in an ErrorBoundary so a faulty widget degrades to an error card instead of crashing the entire Reports page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add release notes for PR #7382 * [autofix.ci] apply automated fixes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3d5881ea57
commit
e0772e24cd
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Dialog, DialogTrigger } from 'react-aria-components';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import ReactGridLayout from 'react-grid-layout';
|
||||
import type { Layout } from 'react-grid-layout';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
@@ -34,6 +35,7 @@ import { CrossoverCard } from './reports/CrossoverCard';
|
||||
import { CustomReportListCards } from './reports/CustomReportListCards';
|
||||
import { FormulaCard } from './reports/FormulaCard';
|
||||
import { MarkdownCard } from './reports/MarkdownCard';
|
||||
import { MissingReportCard } from './reports/MissingReportCard';
|
||||
import { NetWorthCard } from './reports/NetWorthCard';
|
||||
import { SankeyCard } from './reports/SankeyCard';
|
||||
import './overview.scss';
|
||||
@@ -770,142 +772,176 @@ export function Overview({ dashboard }: OverviewProps) {
|
||||
|
||||
return (
|
||||
<div key={item.i}>
|
||||
{widget.type === 'net-worth-card' ? (
|
||||
<NetWorthCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
accounts={accounts}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'crossover-card' &&
|
||||
crossoverReportEnabled ? (
|
||||
<CrossoverCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
accounts={accounts}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'age-of-money-card' &&
|
||||
ageOfMoneyReportEnabled ? (
|
||||
<AgeOfMoneyCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'cash-flow-card' ? (
|
||||
<CashFlowCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'spending-card' ? (
|
||||
<SpendingCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'budget-analysis-card' &&
|
||||
budgetAnalysisReportEnabled ? (
|
||||
<BudgetAnalysisCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'markdown-card' ? (
|
||||
<MarkdownCard
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'custom-report' ? (
|
||||
<CustomReportListCards
|
||||
isEditing={isEditing}
|
||||
report={customReportMap.get(widget.meta.id)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'summary-card' ? (
|
||||
<SummaryCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'calendar-card' ? (
|
||||
<CalendarCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'formula-card' && formulaMode ? (
|
||||
<FormulaCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'sankey-card' && sankeyFeatureFlag ? (
|
||||
<SankeyCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta => onMetaChange(item, newMeta)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<ErrorBoundary
|
||||
fallbackRender={() => (
|
||||
<MissingReportCard
|
||||
isEditing={isEditing}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
>
|
||||
<Trans>This widget has failed to load.</Trans>
|
||||
</MissingReportCard>
|
||||
)}
|
||||
>
|
||||
{widget.type === 'net-worth-card' ? (
|
||||
<NetWorthCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
accounts={accounts}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'crossover-card' &&
|
||||
crossoverReportEnabled ? (
|
||||
<CrossoverCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
accounts={accounts}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'age-of-money-card' &&
|
||||
ageOfMoneyReportEnabled ? (
|
||||
<AgeOfMoneyCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'cash-flow-card' ? (
|
||||
<CashFlowCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'spending-card' ? (
|
||||
<SpendingCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'budget-analysis-card' &&
|
||||
budgetAnalysisReportEnabled ? (
|
||||
<BudgetAnalysisCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'markdown-card' ? (
|
||||
<MarkdownCard
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'custom-report' ? (
|
||||
<CustomReportListCards
|
||||
isEditing={isEditing}
|
||||
report={customReportMap.get(widget.meta.id)}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'summary-card' ? (
|
||||
<SummaryCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'calendar-card' ? (
|
||||
<CalendarCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'formula-card' && formulaMode ? (
|
||||
<FormulaCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : widget.type === 'sankey-card' &&
|
||||
sankeyFeatureFlag ? (
|
||||
<SankeyCard
|
||||
widgetId={item.i}
|
||||
isEditing={isEditing}
|
||||
meta={widget.meta}
|
||||
onMetaChange={newMeta =>
|
||||
onMetaChange(item, newMeta)
|
||||
}
|
||||
onRemove={() => onRemoveWidget(item.i)}
|
||||
onCopy={targetDashboardId =>
|
||||
onCopyWidget(item.i, targetDashboardId)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
6
upcoming-release-notes/7382.md
Normal file
6
upcoming-release-notes/7382.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Add error boundary to dashboard widgets, displaying fallback UI for rendering failures.
|
||||
Reference in New Issue
Block a user