mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +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: {
|
<%= tag.div data: {
|
||||||
controller: "tabs",
|
controller: "tabs",
|
||||||
testid: testid,
|
testid: testid,
|
||||||
|
tabs_session_key_value: session_key,
|
||||||
tabs_url_param_key_value: url_param_key,
|
tabs_url_param_key_value: url_param_key,
|
||||||
tabs_nav_btn_active_class: active_btn_classes,
|
tabs_nav_btn_active_class: active_btn_classes,
|
||||||
tabs_nav_btn_inactive_class: inactive_btn_classes
|
tabs_nav_btn_inactive_class: inactive_btn_classes
|
||||||
|
@ -10,6 +11,11 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= nav %>
|
<%= 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| %>
|
<% panels.each do |panel| %>
|
||||||
<%= panel %>
|
<%= panel %>
|
||||||
<% end %>
|
<% 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
|
@active_tab = active_tab
|
||||||
@url_param_key = url_param_key
|
@url_param_key = url_param_key
|
||||||
|
@session_key = session_key
|
||||||
@variant = variant.to_sym
|
@variant = variant.to_sym
|
||||||
@active_btn_classes = active_btn_classes
|
@active_btn_classes = active_btn_classes
|
||||||
@inactive_btn_classes = inactive_btn_classes
|
@inactive_btn_classes = inactive_btn_classes
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { Controller } from "@hotwired/stimulus";
|
||||||
// Connects to data-controller="tabs--components"
|
// Connects to data-controller="tabs--components"
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static classes = ["navBtnActive", "navBtnInactive"];
|
static classes = ["navBtnActive", "navBtnInactive"];
|
||||||
static targets = ["panel", "navBtn"];
|
static targets = ["panel", "navBtn", "persistSelectionForm", "selectionInput"];
|
||||||
static values = { urlParamKey: String };
|
static values = { sessionKey: String, urlParamKey: String };
|
||||||
|
|
||||||
show(e) {
|
show(e) {
|
||||||
const btn = e.target.closest("button");
|
const btn = e.target.closest("button");
|
||||||
|
@ -28,11 +28,16 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update URL with the selected tab
|
|
||||||
if (this.urlParamKeyValue) {
|
if (this.urlParamKeyValue) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set(this.urlParamKeyValue, selectedTabId);
|
url.searchParams.set(this.urlParamKeyValue, selectedTabId);
|
||||||
window.history.replaceState({}, "", url);
|
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
|
before_action :set_account_group_tab
|
||||||
end
|
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
|
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
|
def account_group_tab_param
|
||||||
valid_tabs = %w[asset liability all]
|
param_value = params[:account_group_tab]
|
||||||
params[:account_group_tab].in?(valid_tabs) ? params[:account_group_tab] : nil
|
return nil unless param_value.in?(valid_account_group_tabs)
|
||||||
end
|
param_value
|
||||||
|
|
||||||
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 }
|
|
||||||
end
|
end
|
||||||
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
|
def set_session
|
||||||
@session = Current.user.sessions.find(params[:id])
|
@session = Current.user.sessions.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def session_params
|
||||||
|
params.require(:session).permit(:tab_key, :tab_value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,7 +87,7 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(
|
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,
|
: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 ],
|
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id ],
|
||||||
goals: []
|
goals: []
|
||||||
|
|
|
@ -48,9 +48,10 @@ class BalanceSheet
|
||||||
def account_groups(classification = nil)
|
def account_groups(classification = nil)
|
||||||
classification_accounts = classification ? totals_query.filter { |t| t.classification == classification } : totals_query
|
classification_accounts = classification ? totals_query.filter { |t| t.classification == classification } : totals_query
|
||||||
classification_total = classification_accounts.sum(&:converted_balance)
|
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)
|
group_total = accounts.sum(&:converted_balance)
|
||||||
|
|
||||||
AccountGroup.new(
|
AccountGroup.new(
|
||||||
|
@ -71,7 +72,13 @@ class BalanceSheet
|
||||||
account
|
account
|
||||||
end.sort_by(&:weight).reverse
|
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
|
end
|
||||||
|
|
||||||
def net_worth_series(period: Period.last_30_days)
|
def net_worth_series(period: Period.last_30_days)
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</details>
|
</details>
|
||||||
<% end %>
|
<% 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| %>
|
<% tabs.with_nav do |nav| %>
|
||||||
<% nav.with_btn(id: "asset", label: "Assets") %>
|
<% nav.with_btn(id: "asset", label: "Assets") %>
|
||||||
<% nav.with_btn(id: "liability", label: "Debts") %>
|
<% nav.with_btn(id: "liability", label: "Debts") %>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<%= render(
|
<%= render(
|
||||||
"accounts/account_sidebar_tabs",
|
"accounts/account_sidebar_tabs",
|
||||||
family: Current.family,
|
family: Current.family,
|
||||||
active_account_group_tab: params[:account_group_tab] || "assets"
|
active_account_group_tab: @account_group_tab
|
||||||
) %>
|
) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
<div class="overflow-y-auto grow">
|
<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>
|
</div>
|
||||||
|
|
||||||
<% if Current.family.trialing? && !self_hosted? %>
|
<% if Current.family.trialing? && !self_hosted? %>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<%# locals: (rule:) %>
|
<%# 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">
|
<div class="text-sm space-y-1.5">
|
||||||
<% if rule.name.present? %>
|
<% if rule.name.present? %>
|
||||||
<h3 class="font-medium text-md"><%= rule.name %></h3>
|
<h3 class="font-medium text-md"><%= rule.name %></h3>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<% if rule.effective_date.nil? %>
|
<% if rule.effective_date.nil? %>
|
||||||
All past and future <%= rule.resource_type.pluralize %>
|
All past and future <%= rule.resource_type.pluralize %>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -26,6 +26,8 @@ Rails.application.routes.draw do
|
||||||
get "changelog", to: "pages#changelog"
|
get "changelog", to: "pages#changelog"
|
||||||
get "feedback", to: "pages#feedback"
|
get "feedback", to: "pages#feedback"
|
||||||
|
|
||||||
|
resource :cookie_session, only: %i[update]
|
||||||
|
|
||||||
resource :registration, only: %i[new create]
|
resource :registration, only: %i[new create]
|
||||||
resources :sessions, only: %i[new create destroy]
|
resources :sessions, only: %i[new create destroy]
|
||||||
resource :password_reset, only: %i[new create edit update]
|
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
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.text "goals", default: [], array: true
|
||||||
t.datetime "set_onboarding_preferences_at"
|
t.datetime "set_onboarding_preferences_at"
|
||||||
t.datetime "set_onboarding_goals_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 ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["family_id"], name: "index_users_on_family_id"
|
t.index ["family_id"], name: "index_users_on_family_id"
|
||||||
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_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