Revamp contest settings into tabs
Some checks failed
CI / scan_ruby (push) Successful in 20s
CI / scan_js (push) Successful in 13s
CI / lint (push) Successful in 13s
CI / test (push) Failing after 37s

This commit is contained in:
sto
2025-11-12 11:33:44 +01:00
parent f4136ea58a
commit 86dd0b7b9e
14 changed files with 212 additions and 89 deletions

View File

@@ -8,9 +8,9 @@ class CategoriesController < ApplicationController
@category = Category.new(category_params) @category = Category.new(category_params)
@category.contest_id = @contest.id @category.contest_id = @contest.id
if @category.save if @category.save
redirect_to edit_contest_path(@contest), notice: t("categories.new.notice") redirect_to "/contests/#{@contest.id}/settings/categories", notice: t("categories.new.notice")
else else
redirect_to edit_contest_path(@contest), notice: t("categories.new.error") redirect_to "/contests/#{@contest.id}/settings/categories", notice: t("categories.new.error")
end end
end end
@@ -18,7 +18,7 @@ class CategoriesController < ApplicationController
authorize @contest authorize @contest
@category.destroy @category.destroy
redirect_to edit_contest_path(@contest), notice: t("categories.destroy.notice") redirect_to "/contests/#{@contest.id}/settings/categories", notice: t("categories.destroy.notice")
end end
private private

View File

@@ -2,6 +2,7 @@ class ContestsController < ApplicationController
include CompletionsConcern include CompletionsConcern
before_action :set_contest, only: %i[ destroy edit show update ] before_action :set_contest, only: %i[ destroy edit show update ]
before_action :set_settings_contest, only: %i[ settings_general_edit settings_general_update settings_offline_edit settings_offline_update settings_categories_edit ]
before_action :offline_setup, only: %i[ offline_new offline_create offline_edit offline_update offline_completed ] before_action :offline_setup, only: %i[ offline_new offline_create offline_edit offline_update offline_completed ]
skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create offline_edit offline_update offline_completed ] skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create offline_edit offline_update offline_completed ]
@@ -16,8 +17,8 @@ class ContestsController < ApplicationController
authorize @contest authorize @contest
@title = I18n.t("contests.show.title", name: @contest.name) @title = I18n.t("contests.show.title", name: @contest.name)
@action_name = t("helpers.buttons.edit") @action_name = t("helpers.buttons.settings")
@action_path = edit_contest_path(@contest) @action_path = "/contests/#{@contest.id}/settings/general"
@contestants = @contest.contestants.sort_by { |contestant| [ @contestants = @contest.contestants.sort_by { |contestant| [
-contestant.completions.where(remaining_pieces: nil).size, -contestant.completions.where(remaining_pieces: nil).size,
(contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds, (contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
@@ -37,6 +38,56 @@ class ContestsController < ApplicationController
@action_path = contest_path(@contest) @action_path = contest_path(@contest)
end end
def settings_general_edit
authorize @contest
@action_name = t("helpers.buttons.back")
@action_path = contest_path(@contest)
@title = t("contests.edit.title")
end
def settings_offline_edit
authorize @contest
@action_name = t("helpers.buttons.back")
@action_path = contest_path(@contest)
@title = t("contests.edit.title")
end
def settings_categories_edit
authorize @contest
@action_name = t("helpers.buttons.back")
@action_path = contest_path(@contest)
@title = t("contests.edit.title")
end
def settings_general_update
authorize @contest
if @contest.update(settings_general_params)
redirect_to "/contests/#{@contest.id}/settings/general", notice: t("contests.edit.notice")
else
@action_name = t("helpers.buttons.back")
@action_path = contest_path(@contest)
@title = t("contests.edit.title")
render :settings_general_edit, status: :unprocessable_entity
end
end
def settings_offline_update
authorize @contest
if @contest.update(settings_offline_params)
redirect_to "/contests/#{@contest.id}/settings/offline", notice: t("contests.edit.notice")
else
@action_name = t("helpers.buttons.back")
@action_path = contest_path(@contest)
@title = t("contests.edit.title")
render :settings_offline_edit, status: :unprocessable_entity
end
end
def new def new
authorize :contest authorize :contest
@@ -49,7 +100,7 @@ class ContestsController < ApplicationController
@contest = Contest.new(contest_params) @contest = Contest.new(contest_params)
@contest.user_id = current_user.id @contest.user_id = current_user.id
if @contest.save if @contest.save
redirect_to @contest, notice: t("contests.new.notice") redirect_to "/contests/#{@contest.id}/settings/general", notice: t("contests.new.notice")
else else
render :new, status: :unprocessable_entity render :new, status: :unprocessable_entity
end end
@@ -199,10 +250,22 @@ class ContestsController < ApplicationController
@contest = Contest.find(params[:id]) @contest = Contest.find(params[:id])
end end
def set_settings_contest
@contest = Contest.find(params[:contest_id])
end
def contest_params def contest_params
params.expect(contest: [ :lang, :name, :offline_form, :public, :team, :allow_registration ]) params.expect(contest: [ :lang, :name, :offline_form, :public, :team, :allow_registration ])
end end
def settings_general_params
params.expect(contest: [ :lang, :name, :public, :team, :allow_registration ])
end
def settings_offline_params
params.expect(contest: [ :offline_form ])
end
def filter_contestants_per_category def filter_contestants_per_category
if params.key?(:category) && params[:category] != "-1" if params.key?(:category) && params[:category] != "-1"
if params[:category] == "-2" if params[:category] == "-2"

View File

@@ -0,0 +1,5 @@
module StyleHelper
def active_page(path)
request.path.starts_with?(path) ? "active" : ""
end
end

View File

@@ -27,6 +27,26 @@ class ContestPolicy < ApplicationPolicy
record.user.id == user.id || user.admin? record.user.id == user.id || user.admin?
end end
def settings_general_edit?
edit?
end
def settings_general_update?
edit?
end
def settings_offline_edit?
edit?
end
def settings_offline_update?
edit?
end
def settings_categories_edit?
edit?
end
def finalize_import? def finalize_import?
record.user.id == user.id || user.admin? record.user.id == user.id || user.admin?
end end

View File

@@ -1,79 +0,0 @@
h4.mt-5 = t("contests.form.general")
= form_with model: contest do |form|
.row.mb-3
.col
.form-floating
= form.text_field :name, autocomplete: "off", class: "form-control"
= form.label :name, class: "required"
.row.mb-3
.col
.form-floating
= form.select :lang, Languages::AVAILABLE_LANGUAGES.map { |lang| [ lang[:name], lang[:id] ] }, {}, class: "form-select"
= form.label :lang
.row.mb-3
.col
.form-check.form-switch
= form.check_box :public, class: "form-check-input"
= form.label :public
.row.mb-3
.col
- if @contest.puzzles.length <= 1
.form-check.form-switch
= form.check_box :offline_form, class: "form-check-input"
= form.label :offline_form
.form-text = t("activerecord.attributes.contest.offline_form_warning")
.form-text = t("activerecord.attributes.contest.offline_form_description")
- else
.form-check.form-switch
= form.check_box :offline_form_fake, class: "form-check-input", disabled: true
= form.label :offline_form
.form-text = t("activerecord.attributes.contest.offline_form_warning")
.form-text = t("activerecord.attributes.contest.offline_form_description")
.row.mb-3
.col
.form-check.form-switch
= form.check_box :team, class: "form-check-input"
= form.label :team
.form-text = t("activerecord.attributes.contest.team_description")
.row.mb-3 style="display: none"
.col
.form-check.form-switch
= form.check_box :allow_registration, class: "form-check-input"
= form.label :allow_registration
.form-text = t("activerecord.attributes.contest.allow_registration_description")
.row
.col
= form.submit submit_text, class: "btn btn-primary"
h4.mt-5 = t("contests.form.categories")
= form_with model: Category, url: "/contests/#{@contest.id}/categories" do |form|
- if @contest.categories.size > 0
.row
.col-6
table.table.table-striped.table-hover
thead
tr
th
= t("activerecord.attributes.category.name")
th
= t("activerecord.attributes.category.contestant_count")
tbody
- @contest.categories.each do |category|
tr.align-middle scope="row"
td
= category.name
td
= category.contestants.size
td
= link_to t("helpers.buttons.delete"), contest_category_path(@contest, category), data: { turbo_method: :delete }, class: "btn btn-sm btn-danger ms-2"
.row.mt-3
.col-4
.form-floating
= form.text_field :name, autocomplete: "off", value: nil, class: "form-control"
= form.label :name, class: "required"
= t("activerecord.attributes.category.new")
.row.mt-3
.col
= form.submit t("helpers.buttons.add"), class: "btn btn-primary"

View File

@@ -0,0 +1,12 @@
.row
.col
ul.nav.nav-tabs.mb-4
li.nav-item
a.nav-link class=active_page("/contests/#{@contest.id}/settings/general") href="/contests/#{@contest.id}/settings/general"
= t("contests.form.general")
li.nav-item
a.nav-link class=active_page("/contests/#{@contest.id}/settings/offline") href="/contests/#{@contest.id}/settings/offline"
= t("contests.form.offline")
li.nav-item
a.nav-link class=active_page("/contests/#{@contest.id}/settings/categories") href="/contests/#{@contest.id}/settings/categories"
= t("contests.form.categories")

View File

@@ -1 +0,0 @@
= render "form", contest: @contest, submit_text: t("helpers.buttons.save")

View File

@@ -1 +1,9 @@
= render "form", contest: @contest, submit_text: t("helpers.buttons.create") = form_with model: @contest do |form|
.row.mb-3
.col
.form-floating
= form.text_field :name, autocomplete: "off", class: "form-control"
= form.label :name, class: "required"
.row.mt-4
.col
= form.submit t("helpers.buttons.create"), class: "btn btn-primary"

View File

@@ -0,0 +1,31 @@
= render "settings_nav"
= form_with model: Category, url: "/contests/#{@contest.id}/categories" do |form|
- if @contest.categories.size > 0
.row
.col-6
table.table.table-striped.table-hover
thead
tr
th
= t("activerecord.attributes.category.name")
th
= t("activerecord.attributes.category.contestant_count")
tbody
- @contest.categories.each do |category|
tr.align-middle scope="row"
td
= category.name
td
= category.contestants.size
td
= link_to t("helpers.buttons.delete"), contest_category_path(@contest, category), data: { turbo_method: :delete }, class: "btn btn-sm btn-danger ms-2"
.row.mt-3
.col-4
.form-floating
= form.text_field :name, autocomplete: "off", value: nil, class: "form-control"
= form.label :name, class: "required"
= t("activerecord.attributes.category.new")
.row.mt-3
.col
= form.submit t("helpers.buttons.add"), class: "btn btn-primary"

View File

@@ -0,0 +1,33 @@
= render "settings_nav"
= form_with model: @contest, url: "/contests/#{@contest.id}/settings/general" do |form|
.row.mt-2.mb-3
.col
.form-floating
= form.text_field :name, autocomplete: "off", class: "form-control"
= form.label :name, class: "required"
.row.mb-3
.col
.form-floating
= form.select :lang, Languages::AVAILABLE_LANGUAGES.map { |lang| [ lang[:name], lang[:id] ] }, {}, class: "form-select"
= form.label :lang
.row.mt-4.mb-3
.col
.form-check.form-switch
= form.check_box :public, class: "form-check-input"
= form.label :public
.row.mb-3
.col
.form-check.form-switch
= form.check_box :team, class: "form-check-input"
= form.label :team
.form-text = t("activerecord.attributes.contest.team_description")
.row.mb-3 style="display: none"
.col
.form-check.form-switch
= form.check_box :allow_registration, class: "form-check-input"
= form.label :allow_registration
.form-text = t("activerecord.attributes.contest.allow_registration_description")
.row.mt-4
.col
= form.submit t("helpers.buttons.update"), class: "btn btn-primary"

View File

@@ -0,0 +1,20 @@
= render "settings_nav"
= form_with model: @contest, url: "/contests/#{@contest.id}/settings/offline" do |form|
.row.mt-2.mb-3
.col
- if @contest.puzzles.length <= 1
.form-check.form-switch
= form.check_box :offline_form, class: "form-check-input"
= form.label :offline_form
.form-text = t("activerecord.attributes.contest.offline_form_warning")
.form-text = t("activerecord.attributes.contest.offline_form_description")
- else
.form-check.form-switch
= form.check_box :offline_form_fake, class: "form-check-input", disabled: true
= form.label :offline_form
.form-text = t("activerecord.attributes.contest.offline_form_warning")
.form-text = t("activerecord.attributes.contest.offline_form_description")
.row.mt-4
.col
= form.submit t("helpers.buttons.update"), class: "btn btn-primary"

View File

@@ -176,6 +176,7 @@ en:
form: form:
categories: Participant categories categories: Participant categories
general: General parameters general: General parameters
offline: Offline participation
index: index:
title: Welcome %{username}! title: Welcome %{username}!
manage_contests: Manage my contests manage_contests: Manage my contests
@@ -249,9 +250,11 @@ en:
import: CSV Import import: CSV Import
open: Open open: Open
refresh: Refresh refresh: Refresh
settings: Settings
sign_in: Sign in sign_in: Sign in
save: Save save: Save
start: Click here to start your participation start: Click here to start your participation
update: Save modifications
field: Field field: Field
none: No field selected none: No field selected
rank: Rank rank: Rank
@@ -266,7 +269,7 @@ en:
nav: nav:
users: Users users: Users
home: My contests home: My contests
settings: Settings settings: My account
log_out: Log out log_out: Log out
offlines: offlines:
form: form:

View File

@@ -147,6 +147,7 @@ fr:
form: form:
categories: Catégories de participant.e.s categories: Catégories de participant.e.s
general: Paramètres généraux general: Paramètres généraux
offline: Participation hors-ligne
index: index:
title: Bienvenue %{username} ! title: Bienvenue %{username} !
manage_contests: Mes concours de puzzle manage_contests: Mes concours de puzzle
@@ -220,9 +221,11 @@ fr:
import: Importer un CSV import: Importer un CSV
open: Détails open: Détails
refresh: Rafraîchir refresh: Rafraîchir
settings: Paramètres
sign_in: Se connecter sign_in: Se connecter
save: Modifier save: Modifier
start: Clique ici pour démarrer ta participation start: Clique ici pour démarrer ta participation
update: Enregistrer les modifications
field: Champ field: Champ
none: Aucun champ sélectionné none: Aucun champ sélectionné
rank: Rang rank: Rang
@@ -237,7 +240,7 @@ fr:
nav: nav:
users: Utilisateur.ices users: Utilisateur.ices
home: Mes concours home: Mes concours
settings: Paramètres settings: Mon compte
log_out: Déconnexion log_out: Déconnexion
offlines: offlines:
form: form:

View File

@@ -9,6 +9,11 @@ Rails.application.routes.draw do
root "contests#index" root "contests#index"
resources :contests do resources :contests do
get "settings/general", to: "contests#settings_general_edit"
patch "settings/general", to: "contests#settings_general_update"
get "settings/offline", to: "contests#settings_offline_edit"
patch "settings/offline", to: "contests#settings_offline_update"
get "settings/categories", to: "contests#settings_categories_edit"
resources :categories, only: [ :create, :destroy ] resources :categories, only: [ :create, :destroy ]
resources :completions resources :completions
resources :contestants resources :contestants