Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Budgeting V1 #1609

Merged
merged 20 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions app/controllers/budget_categories_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class BudgetCategoriesController < ApplicationController
def index
@budget = Current.family.budgets.find(params[:budget_id])
render layout: "wizard"
end

def show
@budget = Current.family.budgets.find(params[:budget_id])

@recent_transactions = @budget.entries

if params[:id] == BudgetCategory.uncategorized.id
@budget_category = @budget.uncategorized_budget_category
@recent_transactions = @recent_transactions.where(account_transactions: { category_id: nil })
else
@budget_category = Current.family.budget_categories.find(params[:id])
@recent_transactions = @recent_transactions.joins("LEFT JOIN categories ON categories.id = account_transactions.category_id")
.where("categories.id = ? OR categories.parent_id = ?", @budget_category.category.id, @budget_category.category.id)
end

@recent_transactions = @recent_transactions.order("account_entries.date DESC, ABS(account_entries.amount) DESC").take(3)
end

def update
@budget_category = Current.family.budget_categories.find(params[:id])
@budget_category.update!(budget_category_params)

redirect_to budget_budget_categories_path(@budget_category.budget)
end

private
def budget_category_params
params.require(:budget_category).permit(:budgeted_spending)
end
end
55 changes: 55 additions & 0 deletions app/controllers/budgets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class BudgetsController < ApplicationController
before_action :set_budget, only: %i[show edit update]

def index
redirect_to_current_month_budget
end

def show
@next_budget = @budget.next_budget
@previous_budget = @budget.previous_budget
@latest_budget = Budget.find_or_bootstrap(Current.family)
render layout: with_sidebar
end

def edit
render layout: "wizard"
end

def update
@budget.update!(budget_params)
redirect_to budget_budget_categories_path(@budget)
end

def create
start_date = Date.parse(budget_create_params[:start_date])
@budget = Budget.find_or_bootstrap(Current.family, date: start_date)
redirect_to budget_path(@budget)
end

def picker
render partial: "budgets/picker", locals: {
family: Current.family,
year: params[:year].to_i || Date.current.year
}
end

private
def budget_create_params
params.require(:budget).permit(:start_date)
end

def budget_params
params.require(:budget).permit(:budgeted_spending, :expected_income)
end

def set_budget
@budget = Current.family.budgets.find(params[:id])
@budget.sync_budget_categories
end

def redirect_to_current_month_budget
current_budget = Budget.find_or_bootstrap(Current.family)
redirect_to budget_path(current_budget)
end
end
12 changes: 9 additions & 3 deletions app/controllers/categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ def create
if @category.save
@transaction.update(category_id: @category.id) if @transaction

redirect_back_or_to categories_path, notice: t(".success")
flash[:notice] = t(".success")

redirect_target_url = request.referer || categories_path
respond_to do |format|
format.html { redirect_back_or_to categories_path, notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, redirect_target_url) }
end
else
@categories = Current.family.categories.alphabetically.where(parent_id: nil)
@categories = Current.family.categories.alphabetically.where(parent_id: nil).where.not(id: @category.id)
render :new, status: :unprocessable_entity
end
end
Expand Down Expand Up @@ -60,6 +66,6 @@ def set_transaction
end

def category_params
params.require(:category).permit(:name, :color, :parent_id)
params.require(:category).permit(:name, :color, :parent_id, :classification, :lucide_icon)
end
end
2 changes: 1 addition & 1 deletion app/controllers/transactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class TransactionsController < ApplicationController

def index
@q = search_params
search_query = Current.family.transactions.search(@q).includes(:entryable).reverse_chronological
search_query = Current.family.transactions.search(@q).reverse_chronological
@pagy, @transaction_entries = pagy(search_query, limit: params[:per_page] || "50")

totals_query = search_query.incomes_and_expenses
Expand Down
9 changes: 7 additions & 2 deletions app/controllers/transfers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def new
end

def show
@categories = Current.family.categories.expenses
end

def create
Expand Down Expand Up @@ -37,7 +38,11 @@ def create
end

def update
@transfer.update!(transfer_update_params)
Transfer.transaction do
@transfer.update!(transfer_update_params.except(:category_id))
@transfer.outflow_transaction.update!(category_id: transfer_update_params[:category_id])
end

respond_to do |format|
format.html { redirect_back_or_to transactions_url, notice: t(".success") }
format.turbo_stream
Expand All @@ -61,6 +66,6 @@ def transfer_params
end

def transfer_update_params
params.require(:transfer).permit(:notes, :status)
params.require(:transfer).permit(:notes, :status, :category_id)
end
end
10 changes: 10 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ def date_format_options
]
end

def icon(key, size: "md", color: "current")
render partial: "shared/icon", locals: { key:, size:, color: }
end

# Convert alpha (0-1) to 8-digit hex (00-FF)
def hex_with_alpha(hex, alpha)
alpha_hex = (alpha * 255).round.to_s(16).rjust(2, "0")
"#{hex}#{alpha_hex}"
end

def title(page_title)
content_for(:title) { page_title }
end
Expand Down
18 changes: 7 additions & 11 deletions app/helpers/categories_helper.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
module CategoriesHelper
def null_category
Category.new \
name: "Uncategorized",
color: Category::UNCATEGORIZED_COLOR
end

def transfer_category
Category.new \
name: "⇄ Transfer",
color: Category::TRANSFER_COLOR
name: "Transfer",
color: Category::TRANSFER_COLOR,
lucide_icon: "arrow-right-left"
end

def payment_category
Category.new \
name: "→ Payment",
color: Category::PAYMENT_COLOR
name: "Payment",
color: Category::PAYMENT_COLOR,
lucide_icon: "arrow-right"
end

def trade_category
Expand All @@ -24,6 +20,6 @@ def trade_category
end

def family_categories
[ null_category ].concat(Current.family.categories.alphabetically)
[ Category.uncategorized ].concat(Current.family.categories.alphabetically)
end
end
25 changes: 25 additions & 0 deletions app/javascript/controllers/budget_form_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="budget-form"
export default class extends Controller {
toggleAutoFill(e) {
const expectedIncome = e.params.income;
const budgetedSpending = e.params.spending;

if (e.target.checked) {
this.#fillField(expectedIncome.key, expectedIncome.value);
this.#fillField(budgetedSpending.key, budgetedSpending.value);
} else {
this.#clearField(expectedIncome.key);
this.#clearField(budgetedSpending.key);
}
}

#fillField(id, value) {
this.element.querySelector(`input[id="${id}"]`).value = value;
}

#clearField(id) {
this.element.querySelector(`input[id="${id}"]`).value = "";
}
}
Loading
Loading