mirror of
https://github.com/we-promise/sure.git
synced 2026-07-27 20:22:16 +00:00
* Add "Money In / Out" dashboard widget Adds a new dashboard section showing a monthly bar chart of cash activity alongside a summary card (net balance, income, expenses), with per-widget month navigation and account filtering. - IncomeStatement#totals_for computes income/expense totals for an arbitrary period, optionally scoped to a set of account ids - New bar_chart_controller.js (D3) renders the monthly bars - Income/expense rows link through to filtered transactions Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix tooltip/dropdown positioning and split money flow chart by income/expense The widget's @container wrapper established a new containing block for position:fixed descendants, so the month-picker and account-filter menus (floating-ui, strategy: fixed) and the D3 tooltip (container- relative coordinates) rendered away from their trigger/bar. Drop @container in favor of regular viewport breakpoints for this full-width widget, and position the tooltip with page-relative coordinates like the other chart controllers. Also replace the single combined bar per month with a grouped expense/income pair (red/green, with a legend) so each month's inflow and outflow are visible independently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Cap and scroll the money flow widget's month picker Add an optional max_height to DS::Menu (opt-in, backward compatible) so a long item list scrolls inside a fixed-height panel instead of overflowing the viewport. Use it for the money flow widget's 12-month picker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Use design system tokens for income/expense indicators in money flow widget Swap the four bg-green-500/bg-red-500 dot indicators (legend + income/expense row links) in the money flow widget for bg-success/bg-destructive, matching the "functional tokens only" rule. The same partial already uses text-success/text-destructive for the balance figure, and bg-success/ bg-destructive are already established elsewhere (DS::Alert, budget_categories/_budget_category.html.erb). * Fix future-month 500 and pending-transaction link mismatch in money flow widget Two CI review findings on the money flow widget: - A future month passed via ?money_flow_month= (e.g. a bookmarked/hand-edited URL) made end_date earlier than month_start once capped at Date.current, which Period.custom rejects, causing a 500. money_flow_month_param now clamps future months to the current month, same as it already does for malformed input. - The income/expense row links passed type/date/account filters but no status, so Transaction::Search included pending transactions even though the displayed totals (IncomeStatement#totals_for) exclude them via excluding_pending. Add status: ["confirmed"] to both links so the linked list matches the card total. Also strengthens the widget's controller tests: asserts the highlighted bar's actual income/expense values instead of just its presence, and adds a regression test proving an account id outside the current user's accessible accounts is dropped (falls back to the unfiltered state) rather than leaking or erroring. * Fix account filter eligibility and SVG dark mode fill in money flow widget Two more CI review findings on the money flow widget: The account filter iterated over all visible/accessible accounts, a broader set than IncomeStatement actually counts (accounts excluded from reports, tax-advantaged accounts like 401k/IRA, or shared accounts not included in the user's finances). Selecting one of these silently computed to zero while its drill-down link could still list its transactions. Add IncomeStatement#eligible_accounts, mirroring the same criteria already applied in the totals SQL, and use it for both the checkbox list and the account_ids intersection. The D3 axis tick text elements used text-primary/text-secondary, which set CSS color, not SVG fill, so labels rendered with the default black fill and were unreadable in dark mode. Add fill-current, matching the pattern already used in sankey_chart_controller.js. Adds controller/model tests for eligible_accounts (excluded from the account filter, ignored when passed as a filter id, excluded from totals) and verifies the dark-mode fill fix visually. * Extract duplicated bar-chart JSON parsing into a test helper The css_select("[data-controller='bar-chart']").first + JSON.parse(chart["data-bar-chart-data-value"]) pattern was repeated across four money flow widget tests in pages_controller_test.rb. Extract it into a private money_flow_bars helper and use it everywhere instead. * Preserve eligible account scope in money flow drill-down links when unfiltered The income/expense drill-down links used money_flow_data[:account_ids] directly, which is nil in the widget's default unfiltered state, so .compact dropped the account filter entirely from the link. TransactionsController treats an absent account_ids as all accessible accounts, a broader set than IncomeStatement#eligible_accounts (which excludes tax-advantaged, excluded-from-reports, and non-finance shared accounts). Users with any such account could click a displayed total and see transactions that were never counted in it. Use selected_account_ids (already computed as money_flow_data[:account_ids] || accounts.map(&:id)) for both links instead, so they always pin to the same eligible accounts backing the total, filtered or not. Left the month-picker link on money_flow_data[:account_ids] so navigating months while unfiltered doesn't bloat the URL with every eligible account id. Adds a regression test confirming the default (unfiltered) links include account_ids and exclude an ineligible account's id. * Refine Money In/Out dashboard widget - 6-month window (was 3), half-width default with responsive stack, income-first bars, 2px floor + faded in-progress (partial) month - Expense series/figures neutral (gray/text-primary) — app reserves red for negative/overspend; neutral zero balance - Account filter: outline DS::Button + list-filter icon trigger with in-panel search (DS::SearchInput + list-filter), matching the app's filter convention - i18n search_accounts (en + fr); bump money_flow bar-count test 3 -> 6 * Clarify Money In/Out scope: month label, 6-month caption, filter "All" Addresses confusion between the widget's own month picker and the dashboard's global period, and between the picked month and the 6-month chart span. - Card now headed with the selected month (e.g. "July 2026") so it's clear the totals below are that month's, driven by the picker - Chart legend row captioned "Last N months" so the trailing window is explicit - Info tooltip by the month picker: the widget scopes to the month you pick here, independent of the dashboard's top-level period - Account filter trigger reads "All accounts" when nothing is filtered out, instead of "Filter accounts (N)" - i18n (en + fr) for the new strings * Match tooltip expense dot to the gray bar palette --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Guillem Arias <accounts@gariasf.com>
69 lines
2.8 KiB
Ruby
69 lines
2.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# `DS::Menu` is a strict action-list primitive. Children are `DS::MenuItem`
|
|
# (link / button / divider) only; the container announces as `role="menu"`,
|
|
# items as `role="menuitem"`, dividers as `role="separator"`. Arrow Up/Down
|
|
# and Home/End move focus across items (roving tabindex). Use **only** for
|
|
# flat clickable-action lists.
|
|
#
|
|
# Need a panel that hosts forms, pickers, headings, or user-account
|
|
# content? Use `DS::Popover` — `role="menu"` restricts AT users to
|
|
# menuitem-only navigation and breaks anything that isn't an action.
|
|
class DS::Menu < DesignSystemComponent
|
|
attr_reader :variant, :placement, :offset, :icon_vertical, :no_padding, :testid, :mobile_fullwidth, :max_width, :max_height, :menu_id
|
|
|
|
renders_one :button, ->(**button_options, &block) do
|
|
options_with_target = button_options.deep_dup
|
|
options_with_target[:data] = (options_with_target[:data] || {}).merge(DS__menu_target: "button")
|
|
options_with_target[:aria] = (options_with_target[:aria] || {}).merge(
|
|
haspopup: "menu",
|
|
expanded: "false",
|
|
controls: menu_id
|
|
)
|
|
|
|
if block
|
|
options_with_target[:type] ||= "button"
|
|
content_tag(:button, **options_with_target, &block)
|
|
else
|
|
DS::Button.new(**options_with_target)
|
|
end
|
|
end
|
|
|
|
renders_many :items, DS::MenuItem
|
|
|
|
VARIANTS = %i[icon icon_sm button].freeze
|
|
|
|
def initialize(variant: "icon", placement: "bottom-end", offset: 12, icon_vertical: false, no_padding: false, testid: nil, mobile_fullwidth: true, max_width: nil, max_height: nil)
|
|
@variant = variant.to_sym
|
|
@placement = placement
|
|
@offset = offset
|
|
@icon_vertical = icon_vertical
|
|
@no_padding = no_padding
|
|
@testid = testid
|
|
@mobile_fullwidth = mobile_fullwidth
|
|
@max_width = max_width
|
|
@max_height = max_height
|
|
@menu_id = "menu-#{SecureRandom.hex(4)}"
|
|
|
|
raise ArgumentError, "Invalid variant: #{@variant}. DS::Menu is for action lists only; use DS::Popover for mixed content (forms, pickers, account dropdowns)." unless VARIANTS.include?(@variant)
|
|
end
|
|
|
|
# `:icon_sm` renders the dropdown trigger as a 32x32 icon button (DS::Button
|
|
# `size: :sm`) instead of the default 44x44 `:md`. Use for action menus
|
|
# embedded in dense lists (e.g. the category dropdown row trigger) where the
|
|
# 44x44 enhanced-touch-target trigger introduced in #1840 makes every row
|
|
# ~8px taller and the cumulative list height regresses visibly.
|
|
#
|
|
# Trade-off: the `sm` icon button is 32x32, which meets WCAG 2.5.8 AA
|
|
# (24x24) but not 2.5.5 AAA enhanced (44x44). Acceptable for compact
|
|
# dropdown rows that aren't primary touch surfaces; do not use on
|
|
# standalone toolbar / row-action triggers where 44x44 should remain.
|
|
def icon_only?
|
|
variant == :icon || variant == :icon_sm
|
|
end
|
|
|
|
def icon_button_size
|
|
variant == :icon_sm ? "sm" : "md"
|
|
end
|
|
end
|