diff --git a/app/controllers/completions_controller.rb b/app/controllers/completions_controller.rb
index 810a700..6cf9f2e 100644
--- a/app/controllers/completions_controller.rb
+++ b/app/controllers/completions_controller.rb
@@ -42,7 +42,7 @@ class CompletionsController < ApplicationController
if @contestant && !params[:completion].key?(:message_id)
redirect_to edit_contest_contestant_path(@contest, @contestant), notice: t("completions.new.notice")
else
- redirect_to @contest, notice: t("completions.new.notice")
+ redirect_to contest_messages_path(@contest), notice: t("completions.new.notice")
end
else
if params[:completion].key?(:message_id)
diff --git a/app/controllers/contestants_controller.rb b/app/controllers/contestants_controller.rb
index 3430adc..185d796 100644
--- a/app/controllers/contestants_controller.rb
+++ b/app/controllers/contestants_controller.rb
@@ -3,6 +3,19 @@ class ContestantsController < ApplicationController
before_action :set_contestant, only: %i[ destroy edit update]
before_action :set_completions, only: %i[edit update ]
+ def index
+ authorize @contest
+
+ @title = @contest.name
+ @contestants = @contest.contestants.sort_by { |contestant| [
+ -contestant.completions.where(remaining_pieces: nil).size,
+ (contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
+ contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
+ contestant.time_seconds
+ ] }
+ filter_contestants_per_category
+ end
+
def edit
authorize @contest
@@ -158,4 +171,14 @@ class ContestantsController < ApplicationController
end
@contestant.save
end
+
+ def filter_contestants_per_category
+ if params.key?(:category) && params[:category] != "-1"
+ if params[:category] == "-2"
+ @contestants = @contestants.select { |contestant| contestant.categories.size == 0 }
+ else
+ @contestants = @contestants.select { |contestant| contestant.categories.where(id: params[:category]).any? }
+ end
+ end
+ end
end
diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb
index 8608d51..c2c5102 100644
--- a/app/controllers/contests_controller.rb
+++ b/app/controllers/contests_controller.rb
@@ -16,19 +16,7 @@ class ContestsController < ApplicationController
def show
authorize @contest
- @title = I18n.t("contests.show.title", name: @contest.name)
- @action_name = t("helpers.buttons.settings")
- @action_path = "/contests/#{@contest.id}/settings/general"
- @contestants = @contest.contestants.sort_by { |contestant| [
- -contestant.completions.where(remaining_pieces: nil).size,
- (contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
- contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
- contestant.time_seconds
- ] }
- filter_contestants_per_category
- @puzzles = @contest.puzzles.order(:id)
- @messages = @contest.messages.order(:time_seconds)
- set_badges
+ redirect_to contest_contestants_path(@contest)
end
def edit
@@ -240,12 +228,6 @@ class ContestsController < ApplicationController
@title = I18n.t("contests.scoreboard.title", name: @contest.name)
end
- def set_badges
- @badges = []
- @badges.push(t("helpers.badges.team")) if @contest.team
- @badges.push(t("helpers.badges.registration")) if @contest.allow_registration
- end
-
def set_contest
@contest = Contest.find(params[:id])
end
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index c9a5848..916c807 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -5,7 +5,7 @@ class MessagesController < ApplicationController
skip_before_action :require_authentication, only: %i[ create connect cors_preflight_check ]
before_action :cors_set_access_control_headers, only: %i[ create connect cors_preflight_check ]
- before_action :set_contest, only: %i[ convert destroy ]
+ before_action :set_contest, only: %i[ convert destroy index ]
before_action :set_data, only: %i[ convert ]
def self.local_prefixes
@@ -68,6 +68,14 @@ class MessagesController < ApplicationController
end
end
+ def index
+ authorize @contest
+
+ @title = @contest.name
+ @messages = @contest.messages.order(:time_seconds)
+ @puzzles = @contest.puzzles
+ end
+
def convert
authorize @contest
@@ -76,6 +84,7 @@ class MessagesController < ApplicationController
@completion = Completion.new()
@completion.display_time_from_start = @message.display_time
+ @completion.completed = true
render "completions/new"
end
diff --git a/app/controllers/puzzles_controller.rb b/app/controllers/puzzles_controller.rb
index 3ba86ca..1913ebc 100644
--- a/app/controllers/puzzles_controller.rb
+++ b/app/controllers/puzzles_controller.rb
@@ -2,6 +2,13 @@ class PuzzlesController < ApplicationController
before_action :set_contest
before_action :set_puzzle, only: %i[ destroy edit update]
+ def index
+ authorize @contest
+
+ @title = @contest.name
+ @puzzles = @contest.puzzles.order(:id)
+ end
+
def edit
authorize @contest
diff --git a/app/policies/completion_policy.rb b/app/policies/completion_policy.rb
index 9ed51d7..2aab862 100644
--- a/app/policies/completion_policy.rb
+++ b/app/policies/completion_policy.rb
@@ -1,9 +1,2 @@
class CompletionPolicy < ContestPolicy
- def index?
- false
- end
-
- def show?
- false
- end
end
diff --git a/app/policies/contest_policy.rb b/app/policies/contest_policy.rb
index 332185d..76761b4 100644
--- a/app/policies/contest_policy.rb
+++ b/app/policies/contest_policy.rb
@@ -1,30 +1,38 @@
class ContestPolicy < ApplicationPolicy
+ def owner_or_admin
+ if record == :contest
+ true
+ else
+ record.user.id == user.id || user.admin?
+ end
+ end
+
def index?
- true
+ owner_or_admin
end
def show?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def new?
- true
+ owner_or_admin
end
def create?
- true
+ owner_or_admin
end
def convert?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def convert_csv?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def edit?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def settings_general_edit?
@@ -48,23 +56,27 @@ class ContestPolicy < ApplicationPolicy
end
def finalize_import?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def update?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def destroy?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def import?
- record.user.id == user.id || user.admin?
+ owner_or_admin
end
def export?
- record.user.id == user.id || user.admin?
+ owner_or_admin
+ end
+
+ def upload_csv?
+ owner_or_admin
end
def offline?
@@ -94,8 +106,4 @@ class ContestPolicy < ApplicationPolicy
def scoreboard?
record.public
end
-
- def upload_csv?
- record.user.id == user.id || user.admin?
- end
end
diff --git a/app/policies/contestant_policy.rb b/app/policies/contestant_policy.rb
index 1b35919..e5ef1d0 100644
--- a/app/policies/contestant_policy.rb
+++ b/app/policies/contestant_policy.rb
@@ -1,9 +1,2 @@
class ContestantPolicy < ContestPolicy
- def index?
- false
- end
-
- def show?
- false
- end
end
diff --git a/app/policies/puzzle_policy.rb b/app/policies/puzzle_policy.rb
index 16e7645..16483f9 100644
--- a/app/policies/puzzle_policy.rb
+++ b/app/policies/puzzle_policy.rb
@@ -1,9 +1,2 @@
class PuzzlePolicy < ContestPolicy
- def index?
- false
- end
-
- def show?
- false
- end
end
diff --git a/app/views/application/_contest_nav.html.slim b/app/views/application/_contest_nav.html.slim
new file mode 100644
index 0000000..9534f86
--- /dev/null
+++ b/app/views/application/_contest_nav.html.slim
@@ -0,0 +1,44 @@
+javascript:
+ async function copyExtensionUrlToClipboard() {
+ await navigator.clipboard.writeText("#{message_url}?token=#{@contest.generate_token_for(:token)}");
+ alert("#{t("contests.show.url_copied")}");
+ }
+
+.row.mb-4
+ .col
+ - if @contest.public
+ a.btn.btn-success href="/public/#{@contest.slug}"
+ = t("contests.show.open_public_scoreboard")
+ - else
+ a.btn.btn-success.disabled
+ = t("contests.show.public_scoreboard_disabled")
+ - if @contest.offline_form && @contest.puzzles.length < 2
+ a.ms-3.btn.btn-success href="/public/#{@contest.slug}/offline"
+ = t("contests.show.open_offline_form")
+ - else
+ a.ms-3.btn.btn-success.disabled
+ = t("contests.show.offline_form_disabled")
+ button.btn.btn-success.ms-3 onclick="copyExtensionUrlToClipboard()"
+ css:
+ button > svg {
+ margin-right: 2px;
+ margin-top: -3px;
+ }
+
+ =< t("contests.show.copy_extension_url")
+
+.row
+ .col
+ ul.nav.nav-tabs.mb-4
+ li.nav-item
+ a.nav-link class=active_page(contest_contestants_path(@contest)) href=contest_contestants_path(@contest)
+ = t("contestants.plural").capitalize
+ li.nav-item
+ a.nav-link class=active_page(contest_puzzles_path(@contest)) href=contest_puzzles_path(@contest)
+ = t("puzzles.plural").capitalize
+ li.nav-item
+ a.nav-link class=active_page(contest_messages_path(@contest)) href=contest_messages_path(@contest)
+ = t("messages.plural").capitalize
\ No newline at end of file
diff --git a/app/views/contestants/index.html.slim b/app/views/contestants/index.html.slim
new file mode 100644
index 0000000..8d04bb8
--- /dev/null
+++ b/app/views/contestants/index.html.slim
@@ -0,0 +1,64 @@
+= render "contest_nav"
+
+.row.mb-4 style="height: calc(100vh - 280px)"
+ .col.d-flex.flex-column style="height: 100%"
+ .row.mb-4
+ .col
+ a.btn.btn-primary href=new_contest_contestant_path(@contest) style="margin-top: -3px"
+ | + #{t("helpers.buttons.add")}
+ a.ms-2.btn.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
+ | #{t("helpers.buttons.import")}
+ a.ms-2.btn.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px"
+ | #{t("helpers.buttons.export")}
+
+ - if @contest.categories.size > 0
+ .row
+ .col
+ select.mt-2.mb-2 id="categories" style="padding: 5px"
+ option value=-1
+ | Tous.tes les participant.e.s
+ option value=-2
+ | Participant.e.s sans catégorie
+ - @contest.categories.each do |category|
+ option value=category.id
+ = category.name
+ javascript:
+ categorySelectEl = document.getElementById('categories');
+ urlParams = new URLSearchParams(window.location.search);
+ selectedCategory = urlParams.get('category');
+ Array.from(categorySelectEl.children).forEach((option) => {
+ if (option.value == selectedCategory) option.selected = true;
+ });
+ categorySelectEl.addEventListener('change', (e) => {
+ window.location.replace(`#{contest_path(@contest)}?category=${e.target.value}`);
+ })
+ .d-flex.flex-column style="overflow-y: auto"
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th
+ = t("activerecord.attributes.contestant.name")
+ th
+ = t("activerecord.attributes.contestant.offline")
+ th
+ = t("activerecord.attributes.contestant.completions")
+ th
+ = t("activerecord.attributes.contestant.display_time")
+ tbody
+ - @contestants.each_with_index do |contestant, index|
+ tr scope="row"
+ td
+ = contestant.name
+ td
+ - if contestant.offline.present?
+
+ td
+ = contestant.completions.where(remaining_pieces: nil).length
+ td
+ = contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? "#{contestant.completions.map{|completion| completion.puzzle.pieces}.sum - contestant.completions[-1].remaining_pieces}p" : contestant.display_time
+ td
+ a.btn.btn-sm.btn-secondary href=edit_contest_contestant_path(@contest, contestant)
+ = t("helpers.buttons.open")
\ No newline at end of file
diff --git a/app/views/contests/show.html.slim b/app/views/contests/show.html.slim
deleted file mode 100644
index 8639f99..0000000
--- a/app/views/contests/show.html.slim
+++ /dev/null
@@ -1,180 +0,0 @@
-- if @badges.size > 0 && false
- .row.mb-4
- .col
- .badges style="margin-top: -18px; position: absolute"
- - @badges.each do |badge|
- span.badge.text-bg-info.me-2
- = badge
-
-javascript:
- async function copyExtensionUrlToClipboard() {
- await navigator.clipboard.writeText("#{message_url}?token=#{@contest.generate_token_for(:token)}");
- alert("#{t("contests.show.url_copied")}");
- }
-
-.row.mb-5
- .col
- - if @contest.public
- a.btn.btn-success href="/public/#{@contest.slug}"
- = t("contests.show.open_public_scoreboard")
- - else
- a.btn.btn-success.disabled
- = t("contests.show.public_scoreboard_disabled")
- - if @contest.offline_form && @contest.puzzles.length < 2
- a.ms-3.btn.btn-success href="/public/#{@contest.slug}/offline"
- = t("contests.show.open_offline_form")
- - else
- a.ms-3.btn.btn-success.disabled
- = t("contests.show.offline_form_disabled")
- button.btn.btn-success.ms-3 onclick="copyExtensionUrlToClipboard()"
- css:
- button > svg {
- margin-right: 2px;
- margin-top: -3px;
- }
-
- =< t("contests.show.copy_extension_url")
-
-.row.mb-4 style="height: calc(100vh - 280px)"
- .col-6.d-flex.flex-column style="height: 100%"
- .row
- .col
- h4
- = t("contestants.plural").capitalize
- a.ms-3.btn.btn-sm.btn-primary href=new_contest_contestant_path(@contest) style="margin-top: -3px"
- | + #{t("helpers.buttons.add")}
- a.ms-2.btn.btn-sm.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
- | #{t("helpers.buttons.import")}
- a.ms-2.btn.btn-sm.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px"
- | #{t("helpers.buttons.export")}
- - if @contest.categories.size > 0
- .row
- .col
- select.mt-2.mb-2 id="categories" style="padding: 5px"
- option value=-1
- | Tous.tes les participant.e.s
- option value=-2
- | Participant.e.s sans catégorie
- - @contest.categories.each do |category|
- option value=category.id
- = category.name
- javascript:
- categorySelectEl = document.getElementById('categories');
- urlParams = new URLSearchParams(window.location.search);
- selectedCategory = urlParams.get('category');
- Array.from(categorySelectEl.children).forEach((option) => {
- if (option.value == selectedCategory) option.selected = true;
- });
- categorySelectEl.addEventListener('change', (e) => {
- window.location.replace(`#{contest_path(@contest)}?category=${e.target.value}`);
- })
- .d-flex.flex-column style="overflow-y: auto"
- table.table.table-striped.table-hover
- thead
- tr
- th
- = t("activerecord.attributes.contestant.name")
- th
- = t("activerecord.attributes.contestant.offline")
- th
- = t("activerecord.attributes.contestant.completions")
- th
- = t("activerecord.attributes.contestant.display_time")
- tbody
- - @contestants.each_with_index do |contestant, index|
- tr scope="row"
- td
- = contestant.name
- td
- - if contestant.offline.present?
-
- td
- = contestant.completions.where(remaining_pieces: nil).length
- td
- = contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? "#{contestant.completions.map{|completion| completion.puzzle.pieces}.sum - contestant.completions[-1].remaining_pieces}p" : contestant.display_time
- td
- a.btn.btn-sm.btn-secondary href=edit_contest_contestant_path(@contest, contestant)
- = t("helpers.buttons.open")
- .col-6.d-flex.flex-column style="height: 100%"
- .row
- .col
- h4
- = t("puzzles.plural").capitalize
- a.ms-3.btn.btn-sm.btn-primary href=new_contest_puzzle_path(@contest) style="margin-top: -3px"
- | + #{t("helpers.buttons.add")}
- table.table.table-striped.table-hover
- thead
- tr
- th
- = t("activerecord.attributes.puzzle.image")
- th
- = t("activerecord.attributes.puzzle.name")
- th
- = t("activerecord.attributes.puzzle.brand")
- th
- = t("activerecord.attributes.puzzle.pieces")
- tbody
- - @puzzles.each do |puzzle|
- tr.align-middle scope="row"
- td
- = image_tag(puzzle.image, class: "img-fluid", style: "max-height: 48px;") if puzzle.image.attached?
- td
- = puzzle.name
- td
- = puzzle.brand
- td
- = puzzle.pieces
- td
- a.btn.btn-sm.btn-secondary href=edit_contest_puzzle_path(@contest, puzzle)
- = t("helpers.buttons.edit")
- - if @messages
- .row.mt-5
- .col
- h4 = t("messages.plural").capitalize
- - if @puzzles.size == 0
- .row
- .col.alert.alert-danger
- = t("messages.warning")
- .d-flex.flex-column style="overflow-y: auto"
- table.table.table-striped.table-hover
- thead
- tr
- th scope="col" style="white-space: nowrap"
- = t("activerecord.attributes.message.processed")
- th scope="col"
- = t("activerecord.attributes.message.time")
- th scope="col"
- = t("activerecord.attributes.message.author")
- th.w-25 scope="col"
- = t("activerecord.attributes.message.text")
- th.w-25 scope="col"
- tbody
- - @messages.each do |message|
- tr.align-middle scope="row"
- td style="text-align: center"
- - if message.completions.size > 0
-
- td
- = message.display_time
- td
- = message.author
- td
- = message.text
- td
- .d-inline-flex
- - if @puzzles.size > 0
- a.btn.btn-sm.btn-secondary href=contest_message_convert_path(@contest, message) style="white-space: nowrap;"
- = t("helpers.buttons.add_completion")
- - else
- a.btn.btn-sm.btn-secondary.disabled href=contest_message_convert_path(@contest, message) style="white-space: nowrap;"
- = t("helpers.buttons.add_completion")
- = link_to "x", contest_message_path(@contest, message), data: { turbo_method: :delete }, class: "btn btn-sm btn-danger ms-2"
diff --git a/app/views/messages/index.html.slim b/app/views/messages/index.html.slim
new file mode 100644
index 0000000..78b34e3
--- /dev/null
+++ b/app/views/messages/index.html.slim
@@ -0,0 +1,52 @@
+= render "contest_nav"
+
+.row.mb-4 style="height: calc(100vh - 280px)"
+ .col.d-flex.flex-column style="height: 100%"
+
+ .row.mb-4
+ .col
+ - if @messages.length == 0
+ .alert.alert-warning
+ = t("messages.index.no_messages")
+ - else
+ - if @puzzles.size == 0
+ .row
+ .col.alert.alert-danger
+ = t("messages.warning")
+ .d-flex.flex-column style="overflow-y: auto"
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th scope="col" style="white-space: nowrap"
+ = t("activerecord.attributes.message.processed")
+ th scope="col"
+ = t("activerecord.attributes.message.time")
+ th scope="col"
+ = t("activerecord.attributes.message.author")
+ th.w-25 scope="col"
+ = t("activerecord.attributes.message.text")
+ th.w-25 scope="col"
+ tbody
+ - @messages.each do |message|
+ tr.align-middle scope="row"
+ td style="text-align: center"
+ - if message.completions.size > 0
+
+ td
+ = message.display_time
+ td
+ = message.author
+ td
+ = message.text
+ td
+ .d-inline-flex
+ - if @puzzles.size > 0
+ a.btn.btn-sm.btn-secondary href=contest_message_convert_path(@contest, message) style="white-space: nowrap;"
+ = t("helpers.buttons.add_completion")
+ - else
+ a.btn.btn-sm.btn-secondary.disabled href=contest_message_convert_path(@contest, message) style="white-space: nowrap;"
+ = t("helpers.buttons.add_completion")
+ = link_to "x", contest_message_path(@contest, message), data: { turbo_method: :delete }, class: "btn btn-sm btn-danger ms-2"
diff --git a/app/views/puzzles/index.html.slim b/app/views/puzzles/index.html.slim
new file mode 100644
index 0000000..6f8d55d
--- /dev/null
+++ b/app/views/puzzles/index.html.slim
@@ -0,0 +1,34 @@
+= render "contest_nav"
+
+.row.mb-4 style="height: calc(100vh - 280px)"
+ .col.d-flex.flex-column style="height: 100%"
+
+ .row.mb-4
+ .col
+ a.btn.btn-primary href=new_contest_puzzle_path(@contest) style="margin-top: -3px"
+ | + #{t("helpers.buttons.add")}
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th
+ = t("activerecord.attributes.puzzle.image")
+ th
+ = t("activerecord.attributes.puzzle.name")
+ th
+ = t("activerecord.attributes.puzzle.brand")
+ th
+ = t("activerecord.attributes.puzzle.pieces")
+ tbody
+ - @puzzles.each do |puzzle|
+ tr.align-middle scope="row"
+ td
+ = image_tag(puzzle.image, class: "img-fluid", style: "max-height: 128px;") if puzzle.image.attached?
+ td
+ = puzzle.name
+ td
+ = puzzle.brand
+ td
+ = puzzle.pieces
+ td
+ a.btn.btn-sm.btn-secondary href=edit_contest_puzzle_path(@contest, puzzle)
+ = t("helpers.buttons.edit")
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index dba687f..7828c94 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -260,6 +260,8 @@ en:
none: No field selected
rank: Rank
messages:
+ index:
+ no_messages: No messages received yet
convert:
title: New completion
destroy:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index cd5ffea..bb6b5a7 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -231,6 +231,8 @@ fr:
none: Aucun champ sélectionné
rank: Rang
messages:
+ index:
+ no_messages: Pas de messages reçus pour le moment
convert:
title: Ajout d'une complétion
destroy:
diff --git a/config/routes.rb b/config/routes.rb
index 908ffa3..8ae2cf6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,7 +18,7 @@ Rails.application.routes.draw do
resources :completions
resources :contestants
resources :puzzles
- resources :messages, only: :destroy do
+ resources :messages, only: [ :destroy, :index ] do
get "convert", to: "messages#convert"
end
get "import", to: "contestants#import"