From 8216d5cb02853b021448c8979cd5c486601971ab Mon Sep 17 00:00:00 2001 From: PavelPo99 Date: Mon, 30 Jun 2025 22:58:16 +0300 Subject: [PATCH 1/4] complite task timer --- app/controllers/admin/tests_controller.rb | 2 +- app/controllers/test_passages_controller.rb | 16 ++++++++ app/javascript/application.js | 1 + app/javascript/utilities/timer.js | 33 +++++++++++++++++ app/models/test.rb | 2 + app/models/test_passage.rb | 37 +++++++++++++++++++ app/views/admin/tests/_form.html.erb | 6 +++ app/views/test_passages/result.html.erb | 9 +++++ app/views/test_passages/show.html.erb | 11 ++++++ config/locales/en.yml | 6 ++- config/locales/ru.yml | 8 +++- .../20250620184036_add_timer_to_tests.rb | 5 +++ db/schema.rb | 3 +- db/seeds.rb | 12 +++--- 14 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 app/javascript/utilities/timer.js create mode 100644 db/migrate/20250620184036_add_timer_to_tests.rb diff --git a/app/controllers/admin/tests_controller.rb b/app/controllers/admin/tests_controller.rb index 22e8a73..6d4e0b2 100644 --- a/app/controllers/admin/tests_controller.rb +++ b/app/controllers/admin/tests_controller.rb @@ -57,7 +57,7 @@ def set_tests end def test_params - params.require(:test).permit(:title, :level, :category_id, :author_id) + params.require(:test).permit(:title, :level, :category_id, :author_id, :timer) end def find_test diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index 2db52a4..ef77ddd 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -1,12 +1,21 @@ class TestPassagesController < ApplicationController before_action :authenticate_user! before_action :set_test_passage, only: %i[ show result update ] + before_action :check_timer, only: [ :update ] + def show; end def result; end def update + @test_passage = TestPassage.find(params[:id]) + + if @test_passage.time_over? + redirect_to result_test_passage_path(@test_passage) + return + end + if @test_passage.question_any?(params) @test_passage.accept!(params[:answer_ids]) @@ -18,6 +27,13 @@ def update private + def check_timer + @test_passage = TestPassage.find(params[:id]) + if @test_passage.time_over? + redirect_to result_test_passage_path(@test_passage) + end + end + def set_test_passage @test_passage = TestPassage.find(params[:id]) end diff --git a/app/javascript/application.js b/app/javascript/application.js index eb27e91..2ecfa2d 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -7,3 +7,4 @@ import "utilities/sorting" import "utilities/check_password" import "utilities/form_inline" import "utilities/progress_bar" +import "utilities/timer" diff --git a/app/javascript/utilities/timer.js b/app/javascript/utilities/timer.js new file mode 100644 index 0000000..8497d98 --- /dev/null +++ b/app/javascript/utilities/timer.js @@ -0,0 +1,33 @@ +document.addEventListener("turbo:load", function() { + const timerElement = document.getElementById("timer"); + if (!timerElement) return; + + const initialMinutes = parseInt(timerElement.dataset.minutes) || 0; + const initialSeconds = parseInt(timerElement.dataset.seconds) || 0; + let totalSeconds = initialMinutes * 60 + initialSeconds; + + function updateTimer() { + if (totalSeconds <= 0) { + clearInterval(timerInterval); + + const redirectUrl = timerElement.dataset.timeoutUrl; + if (redirectUrl && redirectUrl !== 'undefined') { + window.location.href = redirectUrl; + } + return; + } + + totalSeconds--; + + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, "0")}`; + } + + const timerInterval = setInterval(updateTimer, 1000); + + document.addEventListener("turbo:before-visit", () => { + clearInterval(timerInterval); + }); +}); diff --git a/app/models/test.rb b/app/models/test.rb index bed8227..4db6f08 100644 --- a/app/models/test.rb +++ b/app/models/test.rb @@ -9,6 +9,8 @@ class Test < ApplicationRecord validates :level, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :title, presence: true, uniqueness: { scope: :level } + validates :timer, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + scope :easy_level, -> { where(level: 0..1) } scope :average_level, -> { where(level: 2..4) } diff --git a/app/models/test_passage.rb b/app/models/test_passage.rb index 680bacd..c673ace 100644 --- a/app/models/test_passage.rb +++ b/app/models/test_passage.rb @@ -7,12 +7,49 @@ class TestPassage < ApplicationRecord before_validation :set_current_question + + def timer_enabled? + test.timer != 0 + end + + def time_over? + return unless timer_enabled? + return false unless test.timer.present? && !completed? + + Time.current >= test_completion_time + end + + def test_completion_time + created_at + (test.timer * 60) if test.timer.present? + end + def remaining_time + return unless timer_enabled? + [ test_completion_time - Time.current, 0 ].max.round + end + + def remaining_minutes + return unless remaining_time + (remaining_time / 60).floor + end + + def remaining_seconds + return unless remaining_time + remaining_time % 60 + end + def passed completed? && test_successful? end + alias_method :passed?, :passed def accept!(answer_ids) + if time_over? + self.current_question = nil + save! + return + end + if correct_answer?(answer_ids) self.correct_question += 1 end diff --git a/app/views/admin/tests/_form.html.erb b/app/views/admin/tests/_form.html.erb index f244c7e..9197b5a 100644 --- a/app/views/admin/tests/_form.html.erb +++ b/app/views/admin/tests/_form.html.erb @@ -21,6 +21,12 @@ <%= form.select :category_id, Category.all.collect { |p| [ p.title, p.id ] }, { prompt: true }, class: 'form-select' %> +
+ <%= form.label :timer, t('.timer'), class: 'form-label' %> + <%= form.number_field :timer, min: 0, class: 'form-control' %> + <%= t('.zero') %> +
+
<%= form.submit class: 'btn btn-primary' %>
diff --git a/app/views/test_passages/result.html.erb b/app/views/test_passages/result.html.erb index 27b605e..b086a8d 100644 --- a/app/views/test_passages/result.html.erb +++ b/app/views/test_passages/result.html.erb @@ -3,6 +3,15 @@ <%= t('.header', title: @test_passage.test.title) %> + <% if @test_passage.timer_enabled? %> +
+ <%= t('.still_time') %> + + <%= "#{@test_passage.remaining_minutes}:#{@test_passage.remaining_seconds.to_s.rjust(2, '0')}" %> + +
+ <% end %> + <%= success_rate_message(@test_passage) %>

diff --git a/app/views/test_passages/show.html.erb b/app/views/test_passages/show.html.erb index a8dc693..f19f6b4 100644 --- a/app/views/test_passages/show.html.erb +++ b/app/views/test_passages/show.html.erb @@ -2,6 +2,17 @@ <%= t('.header', title: @test_passage.test.title) %> <%= @test_passage.current_question_number %> / <%= @test_passage.test.questions.count %> +<% if @test_passage.timer_enabled? %> +

+ <%= t('.still_time') %> + + <%= "#{@test_passage.remaining_minutes}:#{@test_passage.remaining_seconds.to_s.rjust(2, '0')}" %> + +
+<% end %>
<%= content_tag(:div, "", class: "progress-bar", diff --git a/config/locales/en.yml b/config/locales/en.yml index 8849157..8ed663d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -135,6 +135,9 @@ en: success: "Test was successfully update." delete: success: "Test was successfully deleted." + form: + zero: "0 - means there is no timer." + timer: "Timer (minutes)" questions: show: header: "%{test_title} Question" @@ -171,7 +174,8 @@ en: header: "Test %{title} was completed!" show: header: "Pass the %{title} test" - save_gist: "Save Question in Gist" + save_gist: "Save Question in Gist" + still_time: "There's still time left:" test_passages_helper: success_rate_message: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 8306add..4d55f24 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -135,7 +135,10 @@ ru: update: success: "Тест обнавлен успешно." delete: - success: "Тест успешно удален." + success: "Тест успешно удален." + form: + zero: "0 - означает отсутствие таймерa" + timer: "Таймер (минут)" questions: show: header: "%{test_title} вопрос" @@ -172,7 +175,8 @@ ru: header: "Тест %{title} завершен!" show: header: "Прохождение теста: %{title}" - save_gist: "Сохранить вопрос в Gist" + save_gist: "Сохранить вопрос в Gist" + still_time: "Осталось времени:" test_passages_helper: success_rate_message: diff --git a/db/migrate/20250620184036_add_timer_to_tests.rb b/db/migrate/20250620184036_add_timer_to_tests.rb new file mode 100644 index 0000000..f380ab7 --- /dev/null +++ b/db/migrate/20250620184036_add_timer_to_tests.rb @@ -0,0 +1,5 @@ +class AddTimerToTests < ActiveRecord::Migration[7.2] + def change + add_column :tests, :timer, :integer, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 0571c48..d4aa961 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: 2025_05_27_172902) do +ActiveRecord::Schema[7.2].define(version: 2025_06_20_184036) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -95,6 +95,7 @@ t.bigint "author_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "timer" t.index ["author_id"], name: "index_tests_on_author_id" t.index ["category_id"], name: "index_tests_on_category_id" t.index ["title", "level"], name: "index_tests_on_title_and_level", unique: true diff --git a/db/seeds.rb b/db/seeds.rb index aa913e1..491cf00 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -12,12 +12,12 @@ ]) tests = Test.create!([ - { title: 'Ruby', level: 1, category_id: categories[0].id, author_id: Admin.first.id }, - { title: 'Ruby on Rails', level: 2, category_id: categories[0].id, author_id: Admin.first.id }, - { title: 'JavaScript', level: 1, category_id: categories[1].id, author_id: Admin.first.id }, - { title: 'Docker', level: 3, category_id: categories[2].id, author_id: Admin.first.id }, - { title: 'HTML & CSS', level: 2, category_id: categories[1].id, author_id: Admin.first.id }, - { title: 'PostgreSQL', level: 3, category_id: categories[2].id, author_id: Admin.first.id } + { title: 'Ruby', level: 1, category_id: categories[0].id, author_id: Admin.first.id, timer: 0 }, + { title: 'Ruby on Rails', level: 2, category_id: categories[0].id, author_id: Admin.first.id, timer: 0 }, + { title: 'JavaScript', level: 1, category_id: categories[1].id, author_id: Admin.first.id, timer: 0 }, + { title: 'Docker', level: 3, category_id: categories[2].id, author_id: Admin.first.id, timer: 0 }, + { title: 'HTML & CSS', level: 2, category_id: categories[1].id, author_id: Admin.first.id, timer: 0 }, + { title: 'PostgreSQL', level: 3, category_id: categories[2].id, author_id: Admin.first.id, timer: 0 } ]) From da0f41b1e2c509982fa9bdcc49a44044f079fbf0 Mon Sep 17 00:00:00 2001 From: PavelPo99 Date: Tue, 1 Jul 2025 21:12:12 +0300 Subject: [PATCH 2/4] fixed local in test_passage/result --- config/locales/en.yml | 4 ++-- config/locales/ru.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 8ed663d..3675922 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -172,10 +172,10 @@ en: badge: "Получена новая награда: %{name_badge}" result: header: "Test %{title} was completed!" + still_time: "There's still time left:" show: header: "Pass the %{title} test" - save_gist: "Save Question in Gist" - still_time: "There's still time left:" + save_gist: "Save Question in Gist" test_passages_helper: success_rate_message: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 4d55f24..d7b21d6 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -173,6 +173,7 @@ ru: badge: "Получена новая награда: %{name_badge}" result: header: "Тест %{title} завершен!" + still_time: "Осталось времени:" show: header: "Прохождение теста: %{title}" save_gist: "Сохранить вопрос в Gist" From d5dd17bb39eae44d077bf7237006eaf7d925e84b Mon Sep 17 00:00:00 2001 From: PavelPo99 Date: Tue, 22 Jul 2025 18:46:20 +0300 Subject: [PATCH 3/4] fixed test_passages_controller, test_passages, show.html.erb, timer.js. applied rubocop --- app/controllers/admin/answers_controller.rb | 7 ++-- app/controllers/admin/base_controller.rb | 3 +- app/controllers/application_controller.rb | 3 +- app/controllers/gists_controller.rb | 7 ++-- app/controllers/sessions_controller.rb | 3 +- app/controllers/test_passages_controller.rb | 40 +++++++++---------- app/controllers/tests_controller.rb | 7 ++-- app/helpers/answers_helper.rb | 7 ++-- app/helpers/application_helper.rb | 9 ++--- app/helpers/questions_helper.rb | 7 ++-- app/helpers/test_passages_helper.rb | 9 ++--- app/helpers/tests_helper.rb | 7 ++-- app/javascript/utilities/timer.js | 10 ++--- app/mailers/test_mailer.rb | 3 +- app/models/admin.rb | 2 - app/models/answer.rb | 1 - app/models/category.rb | 6 +-- app/models/gist.rb | 2 - app/models/test_passage.rb | 8 ++-- app/services/gist_question_services.rb | 15 ++++--- app/views/test_passages/show.html.erb | 16 +++++++- config/locales/en.yml | 3 +- config/locales/ru.yml | 1 + .../20241221125327_add_index_to_tests.rb | 2 +- .../20250129204714_create_test_passages.rb | 1 - .../20250212175643_add_devise_to_users.rb | 4 +- 26 files changed, 86 insertions(+), 97 deletions(-) diff --git a/app/controllers/admin/answers_controller.rb b/app/controllers/admin/answers_controller.rb index 827d0dd..7305b28 100644 --- a/app/controllers/admin/answers_controller.rb +++ b/app/controllers/admin/answers_controller.rb @@ -1,5 +1,4 @@ class Admin::AnswersController < Admin::BaseController - before_action :find_answer, only: %i[ edit show update destroy ] before_action :find_question, only: %i[ new create ] @@ -16,7 +15,7 @@ def create @answer = @question.answers.new(answer_params) if @answer.save - redirect_to admin_answer_path(@answer), notice: 'Answer was successfully created.' + redirect_to admin_answer_path(@answer), notice: "Answer was successfully created." else render :new end @@ -24,7 +23,7 @@ def create def update if @answer.update(answer_params) - redirect_to admin_answer_path(@answer), notice: 'Answer was successfully update.' + redirect_to admin_answer_path(@answer), notice: "Answer was successfully update." else render :edit end @@ -36,7 +35,7 @@ def destroy end - private + private def find_answer @answer = Answer.find(params[:id]) diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index b40d47a..2c2bfb0 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -1,6 +1,5 @@ class Admin::BaseController < ApplicationController - - layout 'admin' + layout "admin" before_action :authenticate_user! before_action :admin_required! diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0ceb1c5..38e7736 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,12 +1,11 @@ class ApplicationController < ActionController::Base - protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? before_action :set_locale def default_url_options - { lang: ((I18n.locale == I18n.default_locale) ? nil : I18n.locale) } + { lang: ((I18n.locale == I18n.default_locale) ? nil : I18n.locale) } end diff --git a/app/controllers/gists_controller.rb b/app/controllers/gists_controller.rb index 9c87f45..4e126fc 100644 --- a/app/controllers/gists_controller.rb +++ b/app/controllers/gists_controller.rb @@ -1,7 +1,6 @@ class GistsController < ApplicationController - before_action :authenticate_user! - + def create test_passage = TestPassage.find(params[:test_passage_id]) @@ -10,7 +9,7 @@ def create flash_answer = if result.success? { notice: "#{t('.success')} | #{link_in_gist(result.html_url)}" } else - { alert: t('.failure')} + { alert: t(".failure") } end redirect_to test_passage, flash_answer @@ -20,6 +19,6 @@ def create private def link_in_gist(gist_url) - view_context.link_to( t('helpers.link.go_gist'), gist_url, class: "link-dark link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover", target: '_blank') + view_context.link_to(t("helpers.link.go_gist"), gist_url, class: "link-dark link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover", target: "_blank") end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 6f9e7c8..2865d5c 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,7 +1,6 @@ class SessionsController < Devise::SessionsController - def create super - flash[:notice] = t('sessions_controller.welcome', name: resource.first_name || resource.email ) + flash[:notice] = t("sessions_controller.welcome", name: resource.first_name || resource.email) end end diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index ef77ddd..d9a0336 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -1,8 +1,6 @@ class TestPassagesController < ApplicationController before_action :authenticate_user! - before_action :set_test_passage, only: %i[ show result update ] - before_action :check_timer, only: [ :update ] - + before_action :set_test_passage, only: %i[show result update] def show; end @@ -12,11 +10,10 @@ def update @test_passage = TestPassage.find(params[:id]) if @test_passage.time_over? - redirect_to result_test_passage_path(@test_passage) - return - end + flash[:alert] = t("test_passages.times_up") - if @test_passage.question_any?(params) + redirect_to result_test_passage_path(@test_passage) + elsif @test_passage.question_any?(params) @test_passage.accept!(params[:answer_ids]) completed_test @@ -25,14 +22,8 @@ def update end end - private - def check_timer - @test_passage = TestPassage.find(params[:id]) - if @test_passage.time_over? - redirect_to result_test_passage_path(@test_passage) - end - end + private def set_test_passage @test_passage = TestPassage.find(params[:id]) @@ -40,18 +31,23 @@ def set_test_passage def completed_test if @test_passage.completed? - - TestMailer.completed_test(@test_passage).deliver_now - - new_badges = BadgeAwardService.new(@test_passage).call - - if new_badges.any? - flash[:notice] = t("test_passages.badge", name_badge: new_badges.map(&:title).join(", ")) - end + send_completion_notifications + award_badges redirect_to result_test_passage_path(@test_passage) else redirect_to test_passage_path(@test_passage) end end + + def send_completion_notifications + TestMailer.completed_test(@test_passage).deliver_later + end + + def award_badges + new_badges = BadgeAwardService.new(@test_passage).call + return unless new_badges.any? + + flash[:notice] = t("test_passages.badge", name_badge: new_badges.map(&:title).join(", ")) + end end diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 1da58d3..0b9cbe5 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -1,12 +1,11 @@ class TestsController < ApplicationController - def index @tests = Test.all end - def start - authenticate_user! - + def start + authenticate_user! + current_user.tests.push(find_test) redirect_to current_user.test_passage(find_test) end diff --git a/app/helpers/answers_helper.rb b/app/helpers/answers_helper.rb index 0e3f7f0..bf69303 100644 --- a/app/helpers/answers_helper.rb +++ b/app/helpers/answers_helper.rb @@ -1,10 +1,9 @@ module AnswersHelper - def answer_header(answer) if answer.new_record? - t('answers_helper.create_new_answer') + t("answers_helper.create_new_answer") else - t('answers_helper.edit_answer', body: answer.body ) - end + t("answers_helper.edit_answer", body: answer.body) + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9374988..e2849dc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,4 @@ module ApplicationHelper - def current_year Time.current.year end @@ -11,10 +10,10 @@ def github_url(author, repo) def flash_message safe_join(flash.map do |flash_type, message| alert_class = case flash_type.to_sym - when :notice, :success then 'alert-success' - when :alert, :error then 'alert-danger' - when :warning then 'alert-warning' - else 'alert-info' + when :notice, :success then "alert-success" + when :alert, :error then "alert-danger" + when :warning then "alert-warning" + else "alert-info" end content_tag :div, sanitize(message), class: "alert #{alert_class} my-4" diff --git a/app/helpers/questions_helper.rb b/app/helpers/questions_helper.rb index 5a80c23..58b61cf 100644 --- a/app/helpers/questions_helper.rb +++ b/app/helpers/questions_helper.rb @@ -1,10 +1,9 @@ module QuestionsHelper - def question_header(question) if question.new_record? - t('question_helper.create_new_question', question_title: question.test.title ) + t("question_helper.create_new_question", question_title: question.test.title) else - t('question_helper.edit_question', question_title: question.test.title ) - end + t("question_helper.edit_question", question_title: question.test.title) + end end end diff --git a/app/helpers/test_passages_helper.rb b/app/helpers/test_passages_helper.rb index a6cc767..8c8e472 100644 --- a/app/helpers/test_passages_helper.rb +++ b/app/helpers/test_passages_helper.rb @@ -1,12 +1,11 @@ module TestPassagesHelper - def success_rate_message(test_passage) if test_passage.test_successful? - content_tag(:h3, I18n.t('test_passages_helper.success_rate_message.success_test'), class: 'text-start md-4') + - I18n.t('test_passages_helper.success_rate_message.rate') + content_tag(:span, "#{test_passage.result_test}%", style: "color: green;") + content_tag(:h3, I18n.t("test_passages_helper.success_rate_message.success_test"), class: "text-start md-4") + + I18n.t("test_passages_helper.success_rate_message.rate") + content_tag(:span, "#{test_passage.result_test}%", style: "color: green;") else - content_tag(:h3, I18n.t('test_passages_helper.success_rate_message.failed_test'), class: 'text-start') + - I18n.t('test_passages_helper.success_rate_message.rate') + content_tag(:span, "#{test_passage.result_test}%", style: "color: red;") + content_tag(:h3, I18n.t("test_passages_helper.success_rate_message.failed_test"), class: "text-start") + + I18n.t("test_passages_helper.success_rate_message.rate") + content_tag(:span, "#{test_passage.result_test}%", style: "color: red;") end end end diff --git a/app/helpers/tests_helper.rb b/app/helpers/tests_helper.rb index 096b37e..fff72c5 100644 --- a/app/helpers/tests_helper.rb +++ b/app/helpers/tests_helper.rb @@ -1,10 +1,9 @@ module TestsHelper - def test_header(test) if test.new_record? - t('tests_helper.create_new_test') + t("tests_helper.create_new_test") else - t('tests_helper.edit_test', test_title: test.title) - end + t("tests_helper.edit_test", test_title: test.title) + end end end diff --git a/app/javascript/utilities/timer.js b/app/javascript/utilities/timer.js index 8497d98..89387bd 100644 --- a/app/javascript/utilities/timer.js +++ b/app/javascript/utilities/timer.js @@ -2,6 +2,7 @@ document.addEventListener("turbo:load", function() { const timerElement = document.getElementById("timer"); if (!timerElement) return; + const form = document.getElementById("test-passage-form"); const initialMinutes = parseInt(timerElement.dataset.minutes) || 0; const initialSeconds = parseInt(timerElement.dataset.seconds) || 0; let totalSeconds = initialMinutes * 60 + initialSeconds; @@ -9,19 +10,16 @@ document.addEventListener("turbo:load", function() { function updateTimer() { if (totalSeconds <= 0) { clearInterval(timerInterval); + timerElement.textContent = "0:00"; + + if (form) form.submit(); - const redirectUrl = timerElement.dataset.timeoutUrl; - if (redirectUrl && redirectUrl !== 'undefined') { - window.location.href = redirectUrl; - } return; } totalSeconds--; - const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; - timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, "0")}`; } diff --git a/app/mailers/test_mailer.rb b/app/mailers/test_mailer.rb index 0e94604..d9a1a7c 100644 --- a/app/mailers/test_mailer.rb +++ b/app/mailers/test_mailer.rb @@ -1,9 +1,8 @@ class TestMailer < ApplicationMailer - def completed_test(test_passage) @user = test_passage.user @test = test_passage.test - + mail to: @user.email end end diff --git a/app/models/admin.rb b/app/models/admin.rb index 962ec1e..ef25cf2 100644 --- a/app/models/admin.rb +++ b/app/models/admin.rb @@ -1,6 +1,4 @@ class Admin < User - validates :first_name, presence: true validates :last_name, presence: true - end diff --git a/app/models/answer.rb b/app/models/answer.rb index 535964d..8ef5cb0 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -1,5 +1,4 @@ class Answer < ApplicationRecord - belongs_to :question validates :body, presence: true diff --git a/app/models/category.rb b/app/models/category.rb index 17afbe8..cf48f1f 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,9 +1,7 @@ class Category < ApplicationRecord - has_many :tests - + validates :title, presence: true - + default_scope { order(title: :asc) } - end diff --git a/app/models/gist.rb b/app/models/gist.rb index c431189..7adb90b 100644 --- a/app/models/gist.rb +++ b/app/models/gist.rb @@ -1,6 +1,4 @@ class Gist < ApplicationRecord - belongs_to :question belongs_to :user - end diff --git a/app/models/test_passage.rb b/app/models/test_passage.rb index c673ace..65d8c4c 100644 --- a/app/models/test_passage.rb +++ b/app/models/test_passage.rb @@ -13,15 +13,17 @@ def timer_enabled? end def time_over? - return unless timer_enabled? - return false unless test.timer.present? && !completed? + return false unless timer_enabled? && !completed? + return false unless test_completion_time Time.current >= test_completion_time end def test_completion_time - created_at + (test.timer * 60) if test.timer.present? + return unless test.timer.present? + created_at + (test.timer * 60) end + def remaining_time return unless timer_enabled? [ test_completion_time - Time.current, 0 ].max.round diff --git a/app/services/gist_question_services.rb b/app/services/gist_question_services.rb index faf00f1..e266c42 100644 --- a/app/services/gist_question_services.rb +++ b/app/services/gist_question_services.rb @@ -1,8 +1,7 @@ class GistQuestionServices - GistResult = Struct.new(:success?, :html_url) - ACCESS_TOKEN = ENV.fetch('GITHUB_ACCESS_GIST_TOKEN') + ACCESS_TOKEN = ENV.fetch("GITHUB_ACCESS_GIST_TOKEN") def initialize(test_passage, user, client = default_client) @test_passage = test_passage @@ -17,7 +16,7 @@ def call if gist.html_url.present? save_gist_in_db!(question: @question.id, gist_url: gist.html_url, user: @user.id) - + GistResult.new(true, gist.html_url) else GistResult.new(false, nil) @@ -30,24 +29,24 @@ def call def save_gist_in_db!(question:, gist_url:, user:) Gist.create!( question_id: question, - gist_url: gist_url, + gist_url: gist_url, user_id: user ) end def gist_params { - description: I18n.t('services.gist.description', test_title: @test.title), + description: I18n.t("services.gist.description", test_title: @test.title), files: { - 'test-guru-question.txt' => { + "test-guru-question.txt" => { content: gist_content } } - } + } end def gist_content - [@question.body, *@question.answers.pluck(:body)].join("\n") + [ @question.body, *@question.answers.pluck(:body) ].join("\n") end def default_client diff --git a/app/views/test_passages/show.html.erb b/app/views/test_passages/show.html.erb index f19f6b4..07dcbd3 100644 --- a/app/views/test_passages/show.html.erb +++ b/app/views/test_passages/show.html.erb @@ -14,6 +14,12 @@
<% end %> +<% if @test_passage.timer_enabled? %> + +<% end %> +
<%= content_tag(:div, "", class: "progress-bar", role: "progressbar", @@ -34,7 +40,11 @@ <%= @test_passage.current_question.body %>

-<%= form_with url: test_passage_path(@test_passage), method: :put, local: true, class: 'mb-4' do |form| %> +<%= form_with url: test_passage_path(@test_passage), + method: :put, + local: true, + class: 'mb-4', + id: 'test-passage-form' do |form| %>
<%= form.collection_check_boxes :answer_ids, @test_passage.current_question.answers, :id, :body, include_hidden: false do |b| %>
@@ -47,6 +57,8 @@
- <%= form.submit t('helpers.submit.test_passages.next'), class: 'btn btn-primary btn-lg' %> + <%= form.submit t('helpers.submit.test_passages.next'), + class: 'btn btn-primary btn-lg', + id: 'submit-button' %>
<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 3675922..9eb3e1f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -170,12 +170,13 @@ en: test_passages: badge: "Получена новая награда: %{name_badge}" + times_up: "The test is completed. Time's up!" result: header: "Test %{title} was completed!" still_time: "There's still time left:" show: header: "Pass the %{title} test" - save_gist: "Save Question in Gist" + save_gist: "Save Question in Gist" test_passages_helper: success_rate_message: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index d7b21d6..ca18349 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -171,6 +171,7 @@ ru: test_passages: badge: "Получена новая награда: %{name_badge}" + times_up: "Тест завершен. Время вышло!" result: header: "Тест %{title} завершен!" still_time: "Осталось времени:" diff --git a/db/migrate/20241221125327_add_index_to_tests.rb b/db/migrate/20241221125327_add_index_to_tests.rb index ed5b938..6071ec3 100644 --- a/db/migrate/20241221125327_add_index_to_tests.rb +++ b/db/migrate/20241221125327_add_index_to_tests.rb @@ -1,5 +1,5 @@ class AddIndexToTests < ActiveRecord::Migration[6.1] def change - add_index :tests, [:title, :level], unique: true + add_index :tests, [ :title, :level ], unique: true end end diff --git a/db/migrate/20250129204714_create_test_passages.rb b/db/migrate/20250129204714_create_test_passages.rb index 38e279f..2eaebb3 100644 --- a/db/migrate/20250129204714_create_test_passages.rb +++ b/db/migrate/20250129204714_create_test_passages.rb @@ -10,4 +10,3 @@ def change end end end - diff --git a/db/migrate/20250212175643_add_devise_to_users.rb b/db/migrate/20250212175643_add_devise_to_users.rb index a24e3c4..c70881b 100644 --- a/db/migrate/20250212175643_add_devise_to_users.rb +++ b/db/migrate/20250212175643_add_devise_to_users.rb @@ -48,8 +48,8 @@ def self.down remove_columns(:users, :encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip, :confirmation_token, :confirmed_at, :confirmation_sent_at, :unconfirmed_email) - + add_column :users, :password_digest, :string - change_column_default(:users, :email, nil) + change_column_default(:users, :email, nil) end end From a503017e11fde69747ca8fd037d52036da7e8ea8 Mon Sep 17 00:00:00 2001 From: PavelPo99 Date: Tue, 22 Jul 2025 18:55:35 +0300 Subject: [PATCH 4/4] fixed conflict --- app/controllers/test_passages_controller.rb | 1 - app/models/test_passage.rb | 4 +--- db/schema.rb | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index d9a0336..dea520e 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -33,7 +33,6 @@ def completed_test if @test_passage.completed? send_completion_notifications award_badges - redirect_to result_test_passage_path(@test_passage) else redirect_to test_passage_path(@test_passage) diff --git a/app/models/test_passage.rb b/app/models/test_passage.rb index 65d8c4c..a3a3bba 100644 --- a/app/models/test_passage.rb +++ b/app/models/test_passage.rb @@ -39,12 +39,10 @@ def remaining_seconds remaining_time % 60 end - def passed + def passed? completed? && test_successful? end - alias_method :passed?, :passed - def accept!(answer_ids) if time_over? self.current_question = nil diff --git a/db/schema.rb b/db/schema.rb index d4aa961..4ff6dc8 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: 2025_06_20_184036) do +ActiveRecord::Schema[7.2].define(version: 2025_07_08_174941) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql"