Skip to content

Commit

Permalink
Add institution management and account editing controls (#868)
Browse files Browse the repository at this point in the history
* Add institution management

* Allow user to select institution on create or edit

* Improve redirect behavior

* Final cleanup

* i18n normalization
  • Loading branch information
zachgoll authored Jun 13, 2024
1 parent 8c1a7af commit 9956a95
Show file tree
Hide file tree
Showing 36 changed files with 456 additions and 68 deletions.
16 changes: 12 additions & 4 deletions app/controllers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ class AccountsController < ApplicationController
layout "with_sidebar"

include Filterable
before_action :set_account, only: %i[ show destroy sync update ]
before_action :set_account, only: %i[ edit show destroy sync update ]
after_action :sync_account, only: :create

def index
@accounts = Current.family.accounts
@institutions = Current.family.institutions
@accounts = Current.family.accounts.ungrouped.alphabetically
end

def summary
Expand All @@ -26,13 +27,20 @@ def new
balance: nil,
accountable: Accountable.from_type(params[:type])&.new
)

if params[:institution_id]
@account.institution = Current.family.institutions.find_by(id: params[:institution_id])
end
end

def show
@balance_series = @account.series(period: @period)
@valuation_series = @account.valuations.to_series
end

def edit
end

def update
@account.update! account_params.except(:accountable_type)
redirect_back_or_to account_path(@account), notice: t(".success")
Expand All @@ -46,7 +54,7 @@ def create
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]

redirect_to account_path(@account), notice: t(".success")
redirect_back_or_to account_path(@account), notice: t(".success")
end

def destroy
Expand Down Expand Up @@ -80,7 +88,7 @@ def set_account
end

def account_params
params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :start_balance, :currency, :subtype, :is_active)
params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :start_balance, :currency, :subtype, :is_active, :institution_id)
end

def sync_account
Expand Down
35 changes: 35 additions & 0 deletions app/controllers/institutions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class InstitutionsController < ApplicationController
before_action :set_institution, except: %i[ new create ]

def new
@institution = Institution.new
end

def create
Current.family.institutions.create!(institution_params)
redirect_to accounts_path, notice: t(".success")
end

def edit
end

def update
@institution.update!(institution_params)
redirect_to accounts_path, notice: t(".success")
end

def destroy
@institution.destroy!
redirect_to accounts_path, notice: t(".success")
end

private

def institution_params
params.require(:institution).permit(:name, :logo)
end

def set_institution
@institution = Current.family.institutions.find(params[:id])
end
end
5 changes: 5 additions & 0 deletions app/helpers/institutions_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module InstitutionsHelper
def institution_logo(institution)
institution.logo.attached? ? institution.logo : institution.logo_url
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class extends Controller {
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
this.imagePreviewTarget.innerHTML = `<img src="${e.target.result}" alt="Preview" class="w-24 h-24 rounded-full object-cover" />`;
this.imagePreviewTarget.innerHTML = `<img src="${e.target.result}" alt="Preview" class="w-full h-full rounded-full object-cover" />`;
this.templateTarget.classList.add("hidden");
this.clearBtnTarget.classList.remove("hidden");
};
Expand Down
5 changes: 4 additions & 1 deletion app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ class Account < ApplicationRecord
include Syncable
include Monetizable

broadcasts_refreshes

validates :family, presence: true

broadcasts_refreshes
belongs_to :family
belongs_to :institution, optional: true
has_many :balances, dependent: :destroy
has_many :valuations, dependent: :destroy
has_many :transactions, dependent: :destroy
Expand All @@ -19,6 +21,7 @@ class Account < ApplicationRecord
scope :assets, -> { where(classification: "asset") }
scope :liabilities, -> { where(classification: "liability") }
scope :alphabetically, -> { order(:name) }
scope :ungrouped, -> { where(institution_id: nil) }

delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy

Expand Down
1 change: 1 addition & 0 deletions app/models/family.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Family < ApplicationRecord
has_many :users, dependent: :destroy
has_many :tags, dependent: :destroy
has_many :accounts, dependent: :destroy
has_many :institutions, dependent: :destroy
has_many :transactions, through: :accounts
has_many :imports, through: :accounts
has_many :transaction_categories, dependent: :destroy, class_name: "Transaction::Category"
Expand Down
7 changes: 7 additions & 0 deletions app/models/institution.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Institution < ApplicationRecord
belongs_to :family
has_many :accounts, dependent: :nullify
has_one_attached :logo

scope :alphabetically, -> { order(name: :asc) }
end
6 changes: 5 additions & 1 deletion app/views/accounts/_account.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<%= turbo_frame_tag dom_id(account) do %>
<div class="p-4 flex items-center justify-between gap-3">
<div class="p-4 flex items-center justify-between gap-3 group/account">
<div class="flex items-center gap-3">
<div class="w-8 h-8 flex items-center justify-center rounded-full text-xs font-medium <%= account.is_active ? "bg-blue-500/10 text-blue-500" : "bg-gray-500/10 text-gray-500" %>">
<%= account.name[0].upcase %>
</div>
<%= link_to account.name, account, class: [(account.is_active ? "text-gray-900" : "text-gray-400"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %>
<%= link_to edit_account_path(account), data: { turbo_frame: :modal }, class: "group-hover/account:flex hidden hover:opacity-80 items-center justify-center" do %>
<%= lucide_icon "pencil-line", class: "w-4 h-4 text-gray-500" %>
<% end %>
</div>
<div class="flex items-center gap-8">
<p class="text-sm font-medium <%= account.is_active ? "text-gray-900" : "text-gray-400" %>">
Expand Down
2 changes: 1 addition & 1 deletion app/views/accounts/_account_type.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<%= link_to new_account_path(step: "method", type: type.class.name.demodulize), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-25 border border-transparent focus:border focus:border-gray-200 block px-2 hover:bg-gray-25 rounded-lg p-2" do %>
<%= link_to new_account_path(step: "method", type: type.class.name.demodulize, institution_id: params[:institution_id]), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-25 border border-transparent focus:border focus:border-gray-200 block px-2 hover:bg-gray-25 rounded-lg p-2" do %>
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg <%= bg_color %> border border-alpha-black-25">
<%= lucide_icon(icon, class: "#{text_color} w-5 h-5") %>
</span>
Expand Down
17 changes: 17 additions & 0 deletions app/views/accounts/_accountable_group.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<%# locals: (accounts:) %>
<% accounts.group_by(&:accountable_type).each do |group, accounts| %>
<div class="bg-gray-25 p-1 rounded-xl">
<div class="flex items-center px-4 py-2 text-xs font-medium text-gray-500">
<p><%= to_accountable_title(Accountable.from_type(group)) %></p>
<span class="text-gray-400 mx-2">&middot;</span>
<p><%= accounts.count %></p>
<p class="ml-auto"><%= format_money accounts.sum(&:balance_money) %></p>
</div>
<div class="bg-white">
<% accounts.each do |account| %>
<%= render account %>
<% end %>
</div>
</div>
<% end %>
11 changes: 11 additions & 0 deletions app/views/accounts/_empty.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="flex justify-center items-center h-[800px] text-sm">
<div class="text-center flex flex-col items-center max-w-[300px]">
<%= tag.p t(".no_accounts"), class: "text-gray-900 mb-1 font-medium" %>
<%= tag.p t(".empty_message"), class: "text-gray-500 mb-4" %>
<%= link_to new_account_path, class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2 pr-3", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<span><%= t(".new_account") %></span>
<% end %>
</div>
</div>
2 changes: 1 addition & 1 deletion app/views/accounts/_entry_method.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<%= text %>
</span>
<% else %>
<%= link_to new_account_path(type: type.class.name.demodulize), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
<%= link_to new_account_path(type: type.class.name.demodulize, institution_id: params[:institution_id]), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
</span>
Expand Down
69 changes: 69 additions & 0 deletions app/views/accounts/_institution_accounts.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<%# locals: (institution:) %>

<details open class="group bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<summary class="flex items-center gap-2 focus-visible:outline-none">
<%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-gray-500 w-5" %>

<div class="flex items-center justify-center h-8 w-8 bg-blue-600/10 rounded-full bg-black/5">
<% if institution_logo(institution) %>
<%= image_tag institution_logo(institution), class: "rounded-full h-full w-full" %>
<% else %>
<div class="flex items-center justify-center">
<%= tag.p institution.name.first.upcase, class: "text-blue-600 text-xs font-medium" %>
</div>
<% end %>
</div>

<%= link_to institution.name, edit_institution_path(institution), data: { turbo_frame: :modal }, class: "text-sm font-medium text-gray-900 ml-1 mr-auto hover:underline" %>
<%= contextual_menu do %>
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
<%= link_to new_account_path(institution_id: institution.id),
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "plus", class: "w-5 h-5 text-gray-500" %>

<span><%= t(".add_account_to_institution") %></span>
<% end %>
<%= link_to edit_institution_path(institution),
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "pencil-line", class: "w-5 h-5 text-gray-500" %>

<span><%= t(".edit") %></span>
<% end %>
<%= button_to institution_path(institution),
method: :delete,
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
data: {
turbo_confirm: {
title: t(".confirm_title"),
body: t(".confirm_body"),
accept: t(".confirm_accept")
}
} do %>
<%= lucide_icon "trash-2", class: "w-5 h-5" %>

<span><%= t(".delete") %></span>
<% end %>
</div>

<% end %>
</summary>

<div class="space-y-4 mt-4">
<% if institution.accounts.any? %>
<%= render "accountable_group", accounts: institution.accounts %>
<% else %>
<div class="p-4 flex flex-col gap-3 items-center justify-center">
<p class="text-gray-500 text-sm">There are no accounts in this financial institution</p>
<%= link_to new_account_path(institution_id: institution.id), class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-1.5 pr-2", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-4 h-4") %>
<span><%= t(".new_account") %></span>
<% end %>
</div>
<% end %>
</div>
</details>
17 changes: 17 additions & 0 deletions app/views/accounts/_institutionless_accounts.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<%# locals: (accounts:) %>

<details open class="group bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<summary class="flex items-center gap-2 focus-visible:outline-none">
<%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-gray-500 w-5" %>

<div class="flex items-center justify-center h-8 w-8 rounded-full bg-black/5">
<%= lucide_icon("folder-pen", class: "w-5 h-5 text-gray-500") %>
</div>

<span class="mr-auto text-sm font-medium text-gray-900"><%= t(".other_accounts") %></span>
</summary>

<div class="space-y-4 mt-4">
<%= render "accountable_group", accounts: accounts %>
</div>
</details>
21 changes: 21 additions & 0 deletions app/views/accounts/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<%= modal do %>
<article class="mx-auto w-full p-4 space-y-4 min-w-[350px]">
<header class="flex justify-between">
<h2 class="font-medium text-xl"><%= t(".edit", account: @account.name) %></h2>
<%= lucide_icon "x", class: "w-5 h-5 text-gray-500", data: { action: "click->modal#close" } %>
</header>

<%= form_with model: @account, data: { turbo_frame: "_top" } do |f| %>
<%= f.text_field :name, label: "Name" %>

<div class="relative">
<%= f.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
<%= link_to new_institution_path do %>
<%= lucide_icon "plus", class: "text-gray-700 hover:text-gray-500 w-4 h-4 absolute right-3 top-2" %>
<% end %>
</div>

<%= f.submit %>
<% end %>
</article>
<% end %>
Loading

0 comments on commit 9956a95

Please sign in to comment.