1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-03 04:25:21 +02:00

Account namespace updates: part 6 (transactions) (#904)

* Move Transaction to Account namespace

* Fix improper routes, improve separation of concerns

* Replace account transactions list partial with view

* Remove logs

* Consolidate transaction views

* Remove unused code

* Transfer style tweaks

* Remove more unused code

* Add back totals by currency helper
This commit is contained in:
Zach Gollwitzer 2024-06-24 11:58:39 -04:00 committed by GitHub
parent cb3fd34f90
commit da18c3d850
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 575 additions and 522 deletions

View file

@ -1,4 +1,4 @@
class Transaction::RowsController < ApplicationController
class Account::Transaction::RowsController < ApplicationController
before_action :set_transaction, only: %i[ show update ]
def show
@ -7,7 +7,7 @@ class Transaction::RowsController < ApplicationController
def update
@transaction.update! transaction_params
redirect_to transaction_row_path(@transaction)
redirect_to account_transaction_row_path(@transaction.account, @transaction)
end
private
@ -17,6 +17,6 @@ class Transaction::RowsController < ApplicationController
end
def set_transaction
@transaction = Current.family.transactions.find(params[:id])
@transaction = Current.family.accounts.find(params[:account_id]).transactions.find(params[:transaction_id])
end
end

View file

@ -0,0 +1,6 @@
class Account::Transaction::RulesController < ApplicationController
layout "with_sidebar"
def index
end
end

View file

@ -0,0 +1,44 @@
class Account::TransactionsController < ApplicationController
layout "with_sidebar"
before_action :set_account
before_action :set_transaction, only: %i[ show update destroy ]
def index
@transactions = @account.transactions.ordered
end
def show
end
def update
@transaction.update! transaction_params
@transaction.sync_account_later
redirect_back_or_to account_transaction_url(@transaction.account, @transaction), notice: t(".success")
end
def destroy
@transaction.destroy!
@transaction.sync_account_later
redirect_back_or_to account_url(@transaction.account), notice: t(".success")
end
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
def set_transaction
@transaction = @account.transactions.find(params[:id])
end
def search_params
params.fetch(:q, {}).permit(:start_date, :end_date, :search, accounts: [], account_ids: [], categories: [], merchants: [])
end
def transaction_params
params.require(:transaction).permit(:name, :date, :amount, :currency, :notes, :excluded, :category_id, :merchant_id, tag_ids: [])
end
end

View file

@ -21,7 +21,7 @@ class PagesController < ApplicationController
@accounts = Current.family.accounts
@account_groups = @accounts.by_group(period: @period, currency: Current.family.currency)
@transactions = Current.family.transactions.limit(5).order(date: :desc)
@transactions = Current.family.transactions.limit(6).order(date: :desc)
# TODO: Placeholders for trendlines
placeholder_series_data = 10.times.map do |i|

View file

@ -1,6 +0,0 @@
class Transaction::RulesController < ApplicationController
layout "with_sidebar"
def index
end
end

View file

@ -1,8 +1,6 @@
class TransactionsController < ApplicationController
layout "with_sidebar"
before_action :set_transaction, only: %i[ show edit update destroy ]
def index
@q = search_params
result = Current.family.transactions.search(@q).ordered
@ -15,41 +13,22 @@ class TransactionsController < ApplicationController
}
end
def show
end
def new
@transaction = Transaction.new.tap do |txn|
@transaction = Account::Transaction.new.tap do |txn|
if params[:account_id]
txn.account = Current.family.accounts.find(params[:account_id])
end
end
end
def edit
end
def create
@transaction = Current.family.accounts
.find(params[:transaction][:account_id])
.transactions.build(transaction_params.merge(amount: amount))
.find(params[:transaction][:account_id])
.transactions
.create!(transaction_params.merge(amount: amount))
@transaction.save!
@transaction.sync_account_later
redirect_to transactions_url, notice: t(".success")
end
def update
@transaction.update! transaction_params
@transaction.sync_account_later
redirect_to transaction_url(@transaction), notice: t(".success")
end
def destroy
@transaction.destroy!
@transaction.sync_account_later
redirect_to transactions_url, notice: t(".success")
redirect_back_or_to account_path(@transaction.account), notice: t(".success")
end
def bulk_delete
@ -90,10 +69,6 @@ class TransactionsController < ApplicationController
private
def set_transaction
@transaction = Current.family.transactions.find(params[:id])
end
def amount
if nature.income?
transaction_params[:amount].to_d * -1
@ -119,6 +94,6 @@ class TransactionsController < ApplicationController
end
def transaction_params
params.require(:transaction).permit(:name, :date, :amount, :currency, :notes, :excluded, :category_id, :merchant_id, tag_ids: [])
params.require(:transaction).permit(:name, :date, :amount, :currency, :category_id, tag_ids: [])
end
end

View file

@ -1,4 +1,4 @@
module Transactions::SearchesHelper
module Account::Transaction::SearchesHelper
def transaction_search_filters
[
{ key: "account_filter", name: "Account", icon: "layers" },

View file

@ -1,23 +1,4 @@
module TransactionsHelper
def transactions_group(date, transactions, transaction_partial_path = "transactions/transaction")
header_left = content_tag :span do
"#{date.strftime('%b %d, %Y')} · #{transactions.size}".html_safe
end
header_right = content_tag :span do
format_money(-transactions.sum(&:amount_money))
end
header = header_left.concat(header_right)
content = render partial: transaction_partial_path, collection: transactions
render partial: "shared/list_group", locals: {
header: header,
content: content
}
end
module Account::TransactionsHelper
def unconfirmed_transfer?(transaction)
transaction.marked_as_transfer && transaction.transfer.nil?
end

View file

@ -1,6 +1,6 @@
module Account::ValuationsHelper
def valuation_icon(valuation)
if valuation.first_of_series?
if valuation.oldest?
"keyboard"
elsif valuation.trend.direction.up?
"arrow-up"
@ -12,7 +12,7 @@ module Account::ValuationsHelper
end
def valuation_style(valuation)
color = valuation.first_of_series? ? "#D444F1" : valuation.trend.color
color = valuation.oldest? ? "#D444F1" : valuation.trend.color
<<-STYLE.strip
background-color: color-mix(in srgb, #{color} 5%, white);

View file

@ -1,4 +1,4 @@
class Transaction < ApplicationRecord
class Account::Transaction < ApplicationRecord
include Monetizable
monetize :amount
@ -17,22 +17,22 @@ class Transaction < ApplicationRecord
scope :active, -> { where(excluded: false) }
scope :inflows, -> { where("amount <= 0") }
scope :outflows, -> { where("amount > 0") }
scope :by_name, ->(name) { where("transactions.name ILIKE ?", "%#{name}%") }
scope :by_name, ->(name) { where("account_transactions.name ILIKE ?", "%#{name}%") }
scope :with_categories, ->(categories) { joins(:category).where(categories: { name: categories }) }
scope :with_accounts, ->(accounts) { joins(:account).where(accounts: { name: accounts }) }
scope :with_account_ids, ->(account_ids) { joins(:account).where(accounts: { id: account_ids }) }
scope :with_merchants, ->(merchants) { joins(:merchant).where(merchants: { name: merchants }) }
scope :on_or_after_date, ->(date) { where("transactions.date >= ?", date) }
scope :on_or_before_date, ->(date) { where("transactions.date <= ?", date) }
scope :on_or_after_date, ->(date) { where("account_transactions.date >= ?", date) }
scope :on_or_before_date, ->(date) { where("account_transactions.date <= ?", date) }
scope :with_converted_amount, ->(currency = Current.family.currency) {
# Join with exchange rates to convert the amount to the given currency
# If no rate is available, exclude the transaction from the results
select(
"transactions.*",
"transactions.amount * COALESCE(er.rate, 1) AS converted_amount"
"account_transactions.*",
"account_transactions.amount * COALESCE(er.rate, 1) AS converted_amount"
)
.joins(sanitize_sql_array([ "LEFT JOIN exchange_rates er ON transactions.date = er.date AND transactions.currency = er.base_currency AND er.converted_currency = ?", currency ]))
.where("er.rate IS NOT NULL OR transactions.currency = ?", currency)
.joins(sanitize_sql_array([ "LEFT JOIN exchange_rates er ON account_transactions.date = er.date AND account_transactions.currency = er.base_currency AND er.converted_currency = ?", currency ]))
.where("er.rate IS NOT NULL OR account_transactions.currency = ?", currency)
}
def inflow?

View file

@ -15,14 +15,10 @@ class Account::Valuation < ApplicationRecord
@trend ||= create_trend
end
def first_of_series?
def oldest?
account.valuations.chronological.limit(1).pluck(:date).first == self.date
end
def last_of_series?
account.valuations.reverse_chronological.limit(1).pluck(:date).first == self.date
end
def sync_account_later
if destroyed?
sync_start_date = previous_valuation&.date

View file

@ -1,5 +1,5 @@
class Category < ApplicationRecord
has_many :transactions, dependent: :nullify
has_many :transactions, dependent: :nullify, class_name: "Account::Transaction"
belongs_to :family
validates :name, :color, :family, presence: true

View file

@ -3,7 +3,7 @@ class Family < ApplicationRecord
has_many :tags, dependent: :destroy
has_many :accounts, dependent: :destroy
has_many :institutions, dependent: :destroy
has_many :transactions, through: :accounts
has_many :transactions, through: :accounts, class_name: "Account::Transaction"
has_many :imports, through: :accounts
has_many :categories, dependent: :destroy
has_many :merchants, dependent: :destroy
@ -40,9 +40,9 @@ class Family < ApplicationRecord
"COALESCE(SUM(amount) FILTER (WHERE amount > 0), 0) AS spending",
"COALESCE(SUM(-amount) FILTER (WHERE amount < 0), 0) AS income"
)
.where("transactions.date >= ?", period.date_range.begin)
.where("transactions.date <= ?", period.date_range.end)
.where("transactions.marked_as_transfer = ?", false)
.where("account_transactions.date >= ?", period.date_range.begin)
.where("account_transactions.date <= ?", period.date_range.end)
.where("account_transactions.marked_as_transfer = ?", false)
.group("id")
.to_a
@ -60,7 +60,7 @@ class Family < ApplicationRecord
end
def snapshot_transactions
rolling_totals = Transaction.daily_rolling_totals(transactions, period: Period.last_30_days, currency: self.currency)
rolling_totals = Account::Transaction.daily_rolling_totals(transactions, period: Period.last_30_days, currency: self.currency)
spending = []
income = []

View file

@ -1,5 +1,5 @@
class Merchant < ApplicationRecord
has_many :transactions, dependent: :nullify
has_many :transactions, dependent: :nullify, class_name: "Account::Transaction"
belongs_to :family
validates :name, :color, :family, presence: true

View file

@ -1,7 +1,7 @@
class Tag < ApplicationRecord
belongs_to :family
has_many :taggings, dependent: :destroy
has_many :transactions, through: :taggings, source: :taggable, source_type: "Transaction"
has_many :transactions, through: :taggings, source: :taggable, source_type: "Account::Transaction"
validates :name, presence: true, uniqueness: { scope: :family }

View file

@ -0,0 +1 @@
<%= render "account/transactions/transaction", transaction: @transaction %>

View file

@ -1,6 +1,7 @@
<% content_for :sidebar do %>
<%= render "settings/nav" %>
<% end %>
<div class="space-y-4">
<h1 class="text-gray-900 text-xl font-medium mb-4">Rules</h1>
<div class="bg-white shadow-xs border border-alpha-black-25 rounded-xl p-4">

View file

@ -0,0 +1,5 @@
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="p-5 flex justify-center items-center">
<%= tag.p t(".loading"), class: "text-gray-500 animate-pulse text-sm" %>
</div>
</div>

View file

@ -12,6 +12,7 @@
builder: ActionView::Helpers::FormBuilder,
scope: "bulk_update",
data: {
turbo_frame: "_top",
turbo_confirm: {
title: t(".mark_transfers"),
body: t(".mark_transfers_message"),
@ -30,7 +31,7 @@
<%= lucide_icon "pencil-line", class: "w-5 group-hover:text-white" %>
<% end %>
<%= form_with url: bulk_delete_transactions_path, builder: ActionView::Helpers::FormBuilder, data: { turbo_confirm: true } do %>
<%= form_with url: bulk_delete_transactions_path, builder: ActionView::Helpers::FormBuilder, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
<button type="button" data-bulk-select-scope-param="bulk_delete" data-action="bulk-select#submitBulkRequest" class="p-1.5 group hover:bg-gray-700 flex items-center justify-center rounded-md" title="Delete">
<%= lucide_icon "trash-2", class: "w-5 group-hover:text-white" %>
</button>

View file

@ -0,0 +1,89 @@
<%# locals: (transaction:, selectable: true, editable: true, short: false, show_tags: false) %>
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4" do %>
<% name_col_span = transaction.transfer? ? "col-span-10" : short ? "col-span-6" : "col-span-4" %>
<div class="pr-10 flex items-center gap-4 <%= name_col_span %>">
<% if selectable %>
<%= check_box_tag dom_id(transaction, "selection"),
class: "maybe-checkbox maybe-checkbox--light",
data: { id: transaction.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
<% end %>
<div class="max-w-full">
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
<%= transaction.name[0].upcase %>
</div>
<div class="truncate text-gray-900">
<% if transaction.new_record? %>
<%= content_tag :p, transaction.name %>
<% else %>
<%= link_to transaction.name,
account_transaction_path(transaction.account, transaction),
data: { turbo_frame: "drawer", turbo_prefetch: false },
class: "hover:underline hover:text-gray-800" %>
<% end %>
</div>
<% end %>
</div>
<% if unconfirmed_transfer?(transaction) %>
<% if editable %>
<%= form_with url: unmark_transfers_transactions_path, builder: ActionView::Helpers::FormBuilder, class: "flex items-center", data: {
turbo_confirm: {
title: t(".remove_transfer"),
body: t(".remove_transfer_body"),
accept: t(".remove_transfer_confirm"),
},
turbo_frame: "_top"
} do |f| %>
<%= f.hidden_field "bulk_update[transaction_ids][]", value: transaction.id %>
<%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
<%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
<%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
<% end %>
<% end %>
<% else %>
<%= lucide_icon "arrow-left-right", class: "text-gray-500 w-4 h-4" %>
<% end %>
<% end %>
</div>
<% unless transaction.transfer? %>
<% unless short %>
<div class="flex items-center gap-1 <%= show_tags ? "col-span-6" : "col-span-3" %>">
<% if editable %>
<%= render "categories/menu", transaction: transaction %>
<% else %>
<%= render "categories/badge", category: transaction.category %>
<% end %>
<% if show_tags %>
<% transaction.tags.each do |tag| %>
<%= render partial: "tags/badge", locals: { tag: tag } %>
<% end %>
<% end %>
</div>
<% end %>
<% unless show_tags %>
<%= tag.div class: short ? "col-span-4" : "col-span-3" do %>
<% if transaction.new_record? %>
<%= tag.p transaction.account.name %>
<% else %>
<%= link_to transaction.account.name,
account_path(transaction.account, tab: "transactions"),
data: { turbo_frame: "_top" },
class: "hover:underline" %>
<% end %>
<% end %>
<% end %>
<% end %>
<div class="col-span-2 ml-auto">
<%= content_tag :p,
format_money(-transaction.amount_money),
class: ["text-green-600": transaction.inflow?] %>
</div>
<% end %>

View file

@ -0,0 +1,21 @@
<%# locals: (date:, transactions:, transfers: [], selectable: true, **transaction_opts) %>
<div id="date-group-<%= date %>" class="bg-gray-25 rounded-xl p-1 w-full" data-bulk-select-target="group">
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
<div class="flex pl-0.5 items-center gap-4">
<% if selectable %>
<%= check_box_tag "#{date}_transactions_selection",
class: ["maybe-checkbox maybe-checkbox--light", "hidden": transactions.count == 0],
id: "selection_transaction_#{date}",
data: { action: "bulk-select#toggleGroupSelection" } %>
<% end %>
<%= tag.span "#{date.strftime('%b %d, %Y')} · #{transactions.size + (transfers.size * 2)}" %>
</div>
<%= totals_by_currency(collection: transactions, money_method: :amount_money, negate: true) %>
</div>
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
<%= render transactions, selectable:, **transaction_opts.except(:selectable) %>
<%= render transfers %>
</div>
</div>

View file

@ -0,0 +1,29 @@
<%= turbo_frame_tag dom_id(@account, "transactions") do %>
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="flex justify-between items-center">
<h3 class="font-medium text-lg"><%= t(".transactions") %></h3>
<%= link_to new_transaction_path(account_id: @account),
class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg",
data: { turbo_frame: :modal } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
<span class="text-sm"><%= t(".new") %></span>
<% end %>
</div>
<div id="transactions" data-controller="bulk-select" data-bulk-select-resource-value="<%= t(".transaction") %>">
<div hidden id="transaction-selection-bar" data-bulk-select-target="selectionBar">
<%= render "account/transactions/selection_bar" %>
</div>
<% if @transactions.empty? %>
<p class="text-gray-500 py-4"><%= t(".no_transactions") %></p>
<% else %>
<div class="space-y-6">
<% group_transactions_by_date(@transactions).each do |date, group| %>
<%= render "transaction_group", date:, transactions: group[:transactions], transfers: group[:transfers] %>
<% end %>
</div>
<% end %>
</div>
</div>
<% end %>

View file

@ -90,7 +90,7 @@
</div>
<%= button_to t(".delete"),
transaction_path(@transaction),
account_transaction_path(@transaction),
method: :delete,
class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-alpha-black-200",
data: { turbo_confirm: true, turbo_frame: "_top" } %>

View file

@ -1,6 +1,6 @@
<%= turbo_frame_tag dom_id(transfer), class: "block" do %>
<details class="group flex items-center text-gray-900 p-4 text-sm font-medium">
<summary class="flex items-center justify-between">
<details class="group flex items-center text-gray-900 text-sm font-medium">
<summary class="flex items-center justify-between p-4">
<div class="flex items-center gap-4">
<%= button_to account_transfer_path(transfer),
method: :delete,
@ -25,16 +25,9 @@
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
</summary>
<div class="pt-2 divide-y divide-alpha-black-200">
<div class="divide-y divide-alpha-black-200">
<% transfer.transactions.each do |transaction| %>
<div class="py-3 grid grid-cols-12 items-center">
<div class="col-span-10">
<%= render "transactions/name", transaction: transaction %>
</div>
<div class="col-span-2 ml-auto">
<%= render "transactions/amount", transaction: transaction %>
</div>
</div>
<%= render transaction, selectable: false, editable: false %>
<% end %>
</div>
</details>

View file

@ -0,0 +1,5 @@
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="p-5 flex justify-center items-center">
<%= tag.p t(".loading"), class: "text-gray-500 animate-pulse text-sm" %>
</div>
</div>

View file

@ -1,5 +1,3 @@
<%# locals: (valuation:) %>
<%= turbo_frame_tag dom_id(valuation) do %>
<div class="p-4 grid grid-cols-10 items-center">
<div class="col-span-5 flex items-center gap-4">
@ -9,7 +7,7 @@
<div class="text-sm">
<%= tag.p valuation.date, class: "text-gray-900 font-medium" %>
<%= tag.p valuation.first_of_series? ? t(".start_balance") : t(".value_update"), class: "text-gray-500" %>
<%= tag.p valuation.oldest? ? t(".start_balance") : t(".value_update"), class: "text-gray-500" %>
</div>
</div>
@ -43,8 +41,4 @@
<% end %>
</div>
</div>
<% unless valuation.last_of_series? %>
<div class="h-px bg-alpha-black-50 ml-16 mr-4"></div>
<% end %>
<% end %>

View file

@ -0,0 +1 @@
<div class="h-px bg-alpha-black-50 ml-16 mr-4"></div>

View file

@ -1,15 +1,35 @@
<%= turbo_frame_tag dom_id(@account, "valuations") do %>
<div class="grid grid-cols-10 items-center uppercase text-xs font-medium text-gray-500 px-4 py-2">
<%= tag.p t(".date"), class: "col-span-5" %>
<%= tag.p t(".value"), class: "col-span-2 justify-self-end" %>
<%= tag.p t(".change"), class: "col-span-2 justify-self-end" %>
<%= tag.div class: "col-span-1" %>
</div>
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="flex items-center justify-between">
<%= tag.h2 t(".valuations"), class: "font-medium text-lg" %>
<%= link_to new_account_valuation_path(@account),
data: { turbo_frame: dom_id(Account::Valuation.new) },
class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
<%= tag.span t(".new_entry"), class: "text-sm" %>
<% end %>
</div>
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
<%= turbo_frame_tag dom_id(Account::Valuation.new) %>
<% @account.valuations.reverse_chronological.each do |valuation| %>
<%= render valuation %>
<% end %>
<div class="rounded-xl bg-gray-25 p-1">
<div class="grid grid-cols-10 items-center uppercase text-xs font-medium text-gray-500 px-4 py-2">
<%= tag.p t(".date"), class: "col-span-5" %>
<%= tag.p t(".value"), class: "col-span-2 justify-self-end" %>
<%= tag.p t(".change"), class: "col-span-2 justify-self-end" %>
<%= tag.div class: "col-span-1" %>
</div>
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
<%= turbo_frame_tag dom_id(Account::Valuation.new) %>
<% valuations = @account.valuations.reverse_chronological %>
<% if valuations.any? %>
<%= render partial: "valuation",
collection: @account.valuations.reverse_chronological,
spacer_template: "valuation_ruler" %>
<% else %>
<p class="text-gray-500 text-sm p-4"><%= t(".no_valuations") %></p>
<% end %>
</div>
</div>
</div>
<% end %>

View file

@ -1,20 +0,0 @@
<%# locals: (account:, transactions:)%>
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="flex justify-between items-center">
<h3 class="font-medium text-lg">Transactions</h3>
<%= link_to new_transaction_path(account_id: account), class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg", data: { turbo_frame: :modal } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
<span class="text-sm">New transaction</span>
<% end %>
</div>
<% if transactions.empty? %>
<p class="text-gray-500 py-4">No transactions for this account yet.</p>
<% else %>
<div class="space-y-6">
<% transactions.group_by(&:date).each do |date, transactions| %>
<%= transactions_group(date, transactions, "accounts/transactions/transaction") %>
<% end %>
</div>
<% end %>
</div>

View file

@ -1,4 +1,5 @@
<%= turbo_stream_from @account %>
<div class="space-y-4">
<div class="flex justify-between items-center">
<div class="flex items-center gap-3">
@ -70,34 +71,23 @@
<%= render partial: "shared/line_chart", locals: { series: @balance_series } %>
</div>
</div>
<div data-controller="tabs" data-tabs-active-class="bg-gray-100" data-tabs-default-tab-value="account-history-tab">
<div class="flex gap-1 text-sm text-gray-900 font-medium mb-4">
<button data-id="account-history-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">History</button>
<button data-id="account-transactions-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">Transactions</button>
</div>
<div class="min-h-[800px]">
<div data-tabs-target="tab" id="account-history-tab">
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
<div class="flex items-center justify-between">
<%= tag.h2 t(".valuations"), class: "font-medium text-lg" %>
<%= link_to new_account_valuation_path(@account), data: { turbo_frame: dom_id(Account::Valuation.new) }, class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
<%= tag.span t(".new_entry"), class: "text-sm" %>
<% end %>
</div>
<div class="rounded-xl bg-gray-25 p-1">
<%= turbo_frame_tag dom_id(@account, "valuations"), src: account_valuations_path(@account) do %>
<div class="p-5 flex justify-center items-center">
<%= tag.p t(".loading_history"), class: "text-gray-500 animate-pulse text-sm" %>
</div>
<% end %>
</div>
</div>
</div>
<div data-tabs-target="tab" id="account-transactions-tab" class="hidden">
<%= render partial: "accounts/transactions", locals: { account: @account, transactions: @account.transactions.order(date: :desc) } %>
</div>
</div>
<% selected_tab = params[:tab] || "history" %>
<div class="flex gap-1 text-sm text-gray-900 font-medium mb-4">
<%= link_to "History", account_path(tab: "history"), class: ["p-2 rounded-lg", "bg-gray-100": selected_tab == "history"] %>
<%= link_to "Transactions", account_path(tab: "transactions"), class: ["p-2 rounded-lg", "bg-gray-100": selected_tab == "transactions"] %>
</div>
<div class="min-h-[800px]">
<% if selected_tab == "transactions" %>
<%= turbo_frame_tag dom_id(@account, "transactions"), src: account_transactions_path(@account) do %>
<%= render "account/transactions/loading" %>
<% end %>
<% else %>
<%= turbo_frame_tag dom_id(@account, "valuations"), src: account_valuations_path(@account) do %>
<%= render "account/valuations/loading" %>
<% end %>
<% end %>
</div>
</div>

View file

@ -1,24 +0,0 @@
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 py-4 text-sm font-medium px-4" do %>
<div class="col-span-4">
<%= render "transactions/name", transaction: transaction %>
</div>
<div class="col-span-3">
<% if transaction.marked_as_transfer %>
<div class="flex items-center gap-1 text-gray-500 pl-5">
<%= lucide_icon "arrow-right-left", class: "w-4 h-4 text-gray-500" %>
<p>Transfer</p>
</div>
<% else %>
<%= render "categories/badge", category: transaction.category %>
<% end %>
</div>
<%= link_to transaction.account.name,
account_path(transaction.account),
class: ["col-span-3 hover:underline"] %>
<div class="col-span-2 ml-auto">
<%= render "transactions/amount", transaction: transaction %>
</div>
<% end %>

View file

@ -2,7 +2,7 @@
<% is_selected = category.id === @selected_category&.id %>
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
<%= button_to transaction_row_path(@transaction, transaction: { category_id: category.id }), method: :patch, data: { turbo_frame: dom_id(@transaction) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
<%= button_to account_transaction_row_path(@transaction.account, @transaction, transaction: { category_id: category.id }), method: :patch, data: { turbo_frame: dom_id(@transaction) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
<span class="w-5 h-5">
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
</span>

View file

@ -25,7 +25,7 @@
<% end %>
<% if @transaction.category %>
<%= button_to transaction_row_path(@transaction),
<%= button_to account_transaction_row_path(@transaction.account, @transaction),
method: :patch,
data: { turbo_frame: dom_id(@transaction) },
params: { transaction: { category_id: nil } },

View file

@ -9,8 +9,15 @@
</div>
<div class="mb-8 space-y-4">
<% @import.dry_run.group_by(&:date).each do |date, draft_transactions| %>
<%= transactions_group(date, draft_transactions, "imports/transactions/transaction") %>
<% transactions = @import.dry_run %>
<% group_transactions_by_date(transactions).each do |date, group| %>
<%= render "account/transactions/transaction_group",
date: date,
transactions: group[:transactions],
transfers: group[:transfers],
show_tags: true,
selectable: false,
editable: false %>
<% end %>
</div>

View file

@ -25,7 +25,7 @@
<% end %>
</div>
<div class="flex justify-between gap-4">
<%= previous_setting("Rules", transaction_rules_path) %>
<%= previous_setting("Rules", account_transaction_rules_path) %>
<%= next_setting("What's new", changelog_path) %>
</div>
</div>

View file

@ -1,20 +0,0 @@
<%# locals: (transaction:) %>
<div class="text-gray-900 grid grid-cols-8 items-center py-4 text-sm font-medium px-4">
<div class="col-span-3">
<%= render "transactions/name", transaction: transaction %>
</div>
<div class="col-span-2">
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
</div>
<div class="col-span-2 flex items-center gap-1">
<% transaction.tags.each do |tag| %>
<%= render partial: "tags/badge", locals: { tag: tag } %>
<% end %>
</div>
<div class="col-span-1 justify-self-end">
<%= render "transactions/amount", transaction: transaction %>
</div>
</div>

View file

@ -35,6 +35,6 @@
</div>
<div class="flex justify-between gap-4">
<%= previous_setting("Categories", categories_path) %>
<%= next_setting("Rules", transaction_rules_path) %>
<%= next_setting("Rules", account_transaction_rules_path) %>
</div>
</div>

View file

@ -3,7 +3,7 @@
<div>
<h1 class="sr-only">Dashboard</h1>
<p class="text-xl font-medium text-gray-900 mb-1"><%= t(".greeting", name: Current.user.first_name ) %></p>
<% if !@accounts.blank? %>
<% unless @accounts.blank? %>
<p class="text-gray-500 text-sm"><%= t(".subtitle") %></p>
<% end %>
</div>
@ -162,8 +162,14 @@
</div>
<% else %>
<div class="text-gray-500 p-1 space-y-1 bg-gray-25 rounded-xl">
<% @transactions.group_by(&:date).each do |date, transactions| %>
<%= transactions_group(date, transactions, "pages/dashboard/transactions/transaction") %>
<% group_transactions_by_date(@transactions).each do |date, group| %>
<%= render "account/transactions/transaction_group",
date: date,
transactions: group[:transactions],
transfers: group[:transfers],
selectable: false,
editable: false,
short: true %>
<% end %>
<p class="py-2 text-sm text-center"><%= link_to t(".view_all"), transactions_path %></p>

View file

@ -1,9 +0,0 @@
<div class="text-gray-900 flex items-center py-4 text-sm font-medium px-4">
<div class="grow max-w-72">
<%= render "transactions/name", transaction: transaction %>
</div>
<div class="ml-auto">
<%= render "transactions/amount", transaction: transaction %>
</div>
</div>

View file

@ -56,7 +56,7 @@
<%= sidebar_link_to t(".merchants_label"), merchants_path, icon: "store" %>
</li>
<li>
<%= sidebar_link_to t(".rules_label"), transaction_rules_path, icon: "list-checks" %>
<%= sidebar_link_to t(".rules_label"), account_transaction_rules_path, icon: "list-checks" %>
</li>
<li>
<%= sidebar_link_to t(".imports_label"), imports_path, icon: "download" %>

View file

@ -1 +0,0 @@
<%= render "transactions/transaction", transaction: @transaction %>

View file

@ -1,3 +0,0 @@
<%= content_tag :p,
format_money(-transaction.amount_money),
class: ["text-green-600": transaction.inflow?] %>

View file

@ -1,21 +0,0 @@
<%# locals: (date:, group:) %>
<div id="date-group-<%= date %>" class="bg-gray-25 rounded-xl p-1 w-full" data-bulk-select-target="group">
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
<div class="flex pl-0.5 items-center gap-4">
<%= check_box_tag "#{date}_transactions_selection",
class: ["maybe-checkbox maybe-checkbox--light", "hidden": group[:transactions].count == 0],
id: "selection_transaction_#{date}",
data: { action: "bulk-select#toggleGroupSelection" } %>
<%= tag.span "#{date.strftime('%b %d, %Y')} · #{group[:transactions].size + (group[:transfers].size * 2)}" %>
</div>
<div>
<%= totals_by_currency(collection: group[:transactions], money_method: :amount_money, negate: true) %>
</div>
</div>
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
<%= render group[:transactions] %>
<%= render group[:transfers] %>
</div>
</div>

View file

@ -1,4 +1,4 @@
<%= form_with model: @transaction, data: { turbo_frame: "_top" } do |f| %>
<%= form_with model: @transaction, url: transactions_path, scope: "transaction", data: { turbo_frame: "_top" } do |f| %>
<section>
<fieldset class="bg-gray-50 rounded-lg p-1 grid grid-flow-col justify-stretch gap-x-2">
<%= radio_tab_tag form: f, name: :nature, value: :expense, label: t(".expense"), icon: "minus-circle", checked: params[:nature] == "expense" || params[:nature].nil? %>

View file

@ -1,16 +0,0 @@
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
<%= transaction.name[0].upcase %>
</div>
<div class="truncate text-gray-900">
<% if transaction.new_record? %>
<%= content_tag :p, transaction.name %>
<% else %>
<%= link_to transaction.name,
transaction_path(transaction),
data: { turbo_frame: "drawer", turbo_prefetch: false },
class: "hover:underline hover:text-gray-800" %>
<% end %>
</div>
<% end %>

View file

@ -1,3 +1,4 @@
<%# locals: (pagy:) %>
<nav class="flex items-center justify-between px-4 mt-4 sm:px-0 w-full">
<div class="flex">
<div>
@ -38,7 +39,6 @@
<% end %>
</div>
</div>
<% per_page = params[:per_page] || "50" %>
<div class="flex items-center gap-4">
<%= form_with url: transactions_path,
builder: ActionView::Helpers::FormBuilder,

View file

@ -1,43 +0,0 @@
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4" do %>
<div class="pr-10 flex items-center gap-4 <%= unconfirmed_transfer?(transaction) ? "col-span-10" : "col-span-4" %>">
<%= check_box_tag dom_id(transaction, "selection"),
class: "maybe-checkbox maybe-checkbox--light",
data: { id: transaction.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
<div class="max-w-full">
<%= render "transactions/name", transaction: transaction %>
</div>
<% if unconfirmed_transfer?(transaction) %>
<%= form_with url: unmark_transfers_transactions_path, builder: ActionView::Helpers::FormBuilder, class: "flex items-center", data: {
turbo_confirm: {
title: t(".remove_transfer"),
body: t(".remove_transfer_body"),
accept: t(".remove_transfer_confirm"),
},
turbo_frame: "_top"
} do |f| %>
<%= f.hidden_field "bulk_update[transaction_ids][]", value: transaction.id %>
<%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
<%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
<%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
<% end %>
<% end %>
<% end %>
</div>
<% unless unconfirmed_transfer?(transaction) %>
<div class="col-span-3">
<%= render "categories/menu", transaction: transaction %>
</div>
<%= link_to transaction.account.name,
account_path(transaction.account),
data: { turbo_frame: "_top" },
class: ["col-span-3 hover:underline"] %>
<% end %>
<div class="col-span-2 ml-auto">
<%= render "transactions/amount", transaction: transaction %>
</div>
<% end %>

View file

@ -1,17 +1,16 @@
<div class="space-y-4 h-full flex flex-col overflow-y-auto">
<%= render "header" %>
<%= render partial: "transactions/summary", locals: { totals: @totals } %>
<%= render "summary", totals: @totals %>
<div id="transactions" data-controller="bulk-select" data-bulk-select-resource-value="<%= t(".transaction") %>" class="overflow-y-auto flex flex-col bg-white rounded-xl border border-alpha-black-25 shadow-xs pb-4">
<div class="p-4 pb-0">
<%= render partial: "transactions/searches/search", locals: { transactions: @transactions } %>
</div>
<% if @transactions.present? %>
<div hidden id="transaction-selection-bar" data-bulk-select-target="selectionBar">
<%= render "selection_bar" %>
<%= render "account/transactions/selection_bar" %>
</div>
<div class="grow overflow-y-auto px-4">
<div class="grid grid-cols-12 bg-gray-25 rounded-xl px-5 py-3 text-xs uppercase font-medium text-gray-500 items-center mb-4">
@ -27,13 +26,13 @@
<p class="col-span-2 justify-self-end">amount</p>
</div>
<div class="space-y-6">
<% group_transactions_by_date(@transactions).each do |date, group| %>
<%= render partial: "date_group", locals: { date:, group: } %>
<% end %>
<% group_transactions_by_date(@transactions).each do |date, group| %>
<%= render "account/transactions/transaction_group", date:, transactions: group[:transactions], transfers: group[:transfers] %>
<% end %>
</div>
</div>
<% else %>
<%= render "empty" %>
<%= render "account/transactions/empty" %>
<% end %>
<div class="px-4">
@ -41,6 +40,5 @@
<%= render "pagination", pagy: @pagy %>
<% end %>
</div>
</div>
</div>