// @ts-strict-ignore import React, { useState } from 'react'; import { css } from 'glamor'; import { BarChart, Bar, CartesianGrid, XAxis, YAxis, Tooltip, LabelList, ResponsiveContainer, } from 'recharts'; import { amountToCurrency, amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; import { type DataEntity } from 'loot-core/src/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../hooks/useAccounts'; import { useCategories } from '../../../hooks/useCategories'; import { useNavigate } from '../../../hooks/useNavigate'; import { usePrivacyMode } from '../../../hooks/usePrivacyMode'; import { useResponsive } from '../../../ResponsiveProvider'; import { theme } from '../../../style'; import { type CSSProperties } from '../../../style'; import { AlignedText } from '../../common/AlignedText'; import { Container } from '../Container'; import { getCustomTick } from '../getCustomTick'; import { numberFormatterTooltip } from '../numberFormatter'; import { renderCustomLabel } from './renderCustomLabel'; type PayloadItem = { name: string; value: number; color: string; payload: { name: string; color: number | string; }; }; type CustomTooltipProps = { compact: boolean; tooltip: string; active?: boolean; payload?: PayloadItem[]; label?: string; }; const CustomTooltip = ({ compact, tooltip, active, payload, label, }: CustomTooltipProps) => { if (active && payload && payload.length) { let sumTotals = 0; return (
{label}
{payload .slice(0) .reverse() .map((pay, i) => { sumTotals += pay.value; return ( pay.value !== 0 && (compact ? i < 5 : true) && ( ) ); })} {payload.length > 5 && compact && '...'}
); } }; const customLabel = props => { const calcX = props.x + props.width / 2; const calcY = props.y + props.height / 2; const textAnchor = 'middle'; const display = props.value && `${amountToCurrencyNoDecimal(props.value)}`; const textSize = '12px'; const showLabel = props.height; const showLabelThreshold = 20; const fill = theme.reportsInnerLabel; return renderCustomLabel( calcX, calcY, textAnchor, display, textSize, showLabel, showLabelThreshold, fill, ); }; type StackedBarGraphProps = { style?: CSSProperties; data: DataEntity; filters: RuleConditionEntity[]; groupBy: string; compact?: boolean; viewLabels: boolean; balanceTypeOp: string; showHiddenCategories?: boolean; showOffBudget?: boolean; }; export function StackedBarGraph({ style, data, filters, groupBy, compact, viewLabels, balanceTypeOp, showHiddenCategories, showOffBudget, }: StackedBarGraphProps) { const navigate = useNavigate(); const categories = useCategories(); const accounts = useAccounts(); const privacyMode = usePrivacyMode(); const { isNarrowWidth } = useResponsive(); const [pointer, setPointer] = useState(''); const [tooltip, setTooltip] = useState(''); const largestValue = data.intervalData .map(c => c[balanceTypeOp]) .reduce((acc, cur) => (Math.abs(cur) > Math.abs(acc) ? cur : acc), 0); const leftMargin = Math.abs(largestValue) > 1000000 ? 20 : 0; const onShowActivity = (item, id) => { const amount = balanceTypeOp === 'totalDebts' ? 'lte' : 'gte'; const field = groupBy === 'Interval' ? null : groupBy.toLowerCase(); const hiddenCategories = categories.list .filter(f => f.hidden) .map(e => e.id); const offBudgetAccounts = accounts.filter(f => f.offbudget).map(e => e.id); const conditions = [ ...filters, { field, op: 'is', value: id, type: 'id' }, { field: 'date', op: 'is', value: item.dateStart, options: { date: true }, }, balanceTypeOp !== 'totalTotals' && { field: 'amount', op: amount, value: 0, type: 'number', }, hiddenCategories.length > 0 && !showHiddenCategories && { field: 'category', op: 'notOneOf', value: hiddenCategories, type: 'id', }, offBudgetAccounts.length > 0 && !showOffBudget && { field: 'account', op: 'notOneOf', value: offBudgetAccounts, type: 'id', }, ].filter(f => f); navigate('/accounts', { state: { goBack: true, conditions, categoryId: item.id, }, }); }; return ( {(width, height) => data.intervalData && (
{!compact &&
} {(!isNarrowWidth || !compact) && ( } formatter={numberFormatterTooltip} isAnimationActive={false} cursor={{ fill: 'transparent' }} /> )} {!compact && ( <> getCustomTick( amountToCurrencyNoDecimal(value), privacyMode, ) } tick={{ fill: theme.pageText }} tickLine={{ stroke: theme.pageText }} tickSize={0} /> )} {data.legend .slice(0) .reverse() .map(entry => ( { setPointer(''); setTooltip(''); }} onMouseEnter={() => { setTooltip(entry.name); if (!['Group', 'Interval'].includes(groupBy)) { setPointer('pointer'); } }} onClick={e => !isNarrowWidth && !['Group', 'Interval'].includes(groupBy) && onShowActivity(e, entry.id) } > {viewLabels && !compact && ( )} ))}
) } ); }