1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 05:09:38 +02:00
Maybe/app/controllers/pages_controller.rb
Zach Gollwitzer 662f2c04ce
Some checks failed
Publish Docker image / ci (push) Has been cancelled
Publish Docker image / Build docker image (push) Has been cancelled
Multi-step account forms + clearer balance editing (#2427)
* Initial multi-step property form

* Improve form structure, add optional tooltip help icons to form fields

* Add basic inline alert component

* Clean up and improve property form lifecycle

* Implement Account status concept

* Lint fixes

* Remove whitespace

* Balance editing, scope updates for account

* Passing tests

* Fix brakeman warning

* Remove stale columns

* data constraint tweaks

* Redundant property
2025-07-03 09:33:07 -04:00

155 lines
5.3 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class PagesController < ApplicationController
include Periodable
skip_authentication only: :redis_configuration_error
def dashboard
@balance_sheet = Current.family.balance_sheet
@accounts = Current.family.accounts.visible.with_attached_logo
period_param = params[:cashflow_period]
@cashflow_period = if period_param.present?
begin
Period.from_key(period_param)
rescue Period::InvalidKeyError
Period.last_30_days
end
else
Period.last_30_days
end
family_currency = Current.family.currency
income_totals = Current.family.income_statement.income_totals(period: @cashflow_period)
expense_totals = Current.family.income_statement.expense_totals(period: @cashflow_period)
@cashflow_sankey_data = build_cashflow_sankey_data(income_totals, expense_totals, family_currency)
@breadcrumbs = [ [ "Home", root_path ], [ "Dashboard", nil ] ]
end
def changelog
@release_notes = github_provider.fetch_latest_release_notes
# Fallback if no release notes are available
if @release_notes.nil?
@release_notes = {
avatar: "https://github.com/maybe-finance.png",
username: "maybe-finance",
name: "Release notes unavailable",
published_at: Date.current,
body: "<p>Unable to fetch the latest release notes at this time. Please check back later or visit our <a href='https://github.com/maybe-finance/maybe/releases' target='_blank'>GitHub releases page</a> directly.</p>"
}
end
render layout: "settings"
end
def feedback
render layout: "settings"
end
def redis_configuration_error
render layout: "blank"
end
private
def github_provider
Provider::Registry.get_provider(:github)
end
def build_cashflow_sankey_data(income_totals, expense_totals, currency_symbol)
nodes = []
links = []
node_indices = {} # Memoize node indices by a unique key: "type_categoryid"
# Helper to add/find node and return its index
add_node = ->(unique_key, display_name, value, percentage, color) {
node_indices[unique_key] ||= begin
nodes << { name: display_name, value: value.to_f.round(2), percentage: percentage.to_f.round(1), color: color }
nodes.size - 1
end
}
total_income_val = income_totals.total.to_f.round(2)
total_expense_val = expense_totals.total.to_f.round(2)
# --- Create Central Cash Flow Node ---
cash_flow_idx = add_node.call("cash_flow_node", "Cash Flow", total_income_val, 0, "var(--color-success)")
# --- Process Income Side (Top-level categories only) ---
income_totals.category_totals.each do |ct|
# Skip subcategories only include root income categories
next if ct.category.parent_id.present?
val = ct.total.to_f.round(2)
next if val.zero?
percentage_of_total_income = total_income_val.zero? ? 0 : (val / total_income_val * 100).round(1)
node_display_name = ct.category.name
node_color = ct.category.color.presence || Category::COLORS.sample
current_cat_idx = add_node.call(
"income_#{ct.category.id}",
node_display_name,
val,
percentage_of_total_income,
node_color
)
links << {
source: current_cat_idx,
target: cash_flow_idx,
value: val,
color: node_color,
percentage: percentage_of_total_income
}
end
# --- Process Expense Side (Top-level categories only) ---
expense_totals.category_totals.each do |ct|
# Skip subcategories only include root expense categories to keep Sankey shallow
next if ct.category.parent_id.present?
val = ct.total.to_f.round(2)
next if val.zero?
percentage_of_total_expense = total_expense_val.zero? ? 0 : (val / total_expense_val * 100).round(1)
node_display_name = ct.category.name
node_color = ct.category.color.presence || Category::UNCATEGORIZED_COLOR
current_cat_idx = add_node.call(
"expense_#{ct.category.id}",
node_display_name,
val,
percentage_of_total_expense,
node_color
)
links << {
source: cash_flow_idx,
target: current_cat_idx,
value: val,
color: node_color,
percentage: percentage_of_total_expense
}
end
# --- Process Surplus ---
leftover = (total_income_val - total_expense_val).round(2)
if leftover.positive?
percentage_of_total_income_for_surplus = total_income_val.zero? ? 0 : (leftover / total_income_val * 100).round(1)
surplus_idx = add_node.call("surplus_node", "Surplus", leftover, percentage_of_total_income_for_surplus, "var(--color-success)")
links << { source: cash_flow_idx, target: surplus_idx, value: leftover, color: "var(--color-success)", percentage: percentage_of_total_income_for_surplus }
end
# Update Cash Flow and Income node percentages (relative to total income)
if node_indices["cash_flow_node"]
nodes[node_indices["cash_flow_node"]][:percentage] = 100.0
end
# No primary income node anymore, percentages are on individual income cats relative to total_income_val
{ nodes: nodes, links: links, currency_symbol: Money::Currency.new(currency_symbol).symbol }
end
end