Initial implementation of collapsible sections and re-order feature (#355)

* Initial implementation

* Add support for reports section too

* UI Improvement

now it looks a lot nicer :)

* Remove duplicate section titles

* FIX malformed DIV

* Add accessibility and touch support

WCAG 2.1 Level AA Compliant
  - Keyboard operable (Success Criterion 2.1.1)
  - Focus visible (Success Criterion 2.4.7)
  - Name, Role, Value (Success Criterion 4.1.2)

 Screen Reader Support
  - Clear instructions in aria-label
  - Proper semantic roles
  - State changes announced via aria-grabbed

* Add proper UI for tab highlight

* Add keyboard support to collapse also

* FIX js errors

* Fix rabbit

* FIX we don't need the html

* FIX CSRF and error handling

* Simplify into one single DB migration

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
soky srm
2025-11-26 17:51:38 +01:00
committed by GitHub
parent 6e6fce1737
commit db8353e895
21 changed files with 1179 additions and 83 deletions

View File

@@ -16,9 +16,19 @@ class PagesController < ApplicationController
@cashflow_sankey_data = build_cashflow_sankey_data(income_totals, expense_totals, family_currency)
@outflows_data = build_outflows_donut_data(expense_totals)
@dashboard_sections = build_dashboard_sections
@breadcrumbs = [ [ "Home", root_path ], [ "Dashboard", nil ] ]
end
def update_preferences
if Current.user.update_dashboard_preferences(preferences_params)
head :ok
else
head :unprocessable_entity
end
end
def changelog
@release_notes = github_provider.fetch_latest_release_notes
@@ -45,6 +55,64 @@ class PagesController < ApplicationController
end
private
def preferences_params
prefs = params.require(:preferences)
{}.tap do |permitted|
permitted["collapsed_sections"] = prefs[:collapsed_sections].to_unsafe_h if prefs[:collapsed_sections]
permitted["section_order"] = prefs[:section_order] if prefs[:section_order]
end
end
def build_dashboard_sections
all_sections = [
{
key: "cashflow_sankey",
title: "pages.dashboard.cashflow_sankey.title",
partial: "pages/dashboard/cashflow_sankey",
locals: { sankey_data: @cashflow_sankey_data, period: @period },
visible: Current.family.accounts.any?,
collapsible: true
},
{
key: "outflows_donut",
title: "pages.dashboard.outflows_donut.title",
partial: "pages/dashboard/outflows_donut",
locals: { outflows_data: @outflows_data, period: @period },
visible: Current.family.accounts.any? && @outflows_data[:categories].present?,
collapsible: true
},
{
key: "net_worth_chart",
title: "pages.dashboard.net_worth_chart.title",
partial: "pages/dashboard/net_worth_chart",
locals: { balance_sheet: @balance_sheet, period: @period },
visible: Current.family.accounts.any?,
collapsible: true
},
{
key: "balance_sheet",
title: "pages.dashboard.balance_sheet.title",
partial: "pages/dashboard/balance_sheet",
locals: { balance_sheet: @balance_sheet },
visible: Current.family.accounts.any?,
collapsible: true
}
]
# Order sections according to user preference
section_order = Current.user.dashboard_section_order
ordered_sections = section_order.map do |key|
all_sections.find { |s| s[:key] == key }
end.compact
# Add any new sections that aren't in the saved order (future-proofing)
all_sections.each do |section|
ordered_sections << section unless ordered_sections.include?(section)
end
ordered_sections
end
def github_provider
Provider::Registry.get_provider(:github)
end

View File

@@ -37,9 +37,20 @@ class ReportsController < ApplicationController
# Transactions breakdown
@transactions = build_transactions_breakdown
# Build reports sections for collapsible/reorderable UI
@reports_sections = build_reports_sections
@breadcrumbs = [ [ "Home", root_path ], [ "Reports", nil ] ]
end
def update_preferences
if Current.user.update_reports_preferences(preferences_params)
head :ok
else
head :unprocessable_entity
end
end
def export_transactions
@period_type = params[:period_type]&.to_sym || :monthly
@start_date = parse_date_param(:start_date) || default_start_date
@@ -100,6 +111,52 @@ class ReportsController < ApplicationController
end
private
def preferences_params
prefs = params.require(:preferences)
{}.tap do |permitted|
permitted["reports_collapsed_sections"] = prefs[:reports_collapsed_sections].to_unsafe_h if prefs[:reports_collapsed_sections]
permitted["reports_section_order"] = prefs[:reports_section_order] if prefs[:reports_section_order]
end
end
def build_reports_sections
all_sections = [
{
key: "trends_insights",
title: "reports.trends.title",
partial: "reports/trends_insights",
locals: { trends_data: @trends_data, spending_patterns: @spending_patterns },
visible: Current.family.transactions.any?,
collapsible: true
},
{
key: "transactions_breakdown",
title: "reports.transactions_breakdown.title",
partial: "reports/transactions_breakdown",
locals: {
transactions: @transactions,
period_type: @period_type,
start_date: @start_date,
end_date: @end_date
},
visible: Current.family.transactions.any?,
collapsible: true
}
]
# Order sections according to user preference
section_order = Current.user.reports_section_order
ordered_sections = section_order.map do |key|
all_sections.find { |s| s[:key] == key }
end.compact
# Add any new sections that aren't in the saved order (future-proofing)
all_sections.each do |section|
ordered_sections << section unless ordered_sections.include?(section)
end
ordered_sections
end
def validate_and_fix_date_range(show_flash: false)
return unless @start_date > @end_date