feat(cashflow): deep-link category labels to filtered transactions (#2083)

* feat(cashflow): deep-link category labels to filtered transactions

Clicking a category's text label in the dashboard cashflow Sankey chart
now navigates to the transactions page filtered by that category and the
cashflow's active period date range. The colored node bar keeps its
existing zoom-into-subcategories behavior; structural nodes (Cash Flow,
Surplus) do not navigate.

URL-building lives in a pure, unit-tested utils/transactions_filter_url
module (mirroring utils/sankey_zoom) pinned in the importmap. Period dates
are threaded from the dashboard view into the Stimulus controller via data
values. This matches the existing donut chart's click-to-filter behavior.

* Update app/javascript/controllers/sankey_chart_controller.js

Co-authored-by: Guillem Arias Fauste <gariasf@proton.me>
Signed-off-by: Will Wilson <will@willwilson.uk>

---------

Signed-off-by: Will Wilson <will@willwilson.uk>
Co-authored-by: Guillem Arias Fauste <gariasf@proton.me>
This commit is contained in:
Will Wilson
2026-06-11 20:21:56 +01:00
committed by GitHub
parent 749d54dd96
commit d908560ed9
6 changed files with 102 additions and 4 deletions

View File

@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
isNavigableCategoryNode,
buildCategoryTransactionsUrl,
} from "../../../app/javascript/utils/transactions_filter_url.mjs";
test("category nodes are navigable, structural nodes are not", () => {
assert.equal(isNavigableCategoryNode("expense_15"), true);
assert.equal(isNavigableCategoryNode("income_3"), true);
assert.equal(isNavigableCategoryNode("expense_sub_19"), true);
assert.equal(isNavigableCategoryNode("income_sub_7"), true);
assert.equal(isNavigableCategoryNode("cash_flow_node"), false);
assert.equal(isNavigableCategoryNode("surplus_node"), false);
// Bare aggregate ids are not category nodes.
assert.equal(isNavigableCategoryNode("income"), false);
assert.equal(isNavigableCategoryNode("expense"), false);
});
test("builds a transactions deep link with category and date range", () => {
const url = buildCategoryTransactionsUrl({
name: "Groceries",
startDate: "2026-05-01",
endDate: "2026-05-31",
});
assert.equal(
url,
"/transactions?q%5Bcategories%5D%5B%5D=Groceries&q%5Bstart_date%5D=2026-05-01&q%5Bend_date%5D=2026-05-31",
);
});
test("encodes category names with special characters", () => {
const url = buildCategoryTransactionsUrl({
name: "Food & Drink",
startDate: "2026-05-01",
endDate: "2026-05-31",
});
assert.match(url, /q%5Bcategories%5D%5B%5D=Food\+%26\+Drink/);
});
test("omits date params when dates are blank", () => {
const url = buildCategoryTransactionsUrl({
name: "Groceries",
startDate: "",
endDate: "",
});
assert.equal(url, "/transactions?q%5Bcategories%5D%5B%5D=Groceries");
});