From 64f3854e02d6e1a6749f7ead73843447526e6626 Mon Sep 17 00:00:00 2001 From: Guillem Arias Date: Mon, 11 May 2026 16:21:51 +0200 Subject: [PATCH] fix(savings_goals/show): chart contrast, breadcrumbs, contributions list polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chart — Sure's "text-X" / "border-X" tokens are Tailwind utility classes, not CSS custom properties, so var(--text-secondary) etc. resolved to empty inside SVG attributes. Read data-theme on draw and pass real hex colors (textPrimary, textSecondary, borderSubdued, containerBg) into d3 fills/strokes. "Target · $X" label and axis tick labels now have proper contrast in both themes. Breadcrumbs — set @breadcrumbs in the show action so the layout renders Home › Savings › with the middle entry clickable back to the index. Matches the convention used by imports / reports / family exports. Contributions list — drop the broken divide-y divide-subdued (Tailwind divide- utilities don't pick up Sure's semantic border tokens). Switch to space-y-3 rows matching the funding-accounts breakdown component. Drop the border-b separator under the heading; the card now reads as one continuous panel. Move the delete X to hover-revealed and reserve an inert spacer for non-manual rows so the right column stays aligned. --- app/controllers/savings_goals_controller.rb | 5 ++++ ...avings_goal_projection_chart_controller.js | 24 ++++++++++++------- .../_contributions_list.html.erb | 10 ++++---- app/views/savings_goals/show.html.erb | 6 ++--- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/controllers/savings_goals_controller.rb b/app/controllers/savings_goals_controller.rb index e35a2c3ab..29d23e8fe 100644 --- a/app/controllers/savings_goals_controller.rb +++ b/app/controllers/savings_goals_controller.rb @@ -23,6 +23,11 @@ class SavingsGoalsController < ApplicationController @contributions = @savings_goal.savings_contributions.includes(:account).chronological @funding_breakdown = funding_breakdown_for(@savings_goal) @stats = stats_for(@savings_goal) + @breadcrumbs = [ + [ t("breadcrumbs.home"), root_path ], + [ t("savings_goals.index.title"), savings_goals_path ], + [ @savings_goal.name, nil ] + ] end def new diff --git a/app/javascript/controllers/savings_goal_projection_chart_controller.js b/app/javascript/controllers/savings_goal_projection_chart_controller.js index b898d7916..57df9c9ab 100644 --- a/app/javascript/controllers/savings_goal_projection_chart_controller.js +++ b/app/javascript/controllers/savings_goal_projection_chart_controller.js @@ -32,6 +32,12 @@ export default class extends Controller { const height = root.clientHeight || 240; if (width <= 0 || height <= 0) return; + const isDark = document.documentElement.getAttribute("data-theme") === "dark"; + const textPrimary = isDark ? "#ffffff" : "#171717"; + const textSecondary = isDark ? "#cfcfcf" : "#737373"; + const borderSubdued = isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.10)"; + const containerBg = isDark ? "#0a0a0a" : "#ffffff"; + const margin = { top: 28, right: 24, bottom: 28, left: 16 }; const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; @@ -80,8 +86,8 @@ export default class extends Controller { .append("linearGradient") .attr("id", `saved-fill-${this._id()}`) .attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", 1); - gradient.append("stop").attr("offset", "0%").attr("stop-color", "var(--text-primary)").attr("stop-opacity", 0.10); - gradient.append("stop").attr("offset", "100%").attr("stop-color", "var(--text-primary)").attr("stop-opacity", 0); + gradient.append("stop").attr("offset", "0%").attr("stop-color", textPrimary).attr("stop-opacity", 0.10); + gradient.append("stop").attr("offset", "100%").attr("stop-color", textPrimary).attr("stop-opacity", 0); if (targetAmount > 0) { svg @@ -90,7 +96,7 @@ export default class extends Controller { .attr("x2", margin.left + innerWidth) .attr("y1", y(targetAmount)) .attr("y2", y(targetAmount)) - .attr("stroke", "var(--border-strong)") + .attr("stroke", borderSubdued) .attr("stroke-width", 1) .attr("stroke-dasharray", "3 3"); @@ -100,7 +106,7 @@ export default class extends Controller { .attr("y", y(targetAmount) - 6) .attr("text-anchor", "end") .attr("font-size", 10) - .attr("fill", "var(--text-secondary)") + .attr("fill", textPrimary) .text(`Target · ${this._fmtMoney(targetAmount, data.currency)}`); } @@ -127,7 +133,7 @@ export default class extends Controller { .append("path") .datum(savedSeries) .attr("fill", "none") - .attr("stroke", "var(--text-primary)") + .attr("stroke", textPrimary) .attr("stroke-width", 2) .attr("stroke-linejoin", "round") .attr("stroke-linecap", "round") @@ -152,7 +158,7 @@ export default class extends Controller { .attr("x2", x(today)) .attr("y1", margin.top) .attr("y2", margin.top + innerHeight) - .attr("stroke", "var(--border-subdued)") + .attr("stroke", borderSubdued) .attr("stroke-width", 1) .attr("stroke-dasharray", "2 4"); @@ -161,8 +167,8 @@ export default class extends Controller { .attr("cx", x(today)) .attr("cy", y(currentAmount)) .attr("r", 4) - .attr("fill", "var(--text-primary)") - .attr("stroke", "var(--bg-container)") + .attr("fill", textPrimary) + .attr("stroke", containerBg) .attr("stroke-width", 2); const tickFmt = d3.timeFormat("%b %y"); @@ -178,7 +184,7 @@ export default class extends Controller { .attr("y", height - 8) .attr("text-anchor", "middle") .attr("font-size", 10) - .attr("fill", "var(--text-subdued)") + .attr("fill", textSecondary) .text((d) => tickFmt(d)); } diff --git a/app/views/savings_goals/_contributions_list.html.erb b/app/views/savings_goals/_contributions_list.html.erb index 686aabc54..0237b1151 100644 --- a/app/views/savings_goals/_contributions_list.html.erb +++ b/app/views/savings_goals/_contributions_list.html.erb @@ -5,9 +5,9 @@

<%= t("savings_goals.show.no_contributions_yet") %>

<% else %> -