fix: média em category-trends ignora meses sem gastos

Corrige o cálculo da coluna Média para dividir apenas pelo número de
meses com valores > 0, evitando distorção causada por meses sem
movimentação. Adiciona ícone de informação com tooltip explicativo
no cabeçalho da coluna.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-03-21 14:04:34 +00:00
parent a20fe255f3
commit bdb3908dab

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import { RiInformationLine } from "@remixicon/react";
import Link from "next/link"; import Link from "next/link";
import { useMemo } from "react"; import { useMemo } from "react";
import { formatPeriodLabel } from "@/features/reports/utils"; import { formatPeriodLabel } from "@/features/reports/utils";
@@ -15,6 +16,11 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/shared/components/ui/table"; } from "@/shared/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/shared/components/ui/tooltip";
import type { CategoryReportItem } from "@/shared/lib/types/reports"; import type { CategoryReportItem } from "@/shared/lib/types/reports";
import { formatCurrency } from "@/shared/utils/currency"; import { formatCurrency } from "@/shared/utils/currency";
import { formatPeriodForUrl } from "@/shared/utils/period"; import { formatPeriodForUrl } from "@/shared/utils/period";
@@ -35,7 +41,6 @@ export function CategoryTable({
const sectionTotals = useMemo(() => { const sectionTotals = useMemo(() => {
const totalsMap = new Map<string, number>(); const totalsMap = new Map<string, number>();
let grandTotal = 0; let grandTotal = 0;
const periodCount = Math.max(periods.length, 1);
for (const category of categories) { for (const category of categories) {
grandTotal += category.total; grandTotal += category.total;
@@ -46,10 +51,15 @@ export function CategoryTable({
} }
} }
const nonZeroPeriodCount = periods.filter(
(p) => (totalsMap.get(p) ?? 0) > 0,
).length;
return { return {
totalsMap, totalsMap,
grandTotal, grandTotal,
averageMonthlyTotal: grandTotal / periodCount, averageMonthlyTotal:
nonZeroPeriodCount > 0 ? grandTotal / nonZeroPeriodCount : 0,
}; };
}, [categories, periods]); }, [categories, periods]);
@@ -74,7 +84,21 @@ export function CategoryTable({
</TableHead> </TableHead>
))} ))}
<TableHead className="text-right min-w-[140px] font-bold"> <TableHead className="text-right min-w-[140px] font-bold">
Média <div className="flex items-center justify-end gap-1">
Média
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-default inline-flex">
<RiInformationLine className="size-3.5 text-muted-foreground" />
</span>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-[280px]">
A média considera apenas os meses com gastos registrados
(valores maiores que zero). Meses sem movimentação não
entram no cálculo.
</TooltipContent>
</Tooltip>
</div>
</TableHead> </TableHead>
<TableHead className="text-right min-w-[120px] font-bold"> <TableHead className="text-right min-w-[120px] font-bold">
Total Total
@@ -126,7 +150,14 @@ export function CategoryTable({
); );
})} })}
<TableCell className="text-right font-semibold text-info"> <TableCell className="text-right font-semibold text-info">
{formatCurrency(category.total / Math.max(periods.length, 1))} {(() => {
const nonZeroCount = periods.filter(
(p) => (category.monthlyData.get(p)?.amount ?? 0) > 0,
).length;
return formatCurrency(
nonZeroCount > 0 ? category.total / nonZeroCount : 0,
);
})()}
</TableCell> </TableCell>
<TableCell className="text-right font-semibold"> <TableCell className="text-right font-semibold">
{formatCurrency(category.total)} {formatCurrency(category.total)}