From e6acb789730a6daf5a9d5d42d61b78e3459fe6a3 Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Mon, 27 Jan 2025 22:13:15 -0500 Subject: [PATCH 1/8] feat: add validation to require consistent category color --- app/models/category.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/category.rb b/app/models/category.rb index ebeadbf9f53..5734bb38c7d 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -13,6 +13,7 @@ class Category < ApplicationRecord validate :category_level_limit validate :nested_category_matches_parent_classification + validate :nested_category_matches_parent_color scope :alphabetically, -> { order(:name) } scope :incomes, -> { where(classification: "income") } @@ -131,4 +132,10 @@ def nested_category_matches_parent_classification errors.add(:parent, "must have the same classification as its parent") end end + + def nested_category_matches_parent_color + if subcategory? && parent.color != color + errors.add(:category, "must have the same color as its parent") + end + end end From b2349aa780070782342b38eaea8159fa44b03f46 Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Mon, 27 Jan 2025 22:13:49 -0500 Subject: [PATCH 2/8] feat: reflect color requirement in new category form --- app/views/categories/_form.html.erb | 9 +-------- app/views/shared/_color_selection.html.erb | 11 +++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 app/views/shared/_color_selection.html.erb diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index 8927c0bf50b..be9bcd190f9 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -7,14 +7,7 @@ <%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %> -
- <% Category::COLORS.each do |color| %> - - <% end %> -
+ <%= render partial: "shared/color_selection", locals: { f: f, parent_color: category.parent&.color } %> <% if category.errors.any? %> <%= render "shared/form_errors", model: category %> diff --git a/app/views/shared/_color_selection.html.erb b/app/views/shared/_color_selection.html.erb new file mode 100644 index 00000000000..52344b9d83d --- /dev/null +++ b/app/views/shared/_color_selection.html.erb @@ -0,0 +1,11 @@ +<%# locals: (f:, parent_color: nil) %> + +<% selectable_colors = Category::COLORS.map { |color| parent_color && parent_color != color ? "color-mix(in srgb, #{color} 35%, lightgray)" : color } %> +
+ <% selectable_colors.each do |color| %> + + <% end %> +
\ No newline at end of file From 2979b2a9255d269cef7eb14aca4a3b9d6ac19a87 Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Mon, 27 Jan 2025 22:27:42 -0500 Subject: [PATCH 3/8] refactor: move logic inline over shared component --- app/views/categories/_form.html.erb | 9 ++++++++- app/views/shared/_color_selection.html.erb | 11 ----------- 2 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 app/views/shared/_color_selection.html.erb diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index be9bcd190f9..384c68f1330 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -7,7 +7,14 @@ <%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %> - <%= render partial: "shared/color_selection", locals: { f: f, parent_color: category.parent&.color } %> +
+ <% Category::COLORS.map { |color| category.parent&.color && category.parent&.color != color ? "color-mix(in srgb, #{color} 35%, lightgray)" : color }.each do |color| %> + + <% end %> +
<% if category.errors.any? %> <%= render "shared/form_errors", model: category %> diff --git a/app/views/shared/_color_selection.html.erb b/app/views/shared/_color_selection.html.erb deleted file mode 100644 index 52344b9d83d..00000000000 --- a/app/views/shared/_color_selection.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%# locals: (f:, parent_color: nil) %> - -<% selectable_colors = Category::COLORS.map { |color| parent_color && parent_color != color ? "color-mix(in srgb, #{color} 35%, lightgray)" : color } %> -
- <% selectable_colors.each do |color| %> - - <% end %> -
\ No newline at end of file From 34ba130308e1a1d803eb0df851a5137b55ceeb6d Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Mon, 27 Jan 2025 22:29:25 -0500 Subject: [PATCH 4/8] rubocop --- app/models/category.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/category.rb b/app/models/category.rb index 5734bb38c7d..a0941c4d409 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -137,5 +137,5 @@ def nested_category_matches_parent_color if subcategory? && parent.color != color errors.add(:category, "must have the same color as its parent") end - end + end end From 1f9dde66e4bee341d106a967d7c6d728d196f5b1 Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Mon, 27 Jan 2025 22:38:42 -0500 Subject: [PATCH 5/8] tests: fix breaking and add case for new validation --- test/models/category_test.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/models/category_test.rb b/test/models/category_test.rb index 0f29843d2d2..afeb5060ca8 100644 --- a/test/models/category_test.rb +++ b/test/models/category_test.rb @@ -25,9 +25,19 @@ def setup category = categories(:subcategory) error = assert_raises(ActiveRecord::RecordInvalid) do - category.subcategories.create!(name: "Invalid category", color: "#000", family: @family) + category.subcategories.create!(name: "Invalid category", family: @family) end assert_equal "Validation failed: Parent can't have more than 2 levels of subcategories", error.message end + + test "subcategory must have same color as parent" do + category = categories(:income) + + error = assert_raises(ActiveRecord::RecordInvalid) do + category.subcategories.create!(name: "Invalid category color", color: "#abc123", family: @family) + end + + assert_equal "Validation failed: Category must have the same color as its parent", error.message + end end From 019c3aed7031e64e5a53e4b225c4b27d578c52a0 Mon Sep 17 00:00:00 2001 From: Julien Bertazzo Lambert Date: Wed, 29 Jan 2025 23:49:56 -0500 Subject: [PATCH 6/8] feat: hide color selector when parent category selected --- app/javascript/controllers/color_avatar_controller.js | 8 +++++++- app/views/categories/_form.html.erb | 9 +++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/color_avatar_controller.js b/app/javascript/controllers/color_avatar_controller.js index 276ae0234b2..41b7568b330 100644 --- a/app/javascript/controllers/color_avatar_controller.js +++ b/app/javascript/controllers/color_avatar_controller.js @@ -3,7 +3,7 @@ import { Controller } from "@hotwired/stimulus"; // Connects to data-controller="color-avatar" // Used by the transaction merchant form to show a preview of what the avatar will look like export default class extends Controller { - static targets = ["name", "avatar"]; + static targets = ["name", "avatar", "selection"]; connect() { this.nameTarget.addEventListener("input", this.handleNameChange); @@ -25,4 +25,10 @@ export default class extends Controller { this.avatarTarget.style.borderColor = `color-mix(in srgb, ${color} 10%, white)`; this.avatarTarget.style.color = color; } + + handleParentChange(e) { + const parent = e.currentTarget.value; + const visibility = typeof parent === "string" && parent !== "" ? "hidden" : "visible" + this.selectionTarget.style.visibility = visibility + } } diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index 384c68f1330..1e722feb538 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -7,10 +7,10 @@ <%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %> -
- <% Category::COLORS.map { |color| category.parent&.color && category.parent&.color != color ? "color-mix(in srgb, #{color} 35%, lightgray)" : color }.each do |color| %> +
+ <% Category::COLORS.each do |color| %> <% end %> @@ -21,6 +21,7 @@ <% end %>
+ <% Category.icon_codes.each do |icon| %>