Skip to content

Commit

Permalink
Merge pull request #985 from ita-social-projects/update_for_constructor
Browse files Browse the repository at this point in the history
Develop into calculators constructor base branch
  • Loading branch information
SleekMutt authored Nov 27, 2024
2 parents 9130d91 + ffa5d1e commit ae1497f
Show file tree
Hide file tree
Showing 41 changed files with 940 additions and 30 deletions.
56 changes: 53 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ name: CI
on:
push:

pull_request:
branches:
- develop
- master
types:
- closed

release:
types: [published]

jobs:
rubocop:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -50,16 +60,19 @@ jobs:
with:
ruby-version: 3.3.5
bundler-cache: true


- name: Update packages
run: sudo apt-get update

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libvips42 libvips-dev imagemagick
- uses: actions/setup-node@v1
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
node-version: "14.x"
registry-url: "https://registry.npmjs.org"

- uses: nanasess/setup-chromedriver@master

Expand All @@ -81,3 +94,40 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
json-path: tmp/rspec_results.json
if: always()

deploy-to-staging:
needs: rspec
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true
steps:
- uses: actions/checkout@v2

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.5
bundler-cache: true

- uses: miloserdow/capistrano-deploy@v3
with:
target: staging
deploy_key: ${{ secrets.STAGING_KEY_PASSWORD }}
enc_rsa_key_pth: config/credentials/staging_deploy_id_ed25519_enc

deploy-to-production:
needs: rspec
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/checkout@v2

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.5
bundler-cache: true

- uses: miloserdow/capistrano-deploy@v3
with:
target: production
deploy_key: ${{ secrets.PROD_DEPLOY_KEY }}
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ inherit_gem:
AllCops:
SuggestExtensions: true
NewCops: enable
TargetRubyVersion: 3.0.6
TargetRubyVersion: 3.2

Layout/SpaceInsideHashLiteralBraces:
Enabled: true
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The latest version from the release branch 'master' is automatically deployed to
- Bootstrap

## Clone

$ `git clone https://github.com/ita-social-projects/ZeroWaste.git`

## Local setup
Expand Down
Binary file added app/assets/images/pad_scales.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/pads_bought.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/pads_to_buy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
@import "/components/pagination";
@import "/components/breadcrumbs.scss";
@import "/components/description_block";
@import "/components/showpage_calculator";
@import "/utilities/custom-utilities";
@import "/pages/under_construction"
21 changes: 21 additions & 0 deletions app/assets/stylesheets/components/showpage_calculator.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@layer components {
.main-show-container {
@apply flex flex-col bg-white rounded-lg shadow-md w-full p-6 text-left mt-4;
}

.back-arrow {
@apply rounded mb-4 px-2 flex items-center;
}

.calc-details {
@apply flex flex-col mb-4 px-2;
}

.showpage-buttons {
@apply flex justify-start w-full space-x-4;
}

.showpage-text {
@apply text-slate-600 text-sm;
}
}
11 changes: 11 additions & 0 deletions app/controllers/account/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Account::UsersController < Account::BaseController
layout "account"

before_action :set_paper_trail_whodunnit
before_action :blocking_admin, only: :update

load_and_authorize_resource

Expand Down Expand Up @@ -71,6 +72,16 @@ def user_params
prms
end

def blocking_admin
@user = resource

return if params.dig(:user, :blocked).blank? || !@user.admin?

flash[:alert] = t("errors.messages.blocked_user_cannot_be_admin")

redirect_to account_users_path
end

def collection
User.ordered_by_email
end
Expand Down
20 changes: 20 additions & 0 deletions app/controllers/api/v1/pad_calculators_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Api::V1::PadCalculatorsController < ApplicationController
def calculate
@validation = MhcCalculatorValidator.new(params)

if @validation.valid?
calc_service = Calculators::PadUsageService.new(
user_age: params[:user_age],
menstruation_age: params[:menstruation_age],
menopause_age: params[:menopause_age],
average_menstruation_cycle_duration: params[:average_menstruation_cycle_duration],
pads_per_cycle: params[:pads_per_cycle],
pad_category: params[:pad_category]
)

render json: calc_service.calculate, status: :ok
else
render json: { errors: @validation.errors }, status: :unprocessable_entity
end
end
end
12 changes: 12 additions & 0 deletions app/controllers/calculators_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class CalculatorsController < ApplicationController
before_action :authenticate_user!, only: :receive_recomendations
before_action :check_mhc_flipper, only: :mhc_calculator

def index
if Flipper[:show_calculators_list].enabled?
Expand Down Expand Up @@ -37,6 +38,11 @@ def calculator
end
end

def mhc_calculator
add_breadcrumb t("breadcrumbs.home"), root_path
add_breadcrumb t(".mhc_calculator.calculator_name")
end

def receive_recomendations
current_user.toggle(:receive_recomendations)
current_user.save
Expand All @@ -51,4 +57,10 @@ def collection
def resource
collection.friendly.find(params[:slug])
end

def check_mhc_flipper
return if Flipper[:mhc_calculator_status].enabled?

raise ActionController::RoutingError, "Mhc calculator flipper is disabled"
end
end
9 changes: 9 additions & 0 deletions app/helpers/calculators_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ def link_to_external(text:, url:, **options)
end
end

def mhc_calculator_items
[{ image: "pads_bought.png", data_target: "padsUsed", unit: t(".pieces"), text: t(".bought_products") },
"arrow",
{ image: "pads_to_buy.png", data_target: "padsToBeUsed", unit: t(".pieces"), text: t(".will_buy_products") },
{ image: "money_spent_2.png", data_target: "moneySpent", unit: t(".unit"), text: t(".money_spent") },
"arrow",
{ image: "money_to_spent_2.png", data_target: "moneyWillBeSpent", unit: t(".unit"), text: t(".money_will_be_spent") }]
end

def new_calculator_items
[{ image: "diapers_bought_2.png", data_target: "diapersUsed", unit: t(".pieces"), text_target: "boughtDiapersPluralize", text: t(".bought_diapers", count: 0) },
"arrow",
Expand Down
71 changes: 71 additions & 0 deletions app/javascript/controllers/mhc_calculator_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Controller } from "@hotwired/stimulus";
import { FetchRequest } from "@rails/request.js";

export default class extends Controller {
static targets = ["userAge", "menstruationAge", "menopauseAge", "averageMenstruationCycleDuration", "padsPerCycle", "padCategory"];
static outlets = ["pad-results"];
static values = {
url: {
type: String,
default: "en/api/v1/pad_calculators",
}
};

submit(e) {
e.preventDefault();

let formData = {
user_age: parseInt(this.userAgeTarget.value),
menstruation_age: parseInt(this.menstruationAgeTarget.value),
menopause_age: parseInt(this.menopauseAgeTarget.value),
average_menstruation_cycle_duration: parseInt(this.averageMenstruationCycleDurationTarget.value),
pads_per_cycle: parseInt(this.padsPerCycleTarget.value),
pad_category: this.padCategoryTarget.value
};

const request = new FetchRequest("POST", this.urlValue, {
responseKind: "json",
body: JSON.stringify(formData),
});

this.sendRequest(request);
}

async sendRequest(request) {
const response = await request.perform();
const result = await response.json;

this.clearErrors()

if (response.ok) {
this.padResultsOutlet.showResults(result);
} else if (response.statusCode == 422) {
this.showErrors(result.errors);
}
}

showErrors(errors) {
Object.keys(errors).forEach(errorKey => {
const targetKey = errorKey.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
const feedbackDiv = document.createElement('div');

feedbackDiv.className = 'invalid-feedback';
feedbackDiv.textContent = errors[errorKey];

this[`${targetKey}Target`].classList.add("is-invalid");
this[`${targetKey}Target`].insertAdjacentElement('afterend', feedbackDiv);
});
}

clearErrors(){
this.constructor.targets.forEach(targetKey => {
const targetElement = this[`${targetKey}Target`];
targetElement.classList.remove("is-invalid");

const feedbackDiv = targetElement.nextElementSibling;
if (feedbackDiv && feedbackDiv.classList.contains('invalid-feedback')) {
feedbackDiv.remove();
}
});
}
}
21 changes: 21 additions & 0 deletions app/javascript/controllers/pad_results_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = [
"padsUsed",
"padsToBeUsed",
"moneySpent",
"moneyWillBeSpent"
];

showResults(data) {
let result = data;

this.moneySpentTarget.innerHTML = Math.ceil(result.already_used_products_cost);
this.moneyWillBeSpentTarget.innerHTML = Math.ceil(result.products_to_be_used_cost);
this.padsUsedTarget.innerHTML = Math.ceil(result.already_used_products);
this.padsToBeUsedTarget.innerHTML = Math.ceil(result.products_to_be_used);

this.element.scrollIntoView({ behavior: "smooth" });
}
}
10 changes: 10 additions & 0 deletions app/javascript/controllers/price_form_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class extends Controller {

this.priceInputTargets.forEach(input => {
input.addEventListener('input', this.validatePriceInput.bind(this));
input.addEventListener('keydown', this.restrictDecimalInput.bind(this));
});
}

Expand Down Expand Up @@ -57,4 +58,13 @@ export default class extends Controller {
target.style.borderColor = "";
}
}

restrictDecimalInput(event) {
const target = event.target;
const inputValue = target.value;

if (inputValue.includes('.') && inputValue.split('.')[1].length >= 2 && !["Backspace", "Delete"].includes(event.key)) {
event.preventDefault();
}
}
}
2 changes: 2 additions & 0 deletions app/javascript/controllers/results_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ export default class extends Controller {

this.willBuyDiapersPluralizeTarget.innerHTML = result.to_be_diapers_amount_pluralize;
this.boughtDiapersPluralizeTarget.innerHTML = result.used_diapers_amount_pluralize;

this.element.scrollIntoView({ behavior: "smooth" });
}
}
52 changes: 52 additions & 0 deletions app/services/calculators/pad_usage_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class Calculators::PadUsageService
attr_accessor :user_age, :menstruation_age, :menopause_age,
:average_menstruation_cycle_duration,
:pads_per_cycle, :pad_category

PAD_PRICES = {
budget: 2,
average: 4,
premium: 7
}

def initialize(user_age:, menstruation_age:, menopause_age:, average_menstruation_cycle_duration:,
pads_per_cycle:, pad_category:)
@user_age = user_age
@menstruation_age = menstruation_age
@menopause_age = menopause_age || 48.7
@average_menstruation_cycle_duration = average_menstruation_cycle_duration
@pads_per_cycle = pads_per_cycle
@pad_category = (pad_category || :budget).to_sym
end

def calculate
{
already_used_products:,
already_used_products_cost:,
products_to_be_used:,
products_to_be_used_cost:
}
end

private

def already_used_products
menstruations_from_age_range(menstruation_age, user_age) * pads_per_cycle
end

def products_to_be_used
menstruations_from_age_range(user_age, menopause_age) * pads_per_cycle
end

def already_used_products_cost
already_used_products * PAD_PRICES[pad_category]
end

def products_to_be_used_cost
products_to_be_used * PAD_PRICES[pad_category]
end

def menstruations_from_age_range(from_age, till_age)
(till_age - from_age) * (365 / average_menstruation_cycle_duration)
end
end
Loading

0 comments on commit ae1497f

Please sign in to comment.