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