diff --git a/app/assets/stylesheets/pages/calculator.scss b/app/assets/stylesheets/pages/calculator.scss index 1c8178664..fbd6623f4 100644 --- a/app/assets/stylesheets/pages/calculator.scss +++ b/app/assets/stylesheets/pages/calculator.scss @@ -271,6 +271,10 @@ select.custom-select { margin: 0; } } + .result-wrapper { + display: flex; + width: fit-content; + } .vector-width { width: 100px; .vector { @@ -279,6 +283,21 @@ select.custom-select { .vector-mobile { display: none; } + .vector-mobile-left { + display: none; + } + .vector-mobile-right { + display: none; + } + .right { + padding-left: 30px; + padding-top: 85px; + transform: translateX(-40px); + } + .left { + padding-bottom: 85px; + transform: rotate(180deg) translateX(40px); + } } } @@ -347,6 +366,12 @@ select.custom-select { align-items: center; max-width: 879px; + .result-wrapper { + display: block; + width: 350px; + margin-right: 140px; + margin-top: 0; + } .vector-width { width: 350px; .vector { @@ -357,6 +382,25 @@ select.custom-select { display: block; margin: auto; } + .vector-mobile-left { + display: block; + margin: auto; + transform: rotate(180deg); + } + .vector-mobile-right { + display: block; + margin: auto; + } + .right { + display: none; + padding-top: 25px; + transform: none; + } + .left { + display: none; + padding-top: 25px; + transform: none; + } } } } @@ -389,6 +433,10 @@ select.custom-select { .vector-width { width: 300px; } + .result-wrapper { + width: 280px; + margin-right: 0; + } } } diff --git a/app/controllers/account/calculators_controller.rb b/app/controllers/account/calculators_controller.rb index 554d62fea..ada3a452a 100644 --- a/app/controllers/account/calculators_controller.rb +++ b/app/controllers/account/calculators_controller.rb @@ -82,7 +82,7 @@ def collect_fields_for_kind(kind) def calculator_params params.require(:calculator).permit( :id, :en_name, :uk_name, :color, - formulas_attributes: [:id, :expression, :en_label, :uk_label, :calculator_id, :en_unit, :uk_unit, :priority, :_destroy], + formulas_attributes: [:id, :expression, :en_label, :uk_label, :calculator_id, :en_unit, :uk_unit, :priority, :relation, :_destroy], fields_attributes: [:id, :en_label, :uk_label, :var_name, :kind, :_destroy, categories_attributes: [:id, :en_name, :uk_name, :price, :_destroy]] ) diff --git a/app/models/calculator.rb b/app/models/calculator.rb index 08cb20ae1..8ddbee7e7 100644 --- a/app/models/calculator.rb +++ b/app/models/calculator.rb @@ -28,6 +28,8 @@ class Calculator < ApplicationRecord has_many :fields, dependent: :destroy has_many :formulas, -> { ordered_by_priority }, dependent: :destroy, inverse_of: :calculator + validates_with RelationValidator + accepts_nested_attributes_for :fields, allow_destroy: true accepts_nested_attributes_for :formulas, allow_destroy: true diff --git a/app/services/calculators/calculation_service.rb b/app/services/calculators/calculation_service.rb index 087a2b063..2c3f96862 100644 --- a/app/services/calculators/calculation_service.rb +++ b/app/services/calculators/calculation_service.rb @@ -9,8 +9,12 @@ def initialize(calculator, inputs) def perform @calculator.formulas.map do |formula| result = @dentaku.evaluate(formula.expression, @inputs).round(2) - - { label: formula.label, result: result, unit: formula.unit } + { + label: formula.label, + result: result, + unit: formula.unit, + relation: formula.relation + } end end end diff --git a/app/validators/relation_validator.rb b/app/validators/relation_validator.rb new file mode 100644 index 000000000..f62ba7472 --- /dev/null +++ b/app/validators/relation_validator.rb @@ -0,0 +1,40 @@ +class RelationValidator < ActiveModel::Validator + def validate(record) + relation_is_correct(record) + end + + private + + def relation_is_correct(record) + return if record.formulas.blank? + + if record.formulas.first.relation == "previous" + record.formulas.first.errors.add(:relation, I18n.t("activerecord.errors.models.formula.attributes.expression.first_relation_error")) + end + + if record.formulas.last.relation == "next" + record.formulas.last.errors.add(:relation, I18n.t("activerecord.errors.models.formula.attributes.expression.last_relation_error")) + end + + last_relation = nil + last_formula_has_relation = false + + record.formulas.each do |formula| + if last_relation == "next" && formula.relation == "previous" + formula.errors.add(:relation, I18n.t("activerecord.errors.models.formula.attributes.expression.relation_between_two_error")) + end + + if last_formula_has_relation && formula.relation == "previous" || last_relation == "next" && formula.relation == "next" + formula.errors.add(:relation, I18n.t("activerecord.errors.models.formula.attributes.expression.already_have_relation_error")) + end + + last_formula_has_relation = formula.relation.present? || last_relation == "next" + + last_relation = formula.relation + end + + if record.formulas.any? { |formula| formula.errors.present? } + record.errors.add(:formulas, "contain invalid relations.") + end + end +end diff --git a/app/views/account/calculators/partials/_formula_fields.html.erb b/app/views/account/calculators/partials/_formula_fields.html.erb index f359fa7d9..10d5dd1c9 100644 --- a/app/views/account/calculators/partials/_formula_fields.html.erb +++ b/app/views/account/calculators/partials/_formula_fields.html.erb @@ -10,6 +10,8 @@ <%= f.input :uk_unit, label: "Uk Unit Label:" %> <%= f.input :en_unit, label: "Unit Label:" %> + <%= f.input :relation, as: :select, label: "Relation of formula (optional):", + collection: { "None": nil, "Next": "next", "Previous": "previous"}, default: "None" %> <%= link_to_remove_association "- Remove Formula", f, class: "text-red-500 underline" %> diff --git a/app/views/calculators/partials/_calculation_results.html.erb b/app/views/calculators/partials/_calculation_results.html.erb index 14199c497..535329a07 100644 --- a/app/views/calculators/partials/_calculation_results.html.erb +++ b/app/views/calculators/partials/_calculation_results.html.erb @@ -5,7 +5,12 @@

Calculation Results

<% results.each do |result| %> -
+
+ <% if result[:relation] == "previous" %> + <%= render "calculators/partials/show/relation_arrow", locals: {rotate_direction: "left"} %> + <% end %> + +
<%= image_tag "#{formula_image}", class: "img-margin", alt: "icon" %>

<%= result[:result] %> @@ -16,6 +21,11 @@

<%= result[:label] %>

+
+ + <% if result[:relation] == "next" %> + <%= render "calculators/partials/show/relation_arrow", locals: {rotate_direction: "right"} %> + <% end %>
<% end %>
diff --git a/app/views/calculators/partials/show/_relation_arrow.html.erb b/app/views/calculators/partials/show/_relation_arrow.html.erb new file mode 100644 index 000000000..6cb20210b --- /dev/null +++ b/app/views/calculators/partials/show/_relation_arrow.html.erb @@ -0,0 +1,4 @@ +
+ <%= image_tag "icons/vector_5.png", class: locals[:rotate_direction], alt: "horizontal arrow" %> + <%= image_tag "icons/vector_2.png", class: "vector-mobile-#{locals[:rotate_direction]}", alt: "vertical arrow" %> +
diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index 22f6103b6..686f6dbbf 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -699,6 +699,10 @@ en: expression: uninitialized_fields: "has uninitialized fields %{fields}" mathematically_invalid: "is mathematically invalid" + first_relation_error: "is invalid no previous formula" + last_relation_error: "is invalid no next formula" + relation_between_two_error: "is invalid this formula already has a relation to the previous one" + already_have_relation_error: "is invalid formula can have relation with only one other formula" form_errors: one: "One error prohibited %{model} from being saved" other: "%{count} errors prohibited this %{model} from being saved" diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index 7f8fa9777..a79b36318 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -591,6 +591,10 @@ uk: one: "має не ініціалізоване поле %{fields}" other: "має не ініціалізовані поля %{fields}" mathematically_invalid: "є математично некоректним" + first_relation_error: "некоректний немає попередньої формули" + last_relation_error: "некоректний немає наступної формули" + relation_between_two_error: "некориктний ця формула вже має зв'язок з попередньою" + already_have_relation_error: "формула може мати зв'язок лише з однією іншою формулою" calculator: attributes: name: diff --git a/db/migrate/20241209084523_add_relation_to_formula.rb b/db/migrate/20241209084523_add_relation_to_formula.rb new file mode 100644 index 000000000..f02cada77 --- /dev/null +++ b/db/migrate/20241209084523_add_relation_to_formula.rb @@ -0,0 +1,5 @@ +class AddRelationToFormula < ActiveRecord::Migration[7.2] + def change + add_column :formulas, :relation, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 5243bf18a..fce0e0e14 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_01_141708) do +ActiveRecord::Schema[7.2].define(version: 2024_12_09_084523) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -134,6 +134,7 @@ t.bigint "calculator_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "relation" t.integer "priority", default: 0, null: false t.index ["calculator_id"], name: "index_formulas_on_calculator_id" end diff --git a/spec/helpers/calculators/calculation_service_helper_spec.rb b/spec/helpers/calculators/calculation_service_helper_spec.rb index 1e81b43a8..ec6360e51 100644 --- a/spec/helpers/calculators/calculation_service_helper_spec.rb +++ b/spec/helpers/calculators/calculation_service_helper_spec.rb @@ -6,7 +6,7 @@ let(:calculator) { instance_double("Calculator", formulas: formulas) } let(:formulas) do [ - Formula.new(en_label: "Addition", en_unit: "units", uk_label: "Додавання", uk_unit: "одиниці", expression: "x + y"), + Formula.new(en_label: "Addition", en_unit: "units", uk_label: "Додавання", uk_unit: "одиниці", expression: "x + y", relation: "next"), Formula.new(en_label: "Multiplication", en_unit: "units", uk_label: "Множення", uk_unit: "одиниці", expression: "x * y") ] end @@ -28,8 +28,8 @@ it "returns results with English labels and units" do expect(subject).to eq([ - { label: "Addition", result: 8, unit: "units" }, - { label: "Multiplication", result: 15, unit: "units" } + { label: "Addition", result: 8, unit: "units", relation: "next" }, + { label: "Multiplication", result: 15, unit: "units", relation: nil } ]) end end @@ -39,8 +39,8 @@ it "returns results with Ukrainian labels and units" do expect(subject).to eq([ - { label: "Додавання", result: 8, unit: "одиниці" }, - { label: "Множення", result: 15, unit: "одиниці" } + { label: "Додавання", result: 8, unit: "одиниці", relation: "next" }, + { label: "Множення", result: 15, unit: "одиниці", relation: nil } ]) end end diff --git a/spec/validators/relation_validator_spec.rb b/spec/validators/relation_validator_spec.rb new file mode 100644 index 000000000..a0ac1554a --- /dev/null +++ b/spec/validators/relation_validator_spec.rb @@ -0,0 +1,67 @@ +require "rails_helper" + +RSpec.describe RelationValidator do + let(:calculator) { build(:calculator) } + + describe "validations" do + let(:formula_previous_relation) { build(:formula, relation: "previous") } + let(:formula_next_relation) { build(:formula, relation: "next") } + let(:formula_none_relation) { build(:formula, relation: nil) } + + context "when the first formula has a relation of previous" do + before do + calculator.formulas = [formula_previous_relation] + calculator.valid? + end + + it "adds an error to the first formula" do + expect(formula_previous_relation.errors[:relation]).to include(I18n.t("activerecord.errors.models.formula.attributes.expression.first_relation_error")) + end + end + + context "when the last formula has a relation of next" do + before do + calculator.formulas = [formula_none_relation, formula_next_relation] + calculator.valid? + end + + it "adds an error to the last formula" do + expect(formula_next_relation.errors[:relation]).to include(I18n.t("activerecord.errors.models.formula.attributes.expression.last_relation_error")) + end + end + + context "when formulas have consecutive relations 'next' and 'previous'" do + before do + calculator.formulas = [formula_next_relation, formula_previous_relation] + calculator.valid? + end + + it "adds an error to the second formula" do + expect(formula_previous_relation.errors[:relation]).to include(I18n.t("activerecord.errors.models.formula.attributes.expression.relation_between_two_error")) + end + end + + context "when fomula already have relation" do + before do + calculator.formulas = [formula_next_relation, formula_none_relation, formula_previous_relation] + calculator.valid? + end + + it "adds an error to the second formula" do + expect(formula_previous_relation.errors[:relation]).to include(I18n.t("activerecord.errors.models.formula.attributes.expression.already_have_relation_error")) + end + end + + context "when all formulas have valid relations" do + before do + calculator.formulas = [formula_next_relation, formula_none_relation, formula_next_relation, formula_none_relation] + calculator.valid? + end + + it "does not add errors to the formulas" do + expect(formula_next_relation.errors[:relation]).to be_empty + expect(formula_none_relation.errors[:relation]).to be_empty + end + end + end +end