feat(balance): Modified Dietz returns + transfer linking (#142) #151
1 changed files with 69 additions and 0 deletions
|
|
@ -22,12 +22,14 @@ import {
|
|||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend,
|
||||
ReferenceLine,
|
||||
} from "recharts";
|
||||
import type {
|
||||
SnapshotTotalPoint,
|
||||
SnapshotCategoryBreakdownPoint,
|
||||
} from "../../services/balance.service";
|
||||
import type { BalanceChartMode } from "../../hooks/useBalanceOverview";
|
||||
import type { BalanceAccountTransferWithTransaction } from "../../shared/types";
|
||||
|
||||
// Stable palette for the stacked-by-category areas. Indexed deterministically
|
||||
// by category sort order so the colour assignment stays consistent across
|
||||
|
|
@ -51,6 +53,13 @@ export interface BalanceEvolutionChartProps {
|
|||
byCategory: SnapshotCategoryBreakdownPoint[];
|
||||
/** Map category_key → translated label so the legend reads naturally. */
|
||||
categoryLabels?: Record<string, string>;
|
||||
/**
|
||||
* Issue #142 — every linked transfer in the visible range. Rendered as
|
||||
* vertical `<ReferenceLine>` markers on the X axis: green for `in`
|
||||
* (capital added), red for `out` (capital removed). The label tooltip
|
||||
* shows the underlying transaction date + description.
|
||||
*/
|
||||
transferMarkers?: BalanceAccountTransferWithTransaction[];
|
||||
}
|
||||
|
||||
export default function BalanceEvolutionChart({
|
||||
|
|
@ -58,6 +67,7 @@ export default function BalanceEvolutionChart({
|
|||
totals,
|
||||
byCategory,
|
||||
categoryLabels = {},
|
||||
transferMarkers = [],
|
||||
}: BalanceEvolutionChartProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
|
|
@ -114,6 +124,31 @@ export default function BalanceEvolutionChart({
|
|||
const isEmpty =
|
||||
mode === "line" ? lineData.length === 0 : stackedData.length === 0;
|
||||
|
||||
// Filter transfer markers to dates that are actually rendered on the X
|
||||
// axis (categorical scale ignores unknown ticks). We don't aggregate or
|
||||
// dedupe — the user can have several transfers on the same day across
|
||||
// accounts; ReferenceLine tolerates duplicates fine.
|
||||
const xAxisDates = useMemo(() => {
|
||||
const dates = new Set<string>();
|
||||
if (mode === "line") {
|
||||
for (const p of lineData) dates.add(p.snapshot_date);
|
||||
} else {
|
||||
for (const p of stackedData) dates.add(p.snapshot_date as string);
|
||||
}
|
||||
return dates;
|
||||
}, [mode, lineData, stackedData]);
|
||||
|
||||
const renderableMarkers = useMemo(
|
||||
() =>
|
||||
transferMarkers
|
||||
.filter((m) => xAxisDates.has(m.transaction_date))
|
||||
// Sort so 'in' (green) draws before 'out' (red) for stable z-order.
|
||||
.sort((a, b) =>
|
||||
a.direction === b.direction ? 0 : a.direction === "in" ? -1 : 1
|
||||
),
|
||||
[transferMarkers, xAxisDates]
|
||||
);
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6">
|
||||
|
|
@ -168,6 +203,28 @@ export default function BalanceEvolutionChart({
|
|||
dot={{ r: 3 }}
|
||||
activeDot={{ r: 5 }}
|
||||
/>
|
||||
{renderableMarkers.map((m) => (
|
||||
<ReferenceLine
|
||||
key={`tm-${m.id}`}
|
||||
x={m.transaction_date}
|
||||
stroke={
|
||||
m.direction === "in" ? "var(--positive)" : "var(--negative)"
|
||||
}
|
||||
strokeDasharray="3 3"
|
||||
strokeWidth={1}
|
||||
ifOverflow="extendDomain"
|
||||
label={{
|
||||
value: t(
|
||||
m.direction === "in"
|
||||
? "balance.evolution.transferIn"
|
||||
: "balance.evolution.transferOut"
|
||||
),
|
||||
position: "insideTopRight",
|
||||
fontSize: 9,
|
||||
fill: m.direction === "in" ? "var(--positive)" : "var(--negative)",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
) : (
|
||||
<AreaChart
|
||||
|
|
@ -210,6 +267,18 @@ export default function BalanceEvolutionChart({
|
|||
name={key}
|
||||
/>
|
||||
))}
|
||||
{renderableMarkers.map((m) => (
|
||||
<ReferenceLine
|
||||
key={`tm-${m.id}`}
|
||||
x={m.transaction_date}
|
||||
stroke={
|
||||
m.direction === "in" ? "var(--positive)" : "var(--negative)"
|
||||
}
|
||||
strokeDasharray="3 3"
|
||||
strokeWidth={1}
|
||||
ifOverflow="extendDomain"
|
||||
/>
|
||||
))}
|
||||
</AreaChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
|
|
|
|||
Loading…
Reference in a new issue