mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 23:15:24 +02:00
Persistent account tab selections
This commit is contained in:
parent
db02718d71
commit
c83aa10f01
20 changed files with 86 additions and 58 deletions
|
@ -1,6 +1,7 @@
|
|||
<%= tag.div data: {
|
||||
controller: "tabs",
|
||||
testid: testid,
|
||||
tabs_session_key_value: session_key,
|
||||
tabs_url_param_key_value: url_param_key,
|
||||
tabs_nav_btn_active_class: active_btn_classes,
|
||||
tabs_nav_btn_inactive_class: inactive_btn_classes
|
||||
|
@ -10,6 +11,11 @@
|
|||
<% else %>
|
||||
<%= nav %>
|
||||
|
||||
<%= form_with url: cookie_session_path, scope: :cookie_session, method: :put, data: { tabs_target: "persistSelectionForm" } do |f| %>
|
||||
<%= f.hidden_field :tab_key, value: "account_group_tab" %>
|
||||
<%= f.hidden_field :tab_value, value: "all", data: { tabs_target: "selectionInput" } %>
|
||||
<% end %>
|
||||
|
||||
<% panels.each do |panel| %>
|
||||
<%= panel %>
|
||||
<% end %>
|
||||
|
|
|
@ -27,11 +27,12 @@ class TabsComponent < ViewComponent::Base
|
|||
}
|
||||
}
|
||||
|
||||
attr_reader :active_tab, :url_param_key, :variant, :testid
|
||||
attr_reader :active_tab, :url_param_key, :session_key, :variant, :testid
|
||||
|
||||
def initialize(active_tab:, url_param_key: nil, variant: :default, active_btn_classes: "", inactive_btn_classes: "", testid: nil)
|
||||
def initialize(active_tab:, url_param_key: nil, session_key: nil, variant: :default, active_btn_classes: "", inactive_btn_classes: "", testid: nil)
|
||||
@active_tab = active_tab
|
||||
@url_param_key = url_param_key
|
||||
@session_key = session_key
|
||||
@variant = variant.to_sym
|
||||
@active_btn_classes = active_btn_classes
|
||||
@inactive_btn_classes = inactive_btn_classes
|
||||
|
|
|
@ -3,8 +3,8 @@ import { Controller } from "@hotwired/stimulus";
|
|||
// Connects to data-controller="tabs--components"
|
||||
export default class extends Controller {
|
||||
static classes = ["navBtnActive", "navBtnInactive"];
|
||||
static targets = ["panel", "navBtn"];
|
||||
static values = { urlParamKey: String };
|
||||
static targets = ["panel", "navBtn", "persistSelectionForm", "selectionInput"];
|
||||
static values = { sessionKey: String, urlParamKey: String };
|
||||
|
||||
show(e) {
|
||||
const btn = e.target.closest("button");
|
||||
|
@ -28,11 +28,16 @@ export default class extends Controller {
|
|||
}
|
||||
});
|
||||
|
||||
// Update URL with the selected tab
|
||||
if (this.urlParamKeyValue) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set(this.urlParamKeyValue, selectedTabId);
|
||||
window.history.replaceState({}, "", url);
|
||||
}
|
||||
|
||||
// Update URL with the selected tab
|
||||
if (this.sessionKeyValue) {
|
||||
this.selectionInputTarget.value = selectedTabId;
|
||||
this.persistSelectionFormTarget.requestSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,40 +5,20 @@ module AccountGroupable
|
|||
before_action :set_account_group_tab
|
||||
end
|
||||
|
||||
def set_account_group_tab
|
||||
last_selected_tab = session[:account_group_tab] || "asset"
|
||||
|
||||
selected_tab = if account_group_tab_param
|
||||
account_group_tab_param
|
||||
elsif on_asset_page?
|
||||
"asset"
|
||||
elsif on_liability_page?
|
||||
"liability"
|
||||
else
|
||||
last_selected_tab
|
||||
end
|
||||
|
||||
session[:account_group_tab] = selected_tab
|
||||
@account_group_tab = selected_tab
|
||||
end
|
||||
|
||||
private
|
||||
def set_account_group_tab
|
||||
last_selected_tab = session["custom_account_group_tab"] || "asset"
|
||||
|
||||
@account_group_tab = account_group_tab_param || last_selected_tab
|
||||
end
|
||||
|
||||
def valid_account_group_tabs
|
||||
%w[asset liability all]
|
||||
end
|
||||
|
||||
def account_group_tab_param
|
||||
valid_tabs = %w[asset liability all]
|
||||
params[:account_group_tab].in?(valid_tabs) ? params[:account_group_tab] : nil
|
||||
end
|
||||
|
||||
def on_asset_page?
|
||||
accountable_controller_names_for("asset").include?(controller_name)
|
||||
end
|
||||
|
||||
def on_liability_page?
|
||||
accountable_controller_names_for("liability").include?(controller_name)
|
||||
end
|
||||
|
||||
def accountable_controller_names_for(classification)
|
||||
Accountable::TYPES.map(&:constantize)
|
||||
.select { |a| a.classification == classification }
|
||||
.map { |a| a.model_name.plural }
|
||||
param_value = params[:account_group_tab]
|
||||
return nil unless param_value.in?(valid_account_group_tabs)
|
||||
param_value
|
||||
end
|
||||
end
|
||||
|
|
22
app/controllers/cookie_sessions_controller.rb
Normal file
22
app/controllers/cookie_sessions_controller.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class CookieSessionsController < ApplicationController
|
||||
def update
|
||||
save_kv_to_session(
|
||||
cookie_session_params[:tab_key],
|
||||
cookie_session_params[:tab_value]
|
||||
)
|
||||
|
||||
redirect_back_or_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
def cookie_session_params
|
||||
params.require(:cookie_session).permit(:tab_key, :tab_value)
|
||||
end
|
||||
|
||||
def save_kv_to_session(key, value)
|
||||
raise "Key must be a string" unless key.is_a?(String)
|
||||
raise "Value must be a string" unless value.is_a?(String)
|
||||
|
||||
session["custom_#{key}"] = value
|
||||
end
|
||||
end
|
|
@ -31,4 +31,8 @@ class SessionsController < ApplicationController
|
|||
def set_session
|
||||
@session = Current.user.sessions.find(params[:id])
|
||||
end
|
||||
|
||||
def session_params
|
||||
params.require(:session).permit(:tab_key, :tab_value)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,7 +87,7 @@ class UsersController < ApplicationController
|
|||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at,
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :preferred_account_group_tab,
|
||||
:show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled, :theme, :set_onboarding_preferences_at, :set_onboarding_goals_at,
|
||||
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id ],
|
||||
goals: []
|
||||
|
|
|
@ -48,9 +48,10 @@ class BalanceSheet
|
|||
def account_groups(classification = nil)
|
||||
classification_accounts = classification ? totals_query.filter { |t| t.classification == classification } : totals_query
|
||||
classification_total = classification_accounts.sum(&:converted_balance)
|
||||
account_groups = classification_accounts.group_by(&:accountable_type).transform_keys { |k| Accountable.from_type(k) }
|
||||
account_groups = classification_accounts.group_by(&:accountable_type)
|
||||
.transform_keys { |k| Accountable.from_type(k) }
|
||||
|
||||
account_groups.map do |accountable, accounts|
|
||||
groups = account_groups.map do |accountable, accounts|
|
||||
group_total = accounts.sum(&:converted_balance)
|
||||
|
||||
AccountGroup.new(
|
||||
|
@ -71,7 +72,13 @@ class BalanceSheet
|
|||
account
|
||||
end.sort_by(&:weight).reverse
|
||||
)
|
||||
end.sort_by(&:weight).reverse
|
||||
end
|
||||
|
||||
groups.sort_by do |group|
|
||||
manual_order = Accountable::TYPES
|
||||
type_name = group.key.camelize
|
||||
manual_order.index(type_name) || Float::INFINITY
|
||||
end
|
||||
end
|
||||
|
||||
def net_worth_series(period: Period.last_30_days)
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</details>
|
||||
<% end %>
|
||||
|
||||
<%= render TabsComponent.new(active_tab: active_account_group_tab, testid: "account-sidebar-tabs") do |tabs| %>
|
||||
<%= render TabsComponent.new(active_tab: active_account_group_tab, session_key: "account_group_tab", testid: "account-sidebar-tabs") do |tabs| %>
|
||||
<% tabs.with_nav do |nav| %>
|
||||
<% nav.with_btn(id: "asset", label: "Assets") %>
|
||||
<% nav.with_btn(id: "liability", label: "Debts") %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<% trend = series.trend %>
|
||||
|
||||
<%= turbo_frame_tag dom_id(@account, :chart_details) do %>
|
||||
<% if @account.syncing?%>
|
||||
<% if @account.syncing? %>
|
||||
<%= render "accounts/chart_loader" %>
|
||||
<% else %>
|
||||
<div class="px-4">
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<%= render(
|
||||
"accounts/account_sidebar_tabs",
|
||||
family: Current.family,
|
||||
active_account_group_tab: params[:account_group_tab] || "assets"
|
||||
active_account_group_tab: @account_group_tab
|
||||
) %>
|
||||
</div>
|
||||
|
||||
|
@ -81,7 +81,7 @@
|
|||
<% else %>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="overflow-y-auto grow">
|
||||
<%= render "accounts/account_sidebar_tabs", family: Current.family, active_account_group_tab: params[:account_group_tab] || "assets" %>
|
||||
<%= render "accounts/account_sidebar_tabs", family: Current.family, active_account_group_tab: @account_group_tab %>
|
||||
</div>
|
||||
|
||||
<% if Current.family.trialing? && !self_hosted? %>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end%>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface rounded-xl p-1 space-y-1 overflow-x-auto">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: (rule:) %>
|
||||
<div class="flex justify-between items-center p-4 <%= rule.active? ? 'text-primary' : 'text-secondary' %>">
|
||||
<div class="flex justify-between items-center p-4 <%= rule.active? ? "text-primary" : "text-secondary" %>">
|
||||
<div class="text-sm space-y-1.5">
|
||||
<% if rule.name.present? %>
|
||||
<h3 class="font-medium text-md"><%= rule.name %></h3>
|
||||
|
@ -49,7 +49,7 @@
|
|||
<% if rule.effective_date.nil? %>
|
||||
All past and future <%= rule.resource_type.pluralize %>
|
||||
<% else %>
|
||||
<%= rule.resource_type.pluralize %> on or after <%= rule.effective_date.strftime('%b %-d, %Y') %>
|
||||
<%= rule.resource_type.pluralize %> on or after <%= rule.effective_date.strftime("%b %-d, %Y") %>
|
||||
<% end %>
|
||||
</span>
|
||||
</p>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<div class="p-1">
|
||||
<div class="flex flex-col bg-container rounded-xl shadow-border-xs first_child:rounded-t-xl last_child:rounded-b-xl">
|
||||
<% @rules.each_with_index do |rule, idx| %>
|
||||
<%= render "rule", rule: rule%>
|
||||
<%= render "rule", rule: rule %>
|
||||
<% unless idx == @rules.size - 1 %>
|
||||
<div class="h-px bg-divider ml-4 mr-6"></div>
|
||||
<% end %>
|
||||
|
|
|
@ -26,6 +26,8 @@ Rails.application.routes.draw do
|
|||
get "changelog", to: "pages#changelog"
|
||||
get "feedback", to: "pages#feedback"
|
||||
|
||||
resource :cookie_session, only: %i[update]
|
||||
|
||||
resource :registration, only: %i[new create]
|
||||
resources :sessions, only: %i[new create destroy]
|
||||
resource :password_reset, only: %i[new create edit update]
|
||||
|
|
3
db/schema.rb
generated
3
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_05_14_143017) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
@ -696,6 +696,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do
|
|||
t.text "goals", default: [], array: true
|
||||
t.datetime "set_onboarding_preferences_at"
|
||||
t.datetime "set_onboarding_goals_at"
|
||||
t.string "preferred_account_group_tab", default: "asset"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["family_id"], name: "index_users_on_family_id"
|
||||
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue