mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 06:02:22 -05:00
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:
committed by
GitHub
parent
b651238ad2
commit
d0a72f10b6
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
6
upcoming-release-notes/6733.md
Normal file
6
upcoming-release-notes/6733.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Reports: correctly render categories with identical names
|
||||
Reference in New Issue
Block a user