From c8aa0cf1d3dc4702c7949dd6a9b0314c7bb5d299 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 10 Feb 2026 15:12:22 +0000 Subject: [PATCH] Refactor: extract tooltip components and clean up lint suppressions (#6721) * Refactor: extract tooltip components and clean up lint suppressions Extract CustomTooltip components from CrossoverGraph and NetWorthGraph to module level to fix unstable nested components lint warnings. Also consolidate theme file lint rule into oxlintrc.json and add proper typing to styles object. * Add release notes for maintenance updates addressing lint violations * Remove style prop from CustomTooltip to prevent container layout styles from affecting tooltip Co-authored-by: matiss * Refactor NetWorthGraph component by extracting TrendTooltip and StackedTooltip into separate functions for improved readability and maintainability. Update tooltip props to include necessary parameters for rendering. Clean up unused code and enhance tooltip styling. * Refactor NetWorthGraph component to streamline tooltip handling - Removed unnecessary prop passing for translation function in TrendTooltip. - Adjusted import statements for better clarity and consistency. - Cleaned up code to enhance readability and maintainability. --------- Co-authored-by: Cursor Agent --- .oxlintrc.json | 6 + packages/component-library/src/styles.ts | 3 +- .../reports/graphs/CrossoverGraph.tsx | 224 +++++------ .../reports/graphs/NetWorthGraph.tsx | 374 +++++++++--------- .../desktop-client/src/style/themes/dark.ts | 1 - .../src/style/themes/development.ts | 1 - .../desktop-client/src/style/themes/light.ts | 1 - .../src/style/themes/midnight.ts | 1 - upcoming-release-notes/6721.md | 6 + 9 files changed, 319 insertions(+), 298 deletions(-) create mode 100644 upcoming-release-notes/6721.md diff --git a/.oxlintrc.json b/.oxlintrc.json index 6917f1bd55..7cdbfbaf1c 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -390,6 +390,12 @@ "typescript-paths/absolute-import": ["error", { "enableAlias": false }] } }, + { + "files": ["packages/desktop-client/src/style/themes/*"], + "rules": { + "eslint/no-restricted-imports": "off" + } + }, // TODO: enable these { "files": [ diff --git a/packages/component-library/src/styles.ts b/packages/component-library/src/styles.ts index 863194bd64..d3c00279ea 100644 --- a/packages/component-library/src/styles.ts +++ b/packages/component-library/src/styles.ts @@ -12,8 +12,7 @@ const shadowLarge = { boxShadow: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)', }; -// oxlint-disable-next-line typescript/no-explicit-any -export const styles: Record = { +export const styles: CSSProperties = { incomeHeaderHeight: 70, cardShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', monthRightPadding: 5, diff --git a/packages/desktop-client/src/components/reports/graphs/CrossoverGraph.tsx b/packages/desktop-client/src/components/reports/graphs/CrossoverGraph.tsx index 099c274c01..f55fc9a0f3 100644 --- a/packages/desktop-client/src/components/reports/graphs/CrossoverGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/CrossoverGraph.tsx @@ -20,6 +20,119 @@ import { Container } from '@desktop-client/components/reports/Container'; import { useFormat } from '@desktop-client/hooks/useFormat'; import { usePrivacyMode } from '@desktop-client/hooks/usePrivacyMode'; +type PayloadItem = { + payload: { + x: string; + investmentIncome: number | string; + expenses: number | string; + nestEgg: number | string; + adjustedExpenses?: number | string; + isProjection?: boolean; + }; +}; + +type CustomTooltipProps = { + active?: boolean; + payload?: PayloadItem[]; +}; + +function CustomTooltip({ active, payload }: CustomTooltipProps) { + const { t } = useTranslation(); + const format = useFormat(); + + if (active && payload && payload.length) { + return ( +
+
+
+ {payload[0].payload.x} + {payload[0].payload.isProjection ? ( + + {t('(projected)')} + + ) : null} +
+
+ +
+ Monthly investment income: +
+
+ + {format(payload[0].payload.investmentIncome, 'financial')} + +
+
+ +
+ Monthly expenses: +
+
+ + {format(payload[0].payload.expenses, 'financial')} + +
+
+ {payload[0].payload.adjustedExpenses != null && ( + +
+ Target income: +
+
+ + {format(payload[0].payload.adjustedExpenses, 'financial')} + +
+
+ )} + +
+ Life savings: +
+
+ + {format(payload[0].payload.nestEgg, 'financial')} + +
+
+
+
+
+ ); + } + return null; +} + type CrossoverGraphProps = { style?: CSSProperties; graphData: { @@ -45,7 +158,6 @@ export function CrossoverGraph({ compact = false, showTooltip = true, }: CrossoverGraphProps) { - const { t } = useTranslation(); const privacyMode = usePrivacyMode(); const format = useFormat(); const animationProps = useRechartsAnimation({ isAnimationActive: false }); @@ -57,116 +169,6 @@ export function CrossoverGraph({ return `${format(Math.round(tick), 'financial-no-decimals')}`; }; - type PayloadItem = { - payload: { - x: string; - investmentIncome: number | string; - expenses: number | string; - nestEgg: number | string; - adjustedExpenses?: number | string; - isProjection?: boolean; - }; - }; - - type CustomTooltipProps = { - active?: boolean; - payload?: PayloadItem[]; - }; - - // oxlint-disable-next-line react/no-unstable-nested-components - const CustomTooltip = ({ active, payload }: CustomTooltipProps) => { - if (active && payload && payload.length) { - return ( -
-
-
- {payload[0].payload.x} - {payload[0].payload.isProjection ? ( - - {t('(projected)')} - - ) : null} -
-
- -
- Monthly investment income: -
-
- - {format(payload[0].payload.investmentIncome, 'financial')} - -
-
- -
- Monthly expenses: -
-
- - {format(payload[0].payload.expenses, 'financial')} - -
-
- {payload[0].payload.adjustedExpenses != null && ( - -
- Target income: -
-
- - {format(payload[0].payload.adjustedExpenses, 'financial')} - -
-
- )} - -
- Life savings: -
-
- - {format(payload[0].payload.nestEgg, 'financial')} - -
-
-
-
-
- ); - } - }; - return ( ; +type TrendTooltipProps = TooltipContentProps & { + style?: CSSProperties; +}; + +function TrendTooltip({ active, payload, style }: TrendTooltipProps) { + const { t } = useTranslation(); + + if (active && payload && payload.length) { + return ( +
+
+
+ {payload[0].payload.date} +
+
+ {payload[0].payload.assets}} + /> + {payload[0].payload.debt}} + /> + + {payload[0].payload.networth} + + } + /> + {payload[0].payload.change}} + /> +
+
+
+ ); + } + return null; +} + +type StackedTooltipProps = TooltipContentProps & { + sortedAccounts: Array<{ id: string; name: string }>; + accounts: Array<{ id: string; name: string }>; + hoveredAccountId: string | null; + format: UseFormatResult; +}; + +function StackedTooltip({ + active, + payload, + sortedAccounts, + accounts, + hoveredAccountId, + format, +}: StackedTooltipProps) { + if (active && payload && payload.length) { + // Calculate total from payload (visible accounts) + const total = payload.reduce( + (acc: number, p) => acc + (Number(p.value) || 0), + 0, + ); + const sortedPayload = [...payload].sort((a, b) => { + const indexA = sortedAccounts.findIndex(acc => acc.id === a.dataKey); + const indexB = sortedAccounts.findIndex(acc => acc.id === b.dataKey); + return indexB - indexA; + }); + + const hasPositive = payload.some(p => (Number(p.value) || 0) > 0); + const hasNegative = payload.some(p => (Number(p.value) || 0) < 0); + const showPercentage = !(hasPositive && hasNegative); + + return ( +
+
+ {payload[0].payload.date} +
+ + + + + + {showPercentage && } + + + + {sortedPayload.map(entry => { + const accountId = entry.dataKey as string; + const accountName = + accounts.find(a => a.id === accountId)?.name || accountId; + const value = Number(entry.value); + const percent = total !== 0 ? (value / total) * 100 : 0; + + return ( + + + + {showPercentage && ( + + )} + + ); + })} + + + + {showPercentage && ( + + )} + + +
+ Account + + Value + %
+ {accountName} + + + + {format(value, 'financial')} + + + + + {percent.toFixed(1)}% + +
+ Total + + {format(total, 'financial')} + 100.0%
+
+ ); + } + return null; +} + type NetWorthGraphProps = { style?: CSSProperties; graphData: { @@ -64,7 +247,6 @@ export function NetWorthGraph({ interval = 'Monthly', mode = 'trend', }: NetWorthGraphProps) { - const { t } = useTranslation(); const privacyMode = usePrivacyMode(); const id = useId(); const format = useFormat(); @@ -151,184 +333,6 @@ export function NetWorthGraph({ .map(point => point.x); }, [interval, graphData.data]); - // Trend Tooltip - // oxlint-disable-next-line react/no-unstable-nested-components - const TrendTooltip = ({ - active, - payload, - }: TooltipContentProps) => { - if (active && payload && payload.length) { - return ( -
-
-
- {payload[0].payload.date} -
-
- {payload[0].payload.assets} - } - /> - {payload[0].payload.debt}} - /> - - {payload[0].payload.networth} - - } - /> - {payload[0].payload.change} - } - /> -
-
-
- ); - } - return null; - }; - - // Stacked Tooltip - // oxlint-disable-next-line react/no-unstable-nested-components - const StackedTooltip = ({ - active, - payload, - }: TooltipContentProps) => { - if (active && payload && payload.length) { - // Calculate total from payload (visible accounts) - const total = payload.reduce( - (acc: number, p) => acc + (Number(p.value) || 0), - 0, - ); - const sortedPayload = [...payload].sort((a, b) => { - const indexA = sortedAccounts.findIndex(acc => acc.id === a.dataKey); - const indexB = sortedAccounts.findIndex(acc => acc.id === b.dataKey); - return indexB - indexA; - }); - - const hasPositive = payload.some(p => (Number(p.value) || 0) > 0); - const hasNegative = payload.some(p => (Number(p.value) || 0) < 0); - const showPercentage = !(hasPositive && hasNegative); - - return ( -
-
- {payload[0].payload.date} -
- - - - - - {showPercentage && } - - - - {sortedPayload.map(entry => { - const accountId = entry.dataKey as string; - const accountName = - accounts.find(a => a.id === accountId)?.name || accountId; - const value = Number(entry.value); - const percent = total !== 0 ? (value / total) * 100 : 0; - - return ( - - - - {showPercentage && ( - - )} - - ); - })} - - - - {showPercentage && ( - - )} - - -
- Account - - Value - %
- {accountName} - - - - {format(value, 'financial')} - - - - - {percent.toFixed(1)}% - -
- Total - - {format(total, 'financial')} - 100.0%
-
- ); - } - return null; - }; - return ( {effectiveShowTooltip && mode === 'trend' && ( - content={props => } + content={props => } formatter={numberFormatterTooltip} isAnimationActive={false} /> )} {effectiveShowTooltip && mode === 'stacked' && ( - content={props => } + content={props => ( + + )} isAnimationActive={false} wrapperStyle={{ zIndex: 9999, pointerEvents: 'auto' }} /> diff --git a/packages/desktop-client/src/style/themes/dark.ts b/packages/desktop-client/src/style/themes/dark.ts index a45ab5e21b..9a8fb601d8 100644 --- a/packages/desktop-client/src/style/themes/dark.ts +++ b/packages/desktop-client/src/style/themes/dark.ts @@ -1,4 +1,3 @@ -// oxlint-disable-next-line eslint/no-restricted-imports import * as colorPalette from '@desktop-client/style/palette'; export const pageBackground = colorPalette.gray900; diff --git a/packages/desktop-client/src/style/themes/development.ts b/packages/desktop-client/src/style/themes/development.ts index 48fd175005..b81da66bb5 100644 --- a/packages/desktop-client/src/style/themes/development.ts +++ b/packages/desktop-client/src/style/themes/development.ts @@ -1,4 +1,3 @@ -// oxlint-disable-next-line eslint/no-restricted-imports import * as colorPalette from '@desktop-client/style/palette'; export const pageBackground = colorPalette.navy100; diff --git a/packages/desktop-client/src/style/themes/light.ts b/packages/desktop-client/src/style/themes/light.ts index a51af3ccfa..5846e4b1cb 100644 --- a/packages/desktop-client/src/style/themes/light.ts +++ b/packages/desktop-client/src/style/themes/light.ts @@ -1,4 +1,3 @@ -// oxlint-disable-next-line eslint/no-restricted-imports import * as colorPalette from '@desktop-client/style/palette'; export const pageBackground = colorPalette.navy100; diff --git a/packages/desktop-client/src/style/themes/midnight.ts b/packages/desktop-client/src/style/themes/midnight.ts index f7ff90e8f6..fcca782a10 100644 --- a/packages/desktop-client/src/style/themes/midnight.ts +++ b/packages/desktop-client/src/style/themes/midnight.ts @@ -1,4 +1,3 @@ -// oxlint-disable-next-line eslint/no-restricted-imports import * as colorPalette from '@desktop-client/style/palette'; export const pageBackground = colorPalette.gray600; diff --git a/upcoming-release-notes/6721.md b/upcoming-release-notes/6721.md new file mode 100644 index 0000000000..8f5ae84bc0 --- /dev/null +++ b/upcoming-release-notes/6721.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +lint: fix low-hanging fruit violations