Fix reports to correctly render categories with identical names (#6733)

* Fix reports to correctly render categories with identical names

Use category id instead of name as the data key for chart lookups to prevent
collisions when multiple categories share the same display name.

* Enhance CustomTooltip in LineGraph and StackedBarGraph to utilize legend data for improved display names. This change introduces a new LegendEntity type and updates the tooltip rendering logic to map data keys to their corresponding names, ensuring clarity when displaying chart data.
This commit is contained in:
Matiss Janis Aboltins
2026-01-21 19:01:23 +01:00
committed by GitHub
parent b651238ad2
commit d0a72f10b6
6 changed files with 39 additions and 10 deletions

View File

@@ -17,6 +17,7 @@ import {
import {
type balanceTypeOpType,
type DataEntity,
type LegendEntity,
type RuleConditionEntity,
} from 'loot-core/types/models';
@@ -46,6 +47,7 @@ type PayloadItem = {
type CustomTooltipProps = {
compact: boolean;
tooltip: string;
legend: LegendEntity[];
active?: boolean;
payload?: PayloadItem[];
format: (value: unknown, type: FormatType) => string;
@@ -54,11 +56,17 @@ type CustomTooltipProps = {
const CustomTooltip = ({
compact,
tooltip,
legend,
active,
payload,
format,
}: CustomTooltipProps) => {
const { t } = useTranslation();
const dataKeyToName = useMemo(() => {
return new Map(legend.map(entry => [entry.dataKey, entry.name]));
}, [legend]);
const { sumTotals, items } = useMemo(() => {
return (payload ?? [])
.sort((p1: PayloadItem, p2: PayloadItem) => p2.value - p1.value)
@@ -94,11 +102,12 @@ const CustomTooltip = ({
</div>
<div style={{ lineHeight: 1.5 }}>
{items.map((p: PayloadItem, index: number) => {
const displayName = dataKeyToName.get(p.dataKey) ?? p.dataKey;
return (
(compact ? index < 4 : true) && (
<AlignedText
key={index}
left={p.dataKey}
left={displayName}
right={
<FinancialText>
{format(p.value, 'financial')}
@@ -214,6 +223,7 @@ export function LineGraph({
<CustomTooltip
compact={compact}
tooltip={tooltip}
legend={data.legend}
format={format}
/>
}
@@ -248,13 +258,13 @@ export function LineGraph({
key={index}
strokeWidth={2}
type="monotone"
dataKey={entry.name}
dataKey={entry.dataKey}
stroke={entry.color}
{...animationProps}
activeDot={{
r: entry.name === tooltip && !compact ? 8 : 3,
r: entry.dataKey === tooltip && !compact ? 8 : 3,
onMouseEnter: () => {
setTooltip(entry.name);
setTooltip(entry.dataKey);
if (!['Group', 'Interval'].includes(groupBy)) {
setPointer('pointer');
}

View File

@@ -18,6 +18,7 @@ import {
import {
type balanceTypeOpType,
type DataEntity,
type LegendEntity,
type RuleConditionEntity,
} from 'loot-core/types/models';
@@ -48,6 +49,7 @@ type PayloadItem = {
type CustomTooltipProps = {
compact: boolean;
tooltip: string;
legend: LegendEntity[];
active?: boolean;
payload?: PayloadItem[];
label?: string;
@@ -57,12 +59,18 @@ type CustomTooltipProps = {
const CustomTooltip = ({
compact,
tooltip,
legend,
active,
payload,
label,
format,
}: CustomTooltipProps) => {
const { t } = useTranslation();
const dataKeyToName = useMemo(() => {
return new Map(legend.map(entry => [entry.dataKey, entry.name]));
}, [legend]);
const { sumTotals, items } = useMemo(() => {
return (payload ?? [])
.slice(0)
@@ -99,12 +107,13 @@ const CustomTooltip = ({
</div>
<div style={{ lineHeight: 1.4 }}>
{items.map((pay, i) => {
const displayName = dataKeyToName.get(pay.name) ?? pay.name;
return (
pay.value !== 0 &&
(compact ? i < 5 : true) && (
<AlignedText
key={pay.name}
left={pay.name}
left={displayName}
right={
<FinancialText>
{format(pay.value, 'financial')}
@@ -229,6 +238,7 @@ export function StackedBarGraph({
<CustomTooltip
compact={compact}
tooltip={tooltip}
legend={data.legend}
format={format}
/>
}
@@ -261,8 +271,8 @@ export function StackedBarGraph({
.reverse()
.map(entry => (
<Bar
key={entry.name}
dataKey={entry.name}
key={entry.dataKey}
dataKey={entry.dataKey}
stackId="a"
fill={entry.color}
{...animationProps}
@@ -271,7 +281,7 @@ export function StackedBarGraph({
setTooltip('');
}}
onMouseEnter={() => {
setTooltip(entry.name);
setTooltip(entry.dataKey);
if (!['Group', 'Interval'].includes(groupBy)) {
setPointer('pointer');
}
@@ -298,7 +308,7 @@ export function StackedBarGraph({
>
{viewLabels && !compact && (
<LabelList
dataKey={entry.name}
dataKey={entry.dataKey}
content={customLabelWithFormat}
/>
)}

View File

@@ -55,6 +55,7 @@ export function calculateLegend(
id: item.id || '',
name: item.name || '',
color: getColor(item.data, index),
dataKey: item.id || item.name || '', // Use id for unique data lookup
};
});
return legend;

View File

@@ -220,7 +220,8 @@ export function createCustomSpreadsheet({
stackAmounts += netAmounts;
}
stacked[item.name] = stackAmounts;
// Use id as key to prevent collisions when categories have the same name
stacked[item.id || item.name] = stackAmounts;
perIntervalTotals += netAmounts;

View File

@@ -86,6 +86,7 @@ export type LegendEntity = {
name: string;
id: string | null;
color: string;
dataKey: string; // Uses id for unique data lookup when categories have same name
};
export type IntervalEntity = {

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Reports: correctly render categories with identical names